fix(#173): layout bulle selon maquette (Du/Au + pilules larges)

Titre, dates en français sur deux lignes, actions coche/croix
en pilules pleine largeur côte à côte.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-01 17:10:41 +02:00
co-authored by Cursor
parent 535af3b146
commit 7705a14bb5
+101 -88
View File
@@ -340,30 +340,26 @@ class _BulleTile extends StatelessWidget {
required this.onRefuse, required this.onRefuse,
}); });
String get _periode { String _fmtLong(String? iso) {
final d = card.dateDebut;
final f = card.dateFin;
if (d == null && f == null) return '';
if (d != null && f != null && d == f) return _fmt(d);
return '${_fmt(d)} → ${_fmt(f)}';
}
String _fmt(String? iso) {
if (iso == null || iso.isEmpty) return '…'; if (iso == null || iso.isEmpty) return '…';
final dt = DateTime.tryParse(iso); final dt = DateTime.tryParse(iso);
if (dt == null) return iso; if (dt == null) return iso;
const mois = [
'janvier',
'février',
'mars',
'avril',
'mai',
'juin',
'juillet',
'août',
'septembre',
'octobre',
'novembre',
'décembre',
];
final jj = dt.day.toString().padLeft(2, '0'); final jj = dt.day.toString().padLeft(2, '0');
final mm = dt.month.toString().padLeft(2, '0'); return '$jj ${mois[dt.month - 1]} ${dt.year}';
return '$jj/$mm/${dt.year}';
}
/// Pas de « À traiter / Traité » : seulement un indice utile (refus / attente créateur).
String? get _indice {
if (card.statut == CarteStatut.refusee) return 'Refusé';
if (card.statut == CarteStatut.ouverte && card.isCreator) {
return 'En attente de réponse';
}
return null;
} }
/// Arrêt maladie : reste opaque tant que la période n’est pas finie. /// Arrêt maladie : reste opaque tant que la période n’est pas finie.
@@ -374,53 +370,61 @@ class _BulleTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final indice = _indice; final memeJour = card.dateDebut != null &&
card.dateFin != null &&
card.dateDebut == card.dateFin;
return Opacity( return Opacity(
opacity: _attenuee ? 0.55 : 1, opacity: _attenuee ? 0.55 : 1,
child: Carte( child: Carte(
assetPath: card.backgroundAsset, assetPath: card.backgroundAsset,
constraints: const BoxConstraints(minHeight: 96), constraints: const BoxConstraints(minHeight: 120),
padding: const EdgeInsets.fromLTRB(20, 14, 20, 14), padding: const EdgeInsets.fromLTRB(22, 16, 22, 16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Row( Text(
crossAxisAlignment: CrossAxisAlignment.start, card.titreAffiche,
children: [ maxLines: 1,
Expanded( overflow: TextOverflow.ellipsis,
child: Text( style: GoogleFonts.merienda(
card.titreAffiche, fontSize: 18,
maxLines: 1, fontWeight: FontWeight.w700,
overflow: TextOverflow.ellipsis, color: QuotidienTheme.ink,
style: GoogleFonts.merienda(
fontSize: 16,
fontWeight: FontWeight.w700,
color: QuotidienTheme.ink,
),
),
),
if (indice != null)
Text(
indice,
style: GoogleFonts.merriweather(
fontSize: 11,
color: QuotidienTheme.muted,
),
),
],
),
if (_periode.isNotEmpty) ...[
const SizedBox(height: 4),
Text(
_periode,
style: GoogleFonts.merriweather(
fontSize: 13,
color: QuotidienTheme.ink,
),
), ),
),
if (card.dateDebut != null || card.dateFin != null) ...[
const SizedBox(height: 10),
if (memeJour)
Text(
'Le ${_fmtLong(card.dateDebut)}',
style: GoogleFonts.merriweather(
fontSize: 14,
color: QuotidienTheme.ink,
height: 1.35,
),
)
else ...[
Text(
'Du ${_fmtLong(card.dateDebut)}',
style: GoogleFonts.merriweather(
fontSize: 14,
color: QuotidienTheme.ink,
height: 1.35,
),
),
Text(
'Au ${_fmtLong(card.dateFin)}',
style: GoogleFonts.merriweather(
fontSize: 14,
color: QuotidienTheme.ink,
height: 1.35,
),
),
],
], ],
if (card.motif != null && card.motif!.trim().isNotEmpty) ...[ if (card.motif != null && card.motif!.trim().isNotEmpty) ...[
const SizedBox(height: 2), const SizedBox(height: 6),
Text( Text(
card.motif!, card.motif!,
maxLines: 2, maxLines: 2,
@@ -431,12 +435,13 @@ class _BulleTile extends StatelessWidget {
), ),
), ),
], ],
if (card.statut == CarteStatut.refusee && if (card.statut == CarteStatut.refusee) ...[
card.lastRefuseComment != null && const SizedBox(height: 6),
card.lastRefuseComment!.trim().isNotEmpty) ...[
const SizedBox(height: 4),
Text( Text(
'Motif : ${card.lastRefuseComment}', card.lastRefuseComment != null &&
card.lastRefuseComment!.trim().isNotEmpty
? 'Refusé — ${card.lastRefuseComment}'
: 'Refusé',
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: GoogleFonts.merriweather( style: GoogleFonts.merriweather(
@@ -445,46 +450,55 @@ class _BulleTile extends StatelessWidget {
color: QuotidienTheme.ink, color: QuotidienTheme.ink,
), ),
), ),
] else if (card.statut == CarteStatut.ouverte &&
card.isCreator) ...[
const SizedBox(height: 6),
Text(
'En attente de réponse',
style: GoogleFonts.merriweather(
fontSize: 12,
color: QuotidienTheme.muted,
),
),
], ],
if (card.peutRepondre) ...[ if (card.peutRepondre) ...[
const SizedBox(height: 10), const SizedBox(height: 14),
if (busy) if (busy)
const Align( const Center(
alignment: Alignment.centerRight,
child: SizedBox( child: SizedBox(
width: 18, width: 22,
height: 18, height: 22,
child: CircularProgressIndicator(strokeWidth: 2), child: CircularProgressIndicator(strokeWidth: 2),
), ),
) )
else else if (card.responseMode == CarteResponseMode.acceptRefuse)
Row( Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
if (card.responseMode == Expanded(
CarteResponseMode.acceptRefuse) ...[ child: _ActionPill(
_ActionPill(
asset: QuotidienTheme.pillTurquoiseAsset, asset: QuotidienTheme.pillTurquoiseAsset,
iconAsset: 'assets/images/coche.png', iconAsset: 'assets/images/coche.png',
tooltip: 'Accepter', tooltip: 'Accepter',
onTap: onAccept, onTap: onAccept,
), ),
const SizedBox(width: 10), ),
_ActionPill( const SizedBox(width: 12),
Expanded(
child: _ActionPill(
asset: QuotidienTheme.pillCorailAsset, asset: QuotidienTheme.pillCorailAsset,
iconAsset: 'assets/images/red_cross2.png', iconAsset: 'assets/images/red_cross2.png',
tooltip: 'Refuser', tooltip: 'Refuser',
onTap: onRefuse, onTap: onRefuse,
), ),
], ),
if (card.responseMode == CarteResponseMode.ack)
_ActionPill(
asset: QuotidienTheme.pillTurquoiseAsset,
iconAsset: 'assets/images/coche.png',
tooltip: 'Bien reçu',
onTap: onAck,
),
], ],
)
else if (card.responseMode == CarteResponseMode.ack)
_ActionPill(
asset: QuotidienTheme.pillTurquoiseAsset,
iconAsset: 'assets/images/coche.png',
tooltip: 'Bien reçu',
onTap: onAck,
), ),
], ],
], ],
@@ -494,7 +508,7 @@ class _BulleTile extends StatelessWidget {
} }
} }
/// Bouton pilule dessiné (fond pastel + coche / croix). /// Bouton pilule dessiné pleine largeur (fond pastel + coche / croix).
class _ActionPill extends StatelessWidget { class _ActionPill extends StatelessWidget {
final String asset; final String asset;
final String iconAsset; final String iconAsset;
@@ -514,10 +528,9 @@ class _ActionPill extends StatelessWidget {
message: tooltip, message: tooltip,
child: InkWell( child: InkWell(
onTap: onTap, onTap: onTap,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(24),
child: SizedBox( child: SizedBox(
width: 52, height: 40,
height: 32,
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
@@ -526,8 +539,8 @@ class _ActionPill extends StatelessWidget {
), ),
Image.asset( Image.asset(
iconAsset, iconAsset,
height: 18, height: 22,
width: 18, width: 22,
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
], ],