Browse Source

filtrage

master
andrymodeste 2 months ago
parent
commit
57efa196b9
  1. 45
      lib/main.dart
  2. 162
      lib/pages/caisse_screen.dart
  3. 21
      lib/pages/commandes_screen.dart
  4. 642
      lib/pages/historique_commande.dart
  5. 94
      lib/pages/login_screen.dart
  6. 18
      lib/providers/auth_provider.dart
  7. 16
      pubspec.lock
  8. 1
      pubspec.yaml

45
lib/main.dart

@ -1,17 +1,26 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'providers/auth_provider.dart';
import 'layouts/main_layout.dart'; import 'layouts/main_layout.dart';
import 'pages/login_screen.dart';
import 'pages/tables.dart'; import 'pages/tables.dart';
import 'pages/categorie.dart'; import 'pages/categorie.dart';
import 'pages/commandes_screen.dart'; import 'pages/commandes_screen.dart';
import 'pages/login_screen.dart';
import 'pages/menus_screen.dart'; import 'pages/menus_screen.dart';
import 'pages/historique_commande.dart'; import 'pages/historique_commande.dart';
import 'pages/information.dart'; import 'pages/information.dart';
import 'pages/printer_page.dart'; import 'pages/printer_page.dart';
import 'pages/encaissement_screen.dart'; // NOUVEAU import 'pages/encaissement_screen.dart';
void main() { void main() {
runApp(const MyApp()); runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthProvider()),
],
child: const MyApp(),
),
);
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
@ -22,51 +31,39 @@ class MyApp extends StatelessWidget {
return MaterialApp( return MaterialApp(
title: 'Restaurant App', title: 'Restaurant App',
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: ThemeData( theme: ThemeData(primarySwatch: Colors.green),
primarySwatch: Colors.green,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialRoute: '/login', initialRoute: '/login',
routes: { routes: {
'/login': (context) => const LoginScreen(), '/login': (context) => const LoginScreen(),
'/tables': '/tables': (context) => const MainLayout(
(context) => const MainLayout(
currentRoute: '/tables', currentRoute: '/tables',
child: TablesScreen(), child: TablesScreen(),
), ),
'/categories': '/categories': (context) => const MainLayout(
(context) => const MainLayout(
currentRoute: '/categories', currentRoute: '/categories',
child: CategoriesPage(), child: CategoriesPage(),
), ),
'/commandes': '/commandes': (context) => const MainLayout(
(context) => const MainLayout(
currentRoute: '/commandes', currentRoute: '/commandes',
child: OrdersManagementScreen(), child: OrdersManagementScreen(),
), ),
'/plats': '/plats': (context) => const MainLayout(
(context) => const MainLayout(
currentRoute: '/plats', currentRoute: '/plats',
child: PlatsManagementScreen(), child: PlatsManagementScreen(),
), ),
// NOUVELLE ROUTE pour l'encaissement '/encaissement': (context) => const MainLayout(
'/encaissement':
(context) => const MainLayout(
currentRoute: '/encaissement', currentRoute: '/encaissement',
child: EncaissementScreen(), child: EncaissementScreen(),
), ),
'/historique': '/historique': (context) => MainLayout(
(context) => MainLayout(
currentRoute: '/historique', currentRoute: '/historique',
child: OrderHistoryPage(), child: OrderHistoryPage(),
), ),
'/information': '/information': (context) => MainLayout(
(context) => MainLayout(
currentRoute: '/information', currentRoute: '/information',
child: PrintTemplateManagementScreen(), child: PrintTemplateManagementScreen(),
), ),
'/Setting': '/Setting': (context) => MainLayout(
(context) => MainLayout(
currentRoute: '/Setting', currentRoute: '/Setting',
child: PrinterPage(), child: PrinterPage(),
), ),

162
lib/pages/caisse_screen.dart

@ -352,90 +352,112 @@ class _CaisseScreenState extends State<CaisseScreen> {
); );
} }
Widget _buildPaymentMethodCard(PaymentMethod method) { Widget _buildPaymentMethodCard(PaymentMethod method) {
final isSelected = selectedPaymentMethod?.id == method.id; final isSelected = selectedPaymentMethod?.id == method.id;
final amount = commande?.totalTtc ?? 0.0; final amount = commande?.totalTtc ?? 0.0;
return Container( return Container(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
setState(() { setState(() {
selectedPaymentMethod = method; selectedPaymentMethod = method;
}); });
}, },
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
child: Container( child: AnimatedContainer(
padding: const EdgeInsets.all(20), duration: const Duration(milliseconds: 250),
decoration: BoxDecoration( curve: Curves.easeInOut,
color: method.color, padding: const EdgeInsets.all(20),
borderRadius: BorderRadius.circular(12), decoration: BoxDecoration(
border: color: isSelected ? Colors.white : method.color,
isSelected ? Border.all(color: Colors.white, width: 3) : null, borderRadius: BorderRadius.circular(12),
boxShadow: [ border: Border.all(
color: isSelected ? method.color : Colors.transparent,
width: 2,
),
boxShadow: [
if (isSelected)
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color: method.color.withOpacity(0.4),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 3),
), ),
], ],
), ),
child: Row( child: Row(
children: [ children: [
Container( Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2), color: isSelected
borderRadius: BorderRadius.circular(8), ? method.color.withOpacity(0.1)
), : Colors.white.withOpacity(0.2),
child: Icon(method.icon, color: Colors.white, size: 24), borderRadius: BorderRadius.circular(8),
),
child: Icon(
method.icon,
color: isSelected ? method.color : Colors.white,
size: 24,
), ),
),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
method.name, method.name,
style: const TextStyle( style: TextStyle(
color: Colors.white, color: isSelected ? Colors.black87 : Colors.white,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
),
), ),
const SizedBox(height: 4), ),
Text( const SizedBox(height: 4),
method.description, Text(
style: TextStyle( method.description,
color: Colors.white.withOpacity(0.9), style: TextStyle(
fontSize: 13, color: isSelected
), ? Colors.black54
: Colors.white.withOpacity(0.9),
fontSize: 13,
), ),
], ),
), ],
), ),
),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Icon(
'${NumberFormat("#,##0.00", "fr_FR").format(amount)} MGA', isSelected ? Icons.check_circle : Icons.radio_button_unchecked,
style: const TextStyle( color: isSelected ? method.color : Colors.white,
color: Colors.white, size: 22,
fontSize: 18, ),
fontWeight: FontWeight.bold,
), 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() { Widget _buildPaymentButton() {
final canPay = selectedPaymentMethod != null && !isProcessingPayment; final canPay = selectedPaymentMethod != null && !isProcessingPayment;

21
lib/pages/commandes_screen.dart

@ -4,9 +4,16 @@ import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'dart:convert'; import 'dart:convert';
import 'package:intl/intl.dart'; 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 'commande_item_screen.dart';
import '../services/pdf_impression_commande.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 { class OrdersManagementScreen extends StatefulWidget {
const OrdersManagementScreen({super.key}); const OrdersManagementScreen({super.key});
@ -523,6 +530,7 @@ Future<void> deleteOrder(Order order) async {
} }
} }
class OrderCard extends StatelessWidget { class OrderCard extends StatelessWidget {
final Order order; final Order order;
final Function(Order, String, {String? modePaiement}) onStatusUpdate; final Function(Order, String, {String? modePaiement}) onStatusUpdate;
@ -530,6 +538,8 @@ class OrderCard extends StatelessWidget {
final Function(Order) onDelete; final Function(Order) onDelete;
final VoidCallback onViewDetails; final VoidCallback onViewDetails;
const OrderCard({ const OrderCard({
Key? key, Key? key,
required this.order, required this.order,
@ -579,6 +589,8 @@ class OrderCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final auth = Provider.of<AuthProvider>(context);
final userType = auth.userType;
return Container( return Container(
margin: const EdgeInsets.only(bottom: 16), margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -782,9 +794,11 @@ class OrderCard extends StatelessWidget {
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
if (userType != 'Serveur' || !isMobile(context))
Expanded( Expanded(
flex: 3, flex: 3,
child: ElevatedButton.icon( child:ElevatedButton.icon(
onPressed: () => onProcessPayment(order), onPressed: () => onProcessPayment(order),
icon: const Icon( icon: const Icon(
Icons.point_of_sale, Icons.point_of_sale,
@ -809,7 +823,9 @@ class OrderCard extends StatelessWidget {
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
if (order.statut == 'en_attente') if (order.statut == 'en_attente')
if (userType != 'Serveur' || !isMobile(context))
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
onPressed: onPressed:
@ -827,8 +843,10 @@ class OrderCard extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
if (order.statut == 'en_preparation') if (order.statut == 'en_preparation')
if (userType != 'Serveur' || !isMobile(context))
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
onPressed: onPressed:
@ -846,6 +864,7 @@ class OrderCard extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(

642
lib/pages/historique_commande.dart

@ -14,6 +14,8 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
List<CommandeData> commandes = []; List<CommandeData> commandes = [];
bool isLoading = true; bool isLoading = true;
String? error; String? error;
DateTime? _startDate;
DateTime? _endDate;
// Informations d'affichage et pagination // Informations d'affichage et pagination
int totalItems = 0; int totalItems = 0;
@ -37,144 +39,146 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
_loadCommandes(); _loadCommandes();
} }
Future<void> _loadCommandes({int page = 1}) async {
try {
setState(() {
isLoading = true;
error = null;
});
Future<void> _loadCommandes({int page = 1}) async { // Construction des paramètres de requête
try { Map<String, String> queryParams = {
setState(() { 'statut': 'payee',
isLoading = true; 'page': page.toString(),
error = null; 'limit': itemsPerPage.toString(),
}); };
// Ajouter les paramètres de pagination à l'URL // CORRECTION : Envoyer la date en format ISO avec fuseau horaire UTC
final uri = Uri.parse('$baseUrl/api/commandes').replace(queryParameters: { if (_startDate != null) {
'statut': 'payee', // Début de journée en UTC (00:00:00)
'page': page.toString(), final startUtc = DateTime.utc(
'limit': itemsPerPage.toString(), _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); // Debug: Afficher l'URL complète
final uri = Uri.parse('$baseUrl/api/commandes').replace(queryParameters: queryParams);
if (response.statusCode == 200) { print('🌐 URL appelée: $uri');
final dynamic responseBody = json.decode(response.body);
final response = await http.get(uri, headers: _headers);
List<dynamic> data = [];
print('📡 Status code: ${response.statusCode}');
// Gestion améliorée de la réponse
if (responseBody is Map<String, dynamic>) { if (response.statusCode == 200) {
final dynamic responseBody = json.decode(response.body);
// Structure: {"success": true, "data": {"commandes": [...], "pagination": {...}}} List<dynamic> data = [];
if (responseBody.containsKey('data') && responseBody['data'] is Map<String, dynamic>) {
final dataMap = responseBody['data'] as Map<String, dynamic>; // Gestion selon le format de réponse
if (dataMap.containsKey('commandes')) { if (responseBody is Map<String, dynamic>) {
final commandesValue = dataMap['commandes']; final dataMap = responseBody['data'] as Map<String, dynamic>?;
if (commandesValue is List<dynamic>) { if (dataMap != null) {
data = commandesValue; final commandesValue = dataMap['commandes'];
} else if (commandesValue != null) { if (commandesValue is List) {
data = [commandesValue]; data = commandesValue;
} } else if (commandesValue != null) {
data = [commandesValue];
// Pagination
if (dataMap.containsKey('pagination')) {
final pagination = dataMap['pagination'] as Map<String, dynamic>?;
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<dynamic>) {
data = commandesValue;
} else if (commandesValue != null) {
data = [commandesValue];
}
totalItems = data.length;
currentPage = page;
totalPages = (totalItems / itemsPerPage).ceil();
} else {
totalItems = 0;
currentPage = 1;
totalPages = 1;
} }
} else if (responseBody is List<dynamic>) {
data = responseBody; // Pagination
totalItems = data.length; final pagination = dataMap['pagination'] as Map<String, dynamic>?;
currentPage = page; currentPage = pagination?['currentPage'] ?? page;
totalPages = (totalItems / itemsPerPage).ceil(); totalPages = pagination?['totalPages'] ?? (data.length / itemsPerPage).ceil();
} else { totalItems = pagination?['totalItems'] ?? data.length;
throw Exception('Format de réponse inattendu: ${responseBody.runtimeType}');
} }
} else if (responseBody is List) {
data = responseBody;
// Conversion sécurisée avec prints détaillés totalItems = data.length;
List<CommandeData> parsedCommandes = []; currentPage = page;
for (int i = 0; i < data.length; i++) { totalPages = (totalItems / itemsPerPage).ceil();
try { } else {
final item = data[i]; throw Exception('Format de réponse inattendu: ${responseBody.runtimeType}');
}
if (item is Map<String, dynamic>) {
item.forEach((key, value) { // Parsing sécurisé des commandes
}); List<CommandeData> parsedCommandes = [];
for (int i = 0; i < data.length; i++) {
final commandeData = CommandeData.fromJson(item); try {
final item = data[i];
if (commandeData.items != null) { if (item is Map<String, dynamic>) {
for (int j = 0; j < commandeData.items!.length; j++) { final commandeData = CommandeData.fromJson(item);
final commandeItem = commandeData.items![j]; parsedCommandes.add(commandeData);
} } else {
} print('Item $i invalide: ${item.runtimeType}');
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
} }
} 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('${parsedCommandes.length} commandes chargées');
print('Erreur: $e');
print('Stack trace: $stackTrace'); setState(() {
commandes = parsedCommandes;
isLoading = false;
});
_initializeAnimations();
_startAnimations();
} else {
setState(() { setState(() {
error = 'Erreur de connexion: $e'; error = 'Erreur HTTP ${response.statusCode}: ${response.reasonPhrase}';
isLoading = false; 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<void> _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 // Fonction pour aller à la page suivante
void _goToNextPage() { void _goToNextPage() {
@ -258,73 +262,151 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
), ),
); );
} }
Widget _buildHeader() {
Widget _buildHeader() { return AnimatedBuilder(
return AnimatedBuilder( animation: _animationController,
animation: _animationController, builder: (context, child) {
builder: (context, child) { return Transform.translate(
return Transform.translate( offset: Offset(0, -50 * (1 - _animationController.value)),
offset: Offset(0, -50 * (1 - _animationController.value)), child: Opacity(
child: Opacity( opacity: _animationController.value,
opacity: _animationController.value, child: Container(
child: Container( width: double.infinity,
width: double.infinity, margin: EdgeInsets.symmetric(horizontal: 20, vertical: 5),
margin: EdgeInsets.symmetric(horizontal: 20, vertical: 5), padding: EdgeInsets.all(10),
padding: EdgeInsets.all(10), decoration: BoxDecoration(
decoration: BoxDecoration( color: Colors.white,
color: Colors.white, borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(12), boxShadow: [
boxShadow: [ BoxShadow(
BoxShadow( color: Colors.grey.withOpacity(0.08),
color: Colors.grey.withOpacity(0.08), blurRadius: 6,
blurRadius: 6, offset: Offset(0, 2),
offset: Offset(0, 2), ),
],
),
child: Column(
children: [
Text(
'Historique des commandes payées',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF2c3e50),
), ),
], textAlign: TextAlign.center,
), ),
child: Column( SizedBox(height: 2),
children: [ Text(
Text( 'Consultez toutes les commandes qui ont été payées',
'Historique des commandes payées', style: TextStyle(
style: TextStyle( fontSize: 12,
fontSize: 18, color: Colors.grey.shade600,
fontWeight: FontWeight.bold,
color: Color(0xFF2c3e50),
),
textAlign: TextAlign.center,
), ),
SizedBox(height: 2), textAlign: TextAlign.center,
Text( ),
'Consultez toutes les commandes qui ont été payées', SizedBox(height: 6),
style: TextStyle( // Barre des filtres
fontSize: 12, Row(
color: Colors.grey.shade600, 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, SizedBox(width: 5),
), ElevatedButton(
if (totalItems > 0) onPressed: () => _selectDate(context, false),
Padding( child: Text(_endDate == null
padding: EdgeInsets.only(top: 4), ? 'Date fin'
child: Text( : 'Fin: ${_endDate!.day}/${_endDate!.month}/${_endDate!.year}'),
totalPages > 1 style: ElevatedButton.styleFrom(
? '$totalItems commande${totalItems > 1 ? 's' : ''} • Page $currentPage/$totalPages' backgroundColor: Color(0xFF4CAF50),
: '$totalItems commande${totalItems > 1 ? 's' : ''} trouvée${totalItems > 1 ? 's' : ''}', foregroundColor: Colors.white,
style: TextStyle( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
fontSize: 10, textStyle: TextStyle(fontSize: 10),
color: Colors.grey.shade500,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
), ),
), ),
], 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() { Widget _buildPagination() {
return Container( return Container(
@ -462,91 +544,106 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
return pages; return pages;
} }
Widget _buildContent() { Widget _buildContent() {
if (isLoading) { if (isLoading) {
return Center( return const Center(
child: Column( child: CircularProgressIndicator(color: Color(0xFF4CAF50)),
mainAxisAlignment: MainAxisAlignment.center, );
children: [ }
CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF4CAF50)),
),
SizedBox(height: 16),
Text(
'Chargement des commandes...',
style: TextStyle(color: Colors.grey.shade600),
),
],
),
);
}
if (error != null) { if (error != null) {
return Center( return Center(
child: Column( child: Text(error!, style: TextStyle(color: Colors.red)),
mainAxisAlignment: MainAxisAlignment.center, );
children: [ }
Icon(Icons.error_outline, size: 64, color: Colors.grey),
SizedBox(height: 16), if (commandes.isEmpty) {
Padding( return const Center(
padding: EdgeInsets.symmetric(horizontal: 20), child: Text("Aucune commande trouvée"),
child: Text( );
error!, }
style: TextStyle(color: Colors.grey),
textAlign: TextAlign.center, // Afficher les données sous forme de tableau
), return SingleChildScrollView(
), scrollDirection: Axis.horizontal,
SizedBox(height: 16), child: ConstrainedBox(
ElevatedButton( constraints: BoxConstraints(minWidth: MediaQuery.of(context).size.width * 0.9), // 1.5x la largeur écran
onPressed: () => _loadCommandes(page: currentPage), child: SingleChildScrollView(
child: Text('Réessayer'), child: _buildTableView(),
style: ElevatedButton.styleFrom( ),
backgroundColor: Color(0xFF4CAF50), ),
foregroundColor: Colors.white, );
),
}
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("")),
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) { // Exemple de fonction pour afficher les détails dans un dialog
return Center( void _showCommandeDetails(CommandeData commande) {
child: Column( showDialog(
mainAxisAlignment: MainAxisAlignment.center, context: context,
children: [ builder: (context) {
Icon(Icons.restaurant_menu, size: 64, color: Colors.grey), return AlertDialog(
SizedBox(height: 16), title: Text('Détails commande ${commande.numeroCommande ?? ""}'),
Text( content: SingleChildScrollView(
currentPage > 1 child: Column(
? 'Aucune commande sur cette page' crossAxisAlignment: CrossAxisAlignment.start,
: 'Aucune commande payée', children: [
style: TextStyle(color: Colors.grey, fontSize: 16), Text('Table: ${commande.tablename ?? "-"}'),
), Text(
if (currentPage > 1) ...[ 'Date de paiement: ${commande.datePaiement != null ? _formatDateTime(commande.datePaiement!) : "-"}',
SizedBox(height: 16),
ElevatedButton(
onPressed: () => _goToPage(1),
child: Text('Retour à la première page'),
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF4CAF50),
foregroundColor: Colors.white,
),
), ),
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) { Widget _buildOrderCard(CommandeData commande, int index) {
if (index >= _cardAnimationControllers.length) { if (index >= _cardAnimationControllers.length) {
@ -1052,20 +1149,19 @@ class CommandeData {
return null; return null;
} }
static DateTime? _parseDateTime(dynamic value) { static DateTime? _parseDateTime(dynamic value) {
if (value == null) return null; if (value == null) return null;
if (value is String) { if (value is String) {
try { try {
final result = DateTime.parse(value); return DateTime.parse(value).toLocal(); // converti en heure locale
return result; } catch (e) {
} catch (e) { print('Erreur parsing date: $value - $e');
print('Erreur parsing date: $value - $e'); return null;
return null;
}
} }
print('Impossible de parser en datetime: $value');
return null;
} }
return null;
}
static List<CommandeItem>? _parseItems(dynamic value) { static List<CommandeItem>? _parseItems(dynamic value) {

94
lib/pages/login_screen.dart

@ -1,5 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../providers/auth_provider.dart';
import './tables.dart'; import './tables.dart';
import '../layouts/main_layout.dart'; import '../layouts/main_layout.dart';
@ -33,6 +35,10 @@ class _LoginScreenState extends State<LoginScreen> {
static const String serveurPassword = 'serveur123'; static const String serveurPassword = 'serveur123';
static const String adminEmail = 'admin@restaurant.com'; static const String adminEmail = 'admin@restaurant.com';
static const String adminPassword = 'admin123'; 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 emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController(); final TextEditingController passwordController = TextEditingController();
@ -63,49 +69,67 @@ class _LoginScreenState extends State<LoginScreen> {
} }
} }
} }
void _login() async { void _login() async {
if (!_formKey.currentState!.validate()) return; if (!_formKey.currentState!.validate()) return;
setState(() { setState(() {
_isLoading = true; _isLoading = true;
_errorMessage = null; _errorMessage = null;
}); });
// Simulate network delay // Simule un délai pour l'authentification
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
setState(() { setState(() {
_isLoading = false; _isLoading = false;
}); });
// Check credentials String email = emailController.text.trim();
String email = emailController.text.trim(); String password = passwordController.text;
String password = passwordController.text;
// Vérification des credentials
if ((email == serveurEmail && password == serveurPassword) || if ((email == serveurEmail && password == serveurPassword) ||
(email == adminEmail && password == adminPassword)) { (email == cuisinierEmail && password == cuisinierPassword) ||
// Login successful - navigate to home/dashboard (email == caissierEmail && password == caissierPassword) ||
if (mounted) { (email == adminEmail && password == adminPassword)) {
String userType = email == adminEmail ? 'Admin' : 'Serveur';
Navigator.pushReplacement( // Détermination du rôle
context, String userType;
MaterialPageRoute( if (email == adminEmail) {
builder: userType = 'Admin';
(context) => const MainLayout( } else if (email == serveurEmail) {
currentRoute: '/tables', userType = 'Serveur';
child: TablesScreen(), }
), else if (email == caissierEmail) {
), userType = 'caissier';
);
}
} else { } else {
// Login failed userType = 'Cuisinier';
setState(() {
_errorMessage = 'Email ou mot de passe incorrect';
});
} }
// On enregistre le rôle dans le provider
final authProvider = Provider.of<AuthProvider>(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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

18
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();
}
}

16
pubspec.lock

@ -296,6 +296,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.6" version: "1.0.6"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -456,6 +464,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.14.2" 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: qr:
dependency: transitive dependency: transitive
description: description:

1
pubspec.yaml

@ -29,6 +29,7 @@ dependencies:
intl: ^0.18.1 intl: ^0.18.1
esc_pos_printer: ^4.1.0 esc_pos_printer: ^4.1.0
esc_pos_utils: ^1.1.0 esc_pos_utils: ^1.1.0
provider: ^6.1.1
# Dépendances de développement/test # Dépendances de développement/test
dev_dependencies: dev_dependencies:

Loading…
Cancel
Save