Version propre - Dashboard enhanced

This commit is contained in:
DahoudG
2025-09-13 19:05:06 +00:00
parent 3df010add7
commit 73459b3092
70 changed files with 15317 additions and 1498 deletions

View File

@@ -0,0 +1,338 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../core/di/injection.dart';
import '../../../../shared/theme/app_theme.dart';
import '../../../../shared/widgets/coming_soon_page.dart';
import '../bloc/cotisations_bloc.dart';
import '../bloc/cotisations_event.dart';
import '../bloc/cotisations_state.dart';
import '../widgets/cotisation_card.dart';
import '../widgets/cotisations_stats_card.dart';
/// Page principale pour la liste des cotisations
class CotisationsListPage extends StatefulWidget {
const CotisationsListPage({super.key});
@override
State<CotisationsListPage> createState() => _CotisationsListPageState();
}
class _CotisationsListPageState extends State<CotisationsListPage> {
late final CotisationsBloc _cotisationsBloc;
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_cotisationsBloc = getIt<CotisationsBloc>();
_scrollController.addListener(_onScroll);
// Charger les données initiales
_cotisationsBloc.add(const LoadCotisations());
_cotisationsBloc.add(const LoadCotisationsStats());
}
@override
void dispose() {
_scrollController.dispose();
_cotisationsBloc.close();
super.dispose();
}
void _onScroll() {
if (_isBottom) {
final currentState = _cotisationsBloc.state;
if (currentState is CotisationsLoaded && !currentState.hasReachedMax) {
_cotisationsBloc.add(LoadCotisations(
page: currentState.currentPage + 1,
size: 20,
));
}
}
}
bool get _isBottom {
if (!_scrollController.hasClients) return false;
final maxScroll = _scrollController.position.maxScrollExtent;
final currentScroll = _scrollController.offset;
return currentScroll >= (maxScroll * 0.9);
}
@override
Widget build(BuildContext context) {
return BlocProvider.value(
value: _cotisationsBloc,
child: Scaffold(
backgroundColor: AppTheme.backgroundLight,
body: Column(
children: [
// Header personnalisé
_buildHeader(),
// Contenu principal
Expanded(
child: BlocBuilder<CotisationsBloc, CotisationsState>(
builder: (context, state) {
if (state is CotisationsInitial ||
(state is CotisationsLoading && !state.isRefreshing)) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (state is CotisationsError) {
return _buildErrorState(state);
}
if (state is CotisationsLoaded) {
return _buildLoadedState(state);
}
// État par défaut - Coming Soon
return const ComingSoonPage(
title: 'Module Cotisations',
description: 'Gestion complète des cotisations avec paiements automatiques',
icon: Icons.payment_rounded,
color: AppTheme.accentColor,
features: [
'Tableau de bord des cotisations',
'Relances automatiques par email/SMS',
'Paiements en ligne sécurisés',
'Génération de reçus automatique',
'Suivi des retards de paiement',
'Rapports financiers détaillés',
],
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// TODO: Implémenter la création de cotisation
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Création de cotisation - En cours de développement'),
backgroundColor: AppTheme.accentColor,
),
);
},
backgroundColor: AppTheme.accentColor,
child: const Icon(Icons.add, color: Colors.white),
),
),
);
}
Widget _buildHeader() {
return Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
decoration: const BoxDecoration(
color: AppTheme.accentColor,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Cotisations',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Row(
children: [
IconButton(
icon: const Icon(Icons.search, color: Colors.white),
onPressed: () {
// TODO: Implémenter la recherche
},
),
IconButton(
icon: const Icon(Icons.filter_list, color: Colors.white),
onPressed: () {
// TODO: Implémenter les filtres
},
),
],
),
],
),
const SizedBox(height: 8),
const Text(
'Gérez les cotisations de vos membres',
style: TextStyle(
fontSize: 16,
color: Colors.white70,
),
),
],
),
);
}
Widget _buildLoadedState(CotisationsLoaded state) {
return RefreshIndicator(
onRefresh: () async {
_cotisationsBloc.add(const LoadCotisations(refresh: true));
_cotisationsBloc.add(const LoadCotisationsStats());
},
child: CustomScrollView(
controller: _scrollController,
slivers: [
// Statistiques
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(16),
child: BlocBuilder<CotisationsBloc, CotisationsState>(
buildWhen: (previous, current) => current is CotisationsStatsLoaded,
builder: (context, statsState) {
if (statsState is CotisationsStatsLoaded) {
return CotisationsStatsCard(statistics: statsState.statistics);
}
return const SizedBox.shrink();
},
),
),
),
// Liste des cotisations
if (state.filteredCotisations.isEmpty)
const SliverFillRemaining(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.payment_outlined,
size: 64,
color: AppTheme.textHint,
),
SizedBox(height: 16),
Text(
'Aucune cotisation trouvée',
style: TextStyle(
fontSize: 18,
color: AppTheme.textSecondary,
),
),
SizedBox(height: 8),
Text(
'Commencez par créer une cotisation',
style: TextStyle(
fontSize: 14,
color: AppTheme.textHint,
),
),
],
),
),
)
else
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index >= state.filteredCotisations.length) {
return state.hasReachedMax
? const SizedBox.shrink()
: const Padding(
padding: EdgeInsets.all(16),
child: Center(
child: CircularProgressIndicator(),
),
);
}
final cotisation = state.filteredCotisations[index];
return Padding(
padding: EdgeInsets.fromLTRB(
16,
index == 0 ? 0 : 8,
16,
index == state.filteredCotisations.length - 1 ? 16 : 8,
),
child: CotisationCard(
cotisation: cotisation,
onTap: () {
// TODO: Naviguer vers le détail
},
onPay: () {
// TODO: Implémenter le paiement
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Paiement - En cours de développement'),
backgroundColor: AppTheme.successColor,
),
);
},
),
);
},
childCount: state.filteredCotisations.length +
(state.hasReachedMax ? 0 : 1),
),
),
],
),
);
}
Widget _buildErrorState(CotisationsError state) {
return Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.error_outline,
size: 64,
color: AppTheme.errorColor,
),
const SizedBox(height: 16),
const Text(
'Erreur de chargement',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 8),
Text(
state.message,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 14,
color: AppTheme.textSecondary,
),
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: () {
_cotisationsBloc.add(const LoadCotisations(refresh: true));
_cotisationsBloc.add(const LoadCotisationsStats());
},
icon: const Icon(Icons.refresh),
label: const Text('Réessayer'),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.primaryColor,
foregroundColor: Colors.white,
),
),
],
),
),
);
}
}