|
|
@ -2,8 +2,90 @@ import 'dart:convert'; |
|
|
import 'package:flutter/foundation.dart'; |
|
|
import 'package:flutter/foundation.dart'; |
|
|
import 'package:flutter/material.dart'; |
|
|
import 'package:flutter/material.dart'; |
|
|
import 'package:http/http.dart' as http; |
|
|
import 'package:http/http.dart' as http; |
|
|
import './PlatEdit_screen.dart'; |
|
|
import 'package:intl/intl.dart'; |
|
|
|
|
|
|
|
|
|
|
|
// ===== MODÈLES UNIFIÉS ===== |
|
|
|
|
|
class MenuCategory { |
|
|
|
|
|
final int id; |
|
|
|
|
|
final String nom; |
|
|
|
|
|
final String? description; |
|
|
|
|
|
final int? ordre; |
|
|
|
|
|
final bool actif; |
|
|
|
|
|
|
|
|
|
|
|
MenuCategory({ |
|
|
|
|
|
required this.id, |
|
|
|
|
|
required this.nom, |
|
|
|
|
|
this.description, |
|
|
|
|
|
this.ordre, |
|
|
|
|
|
required this.actif, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
factory MenuCategory.fromJson(Map<String, dynamic> json) { |
|
|
|
|
|
return MenuCategory( |
|
|
|
|
|
id: json['id'], |
|
|
|
|
|
nom: json['nom'], |
|
|
|
|
|
description: json['description'], |
|
|
|
|
|
ordre: json['ordre'], |
|
|
|
|
|
actif: json['actif'] ?? true, |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
|
bool operator ==(Object other) => |
|
|
|
|
|
identical(this, other) || |
|
|
|
|
|
other is MenuCategory && |
|
|
|
|
|
runtimeType == other.runtimeType && |
|
|
|
|
|
id == other.id; |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
|
int get hashCode => id.hashCode; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class MenuPlat { |
|
|
|
|
|
final int id; |
|
|
|
|
|
final String nom; |
|
|
|
|
|
final String? commentaire; |
|
|
|
|
|
final double prix; |
|
|
|
|
|
final String? ingredients; |
|
|
|
|
|
final String? imageUrl; |
|
|
|
|
|
final MenuCategory? category; |
|
|
|
|
|
final bool disponible; |
|
|
|
|
|
|
|
|
|
|
|
MenuPlat({ |
|
|
|
|
|
required this.id, |
|
|
|
|
|
required this.nom, |
|
|
|
|
|
this.commentaire, |
|
|
|
|
|
required this.prix, |
|
|
|
|
|
this.ingredients, |
|
|
|
|
|
this.imageUrl, |
|
|
|
|
|
this.category, |
|
|
|
|
|
required this.disponible, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
factory MenuPlat.fromJson(Map<String, dynamic> json) { |
|
|
|
|
|
double parsePrix(dynamic p) { |
|
|
|
|
|
if (p is int) return p.toDouble(); |
|
|
|
|
|
if (p is double) return p; |
|
|
|
|
|
if (p is String) return double.tryParse(p) ?? 0; |
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return MenuPlat( |
|
|
|
|
|
id: json['id'], |
|
|
|
|
|
nom: json['nom'], |
|
|
|
|
|
commentaire: json['commentaire'], |
|
|
|
|
|
prix: parsePrix(json['prix']), |
|
|
|
|
|
ingredients: json['ingredients'], |
|
|
|
|
|
imageUrl: json['image_url'], |
|
|
|
|
|
category: json['category'] != null |
|
|
|
|
|
? MenuCategory.fromJson(json['category']) |
|
|
|
|
|
: null, |
|
|
|
|
|
disponible: json['disponible'] ?? true, |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ===== ÉCRAN PRINCIPAL ===== |
|
|
class PlatsManagementScreen extends StatefulWidget { |
|
|
class PlatsManagementScreen extends StatefulWidget { |
|
|
const PlatsManagementScreen({super.key}); |
|
|
const PlatsManagementScreen({super.key}); |
|
|
|
|
|
|
|
|
@ -34,6 +116,7 @@ class _PlatsManagementScreenState extends State<PlatsManagementScreen> { |
|
|
try { |
|
|
try { |
|
|
final res = await http.get(Uri.parse('$_baseUrl/menu-categories')); |
|
|
final res = await http.get(Uri.parse('$_baseUrl/menu-categories')); |
|
|
final data = json.decode(res.body); |
|
|
final data = json.decode(res.body); |
|
|
|
|
|
print(data); |
|
|
setState(() { |
|
|
setState(() { |
|
|
categories = |
|
|
categories = |
|
|
(data['data']['categories'] as List) |
|
|
(data['data']['categories'] as List) |
|
|
@ -72,10 +155,65 @@ class _PlatsManagementScreenState extends State<PlatsManagementScreen> { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Nouvelle méthode pour changer la disponibilité |
|
|
|
|
|
Future<void> _toggleDisponibilite(MenuPlat plat) async { |
|
|
|
|
|
try { |
|
|
|
|
|
final newDisponibilite = !plat.disponible; |
|
|
|
|
|
final res = await http.put( |
|
|
|
|
|
Uri.parse('$_baseUrl/menus/${plat.id}'), |
|
|
|
|
|
headers: {'Content-Type': 'application/json'}, |
|
|
|
|
|
body: json.encode({ |
|
|
|
|
|
"nom": plat.nom, |
|
|
|
|
|
"commentaire": plat.commentaire, |
|
|
|
|
|
"ingredients": plat.ingredients, |
|
|
|
|
|
"prix": plat.prix, |
|
|
|
|
|
"categorie_id": plat.category?.id, |
|
|
|
|
|
"disponible": newDisponibilite, |
|
|
|
|
|
}), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if (res.statusCode == 200) { |
|
|
|
|
|
_fetchPlats(); // Recharger la liste |
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar( |
|
|
|
|
|
SnackBar( |
|
|
|
|
|
content: Text( |
|
|
|
|
|
newDisponibilite |
|
|
|
|
|
? '${plat.nom} est maintenant disponible' |
|
|
|
|
|
: '${plat.nom} est maintenant indisponible' |
|
|
|
|
|
), |
|
|
|
|
|
backgroundColor: newDisponibilite ? Colors.green : Colors.orange, |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} else { |
|
|
|
|
|
if (kDebugMode) print("Erreur lors de la mise à jour: ${res.body}"); |
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar( |
|
|
|
|
|
const SnackBar( |
|
|
|
|
|
content: Text('Erreur lors de la mise à jour de la disponibilité'), |
|
|
|
|
|
backgroundColor: Colors.red, |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
if (kDebugMode) print("Erreur: $e"); |
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar( |
|
|
|
|
|
const SnackBar( |
|
|
|
|
|
content: Text('Erreur de connexion'), |
|
|
|
|
|
backgroundColor: Colors.red, |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
Future<void> _deletePlat(int id) async { |
|
|
Future<void> _deletePlat(int id) async { |
|
|
final res = await http.delete(Uri.parse('$_baseUrl/menus/$id')); |
|
|
final res = await http.delete(Uri.parse('$_baseUrl/menus/$id')); |
|
|
if (res.statusCode == 200) { |
|
|
if (res.statusCode == 200) { |
|
|
_fetchPlats(); |
|
|
_fetchPlats(); |
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar( |
|
|
|
|
|
const SnackBar( |
|
|
|
|
|
content: Text('Plat supprimé avec succès'), |
|
|
|
|
|
backgroundColor: Colors.green, |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
} else { |
|
|
} else { |
|
|
if (kDebugMode) print("Error deleting plat: ${res.body}"); |
|
|
if (kDebugMode) print("Error deleting plat: ${res.body}"); |
|
|
// ignore: use_build_context_synchronously |
|
|
// ignore: use_build_context_synchronously |
|
|
@ -88,8 +226,7 @@ class _PlatsManagementScreenState extends State<PlatsManagementScreen> { |
|
|
void _showEditPlatDialog(MenuPlat plat) { |
|
|
void _showEditPlatDialog(MenuPlat plat) { |
|
|
showDialog( |
|
|
showDialog( |
|
|
context: context, |
|
|
context: context, |
|
|
builder: |
|
|
builder: (_) => EditPlatDialog( |
|
|
(_) => EditPlatDialog( |
|
|
|
|
|
plat: plat, |
|
|
plat: plat, |
|
|
onPlatUpdated: _fetchPlats, |
|
|
onPlatUpdated: _fetchPlats, |
|
|
categories: categories, |
|
|
categories: categories, |
|
|
@ -113,8 +250,7 @@ class _PlatsManagementScreenState extends State<PlatsManagementScreen> { |
|
|
Padding( |
|
|
Padding( |
|
|
padding: const EdgeInsets.only(right: 16.0), |
|
|
padding: const EdgeInsets.only(right: 16.0), |
|
|
child: ElevatedButton.icon( |
|
|
child: ElevatedButton.icon( |
|
|
onPressed: |
|
|
onPressed: () => navigateToCreate( |
|
|
() => navigateToCreate( |
|
|
|
|
|
context, |
|
|
context, |
|
|
categories, |
|
|
categories, |
|
|
() => {_fetchPlats()}, |
|
|
() => {_fetchPlats()}, |
|
|
@ -212,6 +348,9 @@ class _PlatsManagementScreenState extends State<PlatsManagementScreen> { |
|
|
shape: RoundedRectangleBorder( |
|
|
shape: RoundedRectangleBorder( |
|
|
borderRadius: BorderRadius.circular(10), |
|
|
borderRadius: BorderRadius.circular(10), |
|
|
), |
|
|
), |
|
|
|
|
|
// Ajouter une opacité si le plat n'est pas disponible |
|
|
|
|
|
child: Opacity( |
|
|
|
|
|
opacity: p.disponible ? 1.0 : 0.6, |
|
|
child: Padding( |
|
|
child: Padding( |
|
|
padding: const EdgeInsets.symmetric( |
|
|
padding: const EdgeInsets.symmetric( |
|
|
vertical: 16, |
|
|
vertical: 16, |
|
|
@ -222,13 +361,51 @@ class _PlatsManagementScreenState extends State<PlatsManagementScreen> { |
|
|
children: [ |
|
|
children: [ |
|
|
Expanded( |
|
|
Expanded( |
|
|
flex: 2, |
|
|
flex: 2, |
|
|
child: Text( |
|
|
child: Column( |
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start, |
|
|
|
|
|
children: [ |
|
|
|
|
|
Text( |
|
|
p.nom, |
|
|
p.nom, |
|
|
style: const TextStyle( |
|
|
style: TextStyle( |
|
|
fontWeight: FontWeight.w600, |
|
|
fontWeight: FontWeight.w600, |
|
|
fontSize: 18, |
|
|
fontSize: 18, |
|
|
|
|
|
decoration: p.disponible |
|
|
|
|
|
? TextDecoration.none |
|
|
|
|
|
: TextDecoration.lineThrough, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(height: 4), |
|
|
|
|
|
// Badge de statut |
|
|
|
|
|
Container( |
|
|
|
|
|
padding: const EdgeInsets.symmetric( |
|
|
|
|
|
horizontal: 8, |
|
|
|
|
|
vertical: 2, |
|
|
|
|
|
), |
|
|
|
|
|
decoration: BoxDecoration( |
|
|
|
|
|
color: p.disponible |
|
|
|
|
|
? Colors.green.withOpacity(0.1) |
|
|
|
|
|
: Colors.red.withOpacity(0.1), |
|
|
|
|
|
borderRadius: BorderRadius.circular(12), |
|
|
|
|
|
border: Border.all( |
|
|
|
|
|
color: p.disponible |
|
|
|
|
|
? Colors.green |
|
|
|
|
|
: Colors.red, |
|
|
|
|
|
width: 1, |
|
|
), |
|
|
), |
|
|
), |
|
|
), |
|
|
|
|
|
child: Text( |
|
|
|
|
|
p.disponible ? 'Disponible' : 'Indisponible', |
|
|
|
|
|
style: TextStyle( |
|
|
|
|
|
color: p.disponible |
|
|
|
|
|
? Colors.green[700] |
|
|
|
|
|
: Colors.red[700], |
|
|
|
|
|
fontSize: 11, |
|
|
|
|
|
fontWeight: FontWeight.w600, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
), |
|
|
), |
|
|
Expanded( |
|
|
Expanded( |
|
|
flex: 6, |
|
|
flex: 6, |
|
|
@ -271,19 +448,81 @@ class _PlatsManagementScreenState extends State<PlatsManagementScreen> { |
|
|
), |
|
|
), |
|
|
), |
|
|
), |
|
|
Expanded( |
|
|
Expanded( |
|
|
flex: 2, |
|
|
flex: 3, |
|
|
child: Row( |
|
|
child: Column( |
|
|
mainAxisAlignment: MainAxisAlignment.end, |
|
|
crossAxisAlignment: CrossAxisAlignment.end, |
|
|
children: [ |
|
|
children: [ |
|
|
Text( |
|
|
Text( |
|
|
"${p.prix.toStringAsFixed(2)} MGA", |
|
|
"${NumberFormat("#,##0.00", "fr_FR").format(p.prix)} MGA", |
|
|
style: const TextStyle( |
|
|
style: const TextStyle( |
|
|
fontWeight: FontWeight.bold, |
|
|
fontWeight: FontWeight.bold, |
|
|
color: Colors.green, |
|
|
color: Colors.green, |
|
|
fontSize: 20, |
|
|
fontSize: 20, |
|
|
), |
|
|
), |
|
|
), |
|
|
), |
|
|
const SizedBox(width: 10), |
|
|
const SizedBox(height: 8), |
|
|
|
|
|
Row( |
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.end, |
|
|
|
|
|
children: [ |
|
|
|
|
|
// Bouton de disponibilité |
|
|
|
|
|
SizedBox( |
|
|
|
|
|
height: 38, |
|
|
|
|
|
width: 38, |
|
|
|
|
|
child: IconButton( |
|
|
|
|
|
icon: Icon( |
|
|
|
|
|
p.disponible |
|
|
|
|
|
? Icons.visibility |
|
|
|
|
|
: Icons.visibility_off, |
|
|
|
|
|
color: p.disponible |
|
|
|
|
|
? Colors.green |
|
|
|
|
|
: Colors.red, |
|
|
|
|
|
), |
|
|
|
|
|
tooltip: p.disponible |
|
|
|
|
|
? 'Rendre indisponible' |
|
|
|
|
|
: 'Rendre disponible', |
|
|
|
|
|
onPressed: () async { |
|
|
|
|
|
final confirm = await showDialog<bool>( |
|
|
|
|
|
context: context, |
|
|
|
|
|
builder: (context) => AlertDialog( |
|
|
|
|
|
title: Text( |
|
|
|
|
|
p.disponible |
|
|
|
|
|
? 'Rendre indisponible ?' |
|
|
|
|
|
: 'Rendre disponible ?' |
|
|
|
|
|
), |
|
|
|
|
|
content: Text( |
|
|
|
|
|
p.disponible |
|
|
|
|
|
? '${p.nom} ne sera plus visible aux clients' |
|
|
|
|
|
: '${p.nom} sera de nouveau visible aux clients' |
|
|
|
|
|
), |
|
|
|
|
|
actions: [ |
|
|
|
|
|
TextButton( |
|
|
|
|
|
onPressed: () => Navigator.pop(context, false), |
|
|
|
|
|
child: const Text('Annuler'), |
|
|
|
|
|
), |
|
|
|
|
|
ElevatedButton( |
|
|
|
|
|
onPressed: () => Navigator.pop(context, true), |
|
|
|
|
|
style: ElevatedButton.styleFrom( |
|
|
|
|
|
backgroundColor: p.disponible |
|
|
|
|
|
? Colors.orange |
|
|
|
|
|
: Colors.green, |
|
|
|
|
|
), |
|
|
|
|
|
child: Text( |
|
|
|
|
|
p.disponible |
|
|
|
|
|
? 'Rendre indisponible' |
|
|
|
|
|
: 'Rendre disponible', |
|
|
|
|
|
style: const TextStyle(color: Colors.white), |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if (confirm == true) { |
|
|
|
|
|
_toggleDisponibilite(p); |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
SizedBox( |
|
|
SizedBox( |
|
|
height: 38, |
|
|
height: 38, |
|
|
width: 38, |
|
|
width: 38, |
|
|
@ -306,8 +545,7 @@ class _PlatsManagementScreenState extends State<PlatsManagementScreen> { |
|
|
onPressed: () async { |
|
|
onPressed: () async { |
|
|
final confirm = await showDialog( |
|
|
final confirm = await showDialog( |
|
|
context: context, |
|
|
context: context, |
|
|
builder: |
|
|
builder: (_) => AlertDialog( |
|
|
(_) => AlertDialog( |
|
|
|
|
|
title: const Text( |
|
|
title: const Text( |
|
|
'Supprimer ce plat ?', |
|
|
'Supprimer ce plat ?', |
|
|
), |
|
|
), |
|
|
@ -316,23 +554,19 @@ class _PlatsManagementScreenState extends State<PlatsManagementScreen> { |
|
|
), |
|
|
), |
|
|
actions: [ |
|
|
actions: [ |
|
|
TextButton( |
|
|
TextButton( |
|
|
onPressed: |
|
|
onPressed: () => Navigator.pop( |
|
|
() => Navigator.pop( |
|
|
|
|
|
context, |
|
|
context, |
|
|
false, |
|
|
false, |
|
|
), |
|
|
), |
|
|
child: const Text("Annuler"), |
|
|
child: const Text("Annuler"), |
|
|
), |
|
|
), |
|
|
ElevatedButton( |
|
|
ElevatedButton( |
|
|
onPressed: |
|
|
onPressed: () => Navigator.pop( |
|
|
() => Navigator.pop( |
|
|
|
|
|
context, |
|
|
context, |
|
|
true, |
|
|
true, |
|
|
), |
|
|
), |
|
|
style: |
|
|
style: ElevatedButton.styleFrom( |
|
|
ElevatedButton.styleFrom( |
|
|
backgroundColor: Colors.red, |
|
|
backgroundColor: |
|
|
|
|
|
Colors.red, |
|
|
|
|
|
), |
|
|
), |
|
|
child: const Text( |
|
|
child: const Text( |
|
|
"Supprimer", |
|
|
"Supprimer", |
|
|
@ -350,10 +584,13 @@ class _PlatsManagementScreenState extends State<PlatsManagementScreen> { |
|
|
), |
|
|
), |
|
|
], |
|
|
], |
|
|
), |
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
), |
|
|
), |
|
|
], |
|
|
], |
|
|
), |
|
|
), |
|
|
), |
|
|
), |
|
|
|
|
|
), |
|
|
); |
|
|
); |
|
|
}, |
|
|
}, |
|
|
), |
|
|
), |
|
|
@ -364,51 +601,12 @@ class _PlatsManagementScreenState extends State<PlatsManagementScreen> { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class MenuPlat { |
|
|
// ===== COMPOSANTS ===== |
|
|
final int id; |
|
|
|
|
|
final String nom; |
|
|
|
|
|
final String? commentaire; |
|
|
|
|
|
final double prix; |
|
|
|
|
|
final String? ingredients; |
|
|
|
|
|
final MenuCategory? category; // single category |
|
|
|
|
|
|
|
|
|
|
|
MenuPlat({ |
|
|
|
|
|
required this.id, |
|
|
|
|
|
required this.nom, |
|
|
|
|
|
this.commentaire, |
|
|
|
|
|
required this.prix, |
|
|
|
|
|
this.ingredients, |
|
|
|
|
|
this.category, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
factory MenuPlat.fromJson(Map<String, dynamic> json) { |
|
|
|
|
|
double parsePrix(dynamic p) { |
|
|
|
|
|
if (p is int) return p.toDouble(); |
|
|
|
|
|
if (p is double) return p; |
|
|
|
|
|
if (p is String) return double.tryParse(p) ?? 0; |
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return MenuPlat( |
|
|
|
|
|
id: json['id'], |
|
|
|
|
|
nom: json['nom'], |
|
|
|
|
|
commentaire: json['commentaire'], |
|
|
|
|
|
prix: parsePrix(json['prix']), |
|
|
|
|
|
ingredients: json['ingredients'], |
|
|
|
|
|
category: |
|
|
|
|
|
json['category'] != null |
|
|
|
|
|
? MenuCategory.fromJson(json['category']) |
|
|
|
|
|
: null, |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
get disponible => null; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class CategoryChip extends StatelessWidget { |
|
|
class CategoryChip extends StatelessWidget { |
|
|
final String label; |
|
|
final String label; |
|
|
final Color color; |
|
|
final Color color; |
|
|
const CategoryChip({super.key, required this.label, required this.color}); |
|
|
const CategoryChip({super.key, required this.label, required this.color}); |
|
|
|
|
|
|
|
|
@override |
|
|
@override |
|
|
Widget build(BuildContext context) => Container( |
|
|
Widget build(BuildContext context) => Container( |
|
|
decoration: BoxDecoration( |
|
|
decoration: BoxDecoration( |
|
|
@ -424,23 +622,12 @@ class CategoryChip extends StatelessWidget { |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Future<void> navigateToCreate( |
|
|
// ===== DIALOGUE D'ÉDITION ===== |
|
|
BuildContext context, |
|
|
|
|
|
List<MenuCategory> categories, |
|
|
|
|
|
Function()? onSaved, |
|
|
|
|
|
) async { |
|
|
|
|
|
await Navigator.push( |
|
|
|
|
|
context, |
|
|
|
|
|
MaterialPageRoute( |
|
|
|
|
|
builder: (_) => PlatEditPage(categories: categories, onSaved: onSaved), |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class EditPlatDialog extends StatefulWidget { |
|
|
class EditPlatDialog extends StatefulWidget { |
|
|
final MenuPlat plat; |
|
|
final MenuPlat plat; |
|
|
final List<MenuCategory> categories; |
|
|
final List<MenuCategory> categories; |
|
|
final VoidCallback onPlatUpdated; |
|
|
final VoidCallback onPlatUpdated; |
|
|
|
|
|
|
|
|
const EditPlatDialog({ |
|
|
const EditPlatDialog({ |
|
|
super.key, |
|
|
super.key, |
|
|
required this.plat, |
|
|
required this.plat, |
|
|
@ -457,7 +644,7 @@ class _EditPlatDialogState extends State<EditPlatDialog> { |
|
|
late String commentaire; |
|
|
late String commentaire; |
|
|
late String ingredients; |
|
|
late String ingredients; |
|
|
late double prix; |
|
|
late double prix; |
|
|
// late bool disponible; |
|
|
late bool disponible; |
|
|
MenuCategory? cat; |
|
|
MenuCategory? cat; |
|
|
final _formKey = GlobalKey<FormState>(); |
|
|
final _formKey = GlobalKey<FormState>(); |
|
|
|
|
|
|
|
|
@ -468,12 +655,14 @@ class _EditPlatDialogState extends State<EditPlatDialog> { |
|
|
commentaire = widget.plat.commentaire ?? ''; |
|
|
commentaire = widget.plat.commentaire ?? ''; |
|
|
ingredients = widget.plat.ingredients ?? ''; |
|
|
ingredients = widget.plat.ingredients ?? ''; |
|
|
prix = widget.plat.prix; |
|
|
prix = widget.plat.prix; |
|
|
// cat = (widget.plat.categories) as MenuCategory?; |
|
|
disponible = widget.plat.disponible; |
|
|
cat = widget.plat.category; |
|
|
cat = widget.plat.category; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Future<void> submit() async { |
|
|
Future<void> submit() async { |
|
|
if (!_formKey.currentState!.validate()) return; |
|
|
if (!_formKey.currentState!.validate()) return; |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
final res = await http.put( |
|
|
final res = await http.put( |
|
|
Uri.parse( |
|
|
Uri.parse( |
|
|
'https://restaurant.careeracademy.mg/api/menus/${widget.plat.id}', |
|
|
'https://restaurant.careeracademy.mg/api/menus/${widget.plat.id}', |
|
|
@ -485,12 +674,38 @@ class _EditPlatDialogState extends State<EditPlatDialog> { |
|
|
"ingredients": ingredients, |
|
|
"ingredients": ingredients, |
|
|
"prix": prix, |
|
|
"prix": prix, |
|
|
"categorie_id": cat?.id, |
|
|
"categorie_id": cat?.id, |
|
|
|
|
|
"disponible": disponible, |
|
|
}), |
|
|
}), |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
if (res.statusCode == 200) { |
|
|
if (res.statusCode == 200) { |
|
|
widget.onPlatUpdated(); |
|
|
widget.onPlatUpdated(); |
|
|
// ignore: use_build_context_synchronously |
|
|
// ignore: use_build_context_synchronously |
|
|
Navigator.pop(context); |
|
|
Navigator.pop(context); |
|
|
|
|
|
// ignore: use_build_context_synchronously |
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar( |
|
|
|
|
|
const SnackBar( |
|
|
|
|
|
content: Text('Plat modifié avec succès'), |
|
|
|
|
|
backgroundColor: Colors.green, |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} else { |
|
|
|
|
|
// ignore: use_build_context_synchronously |
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar( |
|
|
|
|
|
SnackBar( |
|
|
|
|
|
content: Text('Erreur: ${res.body}'), |
|
|
|
|
|
backgroundColor: Colors.red, |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
// ignore: use_build_context_synchronously |
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar( |
|
|
|
|
|
SnackBar( |
|
|
|
|
|
content: Text('Erreur réseau: $e'), |
|
|
|
|
|
backgroundColor: Colors.red, |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -509,37 +724,63 @@ class _EditPlatDialogState extends State<EditPlatDialog> { |
|
|
decoration: const InputDecoration(labelText: "Nom"), |
|
|
decoration: const InputDecoration(labelText: "Nom"), |
|
|
validator: (v) => (v?.isEmpty ?? true) ? "Obligatoire" : null, |
|
|
validator: (v) => (v?.isEmpty ?? true) ? "Obligatoire" : null, |
|
|
), |
|
|
), |
|
|
|
|
|
const SizedBox(height: 8), |
|
|
TextFormField( |
|
|
TextFormField( |
|
|
initialValue: commentaire, |
|
|
initialValue: commentaire, |
|
|
onChanged: (v) => commentaire = v, |
|
|
onChanged: (v) => commentaire = v, |
|
|
decoration: const InputDecoration(labelText: "Commentaire"), |
|
|
decoration: const InputDecoration(labelText: "Commentaire"), |
|
|
|
|
|
maxLines: 2, |
|
|
), |
|
|
), |
|
|
|
|
|
const SizedBox(height: 8), |
|
|
TextFormField( |
|
|
TextFormField( |
|
|
initialValue: ingredients, |
|
|
initialValue: ingredients, |
|
|
onChanged: (v) => ingredients = v, |
|
|
onChanged: (v) => ingredients = v, |
|
|
decoration: const InputDecoration(labelText: "Ingrédients"), |
|
|
decoration: const InputDecoration(labelText: "Ingrédients"), |
|
|
|
|
|
maxLines: 2, |
|
|
), |
|
|
), |
|
|
|
|
|
const SizedBox(height: 8), |
|
|
TextFormField( |
|
|
TextFormField( |
|
|
initialValue: prix.toString(), |
|
|
initialValue: prix.toString(), |
|
|
onChanged: (v) => prix = double.tryParse(v) ?? 0, |
|
|
onChanged: (v) => prix = double.tryParse(v) ?? 0, |
|
|
decoration: const InputDecoration(labelText: "Prix"), |
|
|
decoration: const InputDecoration(labelText: "Prix (MGA)"), |
|
|
keyboardType: const TextInputType.numberWithOptions( |
|
|
keyboardType: const TextInputType.numberWithOptions( |
|
|
decimal: true, |
|
|
decimal: true, |
|
|
), |
|
|
), |
|
|
|
|
|
validator: (v) => (v?.isEmpty ?? true || double.tryParse(v!) == null) |
|
|
|
|
|
? "Prix obligatoire" : null, |
|
|
), |
|
|
), |
|
|
DropdownButton<MenuCategory>( |
|
|
const SizedBox(height: 16), |
|
|
hint: const Text("Catégorie"), |
|
|
DropdownButtonFormField<MenuCategory>( |
|
|
value: cat, |
|
|
value: cat, |
|
|
isExpanded: true, |
|
|
hint: const Text("Catégorie"), |
|
|
items: |
|
|
decoration: const InputDecoration( |
|
|
widget.categories |
|
|
labelText: "Catégorie", |
|
|
.toSet() |
|
|
border: OutlineInputBorder(), |
|
|
|
|
|
), |
|
|
|
|
|
items: widget.categories |
|
|
.map( |
|
|
.map( |
|
|
(c) => DropdownMenuItem(value: c, child: Text(c.nom)), |
|
|
(c) => DropdownMenuItem(value: c, child: Text(c.nom)), |
|
|
) |
|
|
) |
|
|
.toList(), |
|
|
.toList(), |
|
|
onChanged: (v) => setState(() => cat = v), |
|
|
onChanged: (v) => setState(() => cat = v), |
|
|
), |
|
|
), |
|
|
|
|
|
const SizedBox(height: 16), |
|
|
|
|
|
Row( |
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|
|
|
|
|
children: [ |
|
|
|
|
|
const Text( |
|
|
|
|
|
"Disponible", |
|
|
|
|
|
style: TextStyle(fontSize: 16), |
|
|
|
|
|
), |
|
|
|
|
|
Switch( |
|
|
|
|
|
value: disponible, |
|
|
|
|
|
onChanged: (value) { |
|
|
|
|
|
setState(() => disponible = value); |
|
|
|
|
|
}, |
|
|
|
|
|
activeColor: Colors.green, |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
], |
|
|
], |
|
|
), |
|
|
), |
|
|
), |
|
|
), |
|
|
@ -549,7 +790,368 @@ class _EditPlatDialogState extends State<EditPlatDialog> { |
|
|
onPressed: () => Navigator.pop(context), |
|
|
onPressed: () => Navigator.pop(context), |
|
|
child: const Text("Annuler"), |
|
|
child: const Text("Annuler"), |
|
|
), |
|
|
), |
|
|
ElevatedButton(onPressed: submit, child: const Text("Enregistrer")), |
|
|
ElevatedButton( |
|
|
|
|
|
onPressed: submit, |
|
|
|
|
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.green), |
|
|
|
|
|
child: const Text("Enregistrer", style: TextStyle(color: Colors.white)), |
|
|
|
|
|
), |
|
|
], |
|
|
], |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ===== PAGE DE CRÉATION/ÉDITION ===== |
|
|
|
|
|
class PlatEditPage extends StatefulWidget { |
|
|
|
|
|
final List<MenuCategory> categories; |
|
|
|
|
|
final MenuPlat? plat; |
|
|
|
|
|
final Function()? onSaved; |
|
|
|
|
|
|
|
|
|
|
|
const PlatEditPage({ |
|
|
|
|
|
super.key, |
|
|
|
|
|
required this.categories, |
|
|
|
|
|
this.plat, |
|
|
|
|
|
this.onSaved, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
|
State<PlatEditPage> createState() => _PlatEditPageState(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class _PlatEditPageState extends State<PlatEditPage> { |
|
|
|
|
|
final _formKey = GlobalKey<FormState>(); |
|
|
|
|
|
late TextEditingController nomCtrl, descCtrl, prixCtrl, ingredientsCtrl; |
|
|
|
|
|
late bool disponible; |
|
|
|
|
|
MenuCategory? selectedCategory; |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
|
void initState() { |
|
|
|
|
|
super.initState(); |
|
|
|
|
|
nomCtrl = TextEditingController(text: widget.plat?.nom ?? ""); |
|
|
|
|
|
descCtrl = TextEditingController(text: widget.plat?.commentaire ?? ""); |
|
|
|
|
|
prixCtrl = TextEditingController( |
|
|
|
|
|
text: widget.plat != null ? widget.plat!.prix.toStringAsFixed(2) : "", |
|
|
|
|
|
); |
|
|
|
|
|
ingredientsCtrl = TextEditingController(text: widget.plat?.ingredients ?? ""); |
|
|
|
|
|
disponible = widget.plat?.disponible ?? true; |
|
|
|
|
|
selectedCategory = widget.plat?.category; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
|
void dispose() { |
|
|
|
|
|
nomCtrl.dispose(); |
|
|
|
|
|
descCtrl.dispose(); |
|
|
|
|
|
prixCtrl.dispose(); |
|
|
|
|
|
ingredientsCtrl.dispose(); |
|
|
|
|
|
super.dispose(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Future<void> submit() async { |
|
|
|
|
|
if (!_formKey.currentState!.validate()) return; |
|
|
|
|
|
if (selectedCategory == null) { |
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar( |
|
|
|
|
|
const SnackBar(content: Text('Sélectionnez une catégorie.')), |
|
|
|
|
|
); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
final body = { |
|
|
|
|
|
"nom": nomCtrl.text, |
|
|
|
|
|
"commentaire": descCtrl.text, |
|
|
|
|
|
"ingredients": ingredientsCtrl.text, |
|
|
|
|
|
"prix": double.tryParse(prixCtrl.text) ?? 0, |
|
|
|
|
|
"categorie_id": selectedCategory!.id, |
|
|
|
|
|
"disponible": disponible, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
final isEdit = widget.plat != null; |
|
|
|
|
|
final url = isEdit |
|
|
|
|
|
? 'https://restaurant.careeracademy.mg/api/menus/${widget.plat!.id}' |
|
|
|
|
|
: 'https://restaurant.careeracademy.mg/api/menus'; |
|
|
|
|
|
|
|
|
|
|
|
final res = isEdit |
|
|
|
|
|
? await http.put( |
|
|
|
|
|
Uri.parse(url), |
|
|
|
|
|
headers: {"Content-Type": "application/json"}, |
|
|
|
|
|
body: json.encode(body), |
|
|
|
|
|
) |
|
|
|
|
|
: await http.post( |
|
|
|
|
|
Uri.parse(url), |
|
|
|
|
|
headers: {"Content-Type": "application/json"}, |
|
|
|
|
|
body: json.encode(body), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if (res.statusCode == 200 || res.statusCode == 201) { |
|
|
|
|
|
widget.onSaved?.call(); |
|
|
|
|
|
Navigator.pop(context); |
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar( |
|
|
|
|
|
SnackBar( |
|
|
|
|
|
content: Text(isEdit ? 'Plat modifié avec succès' : 'Plat créé avec succès'), |
|
|
|
|
|
backgroundColor: Colors.green, |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} else { |
|
|
|
|
|
print('Error: ${res.body}\nBody sent: $body'); |
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar( |
|
|
|
|
|
SnackBar( |
|
|
|
|
|
content: Text('Erreur lors de ${isEdit ? "la modification" : "la création"} du plat'), |
|
|
|
|
|
backgroundColor: Colors.red, |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar( |
|
|
|
|
|
SnackBar( |
|
|
|
|
|
content: Text('Erreur réseau: $e'), |
|
|
|
|
|
backgroundColor: Colors.red, |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
|
Widget build(BuildContext context) { |
|
|
|
|
|
final isEdit = widget.plat != null; |
|
|
|
|
|
return Scaffold( |
|
|
|
|
|
backgroundColor: const Color(0xfffcfbf9), |
|
|
|
|
|
appBar: AppBar( |
|
|
|
|
|
title: Text(isEdit ? 'Éditer le plat' : 'Nouveau plat'), |
|
|
|
|
|
leading: IconButton( |
|
|
|
|
|
icon: const Icon(Icons.arrow_back), |
|
|
|
|
|
onPressed: () => Navigator.pop(context), |
|
|
|
|
|
), |
|
|
|
|
|
centerTitle: true, |
|
|
|
|
|
elevation: 0, |
|
|
|
|
|
backgroundColor: Colors.transparent, |
|
|
|
|
|
foregroundColor: Colors.black87, |
|
|
|
|
|
), |
|
|
|
|
|
body: Center( |
|
|
|
|
|
child: ConstrainedBox( |
|
|
|
|
|
constraints: const BoxConstraints(maxWidth: 550), |
|
|
|
|
|
child: Card( |
|
|
|
|
|
elevation: 2, |
|
|
|
|
|
margin: const EdgeInsets.all(32), |
|
|
|
|
|
shape: RoundedRectangleBorder( |
|
|
|
|
|
borderRadius: BorderRadius.circular(12), |
|
|
|
|
|
), |
|
|
|
|
|
child: Padding( |
|
|
|
|
|
padding: const EdgeInsets.all(28), |
|
|
|
|
|
child: Form( |
|
|
|
|
|
key: _formKey, |
|
|
|
|
|
child: SingleChildScrollView( |
|
|
|
|
|
child: Column( |
|
|
|
|
|
mainAxisSize: MainAxisSize.min, |
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start, |
|
|
|
|
|
children: [ |
|
|
|
|
|
Text( |
|
|
|
|
|
isEdit ? 'Modifier le plat' : 'Nouveau plat', |
|
|
|
|
|
style: const TextStyle( |
|
|
|
|
|
fontSize: 21, |
|
|
|
|
|
fontWeight: FontWeight.w600, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(height: 20), |
|
|
|
|
|
|
|
|
|
|
|
// Nom du plat |
|
|
|
|
|
TextFormField( |
|
|
|
|
|
controller: nomCtrl, |
|
|
|
|
|
decoration: const InputDecoration( |
|
|
|
|
|
labelText: "Nom du plat *", |
|
|
|
|
|
hintText: "Ex: Steak frites", |
|
|
|
|
|
border: OutlineInputBorder( |
|
|
|
|
|
borderRadius: BorderRadius.all(Radius.circular(8)), |
|
|
|
|
|
), |
|
|
|
|
|
filled: true, |
|
|
|
|
|
fillColor: Color(0xFFF7F7F7), |
|
|
|
|
|
), |
|
|
|
|
|
validator: (v) => (v == null || v.isEmpty) ? "Obligatoire" : null, |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(height: 16), |
|
|
|
|
|
|
|
|
|
|
|
// Description |
|
|
|
|
|
TextFormField( |
|
|
|
|
|
controller: descCtrl, |
|
|
|
|
|
decoration: const InputDecoration( |
|
|
|
|
|
labelText: "Description", |
|
|
|
|
|
hintText: "Description détaillée du plat...", |
|
|
|
|
|
border: OutlineInputBorder( |
|
|
|
|
|
borderRadius: BorderRadius.all(Radius.circular(8)), |
|
|
|
|
|
), |
|
|
|
|
|
filled: true, |
|
|
|
|
|
fillColor: Color(0xFFF7F7F7), |
|
|
|
|
|
), |
|
|
|
|
|
maxLines: 3, |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(height: 16), |
|
|
|
|
|
|
|
|
|
|
|
// Ingrédients |
|
|
|
|
|
TextFormField( |
|
|
|
|
|
controller: ingredientsCtrl, |
|
|
|
|
|
decoration: const InputDecoration( |
|
|
|
|
|
labelText: "Ingrédients", |
|
|
|
|
|
hintText: "Liste des ingrédients...", |
|
|
|
|
|
border: OutlineInputBorder( |
|
|
|
|
|
borderRadius: BorderRadius.all(Radius.circular(8)), |
|
|
|
|
|
), |
|
|
|
|
|
filled: true, |
|
|
|
|
|
fillColor: Color(0xFFF7F7F7), |
|
|
|
|
|
), |
|
|
|
|
|
maxLines: 2, |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(height: 16), |
|
|
|
|
|
|
|
|
|
|
|
Row( |
|
|
|
|
|
children: [ |
|
|
|
|
|
// Prix |
|
|
|
|
|
Expanded( |
|
|
|
|
|
child: TextFormField( |
|
|
|
|
|
controller: prixCtrl, |
|
|
|
|
|
decoration: const InputDecoration( |
|
|
|
|
|
labelText: "Prix (MGA) *", |
|
|
|
|
|
border: OutlineInputBorder( |
|
|
|
|
|
borderRadius: BorderRadius.all(Radius.circular(8)), |
|
|
|
|
|
), |
|
|
|
|
|
filled: true, |
|
|
|
|
|
fillColor: Color(0xFFF7F7F7), |
|
|
|
|
|
), |
|
|
|
|
|
validator: (v) => |
|
|
|
|
|
(v == null || v.isEmpty || double.tryParse(v) == null) |
|
|
|
|
|
? "Obligatoire" |
|
|
|
|
|
: null, |
|
|
|
|
|
keyboardType: const TextInputType.numberWithOptions( |
|
|
|
|
|
decimal: true, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(width: 16), |
|
|
|
|
|
|
|
|
|
|
|
// Catégorie |
|
|
|
|
|
Expanded( |
|
|
|
|
|
child: DropdownButtonFormField<MenuCategory>( |
|
|
|
|
|
value: selectedCategory, |
|
|
|
|
|
decoration: const InputDecoration( |
|
|
|
|
|
labelText: "Catégorie *", |
|
|
|
|
|
border: OutlineInputBorder( |
|
|
|
|
|
borderRadius: BorderRadius.all(Radius.circular(8)), |
|
|
|
|
|
), |
|
|
|
|
|
filled: true, |
|
|
|
|
|
fillColor: Color(0xFFF7F7F7), |
|
|
|
|
|
), |
|
|
|
|
|
validator: (v) => v == null ? "Obligatoire" : null, |
|
|
|
|
|
items: widget.categories.map((cat) { |
|
|
|
|
|
return DropdownMenuItem( |
|
|
|
|
|
value: cat, |
|
|
|
|
|
child: Text(cat.nom), |
|
|
|
|
|
); |
|
|
|
|
|
}).toList(), |
|
|
|
|
|
onChanged: (value) { |
|
|
|
|
|
setState(() => selectedCategory = value); |
|
|
|
|
|
}, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(height: 20), |
|
|
|
|
|
|
|
|
|
|
|
// Switch disponibilité |
|
|
|
|
|
Row( |
|
|
|
|
|
children: [ |
|
|
|
|
|
Switch( |
|
|
|
|
|
value: disponible, |
|
|
|
|
|
activeColor: Colors.green, |
|
|
|
|
|
onChanged: (v) => setState(() => disponible = v), |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(width: 8), |
|
|
|
|
|
Text( |
|
|
|
|
|
'Plat disponible', |
|
|
|
|
|
style: TextStyle( |
|
|
|
|
|
fontSize: 16, |
|
|
|
|
|
color: disponible ? Colors.black87 : Colors.grey, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(height: 30), |
|
|
|
|
|
|
|
|
|
|
|
// Boutons |
|
|
|
|
|
Row( |
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.end, |
|
|
|
|
|
children: [ |
|
|
|
|
|
OutlinedButton( |
|
|
|
|
|
onPressed: () => Navigator.pop(context), |
|
|
|
|
|
style: OutlinedButton.styleFrom( |
|
|
|
|
|
padding: const EdgeInsets.symmetric( |
|
|
|
|
|
horizontal: 24, |
|
|
|
|
|
vertical: 12, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
child: const Text( |
|
|
|
|
|
"Annuler", |
|
|
|
|
|
style: TextStyle(color: Colors.black54), |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(width: 16), |
|
|
|
|
|
ElevatedButton.icon( |
|
|
|
|
|
style: ElevatedButton.styleFrom( |
|
|
|
|
|
backgroundColor: Colors.green[700], |
|
|
|
|
|
foregroundColor: Colors.white, |
|
|
|
|
|
padding: const EdgeInsets.symmetric( |
|
|
|
|
|
horizontal: 24, |
|
|
|
|
|
vertical: 12, |
|
|
|
|
|
), |
|
|
|
|
|
shape: RoundedRectangleBorder( |
|
|
|
|
|
borderRadius: BorderRadius.circular(6), |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
onPressed: submit, |
|
|
|
|
|
icon: const Icon(Icons.save, size: 18), |
|
|
|
|
|
label: Text(isEdit ? "Enregistrer" : "Créer le plat"), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ===== FONCTIONS DE NAVIGATION ===== |
|
|
|
|
|
|
|
|
|
|
|
Future<void> navigateToCreate( |
|
|
|
|
|
BuildContext context, |
|
|
|
|
|
List<MenuCategory> categories, |
|
|
|
|
|
Function()? onSaved, |
|
|
|
|
|
) async { |
|
|
|
|
|
await Navigator.push( |
|
|
|
|
|
context, |
|
|
|
|
|
MaterialPageRoute( |
|
|
|
|
|
builder: (_) => PlatEditPage( |
|
|
|
|
|
categories: categories, |
|
|
|
|
|
onSaved: onSaved |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Future<void> navigateToEdit( |
|
|
|
|
|
BuildContext context, |
|
|
|
|
|
List<MenuCategory> categories, |
|
|
|
|
|
MenuPlat plat, |
|
|
|
|
|
Function()? onSaved, |
|
|
|
|
|
) async { |
|
|
|
|
|
await Navigator.push( |
|
|
|
|
|
context, |
|
|
|
|
|
MaterialPageRoute( |
|
|
|
|
|
builder: (_) => PlatEditPage( |
|
|
|
|
|
categories: categories, |
|
|
|
|
|
plat: plat, |
|
|
|
|
|
onSaved: onSaved, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} |