diff --git a/frontend/assets/images/cartes/icon_calendrier_crayon.png b/frontend/assets/images/cartes/icon_calendrier_crayon.png index e00cc29..64802c5 100644 Binary files a/frontend/assets/images/cartes/icon_calendrier_crayon.png and b/frontend/assets/images/cartes/icon_calendrier_crayon.png differ diff --git a/frontend/assets/images/cartes/icon_carte_absence.png b/frontend/assets/images/cartes/icon_carte_absence.png deleted file mode 100644 index 8fdab38..0000000 Binary files a/frontend/assets/images/cartes/icon_carte_absence.png and /dev/null differ diff --git a/frontend/assets/images/cartes/icon_carte_conges.png b/frontend/assets/images/cartes/icon_carte_conges.png deleted file mode 100644 index 86b0d9e..0000000 Binary files a/frontend/assets/images/cartes/icon_carte_conges.png and /dev/null differ diff --git a/frontend/assets/images/cartes/icon_carte_conges2.png b/frontend/assets/images/cartes/icon_carte_conges2.png deleted file mode 100644 index 840cefa..0000000 Binary files a/frontend/assets/images/cartes/icon_carte_conges2.png and /dev/null differ diff --git a/frontend/assets/images/sac_lapin.png b/frontend/assets/images/sac_lapin.png new file mode 100644 index 0000000..89476a4 Binary files /dev/null and b/frontend/assets/images/sac_lapin.png differ diff --git a/frontend/lib/models/carte_bulle.dart b/frontend/lib/models/carte_bulle.dart index af27408..c2fa7db 100644 --- a/frontend/lib/models/carte_bulle.dart +++ b/frontend/lib/models/carte_bulle.dart @@ -114,7 +114,7 @@ class CarteBulle { return 'assets/images/cartes/icon_carte_maladie.png'; case 'absence_enfant': case 'absence_enfant_modif': - return 'assets/images/cartes/icon_carte_absence.png'; + return 'assets/images/sac_lapin.png'; // soin_enfant : icône selon sous-type payload — à brancher plus tard default: return null; diff --git a/frontend/lib/widgets/quotidien/cartes_feed.dart b/frontend/lib/widgets/quotidien/cartes_feed.dart index e8e02cc..72ca59e 100644 --- a/frontend/lib/widgets/quotidien/cartes_feed.dart +++ b/frontend/lib/widgets/quotidien/cartes_feed.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:p_tits_pas/models/card_assets.dart'; import 'package:p_tits_pas/models/carte_bulle.dart'; import 'package:p_tits_pas/services/api/cards_service.dart'; import 'package:p_tits_pas/widgets/quotidien/carte.dart'; @@ -138,8 +139,10 @@ class _CartesFeedState extends State { style: GoogleFonts.merienda(fontWeight: FontWeight.w600), ), content: Text( - 'Confirmez-vous l’acceptation de « ${card.titreAffiche} » ' - '(${_periodeCourte(card)}) ?', + card.isCongeAm + ? 'Confirmez-vous les congés ${_periodeDuAu(card)} ?' + : 'Confirmez-vous l’acceptation de « ${card.titreAffiche} » ' + '(${_periodeCourte(card)}) ?', style: GoogleFonts.merriweather(fontSize: 14, height: 1.4), ), actions: [ @@ -168,8 +171,14 @@ class _CartesFeedState extends State { style: GoogleFonts.merienda(fontWeight: FontWeight.w600), ), content: Text( - 'Confirmez-vous avoir bien reçu cet arrêt maladie ' - '(${_periodeCourte(card)}) ?', + card.isArretMaladie + ? 'Confirmez-vous avoir pris connaissance de cet arrêt maladie ' + '${_periodeDuAu(card)} ?' + : card.typeCode.startsWith('absence_enfant') + ? 'Confirmez-vous avoir pris connaissance de l’absence ' + '${_periodeDuAu(card)} ?' + : 'Confirmez-vous avoir bien reçu « ${card.titreAffiche} » ' + '(${_periodeCourte(card)}) ?', style: GoogleFonts.merriweather(fontSize: 14, height: 1.4), ), actions: [ @@ -190,59 +199,36 @@ class _CartesFeedState extends State { } Future _onRefuse(CarteBulle card) async { - final controller = TextEditingController(); - final ok = await showDialog( + final comment = await showDialog( context: context, - builder: (ctx) => AlertDialog( - title: Text( - 'Refuser la demande', - style: GoogleFonts.merienda(fontWeight: FontWeight.w600), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - 'Indiquez le motif du refus pour « ${card.titreAffiche} ».', - style: GoogleFonts.merriweather(fontSize: 14, height: 1.4), - ), - const SizedBox(height: 12), - TextField( - controller: controller, - maxLines: 3, - autofocus: true, - decoration: const InputDecoration( - hintText: 'Motif du refus…', - border: OutlineInputBorder(), - ), - ), - ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - child: const Text('Annuler'), - ), - FilledButton( - onPressed: () { - if (controller.text.trim().isEmpty) return; - Navigator.pop(ctx, true); - }, - child: const Text('Refuser'), - ), - ], + builder: (ctx) => _RefuseDialog( + title: card.isCongeAm ? 'Refuser les congés ?' : 'Refuser la demande', + message: card.isCongeAm + ? 'Indiquez le motif du refus des congés ' + '${_periodeDuAu(card)}.' + : 'Indiquez le motif du refus pour « ${card.titreAffiche} ».', ), ); - if (ok == true && controller.text.trim().isNotEmpty) { - await _respond( - card, - action: 'refuse', - comment: controller.text.trim(), - ); + if (comment != null && comment.isNotEmpty) { + await _respond(card, action: 'refuse', comment: comment); } - controller.dispose(); } + static const _mois = [ + 'janvier', + 'février', + 'mars', + 'avril', + 'mai', + 'juin', + 'juillet', + 'août', + 'septembre', + 'octobre', + 'novembre', + 'décembre', + ]; + static String _periodeCourte(CarteBulle card) { final d = card.dateDebut; final f = card.dateFin; @@ -258,6 +244,23 @@ class _CartesFeedState extends State { return '${fmt(d)} → ${fmt(f)}'; } + /// « du 04 novembre 2026 au 06 novembre 2026 » + static String _periodeDuAu(CarteBulle card) { + String fmtLong(String? iso) { + if (iso == null || iso.isEmpty) return '…'; + final dt = DateTime.tryParse(iso); + if (dt == null) return iso; + final jj = dt.day.toString().padLeft(2, '0'); + return '$jj ${_mois[dt.month - 1]} ${dt.year}'; + } + + final d = card.dateDebut; + final f = card.dateFin; + if (d == null && f == null) return 'pour une période non précisée'; + if (d != null && (f == null || d == f)) return 'du ${fmtLong(d)}'; + return 'du ${fmtLong(d)} au ${fmtLong(f)}'; + } + @override Widget build(BuildContext context) { return Column( @@ -404,14 +407,60 @@ class _CartesFeedState extends State { } } -class _BulleTile extends StatelessWidget { - /// Encre aqua foncé — harmonisée fond aqua / valise (pas le violet). - static const Color _aquaInk = Color(0xFF315F63); - /// Date : même famille, ~12 % plus claire. - static const Color _aquaDate = Color(0xFF4A7276); - /// Lien agenda : légère dérive bleue. - static const Color _aquaLink = Color(0xFF2B5868); +/// Encre (titre / date / lien) accordée à la teinte de fond de la bulle. +class _Encre { + final Color ink; + final Color date; + final Color link; + const _Encre(this.ink, this.date, this.link); + /// Aqua → pétrole désaturé ; pêche → brun chaud ; bleu → marine doux ; + /// rose → bois de rose ; lavande → prune. + static _Encre pour(CardColorHorizontal c) { + switch (c) { + case CardColorHorizontal.aqua: + return const _Encre( + Color(0xFF315F63), + Color(0xFF4A7276), + Color(0xFF2B5868), + ); + case CardColorHorizontal.peach: + return const _Encre( + Color(0xFF7A4A2E), + Color(0xFF8F6248), + Color(0xFF6E4530), + ); + case CardColorHorizontal.blue: + return const _Encre( + Color(0xFF2F4A6E), + Color(0xFF486083), + Color(0xFF2A4670), + ); + case CardColorHorizontal.pink: + return const _Encre( + Color(0xFF7A3E52), + Color(0xFF8E5668), + Color(0xFF6E3A50), + ); + case CardColorHorizontal.lavender: + return const _Encre( + Color(0xFF5C3D5C), + Color(0xFF70536F), + Color(0xFF553A60), + ); + case CardColorHorizontal.red: + case CardColorHorizontal.lime: + case CardColorHorizontal.green: + return const _Encre( + Color(0xFF2F2F2F), + Color(0xFF6B6B6B), + Color(0xFF3A3A3A), + ); + } + } +} + +class _BulleTile extends StatelessWidget { final CarteBulle card; final bool busy; final VoidCallback onAccept; @@ -481,12 +530,8 @@ class _BulleTile extends StatelessWidget { @override Widget build(BuildContext context) { - if (card.isCongeAm) return _buildConge(context); - return _buildGeneric(context); - } - - Widget _buildConge(BuildContext context) { final periode = _fmtPeriodeUneLigne(); + final encre = _Encre.pour(card.colorFamily); return Opacity( opacity: _attenuee ? 0.55 : 1, @@ -511,7 +556,7 @@ class _BulleTile extends StatelessWidget { style: GoogleFonts.merienda( fontSize: 18, fontWeight: FontWeight.w700, - color: _aquaInk, + color: encre.ink, ), ), if (periode.isNotEmpty) ...[ @@ -525,6 +570,8 @@ class _BulleTile extends StatelessWidget { height: 28, fit: BoxFit.contain, filterQuality: FilterQuality.high, + color: encre.ink, + colorBlendMode: BlendMode.srcIn, ), const SizedBox(width: 8), Expanded( @@ -535,7 +582,7 @@ class _BulleTile extends StatelessWidget { style: GoogleFonts.merienda( fontSize: 15, fontWeight: FontWeight.w600, - color: _aquaDate, + color: encre.date, height: 1.25, ), ), @@ -555,7 +602,7 @@ class _BulleTile extends StatelessWidget { style: GoogleFonts.merriweather( fontSize: 12, fontStyle: FontStyle.italic, - color: _aquaInk, + color: encre.ink, ), ), ] else if (card.statut == CarteStatut.ouverte && @@ -595,25 +642,51 @@ class _BulleTile extends StatelessWidget { height: 28, child: CircularProgressIndicator(strokeWidth: 2), ) - : Row( - mainAxisSize: MainAxisSize.min, - children: [ - _PilulePeinteButton( + : card.responseMode == CarteResponseMode.ack + ? _PilulePeinteButton( asset: 'assets/images/cartes/btn_coche_pilule.png', - tooltip: 'Accepter', - onTap: onAccept, - ), - const SizedBox(width: 10), - _PilulePeinteButton( - asset: - 'assets/images/cartes/btn_croix_pilule.png', - tooltip: 'Refuser', - onTap: onRefuse, - ), - ], - ), - const Spacer(), + tooltip: 'Bien reçu', + onTap: onAck, + ) + : Row( + mainAxisSize: MainAxisSize.min, + children: [ + _PilulePeinteButton( + asset: + 'assets/images/cartes/btn_coche_pilule.png', + tooltip: 'Accepter', + onTap: onAccept, + ), + const SizedBox(width: 10), + _PilulePeinteButton( + asset: + 'assets/images/cartes/btn_croix_pilule.png', + tooltip: 'Refuser', + onTap: onRefuse, + ), + ], + ) + else if (card.statut == CarteStatut.traitee || + card.statut == CarteStatut.refusee) + Expanded( + child: Text( + card.statut == CarteStatut.refusee + ? 'Refusé' + : 'Validé', + textAlign: TextAlign.center, + style: GoogleFonts.merienda( + fontSize: 14, + fontWeight: FontWeight.w600, + fontStyle: FontStyle.italic, + color: encre.ink.withValues(alpha: 0.75), + ), + ), + ), + if (card.peutRepondre) const SizedBox(width: 8), + if (!(card.statut == CarteStatut.traitee || + card.statut == CarteStatut.refusee)) + const Spacer(), InkWell( onTap: () { ScaffoldMessenger.of(context).showSnackBar( @@ -631,12 +704,15 @@ class _BulleTile extends StatelessWidget { ), child: Text( 'Ouvrir dans l’agenda ↗', + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.right, style: GoogleFonts.merienda( fontSize: 13, fontWeight: FontWeight.w600, - color: _aquaLink, + color: encre.link, decoration: TextDecoration.underline, - decorationColor: _aquaLink, + decorationColor: encre.link, ), ), ), @@ -807,6 +883,80 @@ class _BulleTile extends StatelessWidget { } } +/// Modale de refus : le controller vit avec le State (Échap / Annuler OK). +class _RefuseDialog extends StatefulWidget { + final String title; + final String message; + + const _RefuseDialog({ + required this.title, + required this.message, + }); + + @override + State<_RefuseDialog> createState() => _RefuseDialogState(); +} + +class _RefuseDialogState extends State<_RefuseDialog> { + late final TextEditingController _controller; + + @override + void initState() { + super.initState(); + _controller = TextEditingController(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text( + widget.title, + style: GoogleFonts.merienda(fontWeight: FontWeight.w600), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + widget.message, + style: GoogleFonts.merriweather(fontSize: 14, height: 1.4), + ), + const SizedBox(height: 12), + TextField( + controller: _controller, + maxLines: 3, + autofocus: true, + decoration: const InputDecoration( + hintText: 'Motif du refus…', + border: OutlineInputBorder(), + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Annuler'), + ), + FilledButton( + onPressed: () { + final text = _controller.text.trim(); + if (text.isEmpty) return; + Navigator.pop(context, text); + }, + child: const Text('Refuser'), + ), + ], + ); + } +} + /// Bouton pilule peinte (coche / croix intégrées) — carte congés. class _PilulePeinteButton extends StatelessWidget { final String asset;