import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import '../../data/models/compte_epargne_model.dart'; import '../../data/models/transaction_epargne_model.dart'; import '../../data/repositories/transaction_epargne_repository.dart'; import '../../../../shared/design_system/unionflow_design_system.dart'; /// Bottom sheet affichant l'historique complet des transactions d'un compte (charge et rafraîchit les données). class HistoriqueEpargneSheet extends StatefulWidget { final CompteEpargneModel compte; const HistoriqueEpargneSheet({ super.key, required this.compte, }); @override State createState() => _HistoriqueEpargneSheetState(); } class _HistoriqueEpargneSheetState extends State { List _transactions = []; bool _loading = true; String? _error; @override void initState() { super.initState(); _load(); } Future _load() async { if (widget.compte.id == null) { setState(() { _loading = false; _transactions = []; }); return; } setState(() { _loading = true; _error = null; }); try { final repo = GetIt.I(); final list = await repo.getByCompte(widget.compte.id!); if (!mounted) return; setState(() { _transactions = list.map((e) => TransactionEpargneModel.fromJson(e)).toList(); _loading = false; _error = null; }); } catch (e) { if (!mounted) return; setState(() { _transactions = []; _loading = false; _error = e.toString().replaceFirst('Exception: ', ''); }); } } String _libelleType(String? type) { if (type == null) return '—'; const map = { 'DEPOT': 'Dépôt', 'RETRAIT': 'Retrait', 'TRANSFERT_ENTRANT': 'Virement reçu', 'TRANSFERT_SORTANT': 'Virement envoyé', }; return map[type] ?? type; } @override Widget build(BuildContext context) { final compte = widget.compte; return DraggableScrollableSheet( initialChildSize: 0.6, minChildSize: 0.3, maxChildSize: 0.95, expand: false, builder: (context, scrollController) { return Column( children: [ Container( margin: const EdgeInsets.only(top: 12, bottom: 8), width: 40, height: 4, decoration: BoxDecoration( color: ColorTokens.onSurfaceVariant.withOpacity(0.4), borderRadius: BorderRadius.circular(2), ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: SpacingTokens.lg), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Historique — ${compte.numeroCompte ?? compte.id}', style: TypographyTokens.titleMedium, ), IconButton( icon: _loading ? const SizedBox( width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.refresh), onPressed: _loading ? null : _load, tooltip: 'Actualiser', ), ], ), ), const SizedBox(height: 8), Expanded( child: _loading ? const Center(child: CircularProgressIndicator()) : _error != null ? Center( child: Padding( padding: const EdgeInsets.all(SpacingTokens.lg), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(_error!, style: TextStyle(color: ColorTokens.error), textAlign: TextAlign.center), const SizedBox(height: SpacingTokens.md), FilledButton.tonal(onPressed: _load, child: const Text('Réessayer')), ], ), ), ) : _transactions.isEmpty ? Center( child: Text( 'Aucune transaction', style: TypographyTokens.bodySmall.copyWith(color: ColorTokens.onSurfaceVariant), ), ) : ListView.builder( controller: scrollController, padding: const EdgeInsets.symmetric(horizontal: SpacingTokens.lg, vertical: SpacingTokens.sm), itemCount: _transactions.length, itemBuilder: (context, index) { final t = _transactions[index]; return Card( margin: const EdgeInsets.only(bottom: SpacingTokens.sm), child: ListTile( leading: CircleAvatar( backgroundColor: t.isCredit ? ColorTokens.success.withOpacity(0.2) : ColorTokens.error.withOpacity(0.2), child: Icon( t.isCredit ? Icons.arrow_downward : Icons.arrow_upward, color: t.isCredit ? ColorTokens.success : ColorTokens.error, size: 20, ), ), title: Text( _libelleType(t.type), style: TypographyTokens.bodyMedium, ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (t.dateTransaction != null) Text( '${t.dateTransaction!.day.toString().padLeft(2, '0')}/${t.dateTransaction!.month.toString().padLeft(2, '0')}/${t.dateTransaction!.year} ${t.dateTransaction!.hour.toString().padLeft(2, '0')}:${t.dateTransaction!.minute.toString().padLeft(2, '0')}', style: TypographyTokens.bodySmall.copyWith(color: ColorTokens.onSurfaceVariant), ), if (t.motif != null && t.motif!.isNotEmpty) Text( t.motif!, style: TypographyTokens.bodySmall.copyWith(color: ColorTokens.onSurfaceVariant), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), trailing: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '${t.isCredit ? '+' : '-'}${t.montant.toStringAsFixed(0)} XOF', style: TypographyTokens.titleSmall.copyWith( color: t.isCredit ? ColorTokens.success : ColorTokens.error, fontWeight: FontWeight.w600, ), ), Text( 'Solde: ${t.soldeApres.toStringAsFixed(0)}', style: TypographyTokens.labelSmall.copyWith(color: ColorTokens.onSurfaceVariant), ), ], ), ), ); }, ), ), ], ); }, ); } }