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

162
lib/pages/caisse_screen.dart

@ -352,90 +352,112 @@ class _CaisseScreenState extends State<CaisseScreen> {
);
}
Widget _buildPaymentMethodCard(PaymentMethod method) {
final isSelected = selectedPaymentMethod?.id == method.id;
final amount = commande?.totalTtc ?? 0.0;
return Container(
margin: const EdgeInsets.only(bottom: 12),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
setState(() {
selectedPaymentMethod = method;
});
},
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: method.color,
borderRadius: BorderRadius.circular(12),
border:
isSelected ? Border.all(color: Colors.white, width: 3) : null,
boxShadow: [
Widget _buildPaymentMethodCard(PaymentMethod method) {
final isSelected = selectedPaymentMethod?.id == method.id;
final amount = commande?.totalTtc ?? 0.0;
return Container(
margin: const EdgeInsets.only(bottom: 12),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
setState(() {
selectedPaymentMethod = method;
});
},
borderRadius: BorderRadius.circular(12),
child: AnimatedContainer(
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: isSelected ? Colors.white : method.color,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected ? method.color : Colors.transparent,
width: 2,
),
boxShadow: [
if (isSelected)
BoxShadow(
color: Colors.black.withOpacity(0.1),
color: method.color.withOpacity(0.4),
blurRadius: 8,
offset: const Offset(0, 2),
offset: const Offset(0, 3),
),
],
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: Icon(method.icon, color: Colors.white, size: 24),
],
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: isSelected
? method.color.withOpacity(0.1)
: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
method.icon,
color: isSelected ? method.color : Colors.white,
size: 24,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
method.name,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
method.name,
style: TextStyle(
color: isSelected ? Colors.black87 : Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
const SizedBox(height: 4),
Text(
method.description,
style: TextStyle(
color: Colors.white.withOpacity(0.9),
fontSize: 13,
),
),
const SizedBox(height: 4),
Text(
method.description,
style: TextStyle(
color: isSelected
? Colors.black54
: Colors.white.withOpacity(0.9),
fontSize: 13,
),
],
),
),
],
),
),
const SizedBox(width: 16),
const SizedBox(width: 16),
Text(
'${NumberFormat("#,##0.00", "fr_FR").format(amount)} MGA',
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
Icon(
isSelected ? Icons.check_circle : Icons.radio_button_unchecked,
color: isSelected ? method.color : Colors.white,
size: 22,
),
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() {
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 'dart:convert';
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 '../services/pdf_impression_commande.dart';
bool isMobile(BuildContext context) {
final size = MediaQuery.of(context).size.width;
return size < 600;
}
class OrdersManagementScreen extends StatefulWidget {
const OrdersManagementScreen({super.key});
@ -523,6 +530,7 @@ Future<void> deleteOrder(Order order) async {
}
}
class OrderCard extends StatelessWidget {
final Order order;
final Function(Order, String, {String? modePaiement}) onStatusUpdate;
@ -530,6 +538,8 @@ class OrderCard extends StatelessWidget {
final Function(Order) onDelete;
final VoidCallback onViewDetails;
const OrderCard({
Key? key,
required this.order,
@ -579,6 +589,8 @@ class OrderCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final auth = Provider.of<AuthProvider>(context);
final userType = auth.userType;
return Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
@ -782,9 +794,11 @@ class OrderCard extends StatelessWidget {
),
),
const SizedBox(width: 8),
if (userType != 'Serveur' || !isMobile(context))
Expanded(
flex: 3,
child: ElevatedButton.icon(
child:ElevatedButton.icon(
onPressed: () => onProcessPayment(order),
icon: const Icon(
Icons.point_of_sale,
@ -809,7 +823,9 @@ class OrderCard extends StatelessWidget {
),
),
const SizedBox(width: 8),
if (order.statut == 'en_attente')
if (userType != 'Serveur' || !isMobile(context))
Expanded(
child: ElevatedButton(
onPressed:
@ -827,8 +843,10 @@ class OrderCard extends StatelessWidget {
),
),
),
const SizedBox(width: 8),
if (order.statut == 'en_preparation')
if (userType != 'Serveur' || !isMobile(context))
Expanded(
child: ElevatedButton(
onPressed:
@ -846,6 +864,7 @@ class OrderCard extends StatelessWidget {
),
),
),
const SizedBox(width: 8),
Container(
decoration: BoxDecoration(

642
lib/pages/historique_commande.dart

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

94
lib/pages/login_screen.dart

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../providers/auth_provider.dart';
import './tables.dart';
import '../layouts/main_layout.dart';
@ -33,6 +35,10 @@ class _LoginScreenState extends State<LoginScreen> {
static const String serveurPassword = 'serveur123';
static const String adminEmail = 'admin@restaurant.com';
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 passwordController = TextEditingController();
@ -63,49 +69,67 @@ class _LoginScreenState extends State<LoginScreen> {
}
}
}
void _login() async {
if (!_formKey.currentState!.validate()) return;
if (!_formKey.currentState!.validate()) return;
setState(() {
_isLoading = true;
_errorMessage = null;
});
setState(() {
_isLoading = true;
_errorMessage = null;
});
// Simulate network delay
await Future.delayed(const Duration(seconds: 1));
// Simule un délai pour l'authentification
await Future.delayed(const Duration(seconds: 1));
setState(() {
_isLoading = false;
});
setState(() {
_isLoading = false;
});
// Check credentials
String email = emailController.text.trim();
String password = passwordController.text;
if ((email == serveurEmail && password == serveurPassword) ||
(email == adminEmail && password == adminPassword)) {
// Login successful - navigate to home/dashboard
if (mounted) {
String userType = email == adminEmail ? 'Admin' : 'Serveur';
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder:
(context) => const MainLayout(
currentRoute: '/tables',
child: TablesScreen(),
),
),
);
}
String email = emailController.text.trim();
String password = passwordController.text;
// Vérification des credentials
if ((email == serveurEmail && password == serveurPassword) ||
(email == cuisinierEmail && password == cuisinierPassword) ||
(email == caissierEmail && password == caissierPassword) ||
(email == adminEmail && password == adminPassword)) {
// Détermination du rôle
String userType;
if (email == adminEmail) {
userType = 'Admin';
} else if (email == serveurEmail) {
userType = 'Serveur';
}
else if (email == caissierEmail) {
userType = 'caissier';
} else {
// Login failed
setState(() {
_errorMessage = 'Email ou mot de passe incorrect';
});
userType = 'Cuisinier';
}
// 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
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"
source: hosted
version: "1.0.6"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
path:
dependency: transitive
description:
@ -456,6 +464,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:

1
pubspec.yaml

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

Loading…
Cancel
Save