Refactor event type handling and add data management page (options and event types)

This commit is contained in:
ElPoyo
2025-10-15 19:01:09 +02:00
parent f10a608801
commit 5057bf9a77
16 changed files with 1561 additions and 61 deletions

View File

@@ -205,14 +205,17 @@ class _OptionSelectorWidgetState extends State<OptionSelectorWidget> {
final firestoreData = doc.data()!;
enrichedOptions.add({
'id': optionData['id'],
'name': firestoreData['name'],
'code': firestoreData['code'] ?? optionData['id'], // Récupérer le code
'name': firestoreData['code'] != null && firestoreData['code'].toString().isNotEmpty
? '${firestoreData['code']} - ${firestoreData['name']}'
: firestoreData['name'], // Affichage avec code
'details': firestoreData['details'] ?? '',
'price': optionData['price'],
});
} else {
enrichedOptions.add({
'id': optionData['id'],
'name': 'Option supprimée',
'name': 'Option supprimée (${optionData['id']})',
'details': 'Cette option n\'existe plus',
'price': optionData['price'],
});
@@ -274,7 +277,10 @@ class _OptionPickerDialogState extends State<_OptionPickerDialog> {
final matchesType = opt.eventTypes.contains(widget.eventType);
print(' -> matchesType: $matchesType');
final matchesSearch = opt.name.toLowerCase().contains(_search.toLowerCase());
// Recherche dans le code ET le nom
final searchLower = _search.toLowerCase();
final matchesSearch = opt.name.toLowerCase().contains(searchLower) ||
opt.code.toLowerCase().contains(searchLower);
print(' -> matchesSearch: $matchesSearch');
final result = matchesType && matchesSearch;
@@ -296,7 +302,7 @@ class _OptionPickerDialogState extends State<_OptionPickerDialog> {
padding: const EdgeInsets.all(12.0),
child: TextField(
decoration: const InputDecoration(
labelText: 'Rechercher une option',
labelText: 'Rechercher par code ou nom',
prefixIcon: Icon(Icons.search),
),
onChanged: (v) => setState(() => _search = v),
@@ -312,7 +318,7 @@ class _OptionPickerDialogState extends State<_OptionPickerDialog> {
itemBuilder: (context, i) {
final opt = filtered[i];
return ListTile(
title: Text(opt.name),
title: Text('${opt.code} - ${opt.name}'), // Affichage avec code
subtitle: Text('${opt.details}\nFourchette: ${opt.valMin}€ ~ ${opt.valMax}'),
onTap: () async {
final min = opt.valMin;
@@ -325,7 +331,7 @@ class _OptionPickerDialogState extends State<_OptionPickerDialog> {
final priceController =
TextEditingController(text: defaultPrice);
return AlertDialog(
title: Text('Prix pour ${opt.name}'),
title: Text('Prix pour ${opt.code} - ${opt.name}'), // Affichage avec code
content: TextField(
controller: priceController,
keyboardType:
@@ -408,22 +414,24 @@ class _CreateOptionDialog extends StatefulWidget {
class _CreateOptionDialogState extends State<_CreateOptionDialog> {
final _formKey = GlobalKey<FormState>();
final _codeController = TextEditingController(); // Nouveau champ code
final _nameController = TextEditingController();
final _detailsController = TextEditingController();
final _minPriceController = TextEditingController();
final _maxPriceController = TextEditingController();
final List<String> _selectedTypes = [];
String? _error;
bool _checkingName = false;
bool _checkingCode = false;
List<Map<String,dynamic>> _allEventTypes = [];
bool _loading = true;
Future<bool> _isNameUnique(String name) async {
final snap = await FirebaseFirestore.instance
Future<bool> _isCodeUnique(String code) async {
// Vérifier si le document avec ce code existe déjà
final doc = await FirebaseFirestore.instance
.collection('options')
.where('name', isEqualTo: name)
.doc(code)
.get();
return snap.docs.isEmpty;
return !doc.exists;
}
Future<void> _fetchEventTypes() async {
@@ -453,6 +461,25 @@ class _CreateOptionDialogState extends State<_CreateOptionDialog> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: _codeController,
decoration: const InputDecoration(
labelText: 'Code de l\'option',
hintText: 'Ex: M2',
helperText: 'Max 16 caractères, lettres, chiffres, _ et -',
),
maxLength: 16,
textCapitalization: TextCapitalization.characters,
validator: (v) {
if (v == null || v.isEmpty) return 'Champ requis';
if (v.length > 16) return 'Maximum 16 caractères';
if (!RegExp(r'^[A-Z0-9_-]+$').hasMatch(v)) {
return 'Seuls les lettres, chiffres, _ et - sont autorisés';
}
return null;
},
),
const SizedBox(height: 8),
TextFormField(
controller: _nameController,
decoration:
@@ -535,7 +562,7 @@ class _CreateOptionDialogState extends State<_CreateOptionDialog> {
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: _checkingName
onPressed: _checkingCode
? null
: () async {
if (!_formKey.currentState!.validate()) return;
@@ -553,19 +580,21 @@ class _CreateOptionDialogState extends State<_CreateOptionDialog> {
() => _error = 'Prix min et max doivent être valides');
return;
}
final code = _codeController.text.trim().toUpperCase();
final name = _nameController.text.trim();
setState(() => _checkingName = true);
final unique = await _isNameUnique(name);
setState(() => _checkingName = false);
setState(() => _checkingCode = true);
final unique = await _isCodeUnique(code);
setState(() => _checkingCode = false);
if (!unique) {
setState(
() => _error = 'Ce nom d\'option est déjà utilisé.');
() => _error = 'Ce code d\'option est déjà utilisé.');
return;
}
try {
// Debug : afficher le contenu envoyé
print('Enregistrement option avec eventTypes : $_selectedTypes\u001b');
await FirebaseFirestore.instance.collection('options').add({
// Utiliser le code comme identifiant du document
await FirebaseFirestore.instance.collection('options').doc(code).set({
'code': code,
'name': name,
'details': _detailsController.text.trim(),
'valMin': min,
@@ -574,10 +603,10 @@ class _CreateOptionDialogState extends State<_CreateOptionDialog> {
});
Navigator.pop(context, true);
} catch (e) {
setState(() => _error = 'Erreur lors de la création : $e\nEventTypes=$_selectedTypes');
setState(() => _error = 'Erreur lors de la création : $e');
}
},
child: _checkingName
child: _checkingCode
? const SizedBox(
width: 18,
height: 18,