|
|
@ -1,8 +1,7 @@ |
|
|
import 'dart:convert'; |
|
|
import 'package:flutter/foundation.dart'; |
|
|
import 'dart:io' show Platform; |
|
|
|
|
|
import 'package:flutter/foundation.dart' show kIsWeb; |
|
|
|
|
|
import 'package:flutter/material.dart'; |
|
|
import 'package:flutter/material.dart'; |
|
|
import 'package:http/http.dart' as http; |
|
|
import 'package:http/http.dart' as http; |
|
|
|
|
|
import 'dart:convert'; |
|
|
|
|
|
|
|
|
import 'menu.dart'; // Assure-toi que ce fichier contient la page MenuPage |
|
|
import 'menu.dart'; // Assure-toi que ce fichier contient la page MenuPage |
|
|
|
|
|
|
|
|
@ -31,8 +30,10 @@ class TableData { |
|
|
|
|
|
|
|
|
class TablesScreen extends StatefulWidget { |
|
|
class TablesScreen extends StatefulWidget { |
|
|
const TablesScreen({super.key}); |
|
|
const TablesScreen({super.key}); |
|
|
|
|
|
|
|
|
@override |
|
|
@override |
|
|
State<TablesScreen> createState() => _TablesScreenState(); |
|
|
// ignore: library_private_types_in_public_api |
|
|
|
|
|
_TablesScreenState createState() => _TablesScreenState(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class _TablesScreenState extends State<TablesScreen> { |
|
|
class _TablesScreenState extends State<TablesScreen> { |
|
|
@ -58,11 +59,139 @@ class _TablesScreenState extends State<TablesScreen> { |
|
|
}); |
|
|
}); |
|
|
} else { |
|
|
} else { |
|
|
setState(() => isLoading = false); |
|
|
setState(() => isLoading = false); |
|
|
print('Erreur API: ${response.statusCode}'); |
|
|
if (kDebugMode) { |
|
|
|
|
|
print('Erreur API: ${response.statusCode}'); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
setState(() => isLoading = false); |
|
|
setState(() => isLoading = false); |
|
|
print('Erreur réseau: $e'); |
|
|
if (kDebugMode) { |
|
|
|
|
|
print('Erreur: $e'); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Future<void> _addTable() async { |
|
|
|
|
|
// Add table logic |
|
|
|
|
|
final result = await showDialog<Map<String, dynamic>>( |
|
|
|
|
|
context: context, |
|
|
|
|
|
builder: (context) => const _AddEditTableDialog(), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if (result != null) { |
|
|
|
|
|
// Call API to add table |
|
|
|
|
|
_callAddTableAPI(result); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Future<void> _callAddTableAPI(Map<String, dynamic> 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<void> _editTable(TableData table) async { |
|
|
|
|
|
final result = await showDialog<Map<String, dynamic>>( |
|
|
|
|
|
context: context, |
|
|
|
|
|
builder: (context) => _AddEditTableDialog(table: table), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if (result != null) { |
|
|
|
|
|
_callEditTableAPI(table.id, result); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Future<void> _callEditTableAPI(int id, Map<String, dynamic> 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<void> _deleteTable(int id) async { |
|
|
|
|
|
final confirm = await showDialog<bool>( |
|
|
|
|
|
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<void> _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'))); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -71,7 +200,7 @@ class _TablesScreenState extends State<TablesScreen> { |
|
|
case 'available': |
|
|
case 'available': |
|
|
return Colors.green; |
|
|
return Colors.green; |
|
|
case 'occupied': |
|
|
case 'occupied': |
|
|
return Colors.red; |
|
|
return Colors.orange; |
|
|
case 'reserved': |
|
|
case 'reserved': |
|
|
return Colors.orange; |
|
|
return Colors.orange; |
|
|
default: |
|
|
default: |
|
|
@ -92,143 +221,449 @@ class _TablesScreenState extends State<TablesScreen> { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
bool isDesktop() { |
|
|
bool isTableSelectable(String status) { |
|
|
if (kIsWeb) return true; |
|
|
return status == 'available'; |
|
|
try { |
|
|
|
|
|
return Platform.isWindows || Platform.isMacOS || Platform.isLinux; |
|
|
|
|
|
} catch (_) { |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@override |
|
|
@override |
|
|
Widget build(BuildContext context) { |
|
|
Widget build(BuildContext context) { |
|
|
final screenWidth = MediaQuery.of(context).size.width; |
|
|
final screenWidth = MediaQuery.of(context).size.width; |
|
|
final crossAxisCount = screenWidth > 1200 ? 4 : screenWidth > 800 ? 3 : 2; |
|
|
final isDesktop = screenWidth >= 768; |
|
|
|
|
|
|
|
|
return Scaffold( |
|
|
int crossAxisCount = 2; |
|
|
appBar: AppBar( |
|
|
if (screenWidth > 1200) { |
|
|
title: const Text('Sélectionner une table'), |
|
|
crossAxisCount = 4; |
|
|
actions: isDesktop() |
|
|
} else if (screenWidth > 800) { |
|
|
? [ |
|
|
crossAxisCount = 3; |
|
|
IconButton( |
|
|
} |
|
|
icon: const Icon(Icons.refresh), |
|
|
|
|
|
onPressed: () { |
|
|
if (isLoading) { |
|
|
setState(() => isLoading = true); |
|
|
return const Center(child: CircularProgressIndicator()); |
|
|
fetchTables(); |
|
|
} |
|
|
}, |
|
|
|
|
|
|
|
|
return Column( |
|
|
|
|
|
children: [ |
|
|
|
|
|
// Header |
|
|
|
|
|
Container( |
|
|
|
|
|
color: Colors.white, |
|
|
|
|
|
padding: const EdgeInsets.all(20), |
|
|
|
|
|
child: Row( |
|
|
|
|
|
children: [ |
|
|
|
|
|
Expanded( |
|
|
|
|
|
child: Column( |
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start, |
|
|
|
|
|
children: [ |
|
|
|
|
|
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, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
), |
|
|
), |
|
|
] |
|
|
), |
|
|
: null, |
|
|
// Add button (desktop only) |
|
|
), |
|
|
if (isDesktop) |
|
|
body: isLoading |
|
|
ElevatedButton.icon( |
|
|
? const Center(child: CircularProgressIndicator()) |
|
|
onPressed: _addTable, |
|
|
: Padding( |
|
|
icon: const Icon(Icons.add, size: 20), |
|
|
padding: const EdgeInsets.all(12.0), |
|
|
label: const Text('Ajouter'), |
|
|
child: GridView.builder( |
|
|
style: ElevatedButton.styleFrom( |
|
|
itemCount: tables.length, |
|
|
backgroundColor: Colors.green.shade700, |
|
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( |
|
|
foregroundColor: Colors.white, |
|
|
crossAxisCount: crossAxisCount, |
|
|
padding: const EdgeInsets.symmetric( |
|
|
crossAxisSpacing: 12, |
|
|
horizontal: 20, |
|
|
mainAxisSpacing: 12, |
|
|
vertical: 12, |
|
|
childAspectRatio: 2.3, |
|
|
), |
|
|
|
|
|
), |
|
|
), |
|
|
), |
|
|
itemBuilder: (context, index) { |
|
|
], |
|
|
final table = tables[index]; |
|
|
), |
|
|
final isAvailable = table.status == 'available'; |
|
|
), |
|
|
|
|
|
|
|
|
return Card( |
|
|
// Content - Reduced height |
|
|
shape: RoundedRectangleBorder( |
|
|
Expanded( |
|
|
borderRadius: BorderRadius.circular(14), |
|
|
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 ? 1.1 : 0.85, // Adjusted |
|
|
), |
|
|
), |
|
|
child: Padding( |
|
|
itemCount: tables.length, |
|
|
padding: const EdgeInsets.all(10), |
|
|
itemBuilder: (context, index) { |
|
|
child: Column( |
|
|
final table = tables[index]; |
|
|
crossAxisAlignment: CrossAxisAlignment.start, |
|
|
final isSelectable = isTableSelectable(table.status); |
|
|
children: [ |
|
|
|
|
|
Row( |
|
|
return Container( |
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|
|
decoration: BoxDecoration( |
|
|
children: [ |
|
|
color: Colors.white, |
|
|
Text( |
|
|
borderRadius: BorderRadius.circular(12), |
|
|
table.nom, |
|
|
border: Border.all(color: Colors.grey.shade200), |
|
|
style: const TextStyle( |
|
|
boxShadow: [ |
|
|
fontWeight: FontWeight.bold, |
|
|
BoxShadow( |
|
|
fontSize: 13, |
|
|
// ignore: deprecated_member_use |
|
|
), |
|
|
color: Colors.black.withOpacity(0.05), |
|
|
), |
|
|
blurRadius: 5, |
|
|
Container( |
|
|
offset: const Offset(0, 2), |
|
|
padding: const EdgeInsets.symmetric( |
|
|
), |
|
|
horizontal: 8, vertical: 2), |
|
|
], |
|
|
decoration: BoxDecoration( |
|
|
), |
|
|
color: getStatusColor(table.status), |
|
|
child: Stack( |
|
|
borderRadius: BorderRadius.circular(50), |
|
|
children: [ |
|
|
), |
|
|
// Desktop CRUD menu |
|
|
child: Text( |
|
|
if (isDesktop) |
|
|
getStatusLabel(table.status), |
|
|
Positioned( |
|
|
style: const TextStyle( |
|
|
top: 8, |
|
|
color: Colors.white, |
|
|
right: 8, |
|
|
fontSize: 10, |
|
|
child: PopupMenuButton<String>( |
|
|
|
|
|
icon: Icon( |
|
|
|
|
|
Icons.more_vert, |
|
|
|
|
|
size: 16, |
|
|
|
|
|
color: Colors.grey.shade600, |
|
|
), |
|
|
), |
|
|
), |
|
|
onSelected: (value) { |
|
|
), |
|
|
switch (value) { |
|
|
], |
|
|
case 'edit': |
|
|
), |
|
|
_editTable(table); |
|
|
const Spacer(), |
|
|
break; |
|
|
Row( |
|
|
case 'delete': |
|
|
children: [ |
|
|
_deleteTable(table.id); |
|
|
const Icon(Icons.people_outline, |
|
|
break; |
|
|
size: 14, color: Colors.grey), |
|
|
} |
|
|
const SizedBox(width: 4), |
|
|
}, |
|
|
Text( |
|
|
itemBuilder: |
|
|
'${table.capacity} personnes', |
|
|
(context) => [ |
|
|
style: const TextStyle( |
|
|
const PopupMenuItem( |
|
|
fontSize: 11.5, |
|
|
value: 'edit', |
|
|
color: Colors.grey, |
|
|
child: Row( |
|
|
), |
|
|
children: [ |
|
|
), |
|
|
Icon(Icons.edit, size: 16), |
|
|
], |
|
|
SizedBox(width: 8), |
|
|
), |
|
|
Text('Modifier'), |
|
|
const SizedBox(height: 8), |
|
|
], |
|
|
SizedBox( |
|
|
|
|
|
width: double.infinity, |
|
|
|
|
|
height: 30, |
|
|
|
|
|
child: ElevatedButton( |
|
|
|
|
|
onPressed: isAvailable |
|
|
|
|
|
? () { |
|
|
|
|
|
Navigator.push( |
|
|
|
|
|
context, |
|
|
|
|
|
MaterialPageRoute( |
|
|
|
|
|
builder: (_) => MenuPage( |
|
|
|
|
|
tableId: table.id, |
|
|
|
|
|
personne: table.capacity, |
|
|
|
|
|
), |
|
|
), |
|
|
), |
|
|
), |
|
|
); |
|
|
const PopupMenuItem( |
|
|
} |
|
|
value: 'delete', |
|
|
: null, |
|
|
child: Row( |
|
|
style: ElevatedButton.styleFrom( |
|
|
children: [ |
|
|
backgroundColor: Colors.deepOrange, |
|
|
Icon( |
|
|
padding: EdgeInsets.zero, |
|
|
Icons.delete, |
|
|
shape: RoundedRectangleBorder( |
|
|
size: 16, |
|
|
borderRadius: BorderRadius.circular(6), |
|
|
color: Colors.red, |
|
|
|
|
|
), |
|
|
|
|
|
SizedBox(width: 8), |
|
|
|
|
|
Text( |
|
|
|
|
|
'Supprimer', |
|
|
|
|
|
style: TextStyle( |
|
|
|
|
|
color: Colors.red, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
), |
|
|
), |
|
|
), |
|
|
), |
|
|
child: const Text( |
|
|
|
|
|
"Réserver", |
|
|
// Table content |
|
|
style: TextStyle( |
|
|
Padding( |
|
|
color: Colors.white, |
|
|
padding: const EdgeInsets.all(16), |
|
|
fontSize: 12, |
|
|
child: Column( |
|
|
), |
|
|
crossAxisAlignment: CrossAxisAlignment.start, |
|
|
|
|
|
children: [ |
|
|
|
|
|
Row( |
|
|
|
|
|
children: [ |
|
|
|
|
|
Text( |
|
|
|
|
|
table.nom, |
|
|
|
|
|
style: const TextStyle( |
|
|
|
|
|
fontWeight: FontWeight.w600, |
|
|
|
|
|
fontSize: 16, |
|
|
|
|
|
color: Colors.black87, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
const Spacer(), |
|
|
|
|
|
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: 10, |
|
|
|
|
|
fontWeight: FontWeight.w500, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
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 |
|
|
|
|
|
? () { |
|
|
|
|
|
// Handle table selection |
|
|
|
|
|
ScaffoldMessenger.of( |
|
|
|
|
|
context, |
|
|
|
|
|
).showSnackBar( |
|
|
|
|
|
SnackBar( |
|
|
|
|
|
content: Text( |
|
|
|
|
|
'Table ${table.nom} sélectionnée', |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
: 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<FormState>(); |
|
|
|
|
|
late TextEditingController _nomController; |
|
|
|
|
|
late TextEditingController _capacityController; |
|
|
|
|
|
String _selectedStatus = 'available'; |
|
|
|
|
|
|
|
|
|
|
|
final List<Map<String, String>> _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 la table' : 'Ajouter une table'), |
|
|
|
|
|
content: Form( |
|
|
|
|
|
key: _formKey, |
|
|
|
|
|
child: Column( |
|
|
|
|
|
mainAxisSize: MainAxisSize.min, |
|
|
|
|
|
children: [ |
|
|
|
|
|
TextFormField( |
|
|
|
|
|
controller: _nomController, |
|
|
|
|
|
decoration: const InputDecoration( |
|
|
|
|
|
labelText: 'Nom de la table', |
|
|
|
|
|
border: OutlineInputBorder(), |
|
|
|
|
|
), |
|
|
|
|
|
validator: (value) { |
|
|
|
|
|
if (value == null || value.isEmpty) { |
|
|
|
|
|
return 'Veuillez entrer un nom'; |
|
|
|
|
|
} |
|
|
|
|
|
return null; |
|
|
|
|
|
}, |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(height: 16), |
|
|
|
|
|
TextFormField( |
|
|
|
|
|
controller: _capacityController, |
|
|
|
|
|
decoration: const InputDecoration( |
|
|
|
|
|
labelText: 'Capacité', |
|
|
|
|
|
border: OutlineInputBorder(), |
|
|
), |
|
|
), |
|
|
|
|
|
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; |
|
|
|
|
|
}, |
|
|
), |
|
|
), |
|
|
|
|
|
const SizedBox(height: 16), |
|
|
|
|
|
DropdownButtonFormField<String>( |
|
|
|
|
|
value: _selectedStatus, |
|
|
|
|
|
decoration: const InputDecoration( |
|
|
|
|
|
labelText: 'Statut', |
|
|
|
|
|
border: OutlineInputBorder(), |
|
|
|
|
|
), |
|
|
|
|
|
items: |
|
|
|
|
|
_statusOptions.map((option) { |
|
|
|
|
|
return DropdownMenuItem<String>( |
|
|
|
|
|
value: option['value'], |
|
|
|
|
|
child: Text(option['label']!), |
|
|
|
|
|
); |
|
|
|
|
|
}).toList(), |
|
|
|
|
|
onChanged: (value) { |
|
|
|
|
|
setState(() { |
|
|
|
|
|
_selectedStatus = value!; |
|
|
|
|
|
}); |
|
|
|
|
|
}, |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
actions: [ |
|
|
|
|
|
TextButton( |
|
|
|
|
|
onPressed: () => Navigator.pop(context), |
|
|
|
|
|
child: const Text('Annuler'), |
|
|
|
|
|
), |
|
|
|
|
|
ElevatedButton( |
|
|
|
|
|
onPressed: () { |
|
|
|
|
|
if (_formKey.currentState!.validate()) { |
|
|
|
|
|
final result = { |
|
|
|
|
|
'nom': _nomController.text, |
|
|
|
|
|
'capacity': int.parse(_capacityController.text), |
|
|
|
|
|
'status': _selectedStatus, |
|
|
|
|
|
}; |
|
|
|
|
|
Navigator.pop(context, result); |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
style: ElevatedButton.styleFrom( |
|
|
|
|
|
backgroundColor: Colors.green.shade700, |
|
|
|
|
|
), |
|
|
|
|
|
child: Text(isEditing ? 'Modifier' : 'Ajouter'), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|