import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/theme/app_theme.dart'; import '../../data/database/app_database.dart'; import '../../data/models/movement_detail.dart'; import '../../providers/database_provider.dart'; import '../../providers/parent_providers.dart'; import 'widgets/child_selector.dart'; import 'widgets/rules_grid.dart'; class ParentSpaceScreen extends ConsumerStatefulWidget { const ParentSpaceScreen({super.key}); @override ConsumerState createState() => _ParentSpaceScreenState(); } class _ParentSpaceScreenState extends ConsumerState { int? _selectedChildId; bool _isApplying = false; void _refreshData() { ref.invalidate(childrenProvider); ref.invalidate(movementsProvider); } Future _applyRule(int ruleId) async { final childId = _selectedChildId; if (childId == null || _isApplying) return; setState(() => _isApplying = true); try { final child = await ref .read(parentRepositoryProvider) .applyRule(childId, ruleId); if (!mounted) return; if (child == null) { _showFeedback('Erreur lors de l\'application.'); return; } _refreshData(); _showFeedback('${child.prenom} : ${child.score} pts !'); } finally { if (mounted) setState(() => _isApplying = false); } } Future _confirmCancel(MovementDetail movement) async { final sign = movement.delta > 0 ? '+' : ''; final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Annuler ce mouvement ?'), content: Text( 'Annuler « ${movement.label} » pour ${movement.childName} ' '($sign${movement.delta}) ?\nLes points seront recalculés.', ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Non'), ), FilledButton( onPressed: () => Navigator.pop(context, true), child: const Text('Oui, annuler'), ), ], ), ); if (confirmed != true || !mounted) return; final result = await ref .read(parentRepositoryProvider) .cancelMovement(movement.id); if (!mounted) return; if (result == null) { _showFeedback('Impossible d\'annuler.'); return; } _refreshData(); _showFeedback('Mouvement annulé'); } void _showFeedback(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message), duration: const Duration(seconds: 2)), ); } @override Widget build(BuildContext context) { final childrenAsync = ref.watch(childrenProvider); final rulesAsync = ref.watch(rulesProvider); final movementsAsync = ref.watch(movementsProvider); return DecoratedBox( decoration: const BoxDecoration(gradient: AppTheme.primaryGradient), child: Scaffold( backgroundColor: Colors.transparent, appBar: AppBar( leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.of(context).pop(), ), title: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.family_restroom, size: 22), SizedBox(width: 8), Text('Espace Parents'), ], ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Déconnexion'), ), ], ), body: childrenAsync.when( loading: () => const Center( child: CircularProgressIndicator(color: Colors.white), ), error: (_, _) => const Center( child: Text('Erreur', style: TextStyle(color: Colors.white)), ), data: (children) { if (children.isEmpty) { return const Center( child: Text( 'Aucun enfant', style: TextStyle(color: Colors.white), ), ); } _selectedChildId ??= children.first.id; final selected = children.firstWhere( (c) => c.id == _selectedChildId, orElse: () => children.first, ); return RefreshIndicator( onRefresh: () async => _refreshData(), child: ListView( padding: const EdgeInsets.fromLTRB(16, 0, 16, 24), children: [ _SectionCard( title: 'Enfant sélectionné', child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ChildSelector( children: children, selectedId: selected.id, onSelected: (id) => setState(() => _selectedChildId = id), ), const SizedBox(height: 12), _ScoreBanner(child: selected), ], ), ), const SizedBox(height: 12), _SectionCard( title: 'Appliquer une règle', child: rulesAsync.when( loading: () => const Center( child: Padding( padding: EdgeInsets.all(24), child: CircularProgressIndicator(), ), ), error: (_, _) => const Text('Erreur de chargement'), data: (rules) { if (rules.isEmpty) { return const Text( 'Aucune règle active. Importez le pack par défaut ' 'ou ajoutez des règles.', ); } return RulesGrid( rules: rules, isApplying: _isApplying, onRuleTap: _applyRule, ); }, ), ), const SizedBox(height: 12), _SectionCard( title: 'Derniers mouvements', child: movementsAsync.when( loading: () => const Center( child: Padding( padding: EdgeInsets.all(24), child: CircularProgressIndicator(), ), ), error: (_, _) => const Text('Erreur de chargement'), data: (movements) => _MovementHistory( movements: movements, onCancel: _confirmCancel, ), ), ), ], ), ); }, ), ), ); } } class _SectionCard extends StatelessWidget { const _SectionCard({required this.title, required this.child}); final String title; final Widget child; @override Widget build(BuildContext context) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( title, style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w700, color: const Color(0xFF667EEA), ), ), const SizedBox(height: 12), child, ], ), ), ); } } class _ScoreBanner extends StatelessWidget { const _ScoreBanner({required this.child}); final Enfant child; @override Widget build(BuildContext context) { final color = AppTheme.parseColor(child.couleur); return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( border: Border.all(color: color, width: 2), borderRadius: BorderRadius.circular(12), color: color.withValues(alpha: 0.08), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( child.prenom, style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 16), ), Text( '${child.score} points', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 18, color: color, ), ), ], ), ); } } class _MovementHistory extends StatelessWidget { const _MovementHistory({ required this.movements, required this.onCancel, }); final List movements; final void Function(MovementDetail) onCancel; @override Widget build(BuildContext context) { if (movements.isEmpty) { return Text( 'Aucun mouvement', style: TextStyle(color: Colors.grey.shade600), ); } return Column( children: movements.map((m) { final sign = m.delta > 0 ? '+' : ''; final deltaColor = m.delta >= 0 ? Colors.green.shade700 : Colors.red.shade700; return Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Text( '${m.icon} ${m.childName} — ${m.label}', style: const TextStyle(fontSize: 14), ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '$sign${m.delta}', style: TextStyle( fontWeight: FontWeight.bold, color: deltaColor, ), ), Text( _formatDate(m.createdAt), style: TextStyle(fontSize: 11, color: Colors.grey.shade600), ), ], ), IconButton( icon: const Icon(Icons.close, size: 20), color: Colors.grey.shade600, tooltip: 'Annuler', onPressed: () => onCancel(m), ), ], ), ); }).toList(), ); } static String _formatDate(DateTime dt) { final local = dt.toLocal(); String two(int n) => n.toString().padLeft(2, '0'); return '${two(local.day)}/${two(local.month)} ' '${two(local.hour)}:${two(local.minute)}'; } }