Browse Source

push 03082025

master
andrymodeste 4 months ago
parent
commit
c9f4a0ce45
  1. 3
      lib/models/command_detail.dart
  2. 57
      lib/models/tables_order.dart
  3. 504
      lib/pages/login_screen.dart
  4. 4
      lib/services/restaurant_api_service.dart
  5. 14
      lib/widgets/command_card.dart

3
lib/models/command_detail.dart

@ -16,6 +16,7 @@ class CommandeDetail {
final DateTime? dateService; final DateTime? dateService;
final DateTime createdAt; final DateTime createdAt;
final DateTime updatedAt; final DateTime updatedAt;
final String tablename;
final List<CommandeItem> items; final List<CommandeItem> items;
CommandeDetail({ CommandeDetail({
@ -36,6 +37,7 @@ class CommandeDetail {
required this.createdAt, required this.createdAt,
required this.updatedAt, required this.updatedAt,
required this.items, required this.items,
required this.tablename,
}); });
factory CommandeDetail.fromJson(Map<String, dynamic> json) { factory CommandeDetail.fromJson(Map<String, dynamic> json) {
@ -54,6 +56,7 @@ class CommandeDetail {
totalTtc: double.tryParse(data['total_ttc']?.toString() ?? '0') ?? 0.0, totalTtc: double.tryParse(data['total_ttc']?.toString() ?? '0') ?? 0.0,
modePaiement: data['mode_paiement'], modePaiement: data['mode_paiement'],
commentaires: data['commentaires'], commentaires: data['commentaires'],
tablename: json['tablename'] ?? 'Inconnue',
serveur: data['serveur'] ?? 'Serveur par défaut', serveur: data['serveur'] ?? 'Serveur par défaut',
dateCommande: dateCommande:
data['date_commande'] != null data['date_commande'] != null

57
lib/models/tables_order.dart

@ -10,7 +10,8 @@ class TableOrder {
final double? total; // Optionnel pour les commandes en cours final double? total; // Optionnel pour les commandes en cours
final bool isEncashed; final bool isEncashed;
final String? time; // Heure de la commande si applicable 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 // final int? persons; // Nombre de personnes si applicable
TableOrder({ TableOrder({
@ -25,31 +26,34 @@ class TableOrder {
this.isEncashed = false, this.isEncashed = false,
this.time, this.time,
this.date, this.date,
this.tablename,
// this.persons, // this.persons,
}); });
factory TableOrder.fromJson(Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
@ -58,6 +62,7 @@ class TableOrder {
'capacity': capacity, 'capacity': capacity,
'status': status, 'status': status,
'location': location, 'location': location,
'tablename': tablename,
'created_at': createdAt.toIso8601String(), 'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(), 'updated_at': updatedAt.toIso8601String(),
if (total != null) 'total': total, if (total != null) 'total': total,
@ -122,7 +127,7 @@ class TableOrder {
double? total, double? total,
bool? isEncashed, bool? isEncashed,
String? time, String? time,
String? date, DateTime? date,
int? persons, int? persons,
}) { }) {
return TableOrder( return TableOrder(
@ -153,6 +158,10 @@ class TableOrder {
@override @override
int get hashCode => id.hashCode; 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) // Énumération pour les statuts (optionnel, pour plus de type safety)

504
lib/pages/login_screen.dart

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import './tables.dart'; import './tables.dart';
import '../layouts/main_layout.dart'; import '../layouts/main_layout.dart';
@ -54,6 +55,15 @@ class _LoginScreenState extends State<LoginScreen> {
super.dispose(); 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 { void _login() async {
if (!_formKey.currentState!.validate()) return; if (!_formKey.currentState!.validate()) return;
@ -99,279 +109,295 @@ class _LoginScreenState extends State<LoginScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return KeyboardListener(
backgroundColor: const Color(0xFFF5F5F5), // Light gray background focusNode: FocusNode(),
body: Center( onKeyEvent: _handleKeyPress,
child: SingleChildScrollView( child: Scaffold(
padding: const EdgeInsets.all(24.0), backgroundColor: const Color(0xFFF5F5F5), // Light gray background
child: Card( body: Center(
elevation: 2, child: SingleChildScrollView(
shape: RoundedRectangleBorder( padding: const EdgeInsets.all(24.0),
borderRadius: BorderRadius.circular(8), child: Card(
), elevation: 2,
child: Container( shape: RoundedRectangleBorder(
width: 400, borderRadius: BorderRadius.circular(8),
padding: const EdgeInsets.all(40.0), ),
child: Form( child: Container(
key: _formKey, width: 400,
child: Column( padding: const EdgeInsets.all(40.0),
mainAxisSize: MainAxisSize.min, child: Form(
children: [ key: _formKey,
// Logo personnalisé child: Column(
Container( mainAxisSize: MainAxisSize.min,
width: 80, children: [
height: 80, // Logo personnalisé
child: Image.asset( Container(
'assets/logo_transparent.png', width: 80,
fit: BoxFit.contain, height: 80,
errorBuilder: (context, error, stackTrace) { child: Image.asset(
// Fallback en cas d'erreur de chargement 'assets/logo_transparent.png',
return Container( fit: BoxFit.contain,
width: 64, errorBuilder: (context, error, stackTrace) {
height: 64, // Fallback en cas d'erreur de chargement
decoration: const BoxDecoration( return Container(
color: Colors.green, width: 64,
shape: BoxShape.circle, height: 64,
), decoration: const BoxDecoration(
child: const Icon( color: Colors.green,
Icons.restaurant_menu, shape: BoxShape.circle,
color: Colors.white, ),
size: 32, child: const Icon(
), Icons.restaurant_menu,
); color: Colors.white,
}, size: 32,
),
);
},
),
), ),
), const SizedBox(height: 24),
const SizedBox(height: 24),
// Title // Title
const Text( const Text(
'Restaurant App', 'Restaurant App',
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black87, color: Colors.black87,
),
), ),
), const SizedBox(height: 8),
const SizedBox(height: 8),
// Subtitle // Subtitle
const Text( const Text(
'Connectez-vous pour accéder au système de commandes', 'Connectez-vous pour accéder au système de commandes',
style: TextStyle(color: Colors.grey, fontSize: 14), style: TextStyle(color: Colors.grey, fontSize: 14),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
// Error message // Error message
if (_errorMessage != null) if (_errorMessage != null)
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 16), margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.red.shade50, color: Colors.red.shade50,
border: Border.all(color: Colors.red.shade200), border: Border.all(color: Colors.red.shade200),
borderRadius: BorderRadius.circular(4), 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( child: Text(
_errorMessage!, 'Email',
style: TextStyle( style: TextStyle(
color: Colors.red.shade600,
fontSize: 14, fontSize: 14,
color: Colors.black87,
fontWeight: FontWeight.w500,
), ),
textAlign: TextAlign.center,
), ),
), ),
const SizedBox(height: 8),
// Email label // Email field
const Align( TextFormField(
alignment: Alignment.centerLeft, controller: emailController,
child: Text( decoration: InputDecoration(
'Email', prefixIcon: const Icon(
style: TextStyle( Icons.email_outlined,
fontSize: 14, color: Colors.grey,
color: Colors.black87, size: 20,
fontWeight: FontWeight.w500, ),
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: 16),
const SizedBox(height: 8),
// Email field // Password label
TextFormField( const Align(
controller: emailController, alignment: Alignment.centerLeft,
decoration: InputDecoration( child: Text(
prefixIcon: const Icon( 'Mot de passe',
Icons.email_outlined, style: TextStyle(
color: Colors.grey, fontSize: 14,
size: 20, color: Colors.black87,
), fontWeight: FontWeight.w500,
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, const SizedBox(height: 8),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer votre email';
}
return null;
},
),
const SizedBox(height: 16),
// Password label // Password field
const Align( TextFormField(
alignment: Alignment.centerLeft, controller: passwordController,
child: Text( decoration: InputDecoration(
'Mot de passe', prefixIcon: const Icon(
style: TextStyle( Icons.lock_outline,
fontSize: 14, color: Colors.grey,
color: Colors.black87, size: 20,
fontWeight: FontWeight.w500, ),
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: 24),
const SizedBox(height: 8),
// Password field // Login button
TextFormField( SizedBox(
controller: passwordController, width: double.infinity,
decoration: InputDecoration( height: 48,
prefixIcon: const Icon( child: ElevatedButton(
Icons.lock_outline, onPressed: _isLoading ? null : _login,
color: Colors.grey, style: ElevatedButton.styleFrom(
size: 20, backgroundColor: Colors.green,
), foregroundColor: Colors.white,
hintText: '••••••••', shape: RoundedRectangleBorder(
hintStyle: const TextStyle(color: Colors.grey), borderRadius: BorderRadius.circular(4),
border: OutlineInputBorder( ),
borderRadius: BorderRadius.circular(4), elevation: 0,
borderSide: BorderSide(color: Colors.grey.shade300), ),
), child:
enabledBorder: OutlineInputBorder( _isLoading
borderRadius: BorderRadius.circular(4), ? const SizedBox(
borderSide: BorderSide(color: Colors.grey.shade300), height: 20,
), width: 20,
focusedBorder: OutlineInputBorder( child: CircularProgressIndicator(
borderRadius: BorderRadius.circular(4), color: Colors.white,
borderSide: const BorderSide(color: Colors.green), strokeWidth: 2,
), ),
contentPadding: const EdgeInsets.symmetric( )
horizontal: 12, : const Text(
vertical: 16, 'Se connecter',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
), ),
), ),
obscureText: true, const SizedBox(height: 24),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer votre mot de passe';
}
return null;
},
),
const SizedBox(height: 24),
// Login button // Demo accounts section
SizedBox( Container(
width: double.infinity, width: double.infinity,
height: 48, padding: const EdgeInsets.all(16),
child: ElevatedButton( decoration: BoxDecoration(
onPressed: _isLoading ? null : _login, color: Colors.grey.shade100,
style: ElevatedButton.styleFrom( borderRadius: BorderRadius.circular(4),
backgroundColor: Colors.green,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
elevation: 0,
), ),
child: child: Column(
_isLoading crossAxisAlignment: CrossAxisAlignment.start,
? const SizedBox( children: [
height: 20, const Text(
width: 20, 'Comptes de démonstration :',
child: CircularProgressIndicator( style: TextStyle(
color: Colors.white, fontSize: 14,
strokeWidth: 2, 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),
), ),
) TextSpan(
: const Text( text: 'serveur@restaurant.com / serveur123\n',
'Se connecter',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
), ),
), TextSpan(
), text: 'Admin : ',
), style: TextStyle(fontWeight: FontWeight.w500),
const SizedBox(height: 24), ),
TextSpan(
// Demo accounts section text: 'admin@restaurant.com / admin123',
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,
), ),
), const SizedBox(height: 8),
const SizedBox(height: 8), const Text(
RichText( 'Astuce : Appuyez sur Entrée pour vous connecter',
text: const TextSpan(
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 12,
color: Colors.grey, 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',
),
],
), ),
), ],
], ),
), ),
), ],
], ),
), ),
), ),
), ),

4
lib/services/restaurant_api_service.dart

@ -21,7 +21,7 @@ class RestaurantApiService {
static Future<List<TableOrder>> getCommandes() async { static Future<List<TableOrder>> getCommandes() async {
try { try {
final response = await http final response = await http
.get(Uri.parse('$baseUrl/api/commandes'), headers: _headers) .get(Uri.parse('$baseUrl/api/commandes?statut=servie'), headers: _headers)
.timeout( .timeout(
const Duration(seconds: 30), const Duration(seconds: 30),
onTimeout: () => throw TimeoutException('Délai d\'attente dépassé'), onTimeout: () => throw TimeoutException('Délai d\'attente dépassé'),
@ -171,6 +171,7 @@ class RestaurantApiService {
totalTtc: 14.00, totalTtc: 14.00,
modePaiement: null, modePaiement: null,
commentaires: null, commentaires: null,
tablename: 'a',
serveur: "Serveur par défaut", serveur: "Serveur par défaut",
dateCommande: DateTime.parse("2025-08-02T15:03:44.000Z"), dateCommande: DateTime.parse("2025-08-02T15:03:44.000Z"),
dateService: null, dateService: null,
@ -234,7 +235,6 @@ class RestaurantApiService {
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
total: 27.00, total: 27.00,
time: '00:02', time: '00:02',
date: '02/08/2025',
), ),
// Ajoutez d'autres tables de test... // Ajoutez d'autres tables de test...
]; ];

14
lib/widgets/command_card.dart

@ -12,8 +12,13 @@ class CommandeCard extends StatelessWidget {
required this.onAllerCaisse, 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
margin: EdgeInsets.only(bottom: 16), margin: EdgeInsets.only(bottom: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -38,7 +43,7 @@ class CommandeCard extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'Table ${commande.tableNumber}', ' ${commande.tablename}',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -57,7 +62,7 @@ class CommandeCard extends StatelessWidget {
Icon(Icons.check_circle, color: Colors.white, size: 16), Icon(Icons.check_circle, color: Colors.white, size: 16),
SizedBox(width: 4), SizedBox(width: 4),
Text( Text(
'À encaisser', 'Près à encaisser',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 12, fontSize: 12,
@ -78,7 +83,8 @@ class CommandeCard extends StatelessWidget {
Icon(Icons.access_time, size: 16, color: Colors.grey[600]), Icon(Icons.access_time, size: 16, color: Colors.grey[600]),
SizedBox(width: 6), SizedBox(width: 6),
Text( 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), style: TextStyle(color: Colors.grey[600], fontSize: 14),
), ),
], ],
@ -99,7 +105,7 @@ class CommandeCard extends StatelessWidget {
style: TextStyle(color: Colors.grey[600], fontSize: 14), style: TextStyle(color: Colors.grey[600], fontSize: 14),
), ),
Text( Text(
'${commande.total?.toStringAsFixed(2)} MGA', '${commande.total?.toStringAsFixed(2) ?? '0.00'} MGA',
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

Loading…
Cancel
Save