diff --git a/lib/main.dart b/lib/main.dart index a0352c2..e0d8703 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,17 +1,26 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'providers/auth_provider.dart'; import 'layouts/main_layout.dart'; +import 'pages/login_screen.dart'; import 'pages/tables.dart'; import 'pages/categorie.dart'; import 'pages/commandes_screen.dart'; -import 'pages/login_screen.dart'; import 'pages/menus_screen.dart'; import 'pages/historique_commande.dart'; import 'pages/information.dart'; import 'pages/printer_page.dart'; -import 'pages/encaissement_screen.dart'; // NOUVEAU +import 'pages/encaissement_screen.dart'; void main() { - runApp(const MyApp()); + runApp( + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => AuthProvider()), + ], + child: const MyApp(), + ), + ); } class MyApp extends StatelessWidget { @@ -22,51 +31,39 @@ class MyApp extends StatelessWidget { return MaterialApp( title: 'Restaurant App', debugShowCheckedModeBanner: false, - theme: ThemeData( - primarySwatch: Colors.green, - visualDensity: VisualDensity.adaptivePlatformDensity, - ), + theme: ThemeData(primarySwatch: Colors.green), initialRoute: '/login', routes: { '/login': (context) => const LoginScreen(), - '/tables': - (context) => const MainLayout( + '/tables': (context) => const MainLayout( currentRoute: '/tables', child: TablesScreen(), ), - '/categories': - (context) => const MainLayout( + '/categories': (context) => const MainLayout( currentRoute: '/categories', child: CategoriesPage(), ), - '/commandes': - (context) => const MainLayout( + '/commandes': (context) => const MainLayout( currentRoute: '/commandes', child: OrdersManagementScreen(), ), - '/plats': - (context) => const MainLayout( + '/plats': (context) => const MainLayout( currentRoute: '/plats', child: PlatsManagementScreen(), ), - // NOUVELLE ROUTE pour l'encaissement - '/encaissement': - (context) => const MainLayout( + '/encaissement': (context) => const MainLayout( currentRoute: '/encaissement', child: EncaissementScreen(), ), - '/historique': - (context) => MainLayout( + '/historique': (context) => MainLayout( currentRoute: '/historique', child: OrderHistoryPage(), ), - '/information': - (context) => MainLayout( + '/information': (context) => MainLayout( currentRoute: '/information', child: PrintTemplateManagementScreen(), ), - '/Setting': - (context) => MainLayout( + '/Setting': (context) => MainLayout( currentRoute: '/Setting', child: PrinterPage(), ), diff --git a/lib/pages/caisse_screen.dart b/lib/pages/caisse_screen.dart index 49a4cd5..c970e78 100644 --- a/lib/pages/caisse_screen.dart +++ b/lib/pages/caisse_screen.dart @@ -352,90 +352,112 @@ class _CaisseScreenState extends State { ); } - Widget _buildPaymentMethodCard(PaymentMethod method) { - final isSelected = selectedPaymentMethod?.id == method.id; - final amount = commande?.totalTtc ?? 0.0; - - return Container( - margin: const EdgeInsets.only(bottom: 12), - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - setState(() { - selectedPaymentMethod = method; - }); - }, - borderRadius: BorderRadius.circular(12), - child: Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: method.color, - borderRadius: BorderRadius.circular(12), - border: - isSelected ? Border.all(color: Colors.white, width: 3) : null, - boxShadow: [ + Widget _buildPaymentMethodCard(PaymentMethod method) { + final isSelected = selectedPaymentMethod?.id == method.id; + final amount = commande?.totalTtc ?? 0.0; + + return Container( + margin: const EdgeInsets.only(bottom: 12), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + setState(() { + selectedPaymentMethod = method; + }); + }, + borderRadius: BorderRadius.circular(12), + child: AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: isSelected ? Colors.white : method.color, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: isSelected ? method.color : Colors.transparent, + width: 2, + ), + boxShadow: [ + if (isSelected) BoxShadow( - color: Colors.black.withOpacity(0.1), + color: method.color.withOpacity(0.4), blurRadius: 8, - offset: const Offset(0, 2), + offset: const Offset(0, 3), ), - ], - ), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular(8), - ), - child: Icon(method.icon, color: Colors.white, size: 24), + ], + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: isSelected + ? method.color.withOpacity(0.1) + : Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + method.icon, + color: isSelected ? method.color : Colors.white, + size: 24, ), + ), - const SizedBox(width: 16), - - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - method.name, - style: const TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.bold, - ), + const SizedBox(width: 16), + + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + method.name, + style: TextStyle( + color: isSelected ? Colors.black87 : Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, ), - const SizedBox(height: 4), - Text( - method.description, - style: TextStyle( - color: Colors.white.withOpacity(0.9), - fontSize: 13, - ), + ), + const SizedBox(height: 4), + Text( + method.description, + style: TextStyle( + color: isSelected + ? Colors.black54 + : Colors.white.withOpacity(0.9), + fontSize: 13, ), - ], - ), + ), + ], ), + ), - const SizedBox(width: 16), + const SizedBox(width: 16), - Text( - '${NumberFormat("#,##0.00", "fr_FR").format(amount)} MGA', - style: const TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, - ), + Icon( + isSelected ? Icons.check_circle : Icons.radio_button_unchecked, + color: isSelected ? method.color : Colors.white, + size: 22, + ), + + const SizedBox(width: 8), + + Text( + '${NumberFormat("#,##0.00", "fr_FR").format(amount)} MGA', + style: TextStyle( + color: isSelected ? Colors.black87 : Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, ), - ], - ), + ), + ], ), ), ), - ); - } + ), + ); +} + Widget _buildPaymentButton() { final canPay = selectedPaymentMethod != null && !isProcessingPayment; diff --git a/lib/pages/commandes_screen.dart b/lib/pages/commandes_screen.dart index 2798bf2..e66d28a 100644 --- a/lib/pages/commandes_screen.dart +++ b/lib/pages/commandes_screen.dart @@ -4,9 +4,16 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:intl/intl.dart'; +import 'package:itrimobe/providers/auth_provider.dart'; +import 'package:provider/provider.dart' show Provider; import 'commande_item_screen.dart'; import '../services/pdf_impression_commande.dart'; +bool isMobile(BuildContext context) { + final size = MediaQuery.of(context).size.width; + return size < 600; +} + class OrdersManagementScreen extends StatefulWidget { const OrdersManagementScreen({super.key}); @@ -523,6 +530,7 @@ Future deleteOrder(Order order) async { } } + class OrderCard extends StatelessWidget { final Order order; final Function(Order, String, {String? modePaiement}) onStatusUpdate; @@ -530,6 +538,8 @@ class OrderCard extends StatelessWidget { final Function(Order) onDelete; final VoidCallback onViewDetails; + + const OrderCard({ Key? key, required this.order, @@ -579,6 +589,8 @@ class OrderCard extends StatelessWidget { @override Widget build(BuildContext context) { + final auth = Provider.of(context); + final userType = auth.userType; return Container( margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( @@ -782,9 +794,11 @@ class OrderCard extends StatelessWidget { ), ), const SizedBox(width: 8), + + if (userType != 'Serveur' || !isMobile(context)) Expanded( flex: 3, - child: ElevatedButton.icon( + child:ElevatedButton.icon( onPressed: () => onProcessPayment(order), icon: const Icon( Icons.point_of_sale, @@ -809,7 +823,9 @@ class OrderCard extends StatelessWidget { ), ), const SizedBox(width: 8), + if (order.statut == 'en_attente') + if (userType != 'Serveur' || !isMobile(context)) Expanded( child: ElevatedButton( onPressed: @@ -827,8 +843,10 @@ class OrderCard extends StatelessWidget { ), ), ), + const SizedBox(width: 8), if (order.statut == 'en_preparation') + if (userType != 'Serveur' || !isMobile(context)) Expanded( child: ElevatedButton( onPressed: @@ -846,6 +864,7 @@ class OrderCard extends StatelessWidget { ), ), ), + const SizedBox(width: 8), Container( decoration: BoxDecoration( diff --git a/lib/pages/historique_commande.dart b/lib/pages/historique_commande.dart index f16ada7..cfd5c03 100644 --- a/lib/pages/historique_commande.dart +++ b/lib/pages/historique_commande.dart @@ -14,6 +14,8 @@ class _OrderHistoryPageState extends State List commandes = []; bool isLoading = true; String? error; + DateTime? _startDate; + DateTime? _endDate; // Informations d'affichage et pagination int totalItems = 0; @@ -37,144 +39,146 @@ class _OrderHistoryPageState extends State _loadCommandes(); } +Future _loadCommandes({int page = 1}) async { + try { + setState(() { + isLoading = true; + error = null; + }); - Future _loadCommandes({int page = 1}) async { - try { - setState(() { - isLoading = true; - error = null; - }); + // Construction des paramètres de requête + Map queryParams = { + 'statut': 'payee', + 'page': page.toString(), + 'limit': itemsPerPage.toString(), + }; - // Ajouter les paramètres de pagination à l'URL - final uri = Uri.parse('$baseUrl/api/commandes').replace(queryParameters: { - 'statut': 'payee', - 'page': page.toString(), - 'limit': itemsPerPage.toString(), - }); + // ✅ CORRECTION : Envoyer la date en format ISO avec fuseau horaire UTC + if (_startDate != null) { + // Début de journée en UTC (00:00:00) + final startUtc = DateTime.utc( + _startDate!.year, + _startDate!.month, + _startDate!.day, + 0, 0, 0, + ); + queryParams['date_start'] = startUtc.toIso8601String(); + print('📅 Date début (UTC): ${startUtc.toIso8601String()}'); + } + + if (_endDate != null) { + // Fin de journée en UTC (23:59:59) + final endUtc = DateTime.utc( + _endDate!.year, + _endDate!.month, + _endDate!.day, + 23, 59, 59, 999, + ); + queryParams['date_end'] = endUtc.toIso8601String(); + print('📅 Date fin (UTC): ${endUtc.toIso8601String()}'); + } - final response = await http.get(uri, headers: _headers); - - if (response.statusCode == 200) { - final dynamic responseBody = json.decode(response.body); - - List data = []; - - // Gestion améliorée de la réponse - if (responseBody is Map) { - - // Structure: {"success": true, "data": {"commandes": [...], "pagination": {...}}} - if (responseBody.containsKey('data') && responseBody['data'] is Map) { - final dataMap = responseBody['data'] as Map; - if (dataMap.containsKey('commandes')) { - final commandesValue = dataMap['commandes']; - - if (commandesValue is List) { - data = commandesValue; - } else if (commandesValue != null) { - data = [commandesValue]; - } - - // Pagination - if (dataMap.containsKey('pagination')) { - final pagination = dataMap['pagination'] as Map?; - if (pagination != null) { - currentPage = pagination['currentPage'] ?? page; - totalPages = pagination['totalPages'] ?? 1; - totalItems = pagination['totalItems'] ?? data.length; - } - } else { - // Si pas de pagination dans la réponse, calculer approximativement - totalItems = data.length; - currentPage = page; - totalPages = (totalItems / itemsPerPage).ceil(); - } - } else { - totalItems = 0; - currentPage = 1; - totalPages = 1; - } - } else if (responseBody.containsKey('commandes')) { - // Fallback: commandes directement dans responseBody - final commandesValue = responseBody['commandes']; - - if (commandesValue is List) { - data = commandesValue; - } else if (commandesValue != null) { - data = [commandesValue]; - } - totalItems = data.length; - currentPage = page; - totalPages = (totalItems / itemsPerPage).ceil(); - } else { - totalItems = 0; - currentPage = 1; - totalPages = 1; + // Debug: Afficher l'URL complète + final uri = Uri.parse('$baseUrl/api/commandes').replace(queryParameters: queryParams); + print('🌐 URL appelée: $uri'); + + final response = await http.get(uri, headers: _headers); + + print('📡 Status code: ${response.statusCode}'); + + if (response.statusCode == 200) { + final dynamic responseBody = json.decode(response.body); + List data = []; + + // Gestion selon le format de réponse + if (responseBody is Map) { + final dataMap = responseBody['data'] as Map?; + + if (dataMap != null) { + final commandesValue = dataMap['commandes']; + if (commandesValue is List) { + data = commandesValue; + } else if (commandesValue != null) { + data = [commandesValue]; } - } else if (responseBody is List) { - data = responseBody; - totalItems = data.length; - currentPage = page; - totalPages = (totalItems / itemsPerPage).ceil(); - } else { - throw Exception('Format de réponse inattendu: ${responseBody.runtimeType}'); + + // Pagination + final pagination = dataMap['pagination'] as Map?; + currentPage = pagination?['currentPage'] ?? page; + totalPages = pagination?['totalPages'] ?? (data.length / itemsPerPage).ceil(); + totalItems = pagination?['totalItems'] ?? data.length; } - - - // Conversion sécurisée avec prints détaillés - List parsedCommandes = []; - for (int i = 0; i < data.length; i++) { - try { - final item = data[i]; - - if (item is Map) { - item.forEach((key, value) { - }); - - final commandeData = CommandeData.fromJson(item); - - if (commandeData.items != null) { - for (int j = 0; j < commandeData.items!.length; j++) { - final commandeItem = commandeData.items![j]; - } - } - - parsedCommandes.add(commandeData); - } else { - print('ERROR: Item $i n\'est pas un Map: ${item.runtimeType}'); - } - } catch (e, stackTrace) { - print('ERROR: Erreur lors du parsing de l\'item $i: $e'); - print('Stack trace: $stackTrace'); - // Continue avec les autres items + } else if (responseBody is List) { + data = responseBody; + totalItems = data.length; + currentPage = page; + totalPages = (totalItems / itemsPerPage).ceil(); + } else { + throw Exception('Format de réponse inattendu: ${responseBody.runtimeType}'); + } + + // Parsing sécurisé des commandes + List parsedCommandes = []; + for (int i = 0; i < data.length; i++) { + try { + final item = data[i]; + if (item is Map) { + final commandeData = CommandeData.fromJson(item); + parsedCommandes.add(commandeData); + } else { + print('Item $i invalide: ${item.runtimeType}'); } + } catch (e, stackTrace) { + print('Erreur parsing item $i: $e'); + print(stackTrace); } - - setState(() { - commandes = parsedCommandes; - isLoading = false; - }); - - // Initialiser les animations après avoir mis à jour l'état - _initializeAnimations(); - _startAnimations(); - - } else { - print('ERROR: HTTP ${response.statusCode}: ${response.reasonPhrase}'); - setState(() { - error = 'Erreur HTTP ${response.statusCode}: ${response.reasonPhrase}'; - isLoading = false; - }); } - } catch (e, stackTrace) { - print('=== ERREUR GÉNÉRALE ==='); - print('Erreur: $e'); - print('Stack trace: $stackTrace'); + + print('✅ ${parsedCommandes.length} commandes chargées'); + + setState(() { + commandes = parsedCommandes; + isLoading = false; + }); + + _initializeAnimations(); + _startAnimations(); + } else { setState(() { - error = 'Erreur de connexion: $e'; + error = 'Erreur HTTP ${response.statusCode}: ${response.reasonPhrase}'; isLoading = false; }); + print('Erreur HTTP ${response.statusCode}: ${response.reasonPhrase}'); } + } catch (e, stackTrace) { + print('=== ERREUR GÉNÉRALE ==='); + print('Erreur: $e'); + print('Stack trace: $stackTrace'); + setState(() { + error = 'Erreur de connexion: $e'; + isLoading = false; + }); + } +} + + + Future _selectDate(BuildContext context, bool isStart) async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: isStart ? (_startDate ?? DateTime.now()) : (_endDate ?? DateTime.now()), + firstDate: DateTime(2020), + lastDate: DateTime(2100), + ); + if (picked != null) { + setState(() { + if (isStart) { + _startDate = picked; + } else { + _endDate = picked; + } + }); } +} // Fonction pour aller à la page suivante void _goToNextPage() { @@ -258,73 +262,151 @@ class _OrderHistoryPageState extends State ), ); } - - Widget _buildHeader() { - return AnimatedBuilder( - animation: _animationController, - builder: (context, child) { - return Transform.translate( - offset: Offset(0, -50 * (1 - _animationController.value)), - child: Opacity( - opacity: _animationController.value, - child: Container( - width: double.infinity, - margin: EdgeInsets.symmetric(horizontal: 20, vertical: 5), - padding: EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.08), - blurRadius: 6, - offset: Offset(0, 2), +Widget _buildHeader() { + return AnimatedBuilder( + animation: _animationController, + builder: (context, child) { + return Transform.translate( + offset: Offset(0, -50 * (1 - _animationController.value)), + child: Opacity( + opacity: _animationController.value, + child: Container( + width: double.infinity, + margin: EdgeInsets.symmetric(horizontal: 20, vertical: 5), + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.08), + blurRadius: 6, + offset: Offset(0, 2), + ), + ], + ), + child: Column( + children: [ + Text( + 'Historique des commandes payées', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF2c3e50), ), - ], - ), - child: Column( - children: [ - Text( - 'Historique des commandes payées', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Color(0xFF2c3e50), - ), - textAlign: TextAlign.center, + textAlign: TextAlign.center, + ), + SizedBox(height: 2), + Text( + 'Consultez toutes les commandes qui ont été payées', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, ), - SizedBox(height: 2), - Text( - 'Consultez toutes les commandes qui ont été payées', - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade600, + textAlign: TextAlign.center, + ), + SizedBox(height: 6), + // Barre des filtres + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () => _selectDate(context, true), + child: Text(_startDate == null + ? 'Date début' + : 'Début: ${_startDate!.day}/${_startDate!.month}/${_startDate!.year}'), + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF4CAF50), + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + textStyle: TextStyle(fontSize: 10), + ), ), - textAlign: TextAlign.center, - ), - if (totalItems > 0) - Padding( - padding: EdgeInsets.only(top: 4), - child: Text( - totalPages > 1 - ? '$totalItems commande${totalItems > 1 ? 's' : ''} • Page $currentPage/$totalPages' - : '$totalItems commande${totalItems > 1 ? 's' : ''} trouvée${totalItems > 1 ? 's' : ''}', - style: TextStyle( - fontSize: 10, - color: Colors.grey.shade500, - fontWeight: FontWeight.w500, - ), - textAlign: TextAlign.center, + SizedBox(width: 5), + ElevatedButton( + onPressed: () => _selectDate(context, false), + child: Text(_endDate == null + ? 'Date fin' + : 'Fin: ${_endDate!.day}/${_endDate!.month}/${_endDate!.year}'), + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF4CAF50), + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + textStyle: TextStyle(fontSize: 10), ), ), - ], - ), + SizedBox(width: 5), + ElevatedButton( + onPressed: () => _loadCommandes(page: 1), + child: Text('Filtrer'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.orange, + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + textStyle: TextStyle(fontSize: 10), + ), + ), + SizedBox(width: 5), + ElevatedButton( + onPressed: () { + setState(() { + _startDate = null; + _endDate = null; + }); + _loadCommandes(page: 1); + }, + child: Text('Réinitialiser'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.grey, + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + textStyle: TextStyle(fontSize: 10), + ), + ), + SizedBox(width: 5), + ElevatedButton( + onPressed: () { + final today = DateTime.now(); + setState(() { + _startDate = DateTime(today.year, today.month, today.day); + _endDate = DateTime(today.year, today.month, today.day, 23, 59, 59); + }); + _loadCommandes(page: 1); + }, + child: Text('Aujourd’hui'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + textStyle: TextStyle(fontSize: 10), + ), + ), + ], + ), + SizedBox(height: 4), + if (totalItems > 0) + Padding( + padding: EdgeInsets.only(top: 4), + child: Text( + totalPages > 1 + ? '$totalItems commande${totalItems > 1 ? 's' : ''} • Page $currentPage/$totalPages' + : '$totalItems commande${totalItems > 1 ? 's' : ''} trouvée${totalItems > 1 ? 's' : ''}', + style: TextStyle( + fontSize: 10, + color: Colors.grey.shade500, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ), + ], ), ), - ); - }, - ); - } + ), + ); + }, + ); +} Widget _buildPagination() { return Container( @@ -462,91 +544,106 @@ class _OrderHistoryPageState extends State return pages; } - Widget _buildContent() { - if (isLoading) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(Color(0xFF4CAF50)), - ), - SizedBox(height: 16), - Text( - 'Chargement des commandes...', - style: TextStyle(color: Colors.grey.shade600), - ), - ], - ), - ); - } + Widget _buildContent() { + if (isLoading) { + return const Center( + child: CircularProgressIndicator(color: Color(0xFF4CAF50)), + ); + } - if (error != null) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.error_outline, size: 64, color: Colors.grey), - SizedBox(height: 16), - Padding( - padding: EdgeInsets.symmetric(horizontal: 20), - child: Text( - error!, - style: TextStyle(color: Colors.grey), - textAlign: TextAlign.center, - ), - ), - SizedBox(height: 16), - ElevatedButton( - onPressed: () => _loadCommandes(page: currentPage), - child: Text('Réessayer'), - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFF4CAF50), - foregroundColor: Colors.white, - ), + if (error != null) { + return Center( + child: Text(error!, style: TextStyle(color: Colors.red)), + ); + } + + if (commandes.isEmpty) { + return const Center( + child: Text("Aucune commande trouvée"), + ); + } + + // ✅ Afficher les données sous forme de tableau +return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: MediaQuery.of(context).size.width * 0.9), // 1.5x la largeur écran + child: SingleChildScrollView( + child: _buildTableView(), + ), + ), +); + +} + +Widget _buildTableView() { + return DataTable( + columnSpacing: 20, + headingRowColor: MaterialStateProperty.all(const Color(0xFF4CAF50)), + headingTextStyle: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + dataRowHeight: 48, + columns: const [ + DataColumn(label: Text("N°")), + DataColumn(label: Text("Table")), + DataColumn(label: Text("Total")), + DataColumn(label: Text("Détails")), // Nouvelle colonne pour le bouton + ], + rows: commandes.map((commande) { + return DataRow( + cells: [ + DataCell(Text(commande.numeroCommande ?? '-')), + DataCell(Text(commande.tablename ?? '-')), + DataCell(Text(_formatPrice(commande.totalTtc ?? 0))), + DataCell( + IconButton( + icon: Icon(Icons.info, color: Color(0xFF4CAF50)), + tooltip: 'Voir les détails', + onPressed: () { + _showCommandeDetails(commande); + }, ), - ], - ), + ), + ], ); - } + }).toList(), + ); +} - if (commandes.isEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.restaurant_menu, size: 64, color: Colors.grey), - SizedBox(height: 16), - Text( - currentPage > 1 - ? 'Aucune commande sur cette page' - : 'Aucune commande payée', - style: TextStyle(color: Colors.grey, fontSize: 16), - ), - if (currentPage > 1) ...[ - SizedBox(height: 16), - ElevatedButton( - onPressed: () => _goToPage(1), - child: Text('Retour à la première page'), - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFF4CAF50), - foregroundColor: Colors.white, - ), +// Exemple de fonction pour afficher les détails dans un dialog +void _showCommandeDetails(CommandeData commande) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text('Détails commande ${commande.numeroCommande ?? ""}'), + content: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Table: ${commande.tablename ?? "-"}'), + Text( + 'Date de paiement: ${commande.datePaiement != null ? _formatDateTime(commande.datePaiement!) : "-"}', ), + Text('Total TTC: ${_formatPrice(commande.totalTtc ?? 0)}'), + SizedBox(height: 10), + const Text('Articles:', style: TextStyle(fontWeight: FontWeight.bold)), + ...?commande.items?.map((item) => Text( + '${item.quantite} × ${item.menuNom} - ${_formatPrice(item.totalItem)}')), ], - ], + ), ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Fermer'), + ), + ], ); - } + }, + ); +} + - return ListView.builder( - padding: EdgeInsets.symmetric(horizontal: 20), - itemCount: commandes.length, - itemBuilder: (context, index) { - return _buildOrderCard(commandes[index], index); - }, - ); - } Widget _buildOrderCard(CommandeData commande, int index) { if (index >= _cardAnimationControllers.length) { @@ -1052,20 +1149,19 @@ class CommandeData { return null; } - static DateTime? _parseDateTime(dynamic value) { - if (value == null) return null; - if (value is String) { - try { - final result = DateTime.parse(value); - return result; - } catch (e) { - print('Erreur parsing date: $value - $e'); - return null; - } +static DateTime? _parseDateTime(dynamic value) { + if (value == null) return null; + if (value is String) { + try { + return DateTime.parse(value).toLocal(); // converti en heure locale + } catch (e) { + print('Erreur parsing date: $value - $e'); + return null; } - print('Impossible de parser en datetime: $value'); - return null; } + return null; +} + static List? _parseItems(dynamic value) { diff --git a/lib/pages/login_screen.dart b/lib/pages/login_screen.dart index 17f0e68..97df5a7 100644 --- a/lib/pages/login_screen.dart +++ b/lib/pages/login_screen.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; +import '../providers/auth_provider.dart'; import './tables.dart'; import '../layouts/main_layout.dart'; @@ -33,6 +35,10 @@ class _LoginScreenState extends State { static const String serveurPassword = 'serveur123'; static const String adminEmail = 'admin@restaurant.com'; static const String adminPassword = 'admin123'; + static const String cuisinierEmail = 'cuisinier@restaurant.com'; + static const String cuisinierPassword = 'cuisinier123'; + static const String caissierEmail = 'caissier@restaurant.com'; + static const String caissierPassword = 'caissier123'; final TextEditingController emailController = TextEditingController(); final TextEditingController passwordController = TextEditingController(); @@ -63,49 +69,67 @@ class _LoginScreenState extends State { } } } - void _login() async { - if (!_formKey.currentState!.validate()) return; + if (!_formKey.currentState!.validate()) return; - setState(() { - _isLoading = true; - _errorMessage = null; - }); + setState(() { + _isLoading = true; + _errorMessage = null; + }); - // Simulate network delay - await Future.delayed(const Duration(seconds: 1)); + // Simule un délai pour l'authentification + await Future.delayed(const Duration(seconds: 1)); - setState(() { - _isLoading = false; - }); + setState(() { + _isLoading = false; + }); - // Check credentials - String email = emailController.text.trim(); - String password = passwordController.text; - - if ((email == serveurEmail && password == serveurPassword) || - (email == adminEmail && password == adminPassword)) { - // Login successful - navigate to home/dashboard - if (mounted) { - String userType = email == adminEmail ? 'Admin' : 'Serveur'; - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: - (context) => const MainLayout( - currentRoute: '/tables', - child: TablesScreen(), - ), - ), - ); - } + String email = emailController.text.trim(); + String password = passwordController.text; + + // Vérification des credentials + if ((email == serveurEmail && password == serveurPassword) || + (email == cuisinierEmail && password == cuisinierPassword) || + (email == caissierEmail && password == caissierPassword) || + (email == adminEmail && password == adminPassword)) { + + // Détermination du rôle + String userType; + if (email == adminEmail) { + userType = 'Admin'; + } else if (email == serveurEmail) { + userType = 'Serveur'; + } + else if (email == caissierEmail) { + userType = 'caissier'; } else { - // Login failed - setState(() { - _errorMessage = 'Email ou mot de passe incorrect'; - }); + userType = 'Cuisinier'; } + + // ✅ On enregistre le rôle dans le provider + final authProvider = Provider.of(context, listen: false); + authProvider.loginAs(userType); + + // ✅ Navigation vers la page principale + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const MainLayout( + currentRoute: '/tables', + child: TablesScreen(), + ), + ), + ); + + } else { + // ❌ Erreur de connexion + setState(() { + _errorMessage = 'Email ou mot de passe incorrect'; + }); } +} + + @override Widget build(BuildContext context) { diff --git a/lib/providers/auth_provider.dart b/lib/providers/auth_provider.dart new file mode 100644 index 0000000..dca0844 --- /dev/null +++ b/lib/providers/auth_provider.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +class AuthProvider extends ChangeNotifier { + String? _userType; // "Admin" ou "Serveur" + + String? get userType => _userType; + bool get isLoggedIn => _userType != null; + + void loginAs(String userType) { + _userType = userType; + notifyListeners(); + } + + void logout() { + _userType = null; + notifyListeners(); + } +} diff --git a/pubspec.lock b/pubspec.lock index 6a50846..3220e18 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -296,6 +296,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.6" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -456,6 +464,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.14.2" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" qr: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 12b2a94..b83e172 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,6 +29,7 @@ dependencies: intl: ^0.18.1 esc_pos_printer: ^4.1.0 esc_pos_utils: ^1.1.0 + provider: ^6.1.1 # Dépendances de développement/test dev_dependencies: