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 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 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 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; } class PlatEditPage extends StatefulWidget { final List 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 createState() => _PlatEditPageState(); } class _PlatEditPageState extends State { final _formKey = GlobalKey(); late TextEditingController nomCtrl, descCtrl, prixCtrl, imgCtrl; late bool disponible; late List selectedCategories; @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) : "", ); imgCtrl = TextEditingController(text: widget.plat?.imageUrl ?? ""); disponible = widget.plat?.disponible ?? true; selectedCategories = widget.plat?.categories.toList() ?? []; } @override void dispose() { nomCtrl.dispose(); descCtrl.dispose(); prixCtrl.dispose(); imgCtrl.dispose(); super.dispose(); } void submit() async { if (!_formKey.currentState!.validate()) return; if (selectedCategories.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Sélectionnez au moins une catégorie.')), ); return; } // Build request data final body = { "nom": nomCtrl.text, "commentaire": descCtrl.text, "prix": double.tryParse(prixCtrl.text) ?? 0, "categories": selectedCategories.map((c) => c.id).toList(), "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), ); 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')), ); } } catch (e) { // show error ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('Erreur réseau: $e'))); } } @override Widget build(BuildContext context) { final isEdit = widget.plat != null; return Scaffold( 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.white, foregroundColor: Colors.black, ), 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: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Informations du plat', style: TextStyle( fontSize: 21, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 14), TextFormField( controller: nomCtrl, decoration: const InputDecoration( labelText: "Nom du plat *", hintText: "Ex: Steak frites", ), 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...", ), 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(width: 16), // Multi-category picker Expanded( child: InkWell( onTap: () async { final result = await showDialog( 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( decoration: const InputDecoration( labelText: "Catégorie *", border: OutlineInputBorder(gapPadding: 2), ), child: selectedCategories.isEmpty ? const Text( "Aucune sélection", style: TextStyle(color: Colors.grey), ) : Text(selectedCategories.first.nom), ), ), ), ], ), // 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(width: 18), ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: Colors.green, ), onPressed: submit, icon: const Icon( Icons.save, size: 18, color: Colors.white, ), label: Text( isEdit ? "Enregistrer" : "Ajouter", style: const TextStyle(color: Colors.white), ), ), ], ), ], ), ), ), ), ), ), ); } }