Compare commits

..

No commits in common. "3b7d3f8cca382b43211ed35d083af2c6a9b7ae39" and "059da2af1b00c8bc996a002af2e24ccc66bf5431" have entirely different histories.

9 changed files with 298 additions and 342 deletions

View File

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

View File

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

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import './tables.dart';
import '../layouts/main_layout.dart';
@ -55,15 +54,6 @@ 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;
@ -109,295 +99,279 @@ class _LoginScreenState extends State<LoginScreen> {
@override
Widget build(BuildContext context) {
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),
// Title
const Text(
'Restaurant App',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
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),
// 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,
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,
),
textAlign: TextAlign.center,
),
),
// Email label
const Align(
alignment: Alignment.centerLeft,
child: Text(
'Email',
style: TextStyle(
fontSize: 14,
color: Colors.black87,
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(height: 8),
// 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;
child: const Icon(
Icons.restaurant_menu,
color: Colors.white,
size: 32,
),
);
},
),
const SizedBox(height: 16),
),
const SizedBox(height: 24),
// Password label
const Align(
alignment: Alignment.centerLeft,
child: Text(
'Mot de passe',
style: TextStyle(
fontSize: 14,
color: Colors.black87,
fontWeight: FontWeight.w500,
),
),
// Title
const Text(
'Restaurant App',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
const SizedBox(height: 8),
),
const SizedBox(height: 8),
// 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: 24),
// 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),
// 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,
),
),
),
),
const SizedBox(height: 24),
// Demo accounts section
// Error message
if (_errorMessage != null)
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: Colors.grey.shade100,
color: Colors.red.shade50,
border: Border.all(color: Colors.red.shade200),
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),
RichText(
text: const TextSpan(
style: TextStyle(
fontSize: 13,
color: Colors.grey,
height: 1.4,
),
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',
),
],
),
),
const SizedBox(height: 8),
const Text(
'Astuce : Appuyez sur Entrée pour vous connecter',
style: TextStyle(
fontSize: 12,
color: Colors.grey,
fontStyle: FontStyle.italic,
),
),
],
child: Text(
_errorMessage!,
style: TextStyle(
color: Colors.red.shade600,
fontSize: 14,
),
textAlign: TextAlign.center,
),
),
],
),
// Email label
const Align(
alignment: Alignment.centerLeft,
child: Text(
'Email',
style: TextStyle(
fontSize: 14,
color: Colors.black87,
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(height: 8),
// 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,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer votre email';
}
return null;
},
),
const SizedBox(height: 16),
// Password label
const Align(
alignment: Alignment.centerLeft,
child: Text(
'Mot de passe',
style: TextStyle(
fontSize: 14,
color: Colors.black87,
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(height: 8),
// 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,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer votre mot de passe';
}
return null;
},
),
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,
),
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,
),
),
),
),
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,
),
),
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(
text: 'serveur@restaurant.com / serveur123\n',
),
TextSpan(
text: 'Admin : ',
style: TextStyle(fontWeight: FontWeight.w500),
),
TextSpan(
text: 'admin@restaurant.com / admin123',
),
],
),
),
],
),
),
],
),
),
),

View File

@ -21,7 +21,7 @@ class RestaurantApiService {
static Future<List<TableOrder>> getCommandes() async {
try {
final response = await http
.get(Uri.parse('$baseUrl/api/commandes?statut=servie'), headers: _headers)
.get(Uri.parse('$baseUrl/api/commandes'), headers: _headers)
.timeout(
const Duration(seconds: 30),
onTimeout: () => throw TimeoutException('Délai d\'attente dépassé'),
@ -171,7 +171,6 @@ 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,
@ -235,6 +234,7 @@ class RestaurantApiService {
updatedAt: DateTime.now(),
total: 27.00,
time: '00:02',
date: '02/08/2025',
),
// Ajoutez d'autres tables de test...
];

View File

@ -12,13 +12,8 @@ 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(
@ -43,7 +38,7 @@ class CommandeCard extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
' ${commande.tablename}',
'Table ${commande.tableNumber}',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
@ -62,7 +57,7 @@ class CommandeCard extends StatelessWidget {
Icon(Icons.check_circle, color: Colors.white, size: 16),
SizedBox(width: 4),
Text(
'Près à encaisser',
'À encaisser',
style: TextStyle(
color: Colors.white,
fontSize: 12,
@ -83,8 +78,7 @@ class CommandeCard extends StatelessWidget {
Icon(Icons.access_time, size: 16, color: Colors.grey[600]),
SizedBox(width: 6),
Text(
// Fixed: Pass DateTime directly, not as string
commande.date != null ? _formatTime(commande.date!) : 'Date non disponible',
'${commande.time}${commande.date} ',
style: TextStyle(color: Colors.grey[600], fontSize: 14),
),
],
@ -105,7 +99,7 @@ class CommandeCard extends StatelessWidget {
style: TextStyle(color: Colors.grey[600], fontSize: 14),
),
Text(
'${commande.total?.toStringAsFixed(2) ?? '0.00'} MGA',
'${commande.total?.toStringAsFixed(2)} MGA',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,

View File

@ -1,10 +1,10 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.14)
project(commande LANGUAGES CXX)
project(itrimobe LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "commande")
set(BINARY_NAME "itrimobe")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.

View File

@ -90,12 +90,12 @@ BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "com.example" "\0"
VALUE "FileDescription", "commande" "\0"
VALUE "FileDescription", "itrimobe" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "commande" "\0"
VALUE "InternalName", "itrimobe" "\0"
VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0"
VALUE "OriginalFilename", "commande.exe" "\0"
VALUE "ProductName", "commande" "\0"
VALUE "OriginalFilename", "itrimobe.exe" "\0"
VALUE "ProductName", "itrimobe" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END

View File

@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.Create(L"commande", origin, size)) {
if (!window.Create(L"itrimobe", origin, size)) {
return EXIT_FAILURE;
}
window.SetQuitOnClose(true);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB