push final
This commit is contained in:
parent
4c77552551
commit
cccbe0d684
88
lib/models/menus.dart
Normal file
88
lib/models/menus.dart
Normal file
@ -0,0 +1,88 @@
|
||||
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 bool disponible;
|
||||
final MenuCategory? category; // Single category pour la compatibilité API
|
||||
final List<MenuCategory>? categories; // Multiple categories si besoin
|
||||
|
||||
MenuPlat({
|
||||
required this.id,
|
||||
required this.nom,
|
||||
this.commentaire,
|
||||
required this.prix,
|
||||
this.ingredients,
|
||||
this.imageUrl,
|
||||
required this.disponible,
|
||||
this.category,
|
||||
this.categories,
|
||||
});
|
||||
|
||||
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'],
|
||||
disponible: json['disponible'] ?? true,
|
||||
// Support pour single category (API actuelle)
|
||||
category: json['category'] != null
|
||||
? MenuCategory.fromJson(json['category'])
|
||||
: null,
|
||||
// Support pour multiple categories si l'API évolue
|
||||
categories: json['categories'] != null
|
||||
? (json['categories'] as List)
|
||||
.map((c) => MenuCategory.fromJson(c))
|
||||
.toList()
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,62 +1,9 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
// Use your actual models and http logic
|
||||
class MenuPlat {
|
||||
final int id;
|
||||
final String nom;
|
||||
final String? commentaire;
|
||||
final double prix;
|
||||
final String? imageUrl;
|
||||
final bool disponible;
|
||||
final List<MenuCategory> categories; // Default to true
|
||||
MenuPlat({
|
||||
required this.id,
|
||||
required this.nom,
|
||||
this.commentaire,
|
||||
required this.prix,
|
||||
this.imageUrl,
|
||||
required this.disponible,
|
||||
required this.categories,
|
||||
});
|
||||
factory MenuPlat.fromJson(Map<String, dynamic> json) {
|
||||
return MenuPlat(
|
||||
id: json['id'],
|
||||
nom: json['nom'],
|
||||
commentaire: json['commentaire'],
|
||||
prix: (json['prix'] as num).toDouble(),
|
||||
imageUrl: json['image_url'],
|
||||
disponible: json['disponible'] ?? true,
|
||||
categories:
|
||||
(json['categories'] as List)
|
||||
.map((c) => MenuCategory.fromJson(c))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MenuCategory {
|
||||
final int id;
|
||||
final String nom;
|
||||
|
||||
MenuCategory({required this.id, required this.nom});
|
||||
|
||||
factory MenuCategory.fromJson(Map<String, dynamic> json) {
|
||||
return MenuCategory(id: json['id'], nom: json['nom']);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is MenuCategory &&
|
||||
runtimeType == other.runtimeType &&
|
||||
id == other.id;
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
}
|
||||
import '../models/menus.dart';
|
||||
|
||||
class PlatEditPage extends StatefulWidget {
|
||||
final List<MenuCategory> categories;
|
||||
@ -76,9 +23,9 @@ class PlatEditPage extends StatefulWidget {
|
||||
|
||||
class _PlatEditPageState extends State<PlatEditPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
late TextEditingController nomCtrl, descCtrl, prixCtrl, imgCtrl;
|
||||
late TextEditingController nomCtrl, descCtrl, prixCtrl, ingredientsCtrl;
|
||||
late bool disponible;
|
||||
late List<MenuCategory> selectedCategories;
|
||||
MenuCategory? selectedCategory; // Une seule catégorie pour correspondre à l'API
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -88,9 +35,14 @@ class _PlatEditPageState extends State<PlatEditPage> {
|
||||
prixCtrl = TextEditingController(
|
||||
text: widget.plat != null ? widget.plat!.prix.toStringAsFixed(2) : "",
|
||||
);
|
||||
imgCtrl = TextEditingController(text: widget.plat?.imageUrl ?? "");
|
||||
ingredientsCtrl = TextEditingController(text: widget.plat?.ingredients ?? "");
|
||||
disponible = widget.plat?.disponible ?? true;
|
||||
selectedCategories = widget.plat?.categories.toList() ?? [];
|
||||
|
||||
// Utiliser la catégorie unique ou la première des multiples
|
||||
selectedCategory = widget.plat?.category ??
|
||||
(widget.plat?.categories?.isNotEmpty == true
|
||||
? widget.plat!.categories!.first
|
||||
: null);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -98,56 +50,72 @@ class _PlatEditPageState extends State<PlatEditPage> {
|
||||
nomCtrl.dispose();
|
||||
descCtrl.dispose();
|
||||
prixCtrl.dispose();
|
||||
imgCtrl.dispose();
|
||||
ingredientsCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void submit() async {
|
||||
Future<void> submit() async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
if (selectedCategories.isEmpty) {
|
||||
if (selectedCategory == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Sélectionnez au moins une catégorie.')),
|
||||
const SnackBar(content: Text('Sélectionnez une catégorie.')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build request data
|
||||
// Build request data selon votre API
|
||||
final body = {
|
||||
"nom": nomCtrl.text,
|
||||
"commentaire": descCtrl.text,
|
||||
"ingredients": ingredientsCtrl.text,
|
||||
"prix": double.tryParse(prixCtrl.text) ?? 0,
|
||||
"categories": selectedCategories.map((c) => c.id).toList(),
|
||||
"categorie_id": selectedCategory!.id, // L'API attend categorie_id
|
||||
"disponible": disponible,
|
||||
"categorie_id":
|
||||
selectedCategories.isNotEmpty
|
||||
? selectedCategories.first.id
|
||||
: null, // Use first category if available
|
||||
};
|
||||
|
||||
try {
|
||||
final res = await http.post(
|
||||
Uri.parse(
|
||||
'https://restaurant.careeracademy.mg/api/menus',
|
||||
), // your API URL here
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(body),
|
||||
);
|
||||
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);
|
||||
} else {
|
||||
// show error
|
||||
print('Error creating plat: ${res.body}\n here is body: $body');
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Erreur lors de la création du plat')),
|
||||
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) {
|
||||
// show error
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Erreur réseau: $e')));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Erreur réseau: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,6 +123,7 @@ class _PlatEditPageState extends State<PlatEditPage> {
|
||||
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(
|
||||
@ -163,8 +132,8 @@ class _PlatEditPageState extends State<PlatEditPage> {
|
||||
),
|
||||
centerTitle: true,
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.black87,
|
||||
),
|
||||
body: Center(
|
||||
child: ConstrainedBox(
|
||||
@ -179,187 +148,179 @@ class _PlatEditPageState extends State<PlatEditPage> {
|
||||
padding: const EdgeInsets.all(28),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Informations du plat',
|
||||
style: TextStyle(
|
||||
fontSize: 21,
|
||||
fontWeight: FontWeight.w600,
|
||||
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: 14),
|
||||
TextFormField(
|
||||
controller: nomCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Nom du plat *",
|
||||
hintText: "Ex: Steak frites",
|
||||
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,
|
||||
),
|
||||
validator:
|
||||
(v) =>
|
||||
(v == null || v.isEmpty) ? "Obligatoire" : null,
|
||||
),
|
||||
const SizedBox(height: 13),
|
||||
TextFormField(
|
||||
controller: descCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Description",
|
||||
hintText: "Description détaillée du plat...",
|
||||
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,
|
||||
),
|
||||
maxLines: 3,
|
||||
),
|
||||
const SizedBox(height: 13),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: prixCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Prix (MGA) *",
|
||||
),
|
||||
validator:
|
||||
(v) =>
|
||||
(v == null ||
|
||||
v.isEmpty ||
|
||||
double.tryParse(v) == null)
|
||||
? "Obligatoire"
|
||||
: null,
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
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),
|
||||
// Multi-category picker
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
final result = await showDialog<MenuCategory?>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
MenuCategory? temp =
|
||||
selectedCategories.isNotEmpty
|
||||
? selectedCategories.first
|
||||
: null;
|
||||
|
||||
return StatefulBuilder(
|
||||
builder: (context, setStateDialog) {
|
||||
return AlertDialog(
|
||||
title: const Text("Catégories"),
|
||||
content: SizedBox(
|
||||
width: 330,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children:
|
||||
widget.categories.map((cat) {
|
||||
return RadioListTile<
|
||||
MenuCategory
|
||||
>(
|
||||
value: cat,
|
||||
groupValue: temp,
|
||||
title: Text(cat.nom),
|
||||
onChanged: (value) {
|
||||
setStateDialog(() {
|
||||
temp = value;
|
||||
});
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (temp != null) {
|
||||
Navigator.pop(context, temp);
|
||||
} else {
|
||||
Navigator.pop(context, null);
|
||||
}
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (result != null) {
|
||||
setState(() => selectedCategories = [result]);
|
||||
}
|
||||
},
|
||||
child: InputDecorator(
|
||||
const SizedBox(width: 16),
|
||||
|
||||
// Catégorie
|
||||
Expanded(
|
||||
child: DropdownButtonFormField<MenuCategory>(
|
||||
value: selectedCategory,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Catégorie *",
|
||||
border: OutlineInputBorder(gapPadding: 2),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Color(0xFFF7F7F7),
|
||||
),
|
||||
child:
|
||||
selectedCategories.isEmpty
|
||||
? const Text(
|
||||
"Aucune sélection",
|
||||
style: TextStyle(color: Colors.grey),
|
||||
)
|
||||
: Text(selectedCategories.first.nom),
|
||||
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: 13),
|
||||
// TextFormField(
|
||||
// controller: imgCtrl,
|
||||
// decoration: const InputDecoration(
|
||||
// labelText: "URL de l'image",
|
||||
// hintText: "/api/placeholder/300/200",
|
||||
// ),
|
||||
// ),
|
||||
const SizedBox(height: 13),
|
||||
Row(
|
||||
children: [
|
||||
Switch(
|
||||
value: disponible,
|
||||
activeColor: Colors.green,
|
||||
onChanged: (v) => setState(() => disponible = v),
|
||||
),
|
||||
const SizedBox(width: 7),
|
||||
const Text('Plat disponible'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 22),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text(
|
||||
"Annuler",
|
||||
style: TextStyle(color: Colors.black),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Switch disponibilité
|
||||
Row(
|
||||
children: [
|
||||
Switch(
|
||||
value: disponible,
|
||||
activeColor: Colors.green,
|
||||
onChanged: (v) => setState(() => disponible = v),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 18),
|
||||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Plat disponible',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: disponible ? Colors.black87 : Colors.grey,
|
||||
),
|
||||
),
|
||||
onPressed: submit,
|
||||
icon: const Icon(
|
||||
Icons.save,
|
||||
size: 18,
|
||||
color: Colors.white,
|
||||
],
|
||||
),
|
||||
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),
|
||||
),
|
||||
),
|
||||
label: Text(
|
||||
isEdit ? "Enregistrer" : "Ajouter",
|
||||
style: const TextStyle(color: Colors.white),
|
||||
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"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -368,4 +329,4 @@ class _PlatEditPageState extends State<PlatEditPage> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:intl/intl.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
// Ajoutez cet import pour la page panier
|
||||
@ -75,13 +76,20 @@ class _MenuPageState extends State<MenuPage> {
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final jsonResponse = json.decode(response.body);
|
||||
print(jsonResponse);
|
||||
|
||||
final List<dynamic> menusList =
|
||||
jsonResponse is List ? jsonResponse : (jsonResponse['data'] ?? []);
|
||||
final List<dynamic> menusList =
|
||||
jsonResponse is List ? jsonResponse : (jsonResponse['data'] ?? []);
|
||||
|
||||
setState(() {
|
||||
_menus = menusList;
|
||||
});
|
||||
// Filtrage côté client
|
||||
final List<dynamic> menusDisponibles = menusList.where((menu) {
|
||||
final disponible = menu['disponible'];
|
||||
return disponible == true || disponible == 1 || disponible == '1' || disponible == 'true';
|
||||
}).toList();
|
||||
|
||||
setState(() {
|
||||
_menus = menusDisponibles;
|
||||
});
|
||||
} else {
|
||||
if (kDebugMode) {
|
||||
print("Erreur API menus: ${response.statusCode}");
|
||||
@ -225,7 +233,7 @@ class _MenuPageState extends State<MenuPage> {
|
||||
),
|
||||
subtitle: Text(item['commentaire'] ?? ''),
|
||||
trailing: Text(
|
||||
"${formatPrix(item['prix'])} MGA",
|
||||
"${NumberFormat("#,##0.00", "fr_FR").format(double.tryParse(item['prix'].toString()) ?? 0.0)} MGA",
|
||||
style: TextStyle(
|
||||
color: Colors.green[700],
|
||||
fontWeight: FontWeight.bold,
|
||||
@ -385,7 +393,7 @@ class _AddToCartModalState extends State<AddToCartModal> {
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||
),
|
||||
Text(
|
||||
"${formatPrix(widget.item['prix'])} MGA",
|
||||
"${NumberFormat("#,##0.00", "fr_FR").format(double.tryParse(widget.item['prix'].toString()) ?? 0.0)} MGA",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
@ -483,7 +491,7 @@ class _AddToCartModalState extends State<AddToCartModal> {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${calculateTotal().toStringAsFixed(2)} MGA",
|
||||
"${NumberFormat("#,##0.00", "fr_FR").format(calculateTotal())} MGA",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user