You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

332 lines
13 KiB

8 months ago
8 months ago
import 'dart:convert';
8 months ago
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
8 months ago
import '../models/menus.dart';
8 months ago
class PlatEditPage extends StatefulWidget {
final List<MenuCategory> categories;
final MenuPlat? plat; // if present = edit, else create
final Function()? onSaved; // callback when saved
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>();
8 months ago
late TextEditingController nomCtrl, descCtrl, prixCtrl, ingredientsCtrl;
8 months ago
late bool disponible;
8 months ago
MenuCategory? selectedCategory; // Une seule catégorie pour correspondre à l'API
8 months ago
@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) : "",
);
8 months ago
ingredientsCtrl = TextEditingController(text: widget.plat?.ingredients ?? "");
8 months ago
disponible = widget.plat?.disponible ?? true;
8 months ago
// 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);
8 months ago
}
@override
void dispose() {
nomCtrl.dispose();
descCtrl.dispose();
prixCtrl.dispose();
8 months ago
ingredientsCtrl.dispose();
8 months ago
super.dispose();
}
8 months ago
Future<void> submit() async {
8 months ago
if (!_formKey.currentState!.validate()) return;
8 months ago
if (selectedCategory == null) {
8 months ago
ScaffoldMessenger.of(context).showSnackBar(
8 months ago
const SnackBar(content: Text('Sélectionnez une catégorie.')),
8 months ago
);
return;
}
8 months ago
// Build request data selon votre API
8 months ago
final body = {
"nom": nomCtrl.text,
"commentaire": descCtrl.text,
8 months ago
"ingredients": ingredientsCtrl.text,
8 months ago
"prix": double.tryParse(prixCtrl.text) ?? 0,
8 months ago
"categorie_id": selectedCategory!.id, // L'API attend categorie_id
8 months ago
"disponible": disponible,
};
try {
8 months ago
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),
);
8 months ago
if (res.statusCode == 200 || res.statusCode == 201) {
widget.onSaved?.call();
Navigator.pop(context);
8 months ago
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(isEdit ? 'Plat modifié avec succès' : 'Plat créé avec succès'),
backgroundColor: Colors.green,
),
);
8 months ago
} else {
8 months ago
print('Error: ${res.body}\nBody sent: $body');
8 months ago
ScaffoldMessenger.of(context).showSnackBar(
8 months ago
SnackBar(
content: Text('Erreur lors de ${isEdit ? "la modification" : "la création"} du plat'),
backgroundColor: Colors.red,
),
8 months ago
);
}
} catch (e) {
8 months ago
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur réseau: $e'),
backgroundColor: Colors.red,
),
);
8 months ago
}
}
@override
Widget build(BuildContext context) {
final isEdit = widget.plat != null;
return Scaffold(
8 months ago
backgroundColor: const Color(0xfffcfbf9),
8 months ago
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,
8 months ago
backgroundColor: Colors.transparent,
foregroundColor: Colors.black87,
8 months ago
),
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,
8 months ago
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,
8 months ago
),
8 months ago
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,
8 months ago
),
8 months ago
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,
8 months ago
),
8 months ago
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,
),
8 months ago
),
),
8 months ago
const SizedBox(width: 16),
// Catégorie
Expanded(
child: DropdownButtonFormField<MenuCategory>(
value: selectedCategory,
8 months ago
decoration: const InputDecoration(
labelText: "Catégorie *",
8 months ago
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
filled: true,
fillColor: Color(0xFFF7F7F7),
8 months ago
),
8 months ago
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);
},
8 months ago
),
),
8 months ago
],
),
const SizedBox(height: 20),
// Switch disponibilité
Row(
children: [
Switch(
value: disponible,
activeColor: Colors.green,
onChanged: (v) => setState(() => disponible = v),
8 months ago
),
8 months ago
const SizedBox(width: 8),
Text(
'Plat disponible',
style: TextStyle(
fontSize: 16,
color: disponible ? Colors.black87 : Colors.grey,
),
8 months ago
),
8 months ago
],
),
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),
),
8 months ago
),
8 months ago
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"),
8 months ago
),
8 months ago
],
),
],
),
8 months ago
),
),
),
),
),
),
);
}
8 months ago
}