import 'package:flutter/material.dart'; import '../../../../../shared/design_system/unionflow_design_system.dart'; import '../../../../members/presentation/pages/members_page_wrapper.dart'; import '../../../../events/presentation/pages/events_page_wrapper.dart'; import '../../../../contributions/presentation/pages/contributions_page_wrapper.dart'; import '../../../../reports/presentation/pages/reports_page_wrapper.dart'; import '../../../../settings/presentation/pages/system_settings_page.dart'; /// Widget de recherche rapide pour le dashboard class DashboardSearchWidget extends StatefulWidget { final Function(String)? onSearch; final String? hintText; final List? suggestions; const DashboardSearchWidget({ super.key, this.onSearch, this.hintText, this.suggestions, }); @override State createState() => _DashboardSearchWidgetState(); } class _DashboardSearchWidgetState extends State with TickerProviderStateMixin { final TextEditingController _searchController = TextEditingController(); final FocusNode _focusNode = FocusNode(); late AnimationController _animationController; late Animation _scaleAnimation; bool _isExpanded = false; List _filteredSuggestions = []; List? _defaultSuggestions; @override void initState() { super.initState(); _setupAnimations(); _setupListeners(); _filteredSuggestions = widget.suggestions ?? []; } void _setupAnimations() { _animationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _scaleAnimation = Tween( begin: 1.0, end: 1.05, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, )); } void _setupListeners() { _focusNode.addListener(() { setState(() { _isExpanded = _focusNode.hasFocus; }); if (_focusNode.hasFocus) { _animationController.forward(); } else { _animationController.reverse(); } }); _searchController.addListener(() { _filterSuggestions(_searchController.text); }); } void _filterSuggestions(String query) { if (query.isEmpty) { setState(() { _filteredSuggestions = widget.suggestions ?? _defaultSuggestions ?? []; }); return; } final defaultList = widget.suggestions ?? _defaultSuggestions ?? []; final filtered = defaultList .where((suggestion) => suggestion.title.toLowerCase().contains(query.toLowerCase()) || suggestion.subtitle.toLowerCase().contains(query.toLowerCase())) .toList(); setState(() { _filteredSuggestions = filtered; }); } @override Widget build(BuildContext context) { if (_defaultSuggestions == null) { _defaultSuggestions = _getDefaultSuggestions(context); if (_filteredSuggestions.isEmpty && widget.suggestions == null) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) setState(() => _filteredSuggestions = _defaultSuggestions!); }); } } return Column( children: [ _buildSearchBar(), if (_isExpanded && _filteredSuggestions.isNotEmpty) ...[ const SizedBox(height: 8), _buildSuggestions(), ], ], ); } Widget _buildSearchBar() { return AnimatedBuilder( animation: _scaleAnimation, builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: Container( decoration: BoxDecoration( color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(12), boxShadow: _isExpanded ? [BoxShadow(color: AppColors.shadowMedium, blurRadius: 10, offset: const Offset(0, 4))] : [BoxShadow(color: AppColors.shadow, blurRadius: 5, offset: const Offset(0, 2))], ), child: TextField( controller: _searchController, focusNode: _focusNode, onSubmitted: (value) { if (value.isNotEmpty) { widget.onSearch?.call(value); _focusNode.unfocus(); } }, decoration: InputDecoration( hintText: widget.hintText ?? 'Rechercher...', hintStyle: AppTypography.bodyTextSmall.copyWith( color: AppColors.textSecondary, ), prefixIcon: Icon( Icons.search_outlined, color: _isExpanded ? AppColors.primary : AppColors.textSecondary, size: 20, ), suffixIcon: _searchController.text.isNotEmpty ? IconButton( onPressed: () { _searchController.clear(); _focusNode.unfocus(); }, icon: const Icon( Icons.close_outlined, color: AppColors.textSecondary, size: 18, ), ) : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide( color: AppColors.primary, width: 1.5, ), ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), filled: true, fillColor: Theme.of(context).cardColor, ), style: AppTypography.bodyTextSmall, ), ), ); }, ); } Widget _buildSuggestions() { return Container( constraints: const BoxConstraints(maxHeight: 300), decoration: BoxDecoration( color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: AppColors.shadowMedium, blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: ListView.builder( shrinkWrap: true, itemCount: _filteredSuggestions.length, itemBuilder: (context, index) { final suggestion = _filteredSuggestions[index]; return _buildSuggestionItem(suggestion, index == _filteredSuggestions.length - 1); }, ), ); } Widget _buildSuggestionItem(SearchSuggestion suggestion, bool isLast) { return InkWell( onTap: () { _searchController.text = suggestion.title; widget.onSearch?.call(suggestion.title); _focusNode.unfocus(); suggestion.onTap?.call(); }, child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( border: isLast ? null : const Border( bottom: BorderSide( color: AppColors.border, width: 1, ), ), ), child: Row( children: [ Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: suggestion.color.withOpacity(0.1), borderRadius: BorderRadius.circular(6), ), child: Icon( suggestion.icon, color: suggestion.color, size: 18, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( suggestion.title, style: AppTypography.actionText.copyWith( fontSize: 12, fontWeight: FontWeight.w600, ), ), if (suggestion.subtitle.isNotEmpty) ...[ const SizedBox(height: 2), Text( suggestion.subtitle, style: AppTypography.subtitleSmall.copyWith( color: AppColors.textSecondary, fontSize: 10, ), ), ], ], ), ), const Icon( Icons.chevron_right_outlined, color: AppColors.textSecondary, size: 16, ), ], ), ), ); } List _getDefaultSuggestions(BuildContext context) { return [ SearchSuggestion( title: 'Membres', subtitle: 'Rechercher des membres', icon: Icons.people_outline, color: AppColors.primary, onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const MembersPageWrapper())), ), SearchSuggestion( title: 'Événements', subtitle: 'Trouver des événements', icon: Icons.event_outlined, color: AppColors.primaryDark, onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const EventsPageWrapper())), ), SearchSuggestion( title: 'Contributions', subtitle: 'Historique des paiements', icon: Icons.account_balance_wallet_outlined, color: AppColors.success, onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ContributionsPageWrapper())), ), SearchSuggestion( title: 'Rapports', subtitle: 'Consulter les rapports', icon: Icons.assessment_outlined, color: AppColors.warning, onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ReportsPageWrapper())), ), SearchSuggestion( title: 'Paramètres', subtitle: 'Configuration système', icon: Icons.settings_outlined, color: AppColors.textSecondary, onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const SystemSettingsPage())), ), ]; } @override void dispose() { _searchController.dispose(); _focusNode.dispose(); _animationController.dispose(); super.dispose(); } } class SearchSuggestion { final String title; final String subtitle; final IconData icon; final Color color; final VoidCallback? onTap; const SearchSuggestion({ required this.title, required this.subtitle, required this.icon, required this.color, this.onTap, }); }