import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'menu.dart'; // Assure-toi que ce fichier contient la page MenuPage class TableData { final int id; final String nom; final int capacity; final String status; TableData({ required this.id, required this.nom, required this.capacity, required this.status, }); factory TableData.fromJson(Map json) { return TableData( id: json['id'], nom: json['nom'], capacity: json['capacity'], status: json['status'], ); } } class TablesScreen extends StatefulWidget { const TablesScreen({super.key}); @override // ignore: library_private_types_in_public_api _TablesScreenState createState() => _TablesScreenState(); } class _TablesScreenState extends State { List tables = []; bool isLoading = true; @override void initState() { super.initState(); fetchTables(); } Future fetchTables() async { try { final url = Uri.parse("https://restaurant.careeracademy.mg/api/tables"); final response = await http.get(url); if (response.statusCode == 200) { final List data = json.decode(response.body)['data']; setState(() { tables = data.map((json) => TableData.fromJson(json)).toList(); isLoading = false; }); } else { setState(() => isLoading = false); if (kDebugMode) { print('Erreur API: ${response.statusCode}'); } } } catch (e) { setState(() => isLoading = false); if (kDebugMode) { print('Erreur: $e'); } } } Future _addTable() async { // Add table logic final result = await showDialog>( context: context, builder: (context) => const _AddEditTableDialog(), ); if (result != null) { // Call API to add table _callAddTableAPI(result); } } Future _callAddTableAPI(Map tableData) async { try { final url = Uri.parse("https://restaurant.careeracademy.mg/api/tables"); final response = await http.post( url, headers: {'Content-Type': 'application/json'}, body: json.encode(tableData), ); if (response.statusCode == 201) { fetchTables(); // Refresh the list // ignore: use_build_context_synchronously ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Table ajoutée avec succès')), ); } } catch (e) { ScaffoldMessenger.of( // ignore: use_build_context_synchronously context, ).showSnackBar(SnackBar(content: Text('Erreur: $e'))); } } Future _editTable(TableData table) async { final result = await showDialog>( context: context, builder: (context) => _AddEditTableDialog(table: table), ); if (result != null) { _callEditTableAPI(table.id, result); } } Future _callEditTableAPI(int id, Map tableData) async { try { final url = Uri.parse( "https://restaurant.careeracademy.mg/api/tables/$id", ); final response = await http.put( url, headers: {'Content-Type': 'application/json'}, body: json.encode(tableData), ); if (response.statusCode == 200) { fetchTables(); // ignore: use_build_context_synchronously ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Table modifiée avec succès')), ); } } catch (e) { ScaffoldMessenger.of( // ignore: use_build_context_synchronously context, ).showSnackBar(SnackBar(content: Text('Erreur: $e'))); } } Future _deleteTable(int id) async { final confirm = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Confirmer la suppression'), content: const Text( 'Êtes-vous sûr de vouloir supprimer cette table?', ), 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'), ), ], ), ); if (confirm == true) { _callDeleteTableAPI(id); } } Future _callDeleteTableAPI(int id) async { try { final url = Uri.parse( "https://restaurant.careeracademy.mg/api/tables/$id", ); final response = await http.delete(url); if (response.statusCode == 200) { fetchTables(); // ignore: use_build_context_synchronously ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Table supprimée avec succès')), ); } } catch (e) { ScaffoldMessenger.of( // ignore: use_build_context_synchronously context, ).showSnackBar(SnackBar(content: Text('Erreur: $e'))); } } Color getStatusColor(String status) { switch (status) { case 'available': return Colors.green; case 'occupied': return Colors.orange; case 'reserved': return Colors.orange; default: return Colors.grey; } } String getStatusLabel(String status) { switch (status) { case 'available': return 'Disponible'; case 'occupied': return 'Occupée'; case 'reserved': return 'Réservée'; default: return 'Indisponible'; } } bool isTableSelectable(String status) { return status == 'available'; } @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; final isDesktop = screenWidth >= 768; int crossAxisCount = 1; if (screenWidth > 1200) { crossAxisCount = 4; } else if (screenWidth > 800) { crossAxisCount = 3; } if (isLoading) { return const Center(child: CircularProgressIndicator()); } return Column( children: [ // Header Container( color: Colors.white, padding: const EdgeInsets.all(20), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 30), const Text( 'Sélectionner une table', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black87, ), ), const SizedBox(height: 8), Text( 'Choisissez une table pour commencer une nouvelle commande', style: TextStyle( fontSize: 14, color: Colors.grey.shade600, ), ), ], ), ), // Add button (desktop only) if (isDesktop) ElevatedButton.icon( onPressed: _addTable, icon: const Icon(Icons.add, size: 20), label: const Text('Ajouter'), style: ElevatedButton.styleFrom( backgroundColor: Colors.green.shade700, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 12, ), ), ), ], ), ), // Content - Reduced height Expanded( child: Padding( padding: const EdgeInsets.all(12.0), child: Column( children: [ // Tables Grid - Reduced flex Expanded( flex: isDesktop ? 2 : 4, // Reduced from 3/4 to 2/4 child: GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: crossAxisCount, crossAxisSpacing: 12, mainAxisSpacing: 12, childAspectRatio: isDesktop ? 0.8 : 0.6, // Modifié ici pour diminuer la taille ), itemCount: tables.length, itemBuilder: (context, index) { final table = tables[index]; final isSelectable = isTableSelectable(table.status); return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.shade200), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 5, offset: const Offset(0, 2), ), ], ), child: Stack( children: [ // Desktop CRUD menu if (isDesktop) Positioned( top: 8, right: 8, child: PopupMenuButton( icon: Icon( Icons.more_vert, size: 16, color: Colors.grey.shade600, ), onSelected: (value) { switch (value) { case 'edit': _editTable(table); break; case 'delete': _deleteTable(table.id); break; } }, itemBuilder: (context) => [ const PopupMenuItem( value: 'edit', child: Row( children: [ Icon(Icons.edit, size: 16), SizedBox(width: 8), Text('Modifier'), ], ), ), const PopupMenuItem( value: 'delete', child: Row( children: [ Icon( Icons.delete, size: 16, color: Colors.red, ), SizedBox(width: 8), Text( 'Supprimer', style: TextStyle( color: Colors.red, ), ), ], ), ), ], ), ), Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: getStatusColor(table.status), borderRadius: BorderRadius.circular(12), ), child: Text( getStatusLabel(table.status), style: const TextStyle( color: Colors.white, fontSize: 8, fontWeight: FontWeight.w100, ), ), ), Row( children: [ Text( table.nom, style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 16, color: Colors.black87, ), ), // const Spacer(), ], ), const SizedBox(height: 8), Row( children: [ Icon( Icons.people_outline, size: 16, color: Colors.grey.shade600, ), const SizedBox(width: 6), Text( 'Capacité: ${table.capacity} personnes', style: TextStyle( fontSize: 13, color: Colors.grey.shade600, ), ), ], ), // const Spacer(), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: isSelectable ? () { // Redirection vers MenuPage avec paramètres Navigator.push( context, MaterialPageRoute( builder: (context) => MenuPage( tableId: table.id, personne: table.capacity, ), ), ); } : null, style: ElevatedButton.styleFrom( backgroundColor: isSelectable ? Colors.green.shade700 : Colors.grey.shade300, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( vertical: 8, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 8, ), ), ), child: Text( isSelectable ? 'Sélectionner' : 'Indisponible', style: const TextStyle( color: Colors.white, fontSize: 12, ), ), ), ), ], ), ), ], ), ); }, ), ), // Legend - Compact Container( padding: const EdgeInsets.symmetric(vertical: 16), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildLegendItem('Disponible', Colors.green), const SizedBox(width: 20), _buildLegendItem('Occupée', Colors.orange), const SizedBox(width: 20), _buildLegendItem('Réservée', Colors.orange), ], ), ), ], ), ), ), ], ); } Widget _buildLegendItem(String label, Color color) { return Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 12, height: 12, decoration: BoxDecoration(color: color, shape: BoxShape.circle), ), const SizedBox(width: 6), Text( label, style: TextStyle(fontSize: 13, color: Colors.grey.shade700), ), ], ); } } // Add/Edit Table Dialog class _AddEditTableDialog extends StatefulWidget { final TableData? table; const _AddEditTableDialog({this.table}); @override _AddEditTableDialogState createState() => _AddEditTableDialogState(); } class _AddEditTableDialogState extends State<_AddEditTableDialog> { final _formKey = GlobalKey(); late TextEditingController _nomController; late TextEditingController _capacityController; String _selectedStatus = 'available'; final List> _statusOptions = [ {'value': 'available', 'label': 'Disponible'}, {'value': 'occupied', 'label': 'Occupée'}, {'value': 'reserved', 'label': 'Réservée'}, ]; @override void initState() { super.initState(); _nomController = TextEditingController(text: widget.table?.nom ?? ''); _capacityController = TextEditingController( text: widget.table?.capacity.toString() ?? '', ); _selectedStatus = widget.table?.status ?? 'available'; } @override void dispose() { _nomController.dispose(); _capacityController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final isEditing = widget.table != null; return AlertDialog( title: Text(isEditing ? 'Modifier une table' : 'Ajouter une table'), content: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ TextFormField( controller: _nomController, decoration: const InputDecoration(labelText: 'Nom'), validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez entrer un nom'; } return null; }, ), TextFormField( controller: _capacityController, decoration: const InputDecoration(labelText: 'Capacité'), keyboardType: TextInputType.number, validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez entrer la capacité'; } if (int.tryParse(value) == null) { return 'Veuillez entrer un nombre valide'; } return null; }, ), DropdownButtonFormField( value: _selectedStatus, decoration: const InputDecoration(labelText: 'Statut'), items: _statusOptions.map((status) { return DropdownMenuItem( value: status['value'], child: Text(status['label']!), ); }).toList(), onChanged: (value) { if (value != null) { setState(() { _selectedStatus = value; }); } }, ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), ElevatedButton( onPressed: () { if (_formKey.currentState!.validate()) { final tableData = { 'nom': _nomController.text, 'capacity': int.parse(_capacityController.text), 'status': _selectedStatus, }; Navigator.pop(context, tableData); } }, child: Text(isEditing ? 'Modifier' : 'Ajouter'), ), ], ); } }