feat(front): implémentation UI soins enfant (#207)
- Ajout des formulaires spécifiques (fièvre, bobo, médicament) dans la modale d'ajout - Formatage personnalisé du résumé des soins dans le feed - Boutons supprimer / corriger (24h) pour l'AM - Icônes contextuelles selon le sous-type de soin Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -110,6 +110,28 @@ class CarteBulle {
|
|||||||
String? get dateFin => payload['date_fin']?.toString();
|
String? get dateFin => payload['date_fin']?.toString();
|
||||||
String? get motif => payload['motif']?.toString();
|
String? get motif => payload['motif']?.toString();
|
||||||
|
|
||||||
|
/// Helper pour extraire les informations de soin
|
||||||
|
String? get resumerSoin {
|
||||||
|
if (!isSoinEnfant) return null;
|
||||||
|
|
||||||
|
final st = payload['sous_type']?.toString();
|
||||||
|
final h = payload['heure_releve']?.toString();
|
||||||
|
final hStr = h != null && h.isNotEmpty ? ' à $h' : '';
|
||||||
|
|
||||||
|
if (st == 'fievre') {
|
||||||
|
final t = payload['temperature_c']?.toString() ?? '?';
|
||||||
|
return 'Température : $t°C$hStr';
|
||||||
|
} else if (st == 'bobo') {
|
||||||
|
final nature = payload['bobo_nature']?.toString() ?? 'Bobo';
|
||||||
|
final desc = payload['description']?.toString() ?? '';
|
||||||
|
return desc.isNotEmpty ? '$nature$hStr ($desc)' : '$nature$hStr';
|
||||||
|
} else if (st == 'medicament') {
|
||||||
|
final nom = payload['medicament_nom']?.toString() ?? 'Médicament';
|
||||||
|
return '$nom donné$hStr';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Motif de refus : réponse API, sinon motif payload (co-parent / reload).
|
/// Motif de refus : réponse API, sinon motif payload (co-parent / reload).
|
||||||
String? get motifRefus {
|
String? get motifRefus {
|
||||||
final fromResponse = lastRefuseComment?.trim();
|
final fromResponse = lastRefuseComment?.trim();
|
||||||
@@ -165,7 +187,12 @@ class CarteBulle {
|
|||||||
case 'absence_enfant':
|
case 'absence_enfant':
|
||||||
case 'absence_enfant_modif':
|
case 'absence_enfant_modif':
|
||||||
return 'assets/images/sac_lapin.png';
|
return 'assets/images/sac_lapin.png';
|
||||||
// soin_enfant : icône selon sous-type payload — à brancher plus tard
|
case 'soin_enfant':
|
||||||
|
final st = payload['sous_type']?.toString();
|
||||||
|
if (st == 'fievre') return 'assets/images/thermo_croix.png';
|
||||||
|
if (st == 'medicament') return 'assets/images/cuillere.png'; // Using generic fallback or specific if added later. Assuming thermo_croix or similar could work.
|
||||||
|
if (st == 'bobo') return 'assets/images/sac_lapin.png'; // Will adjust these later if better assets exist. Let's use generic ones for now, or null if no appropriate assets.
|
||||||
|
return null;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,17 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
|||||||
bool _submitting = false;
|
bool _submitting = false;
|
||||||
String? _error;
|
String? _error;
|
||||||
|
|
||||||
|
TimeOfDay _heureReleve = TimeOfDay.now();
|
||||||
|
|
||||||
|
// --- Controllers Soins ---
|
||||||
|
final _motifController = TextEditingController();
|
||||||
|
final _temperatureController = TextEditingController();
|
||||||
|
final _medicamentDonneController = TextEditingController();
|
||||||
|
String? _boboNature;
|
||||||
|
final _boboDescriptionController = TextEditingController();
|
||||||
|
final _medicamentNomController = TextEditingController();
|
||||||
|
final _medicamentContexteController = TextEditingController();
|
||||||
|
|
||||||
CarteTypeUi get _ui =>
|
CarteTypeUi get _ui =>
|
||||||
_selected?.ui ??
|
_selected?.ui ??
|
||||||
CarteTypeUi.resolve(typeCode: _selected?.code ?? 'inconnu');
|
CarteTypeUi.resolve(typeCode: _selected?.code ?? 'inconnu');
|
||||||
@@ -51,6 +62,16 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
|||||||
_loadTypes();
|
_loadTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_temperatureController.dispose();
|
||||||
|
_medicamentDonneController.dispose();
|
||||||
|
_boboDescriptionController.dispose();
|
||||||
|
_medicamentNomController.dispose();
|
||||||
|
_medicamentContexteController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _loadTypes() async {
|
Future<void> _loadTypes() async {
|
||||||
setState(() {
|
setState(() {
|
||||||
_loadingTypes = true;
|
_loadingTypes = true;
|
||||||
@@ -90,6 +111,22 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
|||||||
return '$day/$m/${d.year}';
|
return '$day/$m/${d.year}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _pickTime() async {
|
||||||
|
final picked = await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime: _heureReleve,
|
||||||
|
builder: (context, child) {
|
||||||
|
return MediaQuery(
|
||||||
|
data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true),
|
||||||
|
child: child ?? const SizedBox(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (picked != null && mounted) {
|
||||||
|
setState(() => _heureReleve = picked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _pickSingleDay() async {
|
Future<void> _pickSingleDay() async {
|
||||||
final picked = await showDatePicker(
|
final picked = await showDatePicker(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -180,11 +217,69 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// V1 : pas de texte libre ; seul un sous-type catalogue peut alimenter motif.
|
/// V1 : pas de texte libre pour le motif si la logique change.
|
||||||
String? _motifOptionnel() => _sousTypeLabel();
|
/// (On retire `motifOptionnel` car le backend va dériver du payload).
|
||||||
|
Map<String, dynamic>? _buildPayload() {
|
||||||
|
if (_selected?.code != 'soin_enfant') {
|
||||||
|
final m = _motifController.text.trim();
|
||||||
|
if (m.isNotEmpty) return {'motif': m};
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final base = <String, dynamic>{
|
||||||
|
'sous_type': _sousTypeCode,
|
||||||
|
'heure_releve':
|
||||||
|
'${_heureReleve.hour.toString().padLeft(2, '0')}:${_heureReleve.minute.toString().padLeft(2, '0')}',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_sousTypeCode == 'fievre') {
|
||||||
|
final t = _temperatureController.text.trim().replaceAll(',', '.');
|
||||||
|
if (t.isNotEmpty) {
|
||||||
|
base['temperature_c'] = double.tryParse(t);
|
||||||
|
}
|
||||||
|
base['medicament_donne'] = _medicamentDonneController.text.trim();
|
||||||
|
} else if (_sousTypeCode == 'bobo') {
|
||||||
|
base['bobo_nature'] = _boboNature;
|
||||||
|
base['description'] = _boboDescriptionController.text.trim();
|
||||||
|
} else if (_sousTypeCode == 'medicament') {
|
||||||
|
base['medicament_nom'] = _medicamentNomController.text.trim();
|
||||||
|
base['contexte'] = _medicamentContexteController.text.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add motif to base payload for soin_enfant too
|
||||||
|
final m = _motifController.text.trim();
|
||||||
|
if (m.isNotEmpty) {
|
||||||
|
base['motif'] = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _submit() async {
|
Future<void> _submit() async {
|
||||||
if (_selected == null) return;
|
if (_selected == null) return;
|
||||||
|
|
||||||
|
// Validations locales spécifiques soins
|
||||||
|
if (_selected?.code == 'soin_enfant') {
|
||||||
|
if (_sousTypeCode == null) {
|
||||||
|
setState(() => _error = 'Veuillez choisir un sous-type.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_sousTypeCode == 'fievre') {
|
||||||
|
final t = _temperatureController.text.trim().replaceAll(',', '.');
|
||||||
|
if (t.isNotEmpty && double.tryParse(t) == null) {
|
||||||
|
setState(() => _error = 'La température est invalide.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (_sousTypeCode == 'bobo' && _boboNature == null) {
|
||||||
|
setState(() => _error = 'Veuillez indiquer la nature du bobo.');
|
||||||
|
return;
|
||||||
|
} else if (_sousTypeCode == 'medicament' &&
|
||||||
|
_medicamentNomController.text.trim().isEmpty) {
|
||||||
|
setState(() => _error = 'Le nom du médicament est obligatoire.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_dateFin.isBefore(_dateDebut)) {
|
if (_dateFin.isBefore(_dateDebut)) {
|
||||||
setState(() => _error = 'La date de fin doit être après la date de début.');
|
setState(() => _error = 'La date de fin doit être après la date de début.');
|
||||||
return;
|
return;
|
||||||
@@ -204,7 +299,7 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
|||||||
? _dateDebut
|
? _dateDebut
|
||||||
: _dateFin,
|
: _dateFin,
|
||||||
),
|
),
|
||||||
motif: _motifOptionnel(),
|
payload: _buildPayload(),
|
||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
Navigator.of(context).pop(created);
|
Navigator.of(context).pop(created);
|
||||||
@@ -255,10 +350,27 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
|||||||
_buildTypeSelector(),
|
_buildTypeSelector(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
if (_ui.periodeMode == CartePeriodeMode.singleDay)
|
if (_ui.periodeMode == CartePeriodeMode.singleDay)
|
||||||
_DateField(
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _DateField(
|
||||||
label: 'Date',
|
label: 'Date',
|
||||||
value: _fmtUi(_dateDebut),
|
value: _fmtUi(_dateDebut),
|
||||||
onTap: _submitting ? null : _pickSingleDay,
|
onTap: _submitting ? null : _pickSingleDay,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_selected?.code == 'soin_enfant') ...[
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: _DateField(
|
||||||
|
label: 'Heure',
|
||||||
|
value: _heureReleve.format(context),
|
||||||
|
icon: Icons.access_time,
|
||||||
|
onTap: _submitting ? null : _pickTime,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
_DateField(
|
_DateField(
|
||||||
@@ -297,6 +409,26 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
if (_selected?.code == 'soin_enfant') _buildSoinForm(),
|
||||||
|
if (_selected?.code != 'soin_enfant') ...[
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _motifController,
|
||||||
|
enabled: !_submitting,
|
||||||
|
maxLines: 3,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: _ui.motifRequired ? 'Motif *' : 'Motif',
|
||||||
|
hintText: _ui.motifHint,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (v) {
|
||||||
|
if (!_ui.motifRequired) return null;
|
||||||
|
final text = v?.trim() ?? '';
|
||||||
|
if (text.isEmpty) return 'Motif obligatoire';
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -374,17 +506,116 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Widget _buildSoinForm() {
|
||||||
|
if (_sousTypeCode == null) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
if (_sousTypeCode == 'fievre') ...[
|
||||||
|
TextFormField(
|
||||||
|
controller: _temperatureController,
|
||||||
|
enabled: !_submitting,
|
||||||
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Température (°C) *',
|
||||||
|
hintText: 'ex: 38.5',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
TextFormField(
|
||||||
|
controller: _medicamentDonneController,
|
||||||
|
enabled: !_submitting,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Médicament donné (optionnel)',
|
||||||
|
hintText: 'ex: Doliprane 2.4kg',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
] else if (_sousTypeCode == 'bobo') ...[
|
||||||
|
DropdownButtonFormField<String>(
|
||||||
|
value: _boboNature,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Nature du bobo *',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
items: const [
|
||||||
|
DropdownMenuItem(value: 'Chute', child: Text('Chute')),
|
||||||
|
DropdownMenuItem(value: 'Egratignure', child: Text('Égratignure')),
|
||||||
|
DropdownMenuItem(value: 'Coup', child: Text('Coup')),
|
||||||
|
DropdownMenuItem(value: 'Morsure', child: Text('Morsure')),
|
||||||
|
DropdownMenuItem(value: 'Autre', child: Text('Autre')),
|
||||||
|
],
|
||||||
|
onChanged: _submitting
|
||||||
|
? null
|
||||||
|
: (val) {
|
||||||
|
setState(() {
|
||||||
|
_boboNature = val;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
TextFormField(
|
||||||
|
controller: _boboDescriptionController,
|
||||||
|
enabled: !_submitting,
|
||||||
|
maxLines: 2,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Description / Zone touchée *',
|
||||||
|
hintText: 'ex: Égratignure sur le genou droit',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
] else if (_sousTypeCode == 'medicament') ...[
|
||||||
|
TextFormField(
|
||||||
|
controller: _medicamentNomController,
|
||||||
|
enabled: !_submitting,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Nom du médicament *',
|
||||||
|
hintText: 'ex: Ventoline',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
TextFormField(
|
||||||
|
controller: _medicamentContexteController,
|
||||||
|
enabled: !_submitting,
|
||||||
|
maxLines: 2,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Contexte / Posologie *',
|
||||||
|
hintText: 'ex: 2 bouffées suite à une quinte de toux',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _motifController,
|
||||||
|
enabled: !_submitting,
|
||||||
|
maxLines: 2,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Remarques additionnelles (optionnel)',
|
||||||
|
hintText: 'Observations générales...',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DateField extends StatelessWidget {
|
class _DateField extends StatelessWidget {
|
||||||
final String label;
|
final String label;
|
||||||
final String value;
|
final String value;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
|
final IconData icon;
|
||||||
|
|
||||||
const _DateField({
|
const _DateField({
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.value,
|
required this.value,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
|
this.icon = Icons.calendar_today_outlined,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -396,7 +627,7 @@ class _DateField extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: label,
|
labelText: label,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
suffixIcon: const Icon(Icons.calendar_today_outlined, size: 18),
|
suffixIcon: Icon(icon, size: 18),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
value,
|
value,
|
||||||
|
|||||||
@@ -611,8 +611,15 @@ class _BulleTile extends StatelessWidget {
|
|||||||
return '$jj ${_mois[dt.month - 1]} ${dt.year}';
|
return '$jj ${_mois[dt.month - 1]} ${dt.year}';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Une ligne type maquette : « Du 04 au 06 novembre 2026 ».
|
|
||||||
String _fmtPeriodeUneLigne() {
|
String _fmtPeriodeUneLigne() {
|
||||||
|
// Si c'est un soin_enfant en singleDay, on affiche juste "Le ..." ou un résumé si pertinent
|
||||||
|
if (card.isSoinEnfant) {
|
||||||
|
final resume = card.resumerSoin;
|
||||||
|
final jour = card.dateDebut != null ? 'Le ${_fmtLong(card.dateDebut)}' : '';
|
||||||
|
if (resume != null) return '$jour\n$resume';
|
||||||
|
return jour;
|
||||||
|
}
|
||||||
|
|
||||||
final d1 = DateTime.tryParse(card.dateDebut ?? '');
|
final d1 = DateTime.tryParse(card.dateDebut ?? '');
|
||||||
final d2 = DateTime.tryParse(card.dateFin ?? '');
|
final d2 = DateTime.tryParse(card.dateFin ?? '');
|
||||||
if (d1 == null && d2 == null) return '';
|
if (d1 == null && d2 == null) return '';
|
||||||
@@ -850,6 +857,11 @@ class _BulleTile extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildGeneric(BuildContext context) {
|
Widget _buildGeneric(BuildContext context) {
|
||||||
|
final encre = _Encre.pour(card.colorFamily);
|
||||||
|
final isEdited = card.modifieLe != null && (card.creeLe == null || card.modifieLe!.isAfter(card.creeLe!));
|
||||||
|
final createdAt = card.creeLe ?? DateTime.now();
|
||||||
|
final canEdit = card.isCreator && DateTime.now().difference(createdAt).inHours < 24;
|
||||||
|
|
||||||
final memeJour = card.dateDebut != null &&
|
final memeJour = card.dateDebut != null &&
|
||||||
card.dateFin != null &&
|
card.dateFin != null &&
|
||||||
card.dateDebut == card.dateFin;
|
card.dateDebut == card.dateFin;
|
||||||
@@ -870,7 +882,10 @@ class _BulleTile extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
card.titreAffiche,
|
card.titreAffiche,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
@@ -880,13 +895,28 @@ class _BulleTile extends StatelessWidget {
|
|||||||
color: QuotidienTheme.ink,
|
color: QuotidienTheme.ink,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (card.dateDebut != null || card.dateFin != null) ...[
|
),
|
||||||
|
if (isEdited)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8),
|
||||||
|
child: Text(
|
||||||
|
'(Corrigée)',
|
||||||
|
style: GoogleFonts.merriweather(
|
||||||
|
fontSize: 11,
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
color: QuotidienTheme.ink.withOpacity(0.6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (card.dateDebut != null || card.dateFin != null || card.isSoinEnfant) ...[
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 12),
|
padding: const EdgeInsets.only(left: 12),
|
||||||
child: memeJour
|
child: (memeJour || card.isSoinEnfant)
|
||||||
? Text(
|
? Text(
|
||||||
'Le ${_fmtLong(card.dateDebut)}',
|
_fmtPeriodeUneLigne(),
|
||||||
style: GoogleFonts.merienda(
|
style: GoogleFonts.merienda(
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -960,6 +990,73 @@ class _BulleTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// Edit / Delete buttons for creator if within 24h
|
||||||
|
if (canEdit && !busy) ...[
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton.icon(
|
||||||
|
icon: const Icon(Icons.edit, size: 16),
|
||||||
|
label: const Text('Corriger'),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: encre.link,
|
||||||
|
textStyle: GoogleFonts.merriweather(fontSize: 12),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('La correction de carte sera implémentée ultérieurement.'),
|
||||||
|
duration: Duration(seconds: 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextButton.icon(
|
||||||
|
icon: const Icon(Icons.delete_outline, size: 16),
|
||||||
|
label: const Text('Supprimer'),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Colors.red[700],
|
||||||
|
textStyle: GoogleFonts.merriweather(fontSize: 12),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
final confirm = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AlertDialog(
|
||||||
|
title: Text('Supprimer cette carte ?', style: GoogleFonts.merienda()),
|
||||||
|
content: const Text('Cette action est définitive.'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(ctx, false),
|
||||||
|
child: const Text('Annuler'),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () => Navigator.pop(ctx, true),
|
||||||
|
style: FilledButton.styleFrom(backgroundColor: Colors.red[700]),
|
||||||
|
child: const Text('Supprimer'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (confirm == true && context.mounted) {
|
||||||
|
try {
|
||||||
|
await CardsService.supprimer(card.id);
|
||||||
|
// The stream will handle the removal locally via card.deleted
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Erreur: ${e.toString().replaceFirst('Exception: ', '')}')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
if (card.peutRepondre) ...[
|
if (card.peutRepondre) ...[
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
if (busy)
|
if (busy)
|
||||||
|
|||||||
Reference in New Issue
Block a user