diff --git a/lib/features/members/presentation/pages/members_page_connected.dart b/lib/features/members/presentation/pages/members_page_connected.dart index 6766d5d..64d85f4 100644 --- a/lib/features/members/presentation/pages/members_page_connected.dart +++ b/lib/features/members/presentation/pages/members_page_connected.dart @@ -63,9 +63,9 @@ class _MembersPageState extends State { final ScrollController _scrollController = ScrollController(); String _searchQuery = ''; String _filterStatus = 'Tous'; + String _filterRole = 'Tous'; Timer? _searchDebounce; - bool _isSearchExpanded = false; bool _isGridView = false; bool _isSelectionMode = false; final Set _selectedIds = {}; @@ -144,6 +144,13 @@ class _MembersPageState extends State { }); } + static const _roleFilterMapping = { + 'Admin': 'Administrateur Org', + 'Modérateur': 'Modérateur', + 'Membre Actif': 'Membre Actif', + 'Membre Simple': 'Membre Simple', + }; + List> get _filteredMembers { return _accumulatedMembers.where((m) { final matchesSearch = _searchQuery.isEmpty || @@ -151,7 +158,9 @@ class _MembersPageState extends State { (m['email'] as String? ?? '').toLowerCase().contains(_searchQuery.toLowerCase()) || (m['numeroMembre'] as String? ?? '').toLowerCase().contains(_searchQuery.toLowerCase()); final matchesStatus = _filterStatus == 'Tous' || m['status'] == _filterStatus; - return matchesSearch && matchesStatus; + final matchesRole = _filterRole == 'Tous' || + (m['role'] as String? ?? '') == _roleFilterMapping[_filterRole]; + return matchesSearch && matchesStatus && matchesRole; }).toList(); } @@ -167,15 +176,7 @@ class _MembersPageState extends State { body: Column( children: [ _buildKpiHeader(context), - AnimatedCrossFade( - duration: const Duration(milliseconds: 220), - firstCurve: Curves.easeInOut, - secondCurve: Curves.easeInOut, - sizeCurve: Curves.easeInOut, - crossFadeState: _isSearchExpanded ? CrossFadeState.showFirst : CrossFadeState.showSecond, - firstChild: _buildSearchPanel(context), - secondChild: const SizedBox(width: double.infinity, height: 0), - ), + _buildSearchAndFilters(context), Expanded(child: _buildContent(context)), ], ), @@ -185,30 +186,10 @@ class _MembersPageState extends State { // ─── AppBar principale ──────────────────────────────────────────────────── PreferredSizeWidget _buildMainAppBar() { - final hasFilter = _filterStatus != 'Tous' || _searchQuery.isNotEmpty; return UFAppBar( title: 'Annuaire Membres', moduleGradient: ModuleColors.membresGradient, actions: [ - Stack( - children: [ - IconButton( - icon: Icon(_isSearchExpanded ? Icons.search_off : Icons.search), - onPressed: () => setState(() => _isSearchExpanded = !_isSearchExpanded), - tooltip: 'Recherche & filtres', - ), - if (hasFilter) - Positioned( - right: 10, - top: 10, - child: Container( - width: 7, - height: 7, - decoration: BoxDecoration(color: AppColors.warning, shape: BoxShape.circle), - ), - ), - ], - ), IconButton( icon: Icon(_isGridView ? Icons.view_list_outlined : Icons.grid_view_outlined), onPressed: () => setState(() => _isGridView = !_isGridView), @@ -220,6 +201,14 @@ class _MembersPageState extends State { onPressed: widget.onAddMember, tooltip: 'Ajouter un membre', ), + IconButton( + icon: const Icon(Icons.refresh), + onPressed: () { + setState(() => _accumulatedMembers.clear()); + widget.onRefresh(); + }, + tooltip: 'Rafraîchir', + ), const SizedBox(width: 4), ], ); @@ -337,9 +326,10 @@ class _MembersPageState extends State { Widget _kpiSeparator(BuildContext context) => Container(width: 1, height: 42, color: Theme.of(context).colorScheme.outlineVariant); - // ─── Panneau recherche collapsible ──────────────────────────────────────── + // ─── Recherche + Filtres (toujours visibles) ──────────────────────────── - Widget _buildSearchPanel(BuildContext context) { + Widget _buildSearchAndFilters(BuildContext context) { + final hasFilter = _filterStatus != 'Tous' || _filterRole != 'Tous' || _searchQuery.isNotEmpty; return Container( padding: const EdgeInsets.fromLTRB(12, 8, 12, 10), decoration: BoxDecoration( @@ -348,7 +338,9 @@ class _MembersPageState extends State { ), child: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ + // Barre de recherche TextField( controller: _searchController, onChanged: (v) { @@ -364,10 +356,7 @@ class _MembersPageState extends State { hintStyle: TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.onSurfaceVariant), prefixIcon: Icon(Icons.search, size: 20, color: ModuleColors.membres), suffixIcon: _searchQuery.isNotEmpty - ? IconButton( - icon: const Icon(Icons.clear, size: 18), - onPressed: _clearSearch, - ) + ? IconButton(icon: const Icon(Icons.clear, size: 18), onPressed: _clearSearch) : null, contentPadding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Theme.of(context).colorScheme.outline)), @@ -378,33 +367,88 @@ class _MembersPageState extends State { ), ), const SizedBox(height: 8), + + // Chips statut SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: ['Tous', 'Actif', 'Inactif', 'En attente', 'Suspendu'] .map((f) => Padding( padding: const EdgeInsets.only(right: 8), - child: _buildFilterChip(context, f), + child: _buildFilterChip(context, f, isStatus: true), )) .toList(), ), ), + const SizedBox(height: 6), + + // Chips rôle + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 6), + child: Icon(Icons.badge_outlined, size: 14, color: Theme.of(context).colorScheme.onSurfaceVariant), + ), + ...['Tous', 'Admin', 'Modérateur', 'Membre Actif', 'Membre Simple'] + .map((f) => Padding( + padding: const EdgeInsets.only(right: 8), + child: _buildFilterChip(context, f, isStatus: false), + )) + .toList(), + ], + ), + ), + + // Lien réinitialiser + if (hasFilter) + Padding( + padding: const EdgeInsets.only(top: 6), + child: GestureDetector( + onTap: () { + setState(() { + _filterStatus = 'Tous'; + _filterRole = 'Tous'; + _searchQuery = ''; + _searchController.clear(); + }); + widget.onSearch?.call(null); + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.filter_list_off, size: 13, color: ModuleColors.membres), + const SizedBox(width: 4), + Text('Réinitialiser les filtres', + style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: ModuleColors.membres)), + ], + ), + ), + ), ], ), ); } - Widget _buildFilterChip(BuildContext context, String label) { - final isSelected = _filterStatus == label; + Widget _buildFilterChip(BuildContext context, String label, {bool isStatus = true}) { + final isSelected = isStatus ? _filterStatus == label : _filterRole == label; + final accentColor = isStatus ? ModuleColors.membres : ModuleColors.organisationsDark; return GestureDetector( - onTap: () => setState(() => _filterStatus = label), + onTap: () => setState(() { + if (isStatus) { + _filterStatus = label; + } else { + _filterRole = label; + } + }), child: AnimatedContainer( duration: const Duration(milliseconds: 150), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( - color: isSelected ? ModuleColors.membres : Colors.transparent, + color: isSelected ? accentColor : Colors.transparent, borderRadius: BorderRadius.circular(20), - border: Border.all(color: isSelected ? ModuleColors.membres : Theme.of(context).colorScheme.outline, width: 1), + border: Border.all(color: isSelected ? accentColor : Theme.of(context).colorScheme.outline, width: 1), ), child: Text( label, @@ -926,7 +970,7 @@ class _MembersPageState extends State { // ─── Empty state enrichi ────────────────────────────────────────────────── Widget _buildEmptyState(BuildContext context) { - final hasFilters = _filterStatus != 'Tous' || _searchQuery.isNotEmpty; + final hasFilters = _filterStatus != 'Tous' || _filterRole != 'Tous' || _searchQuery.isNotEmpty; return Center( child: Padding( padding: const EdgeInsets.all(40), @@ -959,7 +1003,7 @@ class _MembersPageState extends State { if (hasFilters) FilledButton.icon( onPressed: () { - setState(() { _filterStatus = 'Tous'; _searchQuery = ''; _searchController.clear(); }); + setState(() { _filterStatus = 'Tous'; _filterRole = 'Tous'; _searchQuery = ''; _searchController.clear(); }); widget.onSearch?.call(null); }, icon: const Icon(Icons.filter_list_off, size: 18),