import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import './PlatEdit_screen.dart'; class PlatsManagementScreen extends StatefulWidget { const PlatsManagementScreen({super.key}); @override State createState() => _PlatsManagementScreenState(); } class _PlatsManagementScreenState extends State { final _baseUrl = 'https://restaurant.careeracademy.mg/api'; List plats = []; List categories = []; String search = ''; int? selectedCategoryId; String disponibilite = ''; bool isLoading = true; final TextEditingController _searchController = TextEditingController(); @override void initState() { super.initState(); _fetchCategories(); _fetchPlats(); } // Fetch categories Future _fetchCategories() async { try { final res = await http.get(Uri.parse('$_baseUrl/menu-categories')); final data = json.decode(res.body); setState(() { categories = (data['data']['categories'] as List) .map((item) => MenuCategory.fromJson(item)) .toList(); }); } catch (_) {} } // Fetch plats Future _fetchPlats() async { setState(() => isLoading = true); try { final uri = Uri.parse('$_baseUrl/menus').replace( queryParameters: { if (search.isNotEmpty) 'search': search, if (selectedCategoryId != null) 'category_id': selectedCategoryId.toString(), }, ); final res = await http.get(uri); final data = json.decode(res.body); setState(() { plats = (data['data']['menus'] as List) .map((item) => MenuPlat.fromJson(item)) .toList(); isLoading = false; }); if (kDebugMode) { // print('fetched plat here: $plats items'); } } catch (e) { setState(() => isLoading = false); if (kDebugMode) print("Error: $e"); } } Future _deletePlat(int id) async { final res = await http.delete(Uri.parse('$_baseUrl/menus/$id')); if (res.statusCode == 200) { _fetchPlats(); } else { if (kDebugMode) print("Error deleting plat: ${res.body}"); // ignore: use_build_context_synchronously ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Des commandes sont liées à ce plat.')), ); } } void _showEditPlatDialog(MenuPlat plat) { showDialog( context: context, builder: (_) => EditPlatDialog( plat: plat, onPlatUpdated: _fetchPlats, categories: categories, ), ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xfffcfbf9), appBar: AppBar( elevation: 0, title: const Text( 'Gestion des plats', style: TextStyle(fontWeight: FontWeight.bold), ), backgroundColor: Colors.transparent, foregroundColor: Colors.black87, actions: [ Padding( padding: const EdgeInsets.only(right: 16.0), child: ElevatedButton.icon( onPressed: () => navigateToCreate( context, categories, () => {_fetchPlats()}, ), icon: const Icon(Icons.add, size: 18), label: const Text('Nouveau plat'), style: ElevatedButton.styleFrom( backgroundColor: Colors.green[700], foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 18, vertical: 12, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), ), ), ), ], ), body: Column( children: [ Card( margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 18), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), child: Padding( padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 18), child: Row( children: [ const Icon(Icons.filter_alt_outlined, size: 22), const SizedBox(width: 10), Expanded( child: TextField( controller: _searchController, onChanged: (value) { setState(() => search = value); _fetchPlats(); }, decoration: const InputDecoration( hintText: "Rechercher un plat...", border: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(8)), borderSide: BorderSide.none, ), filled: true, fillColor: Color(0xFFF7F7F7), isDense: true, contentPadding: EdgeInsets.symmetric( vertical: 10, horizontal: 14, ), ), ), ), const SizedBox(width: 10), // Catégories DropdownButton( value: selectedCategoryId, hint: const Text("Toutes les catégories"), items: [ const DropdownMenuItem( value: null, child: Text("Toutes les catégories"), ), ...categories.map( (cat) => DropdownMenuItem( value: cat.id, child: Text(cat.nom), ), ), ], onChanged: (v) { setState(() => selectedCategoryId = v); _fetchPlats(); }, ), ], ), ), ), if (isLoading) const Expanded(child: Center(child: CircularProgressIndicator())) else Expanded( child: ListView.builder( itemCount: plats.length, padding: const EdgeInsets.symmetric(horizontal: 24), itemBuilder: (ctx, i) { final p = plats[i]; return Card( margin: const EdgeInsets.only(bottom: 20), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), child: Padding( padding: const EdgeInsets.symmetric( vertical: 16, horizontal: 18, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 2, child: Text( p.nom, style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 18, ), ), ), Expanded( flex: 6, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( p.commentaire ?? "", style: const TextStyle(fontSize: 15), ), if (p.ingredients != null && p.ingredients!.isNotEmpty) Padding( padding: const EdgeInsets.symmetric( vertical: 3.0, ), child: Text( p.ingredients!, style: const TextStyle( color: Colors.grey, fontSize: 13, ), ), ), Row( children: [ if (p.category != null) CategoryChip( label: p.category!.nom, color: Colors.black, ) else const CategoryChip( label: "Catégorie", color: Colors.black, ), ], ), ], ), ), Expanded( flex: 2, child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Text( "${p.prix.toStringAsFixed(2)} MGA", style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.green, fontSize: 20, ), ), const SizedBox(width: 10), SizedBox( height: 38, width: 38, child: IconButton( icon: const Icon( Icons.edit, color: Colors.black54, ), onPressed: () => _showEditPlatDialog(p), ), ), SizedBox( height: 38, width: 38, child: IconButton( icon: const Icon( Icons.delete, color: Colors.redAccent, ), onPressed: () async { final confirm = await showDialog( context: context, builder: (_) => AlertDialog( title: const Text( 'Supprimer ce plat ?', ), content: Text( 'Supprimer ${p.nom} ', ), actions: [ TextButton( onPressed: () => Navigator.pop( context, false, ), child: const Text("Annuler"), ), ElevatedButton( onPressed: () => Navigator.pop( context, true, ), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, ), child: const Text( "Supprimer", style: TextStyle( color: Colors.white, ), ), ), ], ), ); if (confirm == true) _deletePlat(p.id); }, ), ), ], ), ), ], ), ), ); }, ), ), ], ), ); } } class MenuPlat { 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 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 { final String label; final Color color; const CategoryChip({super.key, required this.label, required this.color}); @override Widget build(BuildContext context) => Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), // ignore: deprecated_member_use color: color.withOpacity(0.16), ), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Text( label, style: TextStyle(color: color, fontWeight: FontWeight.w600, fontSize: 13), ), ); } Future navigateToCreate( BuildContext context, List categories, Function()? onSaved, ) async { await Navigator.push( context, MaterialPageRoute( builder: (_) => PlatEditPage(categories: categories, onSaved: onSaved), ), ); } class EditPlatDialog extends StatefulWidget { final MenuPlat plat; final List categories; final VoidCallback onPlatUpdated; const EditPlatDialog({ super.key, required this.plat, required this.onPlatUpdated, required this.categories, }); @override State createState() => _EditPlatDialogState(); } class _EditPlatDialogState extends State { late String nom; late String commentaire; late String ingredients; late double prix; // late bool disponible; MenuCategory? cat; final _formKey = GlobalKey(); @override void initState() { super.initState(); nom = widget.plat.nom; commentaire = widget.plat.commentaire ?? ''; ingredients = widget.plat.ingredients ?? ''; prix = widget.plat.prix; // cat = (widget.plat.categories) as MenuCategory?; cat = widget.plat.category; } Future submit() async { if (!_formKey.currentState!.validate()) return; final res = await http.put( Uri.parse( 'https://restaurant.careeracademy.mg/api/menus/${widget.plat.id}', ), headers: {'Content-Type': 'application/json'}, body: json.encode({ "nom": nom, "commentaire": commentaire, "ingredients": ingredients, "prix": prix, "categorie_id": cat?.id, }), ); if (res.statusCode == 200) { widget.onPlatUpdated(); // ignore: use_build_context_synchronously Navigator.pop(context); } } @override Widget build(BuildContext context) => AlertDialog( title: const Text("Éditer le plat"), content: Form( key: _formKey, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextFormField( initialValue: nom, onChanged: (v) => nom = v, decoration: const InputDecoration(labelText: "Nom"), validator: (v) => (v?.isEmpty ?? true) ? "Obligatoire" : null, ), TextFormField( initialValue: commentaire, onChanged: (v) => commentaire = v, decoration: const InputDecoration(labelText: "Commentaire"), ), TextFormField( initialValue: ingredients, onChanged: (v) => ingredients = v, decoration: const InputDecoration(labelText: "Ingrédients"), ), TextFormField( initialValue: prix.toString(), onChanged: (v) => prix = double.tryParse(v) ?? 0, decoration: const InputDecoration(labelText: "Prix"), keyboardType: const TextInputType.numberWithOptions( decimal: true, ), ), DropdownButton( hint: const Text("Catégorie"), value: cat, isExpanded: true, items: widget.categories .toSet() .map( (c) => DropdownMenuItem(value: c, child: Text(c.nom)), ) .toList(), onChanged: (v) => setState(() => cat = v), ), ], ), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text("Annuler"), ), ElevatedButton(onPressed: submit, child: const Text("Enregistrer")), ], ); }