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 createdAt;
final DateTime updatedAt;
final String tablename;
final List<CommandeItem> items;
CommandeDetail({
@ -36,6 +37,7 @@ class CommandeDetail {
required this.createdAt,
required this.updatedAt,
required this.items,
required this.tablename,
});
factory CommandeDetail.fromJson(Map<String, dynamic> 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

57
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<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() {
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)

504
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<LoginScreen> {
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<LoginScreen> {
@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',
),
],
),
),
],
],
),
),
),
],
],
),
),
),
),

4
lib/services/restaurant_api_service.dart

@ -21,7 +21,7 @@ class RestaurantApiService {
static Future<List<TableOrder>> 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...
];

14
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,

Loading…
Cancel
Save