filtrage
This commit is contained in:
parent
c9a0d72a1e
commit
57efa196b9
@ -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(),
|
||||
),
|
||||
|
||||
@ -367,18 +367,23 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
||||
});
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: method.color,
|
||||
color: isSelected ? Colors.white : method.color,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border:
|
||||
isSelected ? Border.all(color: Colors.white, width: 3) : null,
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -387,10 +392,16 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
color: isSelected
|
||||
? method.color.withOpacity(0.1)
|
||||
: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(method.icon, color: Colors.white, size: 24),
|
||||
child: Icon(
|
||||
method.icon,
|
||||
color: isSelected ? method.color : Colors.white,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 16),
|
||||
@ -401,8 +412,8 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
||||
children: [
|
||||
Text(
|
||||
method.name,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
style: TextStyle(
|
||||
color: isSelected ? Colors.black87 : Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
@ -411,7 +422,9 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
||||
Text(
|
||||
method.description,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
color: isSelected
|
||||
? Colors.black54
|
||||
: Colors.white.withOpacity(0.9),
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
@ -421,11 +434,19 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
||||
|
||||
const SizedBox(width: 16),
|
||||
|
||||
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: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
style: TextStyle(
|
||||
color: isSelected ? Colors.black87 : Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@ -435,7 +456,8 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Widget _buildPaymentButton() {
|
||||
final canPay = selectedPaymentMethod != null && !isProcessingPayment;
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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,80 +39,76 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
||||
|
||||
_loadCommandes();
|
||||
}
|
||||
|
||||
Future<void> _loadCommandes({int page = 1}) async {
|
||||
Future<void> _loadCommandes({int page = 1}) async {
|
||||
try {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
error = null;
|
||||
});
|
||||
|
||||
// Ajouter les paramètres de pagination à l'URL
|
||||
final uri = Uri.parse('$baseUrl/api/commandes').replace(queryParameters: {
|
||||
// Construction des paramètres de requête
|
||||
Map<String, String> queryParams = {
|
||||
'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()}');
|
||||
}
|
||||
|
||||
// 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 améliorée de la réponse
|
||||
// Gestion selon le format de réponse
|
||||
if (responseBody is Map<String, dynamic>) {
|
||||
final dataMap = responseBody['data'] as 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')) {
|
||||
if (dataMap != null) {
|
||||
final commandesValue = dataMap['commandes'];
|
||||
|
||||
if (commandesValue is List<dynamic>) {
|
||||
if (commandesValue is List) {
|
||||
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;
|
||||
currentPage = pagination?['currentPage'] ?? page;
|
||||
totalPages = pagination?['totalPages'] ?? (data.length / itemsPerPage).ceil();
|
||||
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>) {
|
||||
} else if (responseBody is List) {
|
||||
data = responseBody;
|
||||
totalItems = data.length;
|
||||
currentPage = page;
|
||||
@ -119,51 +117,38 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
||||
throw Exception('Format de réponse inattendu: ${responseBody.runtimeType}');
|
||||
}
|
||||
|
||||
|
||||
// Conversion sécurisée avec prints détaillés
|
||||
// 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>) {
|
||||
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}');
|
||||
print('Item $i invalide: ${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
|
||||
print('Erreur parsing item $i: $e');
|
||||
print(stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
print('✅ ${parsedCommandes.length} commandes chargées');
|
||||
|
||||
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;
|
||||
});
|
||||
print('Erreur HTTP ${response.statusCode}: ${response.reasonPhrase}');
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
print('=== ERREUR GÉNÉRALE ===');
|
||||
@ -174,7 +159,26 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
||||
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,8 +262,7 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
Widget _buildHeader() {
|
||||
return AnimatedBuilder(
|
||||
animation: _animationController,
|
||||
builder: (context, child) {
|
||||
@ -302,6 +305,85 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
||||
),
|
||||
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),
|
||||
),
|
||||
),
|
||||
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),
|
||||
@ -324,7 +406,7 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildPagination() {
|
||||
return Container(
|
||||
@ -464,89 +546,104 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
||||
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Text(error!, style: TextStyle(color: Colors.red)),
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
return const Center(
|
||||
child: Text("Aucune commande trouvée"),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20),
|
||||
itemCount: commandes.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildOrderCard(commandes[index], index);
|
||||
// ✅ 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("N°")),
|
||||
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(),
|
||||
);
|
||||
}
|
||||
|
||||
// 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'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Widget _buildOrderCard(CommandeData commande, int index) {
|
||||
if (index >= _cardAnimationControllers.length) {
|
||||
@ -1052,20 +1149,19 @@ class CommandeData {
|
||||
return null;
|
||||
}
|
||||
|
||||
static DateTime? _parseDateTime(dynamic value) {
|
||||
static DateTime? _parseDateTime(dynamic value) {
|
||||
if (value == null) return null;
|
||||
if (value is String) {
|
||||
try {
|
||||
final result = DateTime.parse(value);
|
||||
return result;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static List<CommandeItem>? _parseItems(dynamic value) {
|
||||
|
||||
|
||||
@ -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,7 +69,6 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _login() async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
|
||||
@ -72,40 +77,59 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
_errorMessage = null;
|
||||
});
|
||||
|
||||
// Simulate network delay
|
||||
// Simule un délai pour l'authentification
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
|
||||
// Check credentials
|
||||
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)) {
|
||||
// Login successful - navigate to home/dashboard
|
||||
if (mounted) {
|
||||
String userType = email == adminEmail ? 'Admin' : 'Serveur';
|
||||
|
||||
// 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 {
|
||||
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(
|
||||
builder: (context) => const MainLayout(
|
||||
currentRoute: '/tables',
|
||||
child: TablesScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Login failed
|
||||
// ❌ Erreur de connexion
|
||||
setState(() {
|
||||
_errorMessage = 'Email ou mot de passe incorrect';
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
18
lib/providers/auth_provider.dart
Normal file
18
lib/providers/auth_provider.dart
Normal file
@ -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
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:
|
||||
|
||||
@ -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…
Reference in New Issue
Block a user