import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../models/recompense.dart'; import '../services/recompense_repository.dart'; import '../theme.dart'; import '../utils/format.dart'; /// Onglet 3 : catalogue des récompenses (ce qu'un client peut obtenir avec ses /// points). Créer / modifier / activer / supprimer. class RecompensesScreen extends StatefulWidget { const RecompensesScreen({super.key}); @override State createState() => _RecompensesScreenState(); } class _RecompensesScreenState extends State { final _repo = RecompenseRepository.instance; Future _editer([Recompense? existante]) async { final nomCtrl = TextEditingController(text: existante?.nom ?? ''); final coutCtrl = TextEditingController( text: existante == null ? '' : points(existante.coutPoints) .replaceAll(RegExp(r'\s?pts?$'), '')); final formKey = GlobalKey(); final ok = await showDialog( context: context, builder: (ctx) => AlertDialog( title: Text(existante == null ? 'Nouvelle récompense' : 'Modifier'), content: Form( key: formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ TextFormField( controller: nomCtrl, autofocus: true, textCapitalization: TextCapitalization.sentences, decoration: const InputDecoration( labelText: 'Nom (ex : Café offert)', ), validator: (v) => (v == null || v.trim().isEmpty) ? 'Nom obligatoire' : null, ), const SizedBox(height: 12), TextFormField( controller: coutCtrl, keyboardType: const TextInputType.numberWithOptions(decimal: true), inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[0-9.,]')), ], decoration: const InputDecoration( labelText: 'Coût en points', suffixText: 'pts', ), validator: (v) { final n = double.tryParse((v ?? '').replaceAll(',', '.')); if (n == null || n <= 0) return 'Coût invalide'; return null; }, ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(false), child: const Text('Annuler'), ), FilledButton( onPressed: () { if (formKey.currentState!.validate()) Navigator.of(ctx).pop(true); }, child: const Text('Enregistrer'), ), ], ), ); if (ok != true) return; final cout = double.parse(coutCtrl.text.replaceAll(',', '.')); if (existante == null) { await _repo.creer(nom: nomCtrl.text, coutPoints: cout); } else { await _repo.modifier( existante.copyWith(nom: nomCtrl.text.trim(), coutPoints: cout)); } } Future _supprimer(Recompense r) async { final ok = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Supprimer ?'), content: Text('Supprimer la récompense « ${r.nom} » ?'), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(false), child: const Text('Annuler'), ), FilledButton( style: FilledButton.styleFrom(backgroundColor: Colors.red), onPressed: () => Navigator.of(ctx).pop(true), child: const Text('Supprimer'), ), ], ), ); if (ok == true) await _repo.supprimer(r.id); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Récompenses')), floatingActionButton: FloatingActionButton.extended( onPressed: () => _editer(), icon: const Icon(Icons.add), label: const Text('Récompense'), ), body: AnimatedBuilder( animation: _repo, builder: (context, _) { final liste = _repo.recompenses; if (liste.isEmpty) return _vide(); return ListView.separated( padding: const EdgeInsets.fromLTRB(16, 8, 16, 96), itemCount: liste.length, separatorBuilder: (_, _) => const SizedBox(height: 10), itemBuilder: (context, i) => _carte(liste[i]), ); }, ), ); } Widget _carte(Recompense r) { return Card( child: Padding( padding: const EdgeInsets.fromLTRB(14, 6, 6, 6), child: Row( children: [ CircleAvatar( backgroundColor: r.actif ? AppTheme.accent.withValues(alpha: 0.14) : Colors.grey.withValues(alpha: 0.14), child: Icon(Icons.card_giftcard, color: r.actif ? AppTheme.accent : Colors.grey), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(r.nom, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: r.actif ? null : AppTheme.grisTexte, )), const SizedBox(height: 2), Text(points(r.coutPoints), style: const TextStyle( color: AppTheme.grisTexte, fontSize: 13)), ], ), ), Switch( value: r.actif, onChanged: (v) => _repo.modifier(r.copyWith(actif: v)), ), PopupMenuButton( onSelected: (v) { if (v == 'modifier') _editer(r); if (v == 'supprimer') _supprimer(r); }, itemBuilder: (_) => const [ PopupMenuItem(value: 'modifier', child: Text('Modifier')), PopupMenuItem(value: 'supprimer', child: Text('Supprimer')), ], ), ], ), ), ); } Widget _vide() { return Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.card_giftcard_outlined, size: 64, color: AppTheme.grisTexte), const SizedBox(height: 16), const Text('Aucune récompense', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700)), const SizedBox(height: 8), const Text( 'Créez des récompenses (ex : « Café offert » = 10 pts) que les clients pourront obtenir avec leurs points.', textAlign: TextAlign.center, style: TextStyle(color: AppTheme.grisTexte), ), const SizedBox(height: 24), FilledButton.icon( onPressed: () => _editer(), icon: const Icon(Icons.add), label: const Text('Créer une récompense'), ), ], ), ), ); } }