Refactor event type handling and add data management page (options and event types)
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user