Browse Source

commit

master
andrymodeste 4 months ago
parent
commit
ab8731a53e
  1. 5
      lib/layouts/main_layout.dart
  2. 6
      lib/main.dart
  3. 802
      lib/pages/historique_commande.dart
  4. 43
      lib/widgets/bottom_navigation.dart

5
lib/layouts/main_layout.dart

@ -36,6 +36,8 @@ class _MainLayoutState extends State<MainLayout> {
return 4;
case '/encaissement':
return 5;
case '/historique':
return 6;
default:
return 0;
}
@ -66,6 +68,9 @@ class _MainLayoutState extends State<MainLayout> {
case 5:
route = '/encaissement';
break;
case 6:
route = '/historique';
break;
default:
route = '/tables';
}

6
lib/main.dart

@ -5,6 +5,7 @@ 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/encaissement_screen.dart'; // NOUVEAU
void main() {
@ -52,6 +53,11 @@ class MyApp extends StatelessWidget {
currentRoute: '/encaissement',
child: EncaissementScreen(),
),
'/historique':
(context) => MainLayout(
currentRoute: '/historique',
child: OrderHistoryPage(),
),
},
);
}

802
lib/pages/historique_commande.dart

@ -0,0 +1,802 @@
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class OrderHistoryPage extends StatefulWidget {
@override
_OrderHistoryPageState createState() => _OrderHistoryPageState();
}
class _OrderHistoryPageState extends State<OrderHistoryPage>
with TickerProviderStateMixin {
late AnimationController _animationController;
List<AnimationController> _cardAnimationControllers = [];
List<CommandeData> commandes = [];
bool isLoading = true;
String? error;
// Informations de pagination
int currentPage = 1;
int totalPages = 1;
int totalItems = 0;
int itemsPerPage = 10;
final String baseUrl = 'https://restaurant.careeracademy.mg'; // Remplacez par votre URL
final Map<String, String> _headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
};
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: Duration(milliseconds: 1000),
vsync: this,
);
_loadCommandes();
}
Future<void> _loadCommandes() async {
try {
setState(() {
isLoading = true;
error = null;
});
final response = await http.get(
Uri.parse('$baseUrl/api/commandes?statut=payee'),
headers: _headers,
);
final dynamic responseBody = json.decode(response.body);
print('Réponse getCommandes: ${responseBody}');
if (response.statusCode == 200) {
// Adapter la structure de réponse {data: [...], pagination: {...}}
final Map<String, dynamic> responseData = json.decode(response.body);
final List<dynamic> data = responseData['data'] ?? [];
final Map<String, dynamic> pagination = responseData['pagination'] ?? {};
setState(() {
commandes = data.map((json) => CommandeData.fromJson(json)).toList();
// Mettre à jour les informations de pagination
currentPage = pagination['currentPage'] ?? 1;
totalPages = pagination['totalPages'] ?? 1;
totalItems = pagination['totalItems'] ?? 0;
itemsPerPage = pagination['itemsPerPage'] ?? 10;
isLoading = false;
});
_initializeAnimations();
_startAnimations();
} else {
setState(() {
error = 'Erreur lors du chargement des commandes';
isLoading = false;
});
}
} catch (e) {
setState(() {
error = 'Erreur de connexion: $e';
isLoading = false;
});
}
}
void _initializeAnimations() {
_cardAnimationControllers = List.generate(
commandes.length,
(index) => AnimationController(
duration: Duration(milliseconds: 600),
vsync: this,
),
);
}
void _startAnimations() async {
_animationController.forward();
for (int i = 0; i < _cardAnimationControllers.length; i++) {
await Future.delayed(Duration(milliseconds: 150));
if (mounted) {
_cardAnimationControllers[i].forward();
}
}
}
@override
void dispose() {
_animationController.dispose();
for (var controller in _cardAnimationControllers) {
controller.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('Historique des commandes'),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 0,
),
body: RefreshIndicator(
onRefresh: _loadCommandes,
child: Column(
children: [
_buildHeader(),
Expanded(
child: _buildContent(),
),
],
),
),
);
}
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),
),
textAlign: TextAlign.center,
),
SizedBox(height: 2),
Text(
'Consultez toutes les commandes qui ont été payées',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
textAlign: TextAlign.center,
),
if (totalItems > 0)
Padding(
padding: EdgeInsets.only(top: 4),
child: Text(
'$totalItems commande${totalItems > 1 ? 's' : ''} • Page $currentPage/$totalPages',
style: TextStyle(
fontSize: 10,
color: Colors.grey.shade500,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
),
],
),
),
),
);
},
);
}
Widget _buildContent() {
if (isLoading) {
return Center(
child: CircularProgressIndicator(),
);
}
if (error != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text(error!, style: TextStyle(color: Colors.grey)),
SizedBox(height: 16),
ElevatedButton(
onPressed: _loadCommandes,
child: Text('Réessayer'),
),
],
),
);
}
if (commandes.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.payment, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text(
'Aucune commande payée',
style: TextStyle(color: Colors.grey, fontSize: 16),
),
],
),
);
}
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) {
return SizedBox.shrink();
}
return AnimatedBuilder(
animation: _cardAnimationControllers[index],
builder: (context, child) {
return Transform.translate(
offset: Offset(0, 50 * (1 - _cardAnimationControllers[index].value)),
child: Opacity(
opacity: _cardAnimationControllers[index].value,
child: Container(
margin: EdgeInsets.only(bottom: 12),
child: Material(
elevation: 8,
borderRadius: BorderRadius.circular(15),
child: InkWell(
borderRadius: BorderRadius.circular(15),
onTap: () {
// Action au tap
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: Colors.white,
border: Border(
left: BorderSide(
color: Color(0xFF4CAF50),
width: 3,
),
),
),
child: Column(
children: [
_buildOrderHeader(commande),
_buildOrderItems(commande),
_buildOrderFooter(commande),
],
),
),
),
),
),
),
);
},
);
}
Widget _buildOrderHeader(CommandeData commande) {
return Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.grey.shade200,
width: 1,
),
),
),
child: Row(
children: [
Container(
width: 35,
height: 35,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF667eea), Color(0xFF764ba2)],
),
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
commande.getTableShortName(),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 10,
),
),
),
),
SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
commande.tablename,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF2c3e50),
),
),
SizedBox(height: 2),
Text(
commande.numeroCommande,
style: TextStyle(
fontSize: 10,
color: Colors.grey,
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 2),
Row(
children: [
Icon(Icons.calendar_today, size: 12, color: Colors.grey),
SizedBox(width: 3),
Text(
_formatDateTime(commande.dateCommande),
style: TextStyle(color: Colors.grey, fontSize: 10),
),
SizedBox(width: 8),
Icon(Icons.person, size: 12, color: Colors.grey),
SizedBox(width: 3),
Text(
commande.serveur,
style: TextStyle(color: Colors.grey, fontSize: 10),
),
],
),
],
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF4CAF50), Color(0xFF45a049)],
),
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.check_circle,
color: Colors.white,
size: 10,
),
SizedBox(width: 3),
Text(
'PAYÉE',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: 8,
letterSpacing: 0.3,
),
),
],
),
),
],
),
);
}
Widget _buildOrderItems(CommandeData commande) {
return Container(
padding: EdgeInsets.all(10),
child: Column(
children: commande.items.map((item) => _buildOrderItem(item)).toList(),
),
);
}
Widget _buildOrderItem(CommandeItem item) {
return Container(
padding: EdgeInsets.symmetric(vertical: 6),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.grey.shade100,
width: 1,
),
),
),
child: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFFffeaa7), Color(0xFFfdcb6e)],
),
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
_getMenuIcon(item.menuNom),
style: TextStyle(fontSize: 14),
),
),
),
SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.menuNom,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF2c3e50),
),
),
if (item.commentaires != null && item.commentaires!.isNotEmpty)
Text(
item.commentaires!,
style: TextStyle(
fontSize: 9,
color: Colors.grey.shade600,
fontStyle: FontStyle.italic,
),
),
Text(
'${item.quantite}x × ${_formatPrice(item.prixUnitaire)}',
style: TextStyle(
fontSize: 10,
color: Colors.grey,
),
),
],
),
),
Text(
_formatPrice(item.totalItem),
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Color(0xFF4CAF50),
),
),
],
),
);
}
Widget _buildOrderFooter(CommandeData commande) {
return Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
width: 28,
height: 28,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF4CAF50), Color(0xFF45a049)],
),
borderRadius: BorderRadius.circular(6),
),
child: Center(
child: Icon(
_getPaymentIcon(commande.modePaiement),
color: Colors.white,
size: 14,
),
),
),
SizedBox(width: 6),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_getPaymentMethodText(commande.modePaiement),
style: TextStyle(
fontSize: 10,
color: Colors.grey.shade600,
),
),
if (commande.totalTva > 0)
Text(
'TVA: ${_formatPrice(commande.totalTva)}',
style: TextStyle(
fontSize: 9,
color: Colors.grey.shade500,
),
),
],
),
],
),
Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Color(0xFF4CAF50).withOpacity(0.1),
borderRadius: BorderRadius.circular(15),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.attach_money,
color: Color(0xFF4CAF50),
size: 14,
),
Text(
_formatPrice(commande.totalTtc),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Color(0xFF4CAF50),
),
),
],
),
),
],
),
);
}
String _formatDateTime(DateTime dateTime) {
return '${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year} à ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
}
String _formatPrice(double priceInCents) {
// Les prix sont déjà en centimes dans votre API (ex: 20000.00 = 200.00 )
return '${(priceInCents / 100).toStringAsFixed(2)} Ar';
}
String _getMenuIcon(String menuNom) {
String lowerName = menuNom.toLowerCase();
if (lowerName.contains('pizza')) return '🍕';
if (lowerName.contains('steak') || lowerName.contains('steack')) return '🥩';
if (lowerName.contains('frite')) return '🍟';
if (lowerName.contains('salade')) return '🥗';
if (lowerName.contains('soupe')) return '🍲';
if (lowerName.contains('burger')) return '🍔';
if (lowerName.contains('poisson')) return '🐟';
if (lowerName.contains('poulet')) return '🍗';
if (lowerName.contains('pâtes') || lowerName.contains('pasta')) return '🍝';
if (lowerName.contains('dessert') || lowerName.contains('gâteau')) return '🍰';
if (lowerName.contains('boisson')) return '🥤';
return '🍽️';
}
IconData _getPaymentIcon(String? method) {
if (method == null) return Icons.help_outline;
switch (method.toLowerCase()) {
case 'especes':
case 'cash':
return Icons.money;
case 'carte':
case 'card':
return Icons.credit_card;
case 'mobile':
case 'paypal':
return Icons.smartphone;
default:
return Icons.payment;
}
}
String _getPaymentMethodText(String? method) {
if (method == null) return 'Non défini';
switch (method.toLowerCase()) {
case 'especes':
case 'cash':
return 'Espèces';
case 'carte':
case 'card':
return 'Carte';
case 'mobile':
case 'paypal':
return 'Mobile';
default:
return method;
}
}
}
// Modèles de données adaptés à votre API
class CommandeData {
final int id;
final int clientId;
final int tableId;
final int reservationId;
final String numeroCommande;
final String statut;
final double totalHt;
final double totalTva;
final double totalTtc;
final String? modePaiement;
final String? commentaires;
final String serveur;
final DateTime dateCommande;
final DateTime? dateService;
final DateTime createdAt;
final DateTime updatedAt;
final List<CommandeItem> items;
final String tablename;
CommandeData({
required this.id,
required this.clientId,
required this.tableId,
required this.reservationId,
required this.numeroCommande,
required this.statut,
required this.totalHt,
required this.totalTva,
required this.totalTtc,
this.modePaiement,
this.commentaires,
required this.serveur,
required this.dateCommande,
this.dateService,
required this.createdAt,
required this.updatedAt,
required this.items,
required this.tablename,
});
factory CommandeData.fromJson(Map<String, dynamic> json) {
return CommandeData(
id: json['id'],
clientId: json['client_id'],
tableId: json['table_id'],
reservationId: json['reservation_id'],
numeroCommande: json['numero_commande'],
statut: json['statut'],
totalHt: (json['total_ht'] ?? 0).toDouble(),
totalTva: (json['total_tva'] ?? 0).toDouble(),
totalTtc: (json['total_ttc'] ?? 0).toDouble(),
modePaiement: json['mode_paiement'],
commentaires: json['commentaires'],
serveur: json['serveur'],
dateCommande: DateTime.parse(json['date_commande']),
dateService: json['date_service'] != null
? DateTime.parse(json['date_service'])
: null,
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
items: (json['items'] as List)
.map((item) => CommandeItem.fromJson(item))
.toList(),
tablename: json['tablename'] ?? 'Table inconnue',
);
}
String getTableShortName() {
if (tablename.toLowerCase().contains('caisse')) return 'C';
if (tablename.toLowerCase().contains('terrasse')) return 'T';
// Extraire le numéro de la table
RegExp regExp = RegExp(r'\d+');
Match? match = regExp.firstMatch(tablename);
if (match != null) {
return 'T${match.group(0)}';
}
return tablename.substring(0, 1).toUpperCase();
}
}
class CommandeItem {
final int id;
final int commandeId;
final int menuId;
final int quantite;
final double prixUnitaire;
final double totalItem;
final String? commentaires;
final String statut;
final DateTime createdAt;
final DateTime updatedAt;
final String menuNom;
final String menuDescription;
final double menuPrixActuel;
final String tablename;
CommandeItem({
required this.id,
required this.commandeId,
required this.menuId,
required this.quantite,
required this.prixUnitaire,
required this.totalItem,
this.commentaires,
required this.statut,
required this.createdAt,
required this.updatedAt,
required this.menuNom,
required this.menuDescription,
required this.menuPrixActuel,
required this.tablename,
});
factory CommandeItem.fromJson(Map<String, dynamic> json) {
return CommandeItem(
id: json['id'],
commandeId: json['commande_id'],
menuId: json['menu_id'],
quantite: json['quantite'],
prixUnitaire: (json['prix_unitaire'] ?? 0).toDouble(),
totalItem: (json['total_item'] ?? 0).toDouble(),
commentaires: json['commentaires'],
statut: json['statut'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
menuNom: json['menu_nom'],
menuDescription: json['menu_description'],
menuPrixActuel: (json['menu_prix_actuel'] ?? 0).toDouble(),
tablename: json['tablename'] ?? '',
);
}
}
// Usage dans votre app principale
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Historique des Commandes',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: OrderHistoryPage(),
debugShowCheckedModeBanner: false,
);
}
}
void main() {
runApp(MyApp());
}

43
lib/widgets/bottom_navigation.dart

@ -238,6 +238,49 @@ class AppBottomNavigation extends StatelessWidget {
),
),
const SizedBox(width: 20),
GestureDetector(
onTap: () => onItemTapped(6),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color:
selectedIndex == 5
? Colors.green.shade700
: Colors.transparent,
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.payment,
color:
selectedIndex == 5
? Colors.white
: Colors.grey.shade600,
size: 16,
),
const SizedBox(width: 6),
Text(
'Historique',
style: TextStyle(
color:
selectedIndex == 5
? Colors.white
: Colors.grey.shade600,
fontWeight:
selectedIndex == 5
? FontWeight.w500
: FontWeight.normal,
),
),
],
),
),
),
const Spacer(),
// User Profile Section

Loading…
Cancel
Save