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:
@@ -11,6 +11,8 @@ class DossierListItem {
|
|||||||
final String libelle;
|
final String libelle;
|
||||||
final List<String> emails;
|
final List<String> emails;
|
||||||
final String? statut;
|
final String? statut;
|
||||||
|
/// Photo profil (AM) — affichée à la place de l’icône si présente.
|
||||||
|
final String? photoUrl;
|
||||||
|
|
||||||
const DossierListItem({
|
const DossierListItem({
|
||||||
required this.type,
|
required this.type,
|
||||||
@@ -18,6 +20,7 @@ class DossierListItem {
|
|||||||
required this.libelle,
|
required this.libelle,
|
||||||
this.emails = const [],
|
this.emails = const [],
|
||||||
this.statut,
|
this.statut,
|
||||||
|
this.photoUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
bool get isFamille => type == DossierListType.famille;
|
bool get isFamille => type == DossierListType.famille;
|
||||||
@@ -125,12 +128,14 @@ class DossierListItem {
|
|||||||
prenom: u.prenom,
|
prenom: u.prenom,
|
||||||
email: u.email,
|
email: u.email,
|
||||||
);
|
);
|
||||||
|
final photo = (u.photoUrl ?? '').trim();
|
||||||
return DossierListItem(
|
return DossierListItem(
|
||||||
type: DossierListType.assistanteMaternelle,
|
type: DossierListType.assistanteMaternelle,
|
||||||
numeroDossier: e.key,
|
numeroDossier: e.key,
|
||||||
libelle: name.isNotEmpty ? name : 'AM',
|
libelle: name.isNotEmpty ? name : 'AM',
|
||||||
emails: u.email.trim().isEmpty ? const [] : [u.email.trim()],
|
emails: u.email.trim().isEmpty ? const [] : [u.email.trim()],
|
||||||
statut: u.statut?.trim(),
|
statut: u.statut?.trim(),
|
||||||
|
photoUrl: photo.isEmpty ? null : photo,
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ class AdminUserCard extends StatefulWidget {
|
|||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
final Color? titleColor;
|
final Color? titleColor;
|
||||||
final Color? infoColor;
|
final Color? infoColor;
|
||||||
|
/// Fond du cercle avatar / icône (défaut lavande admin).
|
||||||
|
final Color? avatarBackgroundColor;
|
||||||
|
/// Couleur de l’icône fallback (défaut violet admin).
|
||||||
|
final Color? avatarIconColor;
|
||||||
final String? vigilanceTooltip;
|
final String? vigilanceTooltip;
|
||||||
final VoidCallback? onCardTap;
|
final VoidCallback? onCardTap;
|
||||||
final EdgeInsetsGeometry? margin;
|
final EdgeInsetsGeometry? margin;
|
||||||
@@ -28,6 +32,8 @@ class AdminUserCard extends StatefulWidget {
|
|||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
this.titleColor,
|
this.titleColor,
|
||||||
this.infoColor,
|
this.infoColor,
|
||||||
|
this.avatarBackgroundColor,
|
||||||
|
this.avatarIconColor,
|
||||||
this.vigilanceTooltip,
|
this.vigilanceTooltip,
|
||||||
this.onCardTap,
|
this.onCardTap,
|
||||||
this.margin,
|
this.margin,
|
||||||
@@ -159,8 +165,8 @@ class _AdminUserCardState extends State<AdminUserCard> {
|
|||||||
|
|
||||||
Widget _buildAvatar(String url) {
|
Widget _buildAvatar(String url) {
|
||||||
const size = 28.0;
|
const size = 28.0;
|
||||||
const bg = Color(0xFFEDE5FA);
|
final bg = widget.avatarBackgroundColor ?? const Color(0xFFEDE5FA);
|
||||||
const iconColor = Color(0xFF6B3FA0);
|
final iconColor = widget.avatarIconColor ?? const Color(0xFF6B3FA0);
|
||||||
|
|
||||||
if (url.isEmpty) {
|
if (url.isEmpty) {
|
||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ class DashboardUserManagementSubBar extends StatelessWidget {
|
|||||||
final ValueChanged<int> onSubTabChange;
|
final ValueChanged<int> onSubTabChange;
|
||||||
final TextEditingController searchController;
|
final TextEditingController searchController;
|
||||||
final String searchHint;
|
final String searchHint;
|
||||||
|
/// Infobulle au survol de la barre de recherche (ex. critères de recherche).
|
||||||
|
final String? searchTooltip;
|
||||||
final Widget? filterControl;
|
final Widget? filterControl;
|
||||||
final VoidCallback? onAddPressed;
|
final VoidCallback? onAddPressed;
|
||||||
final String addLabel;
|
final String addLabel;
|
||||||
@@ -22,12 +24,16 @@ class DashboardUserManagementSubBar extends StatelessWidget {
|
|||||||
'Administrateurs',
|
'Administrateurs',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// Aligné sur la taille des libellés d’onglets.
|
||||||
|
static const double _searchFontSize = 13;
|
||||||
|
|
||||||
const DashboardUserManagementSubBar({
|
const DashboardUserManagementSubBar({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.selectedSubIndex,
|
required this.selectedSubIndex,
|
||||||
required this.onSubTabChange,
|
required this.onSubTabChange,
|
||||||
required this.searchController,
|
required this.searchController,
|
||||||
required this.searchHint,
|
required this.searchHint,
|
||||||
|
this.searchTooltip,
|
||||||
this.filterControl,
|
this.filterControl,
|
||||||
this.onAddPressed,
|
this.onAddPressed,
|
||||||
this.addLabel = '+ Ajouter',
|
this.addLabel = '+ Ajouter',
|
||||||
@@ -54,22 +60,7 @@ class DashboardUserManagementSubBar extends StatelessWidget {
|
|||||||
_buildSubNavItem(context, labels[i], i),
|
_buildSubNavItem(context, labels[i], i),
|
||||||
],
|
],
|
||||||
const SizedBox(width: 36),
|
const SizedBox(width: 36),
|
||||||
_pillField(
|
_buildSearchField(),
|
||||||
width: 320,
|
|
||||||
child: TextField(
|
|
||||||
controller: searchController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: searchHint,
|
|
||||||
prefixIcon: const Icon(Icons.search, size: 18),
|
|
||||||
border: InputBorder.none,
|
|
||||||
isDense: true,
|
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 10,
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (filterControl != null) ...[
|
if (filterControl != null) ...[
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
_pillField(width: 150, child: filterControl!),
|
_pillField(width: 150, child: filterControl!),
|
||||||
@@ -81,6 +72,40 @@ class DashboardUserManagementSubBar extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final tip = (searchTooltip ?? '').trim();
|
||||||
|
if (tip.isEmpty) return field;
|
||||||
|
return Tooltip(
|
||||||
|
message: tip,
|
||||||
|
preferBelow: false,
|
||||||
|
waitDuration: const Duration(milliseconds: 400),
|
||||||
|
child: field,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _pillField({required double width, required Widget child}) {
|
Widget _pillField({required double width, required Widget child}) {
|
||||||
return Container(
|
return Container(
|
||||||
width: width,
|
width: width,
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:p_tits_pas/widgets/admin/common/admin_user_card.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 l’icône.
|
||||||
class DossierListCard extends StatelessWidget {
|
class DossierListCard extends StatelessWidget {
|
||||||
final String numeroDossier;
|
final String numeroDossier;
|
||||||
final String namesLine;
|
final String namesLine;
|
||||||
final bool isFamille;
|
final bool isFamille;
|
||||||
final VoidCallback onOpen;
|
final VoidCallback onOpen;
|
||||||
|
/// Photo AM (si absente → icône fallback).
|
||||||
|
final String? photoUrl;
|
||||||
|
|
||||||
static const Color familleBg = Color(0xFFF3EEF8);
|
/// Lavande — Famille / Parents.
|
||||||
static const Color familleBorder = Color(0xFFB39DDB);
|
static const Color familleAccent = Color(0xFFB289C9);
|
||||||
static const Color familleAccent = Color(0xFF7E57C2);
|
|
||||||
static const Color amBg = Color(0xFFE8F5F3);
|
/// Menthe logo — AM.
|
||||||
static const Color amBorder = Color(0xFF80CBC4);
|
static const Color amAccent = Color(0xFF5A9D94);
|
||||||
static const Color amAccent = Color(0xFF00897B);
|
|
||||||
|
|
||||||
const DossierListCard({
|
const DossierListCard({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -21,24 +22,24 @@ class DossierListCard extends StatelessWidget {
|
|||||||
required this.namesLine,
|
required this.namesLine,
|
||||||
required this.isFamille,
|
required this.isFamille,
|
||||||
required this.onOpen,
|
required this.onOpen,
|
||||||
|
this.photoUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final bg = isFamille ? familleBg : amBg;
|
|
||||||
final border = isFamille ? familleBorder : amBorder;
|
|
||||||
final accent = isFamille ? familleAccent : amAccent;
|
final accent = isFamille ? familleAccent : amAccent;
|
||||||
final num = numeroDossier.trim().isEmpty ? '–' : numeroDossier.trim();
|
final num = numeroDossier.trim().isEmpty ? '–' : numeroDossier.trim();
|
||||||
final names = namesLine.trim();
|
final names = namesLine.trim();
|
||||||
|
final avatar = (photoUrl ?? '').trim();
|
||||||
|
|
||||||
return AdminUserCard(
|
return AdminUserCard(
|
||||||
title: num,
|
title: num,
|
||||||
subtitleLines: names.isEmpty ? const [] : [names],
|
subtitleLines: names.isEmpty ? const [] : [names],
|
||||||
|
avatarUrl: !isFamille && avatar.isNotEmpty ? avatar : null,
|
||||||
fallbackIcon:
|
fallbackIcon:
|
||||||
isFamille ? Icons.family_restroom_outlined : Icons.person_outline,
|
isFamille ? Icons.family_restroom_outlined : Icons.face,
|
||||||
backgroundColor: bg,
|
// N° = titre neutre (comme Parents / AM) ; accent = icône seule.
|
||||||
borderColor: border,
|
avatarIconColor: accent,
|
||||||
titleColor: accent,
|
|
||||||
infoColor: Colors.black87,
|
infoColor: Colors.black87,
|
||||||
onCardTap: onOpen,
|
onCardTap: onOpen,
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ class _DossiersManagementWidgetState extends State<DossiersManagementWidget> {
|
|||||||
numeroDossier: item.numeroDossier,
|
numeroDossier: item.numeroDossier,
|
||||||
namesLine: item.namesLine,
|
namesLine: item.namesLine,
|
||||||
isFamille: item.isFamille,
|
isFamille: item.isFamille,
|
||||||
|
photoUrl: item.photoUrl,
|
||||||
onOpen: () => _openDossier(item.numeroDossier),
|
onOpen: () => _openDossier(item.numeroDossier),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -290,6 +290,7 @@ class _PendingValidationWidgetState extends State<PendingValidationWidget> {
|
|||||||
numeroDossier: user.numeroDossier ?? '',
|
numeroDossier: user.numeroDossier ?? '',
|
||||||
namesLine: _amNamesLine(user),
|
namesLine: _amNamesLine(user),
|
||||||
isFamille: false,
|
isFamille: false,
|
||||||
|
photoUrl: user.photoUrl,
|
||||||
onOpen: () => _onOpenValidation(numeroDossier: user.numeroDossier),
|
onOpen: () => _onOpenValidation(numeroDossier: user.numeroDossier),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class _UserManagementPanelState extends State<UserManagementPanel> {
|
|||||||
String _searchHintForTab() {
|
String _searchHintForTab() {
|
||||||
switch (_subIndex) {
|
switch (_subIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
return 'Rechercher un dossier (n°, nom, email)…';
|
return 'Rechercher un dossier';
|
||||||
case 1:
|
case 1:
|
||||||
return 'Rechercher un parent...';
|
return 'Rechercher un parent...';
|
||||||
case 2:
|
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() {
|
Widget? _subBarFilterControl() {
|
||||||
// Parents
|
// Parents
|
||||||
if (_subIndex == 1) {
|
if (_subIndex == 1) {
|
||||||
@@ -285,6 +290,7 @@ class _UserManagementPanelState extends State<UserManagementPanel> {
|
|||||||
onSubTabChange: _onSubTabChange,
|
onSubTabChange: _onSubTabChange,
|
||||||
searchController: _searchController,
|
searchController: _searchController,
|
||||||
searchHint: _searchHintForTab(),
|
searchHint: _searchHintForTab(),
|
||||||
|
searchTooltip: _searchTooltipForTab(),
|
||||||
filterControl: _subBarFilterControl(),
|
filterControl: _subBarFilterControl(),
|
||||||
// Pas de « Créer » sur l’onglet Dossiers (#153).
|
// Pas de « Créer » sur l’onglet Dossiers (#153).
|
||||||
onAddPressed: _isDossiersTab ? null : _handleAddPressed,
|
onAddPressed: _isDossiersTab ? null : _handleAddPressed,
|
||||||
|
|||||||
Reference in New Issue
Block a user