fix(#153): peaufiner cartes dossiers et recherche.

Cartes neutres avec accent icône, photo AM si dispo, hint court
et infobulle de critères sur la barre de recherche.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-24 19:39:55 +02:00
co-authored by Cursor
parent 2ce9e9215f
commit 0029c5ab86
7 changed files with 77 additions and 32 deletions
@@ -11,6 +11,8 @@ class DossierListItem {
final String libelle;
final List<String> emails;
final String? statut;
/// Photo profil (AM) — affichée à la place de licône si présente.
final String? photoUrl;
const DossierListItem({
required this.type,
@@ -18,6 +20,7 @@ class DossierListItem {
required this.libelle,
this.emails = const [],
this.statut,
this.photoUrl,
});
bool get isFamille => type == DossierListType.famille;
@@ -125,12 +128,14 @@ class DossierListItem {
prenom: u.prenom,
email: u.email,
);
final photo = (u.photoUrl ?? '').trim();
return DossierListItem(
type: DossierListType.assistanteMaternelle,
numeroDossier: e.key,
libelle: name.isNotEmpty ? name : 'AM',
emails: u.email.trim().isEmpty ? const [] : [u.email.trim()],
statut: u.statut?.trim(),
photoUrl: photo.isEmpty ? null : photo,
);
}).toList();
}
@@ -12,6 +12,10 @@ class AdminUserCard extends StatefulWidget {
final Color? backgroundColor;
final Color? titleColor;
final Color? infoColor;
/// Fond du cercle avatar / icône (défaut lavande admin).
final Color? avatarBackgroundColor;
/// Couleur de licône fallback (défaut violet admin).
final Color? avatarIconColor;
final String? vigilanceTooltip;
final VoidCallback? onCardTap;
final EdgeInsetsGeometry? margin;
@@ -28,6 +32,8 @@ class AdminUserCard extends StatefulWidget {
this.backgroundColor,
this.titleColor,
this.infoColor,
this.avatarBackgroundColor,
this.avatarIconColor,
this.vigilanceTooltip,
this.onCardTap,
this.margin,
@@ -159,8 +165,8 @@ class _AdminUserCardState extends State<AdminUserCard> {
Widget _buildAvatar(String url) {
const size = 28.0;
const bg = Color(0xFFEDE5FA);
const iconColor = Color(0xFF6B3FA0);
final bg = widget.avatarBackgroundColor ?? const Color(0xFFEDE5FA);
final iconColor = widget.avatarIconColor ?? const Color(0xFF6B3FA0);
if (url.isEmpty) {
return CircleAvatar(
+35 -10
View File
@@ -8,6 +8,8 @@ class DashboardUserManagementSubBar extends StatelessWidget {
final ValueChanged<int> onSubTabChange;
final TextEditingController searchController;
final String searchHint;
/// Infobulle au survol de la barre de recherche (ex. critères de recherche).
final String? searchTooltip;
final Widget? filterControl;
final VoidCallback? onAddPressed;
final String addLabel;
@@ -22,12 +24,16 @@ class DashboardUserManagementSubBar extends StatelessWidget {
'Administrateurs',
];
/// Aligné sur la taille des libellés donglets.
static const double _searchFontSize = 13;
const DashboardUserManagementSubBar({
Key? key,
required this.selectedSubIndex,
required this.onSubTabChange,
required this.searchController,
required this.searchHint,
this.searchTooltip,
this.filterControl,
this.onAddPressed,
this.addLabel = '+ Ajouter',
@@ -54,12 +60,32 @@ class DashboardUserManagementSubBar extends StatelessWidget {
_buildSubNavItem(context, labels[i], i),
],
const SizedBox(width: 36),
_pillField(
_buildSearchField(),
if (filterControl != null) ...[
const SizedBox(width: 12),
_pillField(width: 150, child: filterControl!),
],
const Spacer(),
if (onAddPressed != null) _buildAddButton(),
],
),
);
}
Widget _buildSearchField() {
final field = _pillField(
width: 320,
child: TextField(
controller: searchController,
style: const TextStyle(fontSize: _searchFontSize),
decoration: InputDecoration(
hintText: searchHint,
hintStyle: TextStyle(
fontSize: _searchFontSize,
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
color: Colors.black45,
),
prefixIcon: const Icon(Icons.search, size: 18),
border: InputBorder.none,
isDense: true,
@@ -69,15 +95,14 @@ class DashboardUserManagementSubBar extends StatelessWidget {
),
),
),
),
if (filterControl != null) ...[
const SizedBox(width: 12),
_pillField(width: 150, child: filterControl!),
],
const Spacer(),
if (onAddPressed != null) _buildAddButton(),
],
),
);
final tip = (searchTooltip ?? '').trim();
if (tip.isEmpty) return field;
return Tooltip(
message: tip,
preferBelow: false,
waitDuration: const Duration(milliseconds: 400),
child: field,
);
}
@@ -1,19 +1,20 @@
import 'package:flutter/material.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_user_card.dart';
/// Carte dossier unifiée (#153) — pending et liste complète.
/// Carte dossier unifiée (#153) — fond neutre, accent couleur sur licône.
class DossierListCard extends StatelessWidget {
final String numeroDossier;
final String namesLine;
final bool isFamille;
final VoidCallback onOpen;
/// Photo AM (si absente → icône fallback).
final String? photoUrl;
static const Color familleBg = Color(0xFFF3EEF8);
static const Color familleBorder = Color(0xFFB39DDB);
static const Color familleAccent = Color(0xFF7E57C2);
static const Color amBg = Color(0xFFE8F5F3);
static const Color amBorder = Color(0xFF80CBC4);
static const Color amAccent = Color(0xFF00897B);
/// Lavande — Famille / Parents.
static const Color familleAccent = Color(0xFFB289C9);
/// Menthe logo — AM.
static const Color amAccent = Color(0xFF5A9D94);
const DossierListCard({
super.key,
@@ -21,24 +22,24 @@ class DossierListCard extends StatelessWidget {
required this.namesLine,
required this.isFamille,
required this.onOpen,
this.photoUrl,
});
@override
Widget build(BuildContext context) {
final bg = isFamille ? familleBg : amBg;
final border = isFamille ? familleBorder : amBorder;
final accent = isFamille ? familleAccent : amAccent;
final num = numeroDossier.trim().isEmpty ? '' : numeroDossier.trim();
final names = namesLine.trim();
final avatar = (photoUrl ?? '').trim();
return AdminUserCard(
title: num,
subtitleLines: names.isEmpty ? const [] : [names],
avatarUrl: !isFamille && avatar.isNotEmpty ? avatar : null,
fallbackIcon:
isFamille ? Icons.family_restroom_outlined : Icons.person_outline,
backgroundColor: bg,
borderColor: border,
titleColor: accent,
isFamille ? Icons.family_restroom_outlined : Icons.face,
// N° = titre neutre (comme Parents / AM) ; accent = icône seule.
avatarIconColor: accent,
infoColor: Colors.black87,
onCardTap: onOpen,
actions: [
@@ -187,6 +187,7 @@ class _DossiersManagementWidgetState extends State<DossiersManagementWidget> {
numeroDossier: item.numeroDossier,
namesLine: item.namesLine,
isFamille: item.isFamille,
photoUrl: item.photoUrl,
onOpen: () => _openDossier(item.numeroDossier),
);
},
@@ -290,6 +290,7 @@ class _PendingValidationWidgetState extends State<PendingValidationWidget> {
numeroDossier: user.numeroDossier ?? '',
namesLine: _amNamesLine(user),
isFamille: false,
photoUrl: user.photoUrl,
onOpen: () => _onOpenValidation(numeroDossier: user.numeroDossier),
);
}
@@ -89,7 +89,7 @@ class _UserManagementPanelState extends State<UserManagementPanel> {
String _searchHintForTab() {
switch (_subIndex) {
case 0:
return 'Rechercher un dossier (n°, nom, email)…';
return 'Rechercher un dossier';
case 1:
return 'Rechercher un parent...';
case 2:
@@ -105,6 +105,11 @@ class _UserManagementPanelState extends State<UserManagementPanel> {
}
}
String? _searchTooltipForTab() {
if (_subIndex != 0) return null;
return 'Recherche possible : n° de dossier, nom, prénom ou e-mail.';
}
Widget? _subBarFilterControl() {
// Parents
if (_subIndex == 1) {
@@ -285,6 +290,7 @@ class _UserManagementPanelState extends State<UserManagementPanel> {
onSubTabChange: _onSubTabChange,
searchController: _searchController,
searchHint: _searchHintForTab(),
searchTooltip: _searchTooltipForTab(),
filterControl: _subBarFilterControl(),
// Pas de « Créer » sur longlet Dossiers (#153).
onAddPressed: _isDossiersTab ? null : _handleAddPressed,