From c9f4a0ce45d7e0560808f8c200dbd7f6769907c8 Mon Sep 17 00:00:00 2001 From: andrymodeste Date: Sun, 3 Aug 2025 18:36:34 +0200 Subject: [PATCH] push 03082025 --- lib/models/command_detail.dart | 3 + lib/models/tables_order.dart | 57 +-- lib/pages/login_screen.dart | 506 ++++++++++++----------- lib/services/restaurant_api_service.dart | 4 +- lib/widgets/command_card.dart | 16 +- 5 files changed, 315 insertions(+), 271 deletions(-) diff --git a/lib/models/command_detail.dart b/lib/models/command_detail.dart index ee3825d..8d09612 100644 --- a/lib/models/command_detail.dart +++ b/lib/models/command_detail.dart @@ -16,6 +16,7 @@ class CommandeDetail { final DateTime? dateService; final DateTime createdAt; final DateTime updatedAt; + final String tablename; final List items; CommandeDetail({ @@ -36,6 +37,7 @@ class CommandeDetail { required this.createdAt, required this.updatedAt, required this.items, + required this.tablename, }); factory CommandeDetail.fromJson(Map json) { @@ -54,6 +56,7 @@ class CommandeDetail { totalTtc: double.tryParse(data['total_ttc']?.toString() ?? '0') ?? 0.0, modePaiement: data['mode_paiement'], commentaires: data['commentaires'], + tablename: json['tablename'] ?? 'Inconnue', serveur: data['serveur'] ?? 'Serveur par défaut', dateCommande: data['date_commande'] != null diff --git a/lib/models/tables_order.dart b/lib/models/tables_order.dart index ffea5b3..ba9aeae 100644 --- a/lib/models/tables_order.dart +++ b/lib/models/tables_order.dart @@ -10,7 +10,8 @@ class TableOrder { final double? total; // Optionnel pour les commandes en cours final bool isEncashed; final String? time; // Heure de la commande si applicable - final String? date; // Date de la commande si applicable + final DateTime? date; // Date de la commande si applicable + final String? tablename; // Date de la commande si applicable // final int? persons; // Nombre de personnes si applicable TableOrder({ @@ -25,31 +26,34 @@ class TableOrder { this.isEncashed = false, this.time, this.date, + this.tablename, // this.persons, }); +factory TableOrder.fromJson(Map json) { + return TableOrder( + id: json['id'] ?? 0, + nom: json['nom'] ?? '', + capacity: json['capacity'] ?? 1, + status: json['statut'] ?? 'available', + location: json['location'] ?? '', + tablename: json['tablename'] ?? '', + createdAt: json['created_at'] != null + ? DateTime.parse(json['created_at']) + : DateTime.now(), + updatedAt: json['updated_at'] != null + ? DateTime.parse(json['updated_at']) + : DateTime.now(), + total: json['total_ht'] != null + ? double.tryParse(json['total_ht'].toString()) + : null, + isEncashed: json['is_encashed'] ?? false, + time: json['time'], + date: json['date_commande'] != null + ? DateTime.parse(json['date_commande']) // tu avais mis updated_at ici par erreur + : null, + ); +} - factory TableOrder.fromJson(Map json) { - return TableOrder( - id: json['id'] ?? 0, - nom: json['nom'] ?? '', - capacity: json['capacity'] ?? 1, - status: json['status'] ?? 'available', - location: json['location'] ?? '', - createdAt: - json['created_at'] != null - ? DateTime.parse(json['created_at']) - : DateTime.now(), - updatedAt: - json['updated_at'] != null - ? DateTime.parse(json['updated_at']) - : DateTime.now(), - total: json['total'] != null ? (json['total'] as num).toDouble() : null, - isEncashed: json['is_encashed'] ?? false, - time: json['time'], - date: json['date'], - // persons: json['persons'], - ); - } Map toJson() { return { @@ -58,6 +62,7 @@ class TableOrder { 'capacity': capacity, 'status': status, 'location': location, + 'tablename': tablename, 'created_at': createdAt.toIso8601String(), 'updated_at': updatedAt.toIso8601String(), if (total != null) 'total': total, @@ -122,7 +127,7 @@ class TableOrder { double? total, bool? isEncashed, String? time, - String? date, + DateTime? date, int? persons, }) { return TableOrder( @@ -153,6 +158,10 @@ class TableOrder { @override int get hashCode => id.hashCode; + + get items => null; + + where(bool Function(dynamic commande) param0) {} } // Énumération pour les statuts (optionnel, pour plus de type safety) diff --git a/lib/pages/login_screen.dart b/lib/pages/login_screen.dart index b3f4e11..17f0e68 100644 --- a/lib/pages/login_screen.dart +++ b/lib/pages/login_screen.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import './tables.dart'; import '../layouts/main_layout.dart'; @@ -54,6 +55,15 @@ class _LoginScreenState extends State { super.dispose(); } + // Méthode pour gérer la touche Entrée + void _handleKeyPress(KeyEvent event) { + if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.enter) { + if (!_isLoading) { + _login(); + } + } + } + void _login() async { if (!_formKey.currentState!.validate()) return; @@ -99,279 +109,295 @@ class _LoginScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: const Color(0xFFF5F5F5), // Light gray background - body: Center( - child: SingleChildScrollView( - padding: const EdgeInsets.all(24.0), - child: Card( - elevation: 2, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - child: Container( - width: 400, - padding: const EdgeInsets.all(40.0), - child: Form( - key: _formKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Logo personnalisé - Container( - width: 80, - height: 80, - child: Image.asset( - 'assets/logo_transparent.png', - fit: BoxFit.contain, - errorBuilder: (context, error, stackTrace) { - // Fallback en cas d'erreur de chargement - return Container( - width: 64, - height: 64, - decoration: const BoxDecoration( - color: Colors.green, - shape: BoxShape.circle, - ), - child: const Icon( - Icons.restaurant_menu, - color: Colors.white, - size: 32, - ), - ); - }, + return KeyboardListener( + focusNode: FocusNode(), + onKeyEvent: _handleKeyPress, + child: Scaffold( + backgroundColor: const Color(0xFFF5F5F5), // Light gray background + body: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.all(24.0), + child: Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Container( + width: 400, + padding: const EdgeInsets.all(40.0), + child: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Logo personnalisé + Container( + width: 80, + height: 80, + child: Image.asset( + 'assets/logo_transparent.png', + fit: BoxFit.contain, + errorBuilder: (context, error, stackTrace) { + // Fallback en cas d'erreur de chargement + return Container( + width: 64, + height: 64, + decoration: const BoxDecoration( + color: Colors.green, + shape: BoxShape.circle, + ), + child: const Icon( + Icons.restaurant_menu, + color: Colors.white, + size: 32, + ), + ); + }, + ), ), - ), - const SizedBox(height: 24), + const SizedBox(height: 24), - // Title - const Text( - 'Restaurant App', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.black87, + // Title + const Text( + 'Restaurant App', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), ), - ), - const SizedBox(height: 8), + const SizedBox(height: 8), - // Subtitle - const Text( - 'Connectez-vous pour accéder au système de commandes', - style: TextStyle(color: Colors.grey, fontSize: 14), - textAlign: TextAlign.center, - ), - const SizedBox(height: 32), + // Subtitle + const Text( + 'Connectez-vous pour accéder au système de commandes', + style: TextStyle(color: Colors.grey, fontSize: 14), + textAlign: TextAlign.center, + ), + const SizedBox(height: 32), - // Error message - if (_errorMessage != null) - Container( - width: double.infinity, - padding: const EdgeInsets.all(12), - margin: const EdgeInsets.only(bottom: 16), - decoration: BoxDecoration( - color: Colors.red.shade50, - border: Border.all(color: Colors.red.shade200), - borderRadius: BorderRadius.circular(4), + // Error message + if (_errorMessage != null) + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + margin: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: Colors.red.shade50, + border: Border.all(color: Colors.red.shade200), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + _errorMessage!, + style: TextStyle( + color: Colors.red.shade600, + fontSize: 14, + ), + textAlign: TextAlign.center, + ), ), + + // Email label + const Align( + alignment: Alignment.centerLeft, child: Text( - _errorMessage!, + 'Email', style: TextStyle( - color: Colors.red.shade600, fontSize: 14, + color: Colors.black87, + fontWeight: FontWeight.w500, ), - textAlign: TextAlign.center, ), ), + const SizedBox(height: 8), - // Email label - const Align( - alignment: Alignment.centerLeft, - child: Text( - 'Email', - style: TextStyle( - fontSize: 14, - color: Colors.black87, - fontWeight: FontWeight.w500, + // Email field + TextFormField( + controller: emailController, + decoration: InputDecoration( + prefixIcon: const Icon( + Icons.email_outlined, + color: Colors.grey, + size: 20, + ), + hintText: 'serveur@restaurant.com', + hintStyle: const TextStyle(color: Colors.grey), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(4), + borderSide: BorderSide(color: Colors.grey.shade300), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(4), + borderSide: BorderSide(color: Colors.grey.shade300), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(4), + borderSide: const BorderSide(color: Colors.green), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 16, + ), ), + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.next, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Veuillez entrer votre email'; + } + return null; + }, ), - ), - const SizedBox(height: 8), + const SizedBox(height: 16), - // Email field - TextFormField( - controller: emailController, - decoration: InputDecoration( - prefixIcon: const Icon( - Icons.email_outlined, - color: Colors.grey, - size: 20, - ), - hintText: 'serveur@restaurant.com', - hintStyle: const TextStyle(color: Colors.grey), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - borderSide: BorderSide(color: Colors.grey.shade300), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - borderSide: BorderSide(color: Colors.grey.shade300), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - borderSide: const BorderSide(color: Colors.green), - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 16, + // Password label + const Align( + alignment: Alignment.centerLeft, + child: Text( + 'Mot de passe', + style: TextStyle( + fontSize: 14, + color: Colors.black87, + fontWeight: FontWeight.w500, + ), ), ), - keyboardType: TextInputType.emailAddress, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Veuillez entrer votre email'; - } - return null; - }, - ), - const SizedBox(height: 16), + const SizedBox(height: 8), - // Password label - const Align( - alignment: Alignment.centerLeft, - child: Text( - 'Mot de passe', - style: TextStyle( - fontSize: 14, - color: Colors.black87, - fontWeight: FontWeight.w500, + // Password field + TextFormField( + controller: passwordController, + decoration: InputDecoration( + prefixIcon: const Icon( + Icons.lock_outline, + color: Colors.grey, + size: 20, + ), + hintText: '••••••••', + hintStyle: const TextStyle(color: Colors.grey), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(4), + borderSide: BorderSide(color: Colors.grey.shade300), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(4), + borderSide: BorderSide(color: Colors.grey.shade300), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(4), + borderSide: const BorderSide(color: Colors.green), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 16, + ), ), + obscureText: true, + textInputAction: TextInputAction.done, + onFieldSubmitted: (_) => _login(), // Validation quand on appuie sur Entrée + validator: (value) { + if (value == null || value.isEmpty) { + return 'Veuillez entrer votre mot de passe'; + } + return null; + }, ), - ), - const SizedBox(height: 8), + const SizedBox(height: 24), - // Password field - TextFormField( - controller: passwordController, - decoration: InputDecoration( - prefixIcon: const Icon( - Icons.lock_outline, - color: Colors.grey, - size: 20, - ), - hintText: '••••••••', - hintStyle: const TextStyle(color: Colors.grey), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - borderSide: BorderSide(color: Colors.grey.shade300), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - borderSide: BorderSide(color: Colors.grey.shade300), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - borderSide: const BorderSide(color: Colors.green), - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 16, + // Login button + SizedBox( + width: double.infinity, + height: 48, + child: ElevatedButton( + onPressed: _isLoading ? null : _login, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + elevation: 0, + ), + child: + _isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ) + : const Text( + 'Se connecter', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), ), ), - obscureText: true, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Veuillez entrer votre mot de passe'; - } - return null; - }, - ), - const SizedBox(height: 24), + const SizedBox(height: 24), - // Login button - SizedBox( - width: double.infinity, - height: 48, - child: ElevatedButton( - onPressed: _isLoading ? null : _login, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.green, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4), - ), - elevation: 0, + // Demo accounts section + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(4), ), - child: - _isLoading - ? const SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator( - color: Colors.white, - strokeWidth: 2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Comptes de démonstration :', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.black87, + ), + ), + const SizedBox(height: 8), + RichText( + text: const TextSpan( + style: TextStyle( + fontSize: 13, + color: Colors.grey, + height: 1.4, + ), + children: [ + TextSpan( + text: 'Serveur : ', + style: TextStyle(fontWeight: FontWeight.w500), ), - ) - : const Text( - 'Se connecter', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, + TextSpan( + text: 'serveur@restaurant.com / serveur123\n', ), - ), - ), - ), - const SizedBox(height: 24), - - // Demo accounts section - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.grey.shade100, - borderRadius: BorderRadius.circular(4), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Comptes de démonstration :', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.black87, + TextSpan( + text: 'Admin : ', + style: TextStyle(fontWeight: FontWeight.w500), + ), + TextSpan( + text: 'admin@restaurant.com / admin123', + ), + ], + ), ), - ), - const SizedBox(height: 8), - RichText( - text: const TextSpan( + const SizedBox(height: 8), + const Text( + 'Astuce : Appuyez sur Entrée pour vous connecter', style: TextStyle( - fontSize: 13, + fontSize: 12, color: Colors.grey, - height: 1.4, + fontStyle: FontStyle.italic, ), - children: [ - TextSpan( - text: 'Serveur : ', - style: TextStyle(fontWeight: FontWeight.w500), - ), - TextSpan( - text: 'serveur@restaurant.com / serveur123\n', - ), - TextSpan( - text: 'Admin : ', - style: TextStyle(fontWeight: FontWeight.w500), - ), - TextSpan( - text: 'admin@restaurant.com / admin123', - ), - ], ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), ), ), @@ -432,4 +458,4 @@ class HomeScreen extends StatelessWidget { ), ); } -} +} \ No newline at end of file diff --git a/lib/services/restaurant_api_service.dart b/lib/services/restaurant_api_service.dart index 320f3d4..94efa7f 100644 --- a/lib/services/restaurant_api_service.dart +++ b/lib/services/restaurant_api_service.dart @@ -21,7 +21,7 @@ class RestaurantApiService { static Future> getCommandes() async { try { final response = await http - .get(Uri.parse('$baseUrl/api/commandes'), headers: _headers) + .get(Uri.parse('$baseUrl/api/commandes?statut=servie'), headers: _headers) .timeout( const Duration(seconds: 30), onTimeout: () => throw TimeoutException('Délai d\'attente dépassé'), @@ -171,6 +171,7 @@ class RestaurantApiService { totalTtc: 14.00, modePaiement: null, commentaires: null, + tablename: 'a', serveur: "Serveur par défaut", dateCommande: DateTime.parse("2025-08-02T15:03:44.000Z"), dateService: null, @@ -234,7 +235,6 @@ class RestaurantApiService { updatedAt: DateTime.now(), total: 27.00, time: '00:02', - date: '02/08/2025', ), // Ajoutez d'autres tables de test... ]; diff --git a/lib/widgets/command_card.dart b/lib/widgets/command_card.dart index 51a9113..409b706 100644 --- a/lib/widgets/command_card.dart +++ b/lib/widgets/command_card.dart @@ -12,8 +12,13 @@ class CommandeCard extends StatelessWidget { required this.onAllerCaisse, }); + String _formatTime(DateTime dateTime) { + return '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')} ${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year}'; + } + @override Widget build(BuildContext context) { + return Container( margin: EdgeInsets.only(bottom: 16), decoration: BoxDecoration( @@ -38,7 +43,7 @@ class CommandeCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Table ${commande.tableNumber}', + ' ${commande.tablename}', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -57,7 +62,7 @@ class CommandeCard extends StatelessWidget { Icon(Icons.check_circle, color: Colors.white, size: 16), SizedBox(width: 4), Text( - 'À encaisser', + 'Près à encaisser', style: TextStyle( color: Colors.white, fontSize: 12, @@ -78,7 +83,8 @@ class CommandeCard extends StatelessWidget { Icon(Icons.access_time, size: 16, color: Colors.grey[600]), SizedBox(width: 6), Text( - '${commande.time} • ${commande.date} ', + // Fixed: Pass DateTime directly, not as string + commande.date != null ? _formatTime(commande.date!) : 'Date non disponible', style: TextStyle(color: Colors.grey[600], fontSize: 14), ), ], @@ -99,7 +105,7 @@ class CommandeCard extends StatelessWidget { style: TextStyle(color: Colors.grey[600], fontSize: 14), ), Text( - '${commande.total?.toStringAsFixed(2)} MGA', + '${commande.total?.toStringAsFixed(2) ?? '0.00'} MGA', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -129,4 +135,4 @@ class CommandeCard extends StatelessWidget { ), ); } -} +} \ No newline at end of file