push de 22102025
This commit is contained in:
parent
57efa196b9
commit
bb81798e03
@ -35,28 +35,28 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
name: 'MVola',
|
name: 'MVola',
|
||||||
description: 'Paiement mobile MVola',
|
description: 'Paiement mobile MVola',
|
||||||
icon: Icons.phone,
|
icon: Icons.phone,
|
||||||
color: Color(0xFF4285F4),
|
color: Color(0xFF28A745),
|
||||||
),
|
),
|
||||||
const PaymentMethod(
|
const PaymentMethod(
|
||||||
id: 'orange_money',
|
id: 'orange_money',
|
||||||
name: 'Orange Money',
|
name: 'Orange Money',
|
||||||
description: 'Paiement mobile Orange Money',
|
description: 'Paiement mobile Orange Money',
|
||||||
icon: Icons.phone,
|
icon: Icons.phone,
|
||||||
color: Color(0xFF4285F4),
|
color: Color(0xFFFF9500),
|
||||||
),
|
),
|
||||||
const PaymentMethod(
|
const PaymentMethod(
|
||||||
id: 'carte',
|
id: 'carte',
|
||||||
name: 'Carte Bancaire',
|
name: 'Carte Bancaire',
|
||||||
description: 'Paiement par carte',
|
description: 'Paiement par carte',
|
||||||
icon: Icons.credit_card,
|
icon: Icons.credit_card,
|
||||||
color: Color(0xFF28A745),
|
color: Color(0xFF4285F4),
|
||||||
),
|
),
|
||||||
const PaymentMethod(
|
const PaymentMethod(
|
||||||
id: 'especes',
|
id: 'especes',
|
||||||
name: 'Espèces',
|
name: 'Espèces',
|
||||||
description: 'Paiement en liquide',
|
description: 'Paiement en liquide',
|
||||||
icon: Icons.attach_money,
|
icon: Icons.attach_money,
|
||||||
color: Color(0xFFFF9500),
|
color: Color(0xFFFFEB3B),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -332,28 +332,118 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPaymentMethods() {
|
|
||||||
return Column(
|
Widget _buildPaymentSection() {
|
||||||
|
double montantDonne = 0.0;
|
||||||
|
double rendu = 0.0;
|
||||||
|
TextEditingController montantController = TextEditingController();
|
||||||
|
|
||||||
|
return StatefulBuilder(
|
||||||
|
builder: (context, setState) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// 🔵 Colonne gauche : méthodes de paiement
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
'Méthode de paiement',
|
'Méthode de paiement',
|
||||||
style: TextStyle(
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
...paymentMethods.map((method) {
|
||||||
...paymentMethods.map((method) => _buildPaymentMethodCard(method)),
|
final isSelected = selectedPaymentMethod?.id == method.id;
|
||||||
|
return _buildPaymentMethodCard(method, isSelected); // 🔹 plus besoin de StatefulBuilder
|
||||||
|
}).toList(),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
const SizedBox(width: 90),
|
||||||
|
|
||||||
|
// 🟢 Colonne droite : saisie montant et rendu
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Montant à payer :',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold, fontSize: 16),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'${NumberFormat("#,##0.00", "fr_FR").format(commande?.totalTtc ?? 0)} MGA',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 20,
|
||||||
|
color: Color(0xFF28A745)),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
TextField(
|
||||||
|
controller: montantController,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Montant donné par le client",
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
montantDonne = double.tryParse(value) ?? 0.0;
|
||||||
|
rendu = montantDonne - (commande?.totalTtc ?? 0.0);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Text(
|
||||||
|
'Rendu :',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16, vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: rendu >= 0 ? Colors.green[50] : Colors.red[50],
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${rendu >= 0 ? NumberFormat("#,##0.00", "fr_FR").format(rendu) : "0"} MGA',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 24, // rendu plus grand
|
||||||
|
color: rendu >= 0 ? Colors.green : Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (rendu < 0)
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(top: 8.0),
|
||||||
|
child: Text(
|
||||||
|
"Montant insuffisant",
|
||||||
|
style: TextStyle(color: Colors.red),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPaymentMethodCard(PaymentMethod method) {
|
|
||||||
final isSelected = selectedPaymentMethod?.id == method.id;
|
// Modification de _buildPaymentMethodCard pour accepter setState de la StatefulBuilder
|
||||||
|
Widget _buildPaymentMethodCard(PaymentMethod method, bool isSelected) {
|
||||||
final amount = commande?.totalTtc ?? 0.0;
|
final amount = commande?.totalTtc ?? 0.0;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
@ -363,7 +453,7 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedPaymentMethod = method;
|
selectedPaymentMethod = method; // 🔹 setState global
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
@ -403,9 +493,7 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -431,24 +519,18 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
|
|
||||||
Icon(
|
Icon(
|
||||||
isSelected ? Icons.check_circle : Icons.radio_button_unchecked,
|
isSelected ? Icons.check_circle : Icons.radio_button_unchecked,
|
||||||
color: isSelected ? method.color : Colors.white,
|
color: isSelected ? method.color : Colors.white,
|
||||||
size: 22,
|
size: 22,
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
'${NumberFormat("#,##0.00", "fr_FR").format(amount)} MGA',
|
'${NumberFormat("#,##0.00", "fr_FR").format(amount)} MGA',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isSelected ? Colors.black87 : Colors.white,
|
color: isSelected ? Colors.black87 : Colors.white,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -459,6 +541,9 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Widget _buildPaymentButton() {
|
Widget _buildPaymentButton() {
|
||||||
final canPay = selectedPaymentMethod != null && !isProcessingPayment;
|
final canPay = selectedPaymentMethod != null && !isProcessingPayment;
|
||||||
|
|
||||||
@ -600,7 +685,7 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
|
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
|
|
||||||
_buildPaymentMethods(),
|
_buildPaymentSection(), // <-- ici on met le widget avec 2 colonnes
|
||||||
|
|
||||||
_buildPaymentButton(),
|
_buildPaymentButton(),
|
||||||
|
|
||||||
@ -608,6 +693,7 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -422,13 +422,28 @@ class _ValidateAddItemsPageState extends State<ValidateAddItemsPage> {
|
|||||||
|
|
||||||
// Méthode pour naviguer avec succès
|
// Méthode pour naviguer avec succès
|
||||||
void _navigateBackWithSuccess() {
|
void _navigateBackWithSuccess() {
|
||||||
Navigator.of(context).pop({
|
// Fermer toutes les pages précédentes et rediriger vers OrdersManagementScreen
|
||||||
'success': true,
|
Navigator.of(context).pushAndRemoveUntil(
|
||||||
'shouldReload': true,
|
MaterialPageRoute(
|
||||||
'message': 'Articles ajoutés avec succès à la commande ${widget.numeroCommande}'
|
builder: (context) => OrdersManagementScreen(),
|
||||||
|
),
|
||||||
|
(Route<dynamic> route) => false, // Supprime toutes les pages précédentes
|
||||||
|
);
|
||||||
|
|
||||||
|
// Optionnel : petit délai pour laisser le temps à la navigation
|
||||||
|
Future.delayed(Duration(milliseconds: 300), () {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
'Articles ajoutés avec succès à la commande ${widget.numeroCommande}',
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.green[700],
|
||||||
|
),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Ancien dialog de succès (sans impression) - garde comme fallback
|
// Ancien dialog de succès (sans impression) - garde comme fallback
|
||||||
void _showSuccessDialog() {
|
void _showSuccessDialog() {
|
||||||
showDialog(
|
showDialog(
|
||||||
|
|||||||
@ -12,6 +12,7 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
late AnimationController _animationController;
|
late AnimationController _animationController;
|
||||||
List<AnimationController> _cardAnimationControllers = [];
|
List<AnimationController> _cardAnimationControllers = [];
|
||||||
List<CommandeData> commandes = [];
|
List<CommandeData> commandes = [];
|
||||||
|
List<CommandeData> allCommandes = []; // contiendra toutes les commandes
|
||||||
bool isLoading = true;
|
bool isLoading = true;
|
||||||
String? error;
|
String? error;
|
||||||
DateTime? _startDate;
|
DateTime? _startDate;
|
||||||
@ -53,9 +54,8 @@ Future<void> _loadCommandes({int page = 1}) async {
|
|||||||
'limit': itemsPerPage.toString(),
|
'limit': itemsPerPage.toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// ✅ CORRECTION : Envoyer la date en format ISO avec fuseau horaire UTC
|
// Gestion des filtres de date
|
||||||
if (_startDate != null) {
|
if (_startDate != null) {
|
||||||
// Début de journée en UTC (00:00:00)
|
|
||||||
final startUtc = DateTime.utc(
|
final startUtc = DateTime.utc(
|
||||||
_startDate!.year,
|
_startDate!.year,
|
||||||
_startDate!.month,
|
_startDate!.month,
|
||||||
@ -67,7 +67,6 @@ Future<void> _loadCommandes({int page = 1}) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_endDate != null) {
|
if (_endDate != null) {
|
||||||
// Fin de journée en UTC (23:59:59)
|
|
||||||
final endUtc = DateTime.utc(
|
final endUtc = DateTime.utc(
|
||||||
_endDate!.year,
|
_endDate!.year,
|
||||||
_endDate!.month,
|
_endDate!.month,
|
||||||
@ -78,19 +77,18 @@ Future<void> _loadCommandes({int page = 1}) async {
|
|||||||
print('📅 Date fin (UTC): ${endUtc.toIso8601String()}');
|
print('📅 Date fin (UTC): ${endUtc.toIso8601String()}');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug: Afficher l'URL complète
|
// URL complète
|
||||||
final uri = Uri.parse('$baseUrl/api/commandes').replace(queryParameters: queryParams);
|
final uri = Uri.parse('$baseUrl/api/commandes').replace(queryParameters: queryParams);
|
||||||
print('🌐 URL appelée: $uri');
|
print('🌐 URL appelée: $uri');
|
||||||
|
|
||||||
final response = await http.get(uri, headers: _headers);
|
final response = await http.get(uri, headers: _headers);
|
||||||
|
|
||||||
print('📡 Status code: ${response.statusCode}');
|
print('📡 Status code: ${response.statusCode}');
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final dynamic responseBody = json.decode(response.body);
|
final dynamic responseBody = json.decode(response.body);
|
||||||
List<dynamic> data = [];
|
List<dynamic> data = [];
|
||||||
|
|
||||||
// Gestion selon le format de réponse
|
// Gestion du format de réponse
|
||||||
if (responseBody is Map<String, dynamic>) {
|
if (responseBody is Map<String, dynamic>) {
|
||||||
final dataMap = responseBody['data'] as Map<String, dynamic>?;
|
final dataMap = responseBody['data'] as Map<String, dynamic>?;
|
||||||
|
|
||||||
@ -102,7 +100,6 @@ Future<void> _loadCommandes({int page = 1}) async {
|
|||||||
data = [commandesValue];
|
data = [commandesValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pagination
|
|
||||||
final pagination = dataMap['pagination'] as Map<String, dynamic>?;
|
final pagination = dataMap['pagination'] as Map<String, dynamic>?;
|
||||||
currentPage = pagination?['currentPage'] ?? page;
|
currentPage = pagination?['currentPage'] ?? page;
|
||||||
totalPages = pagination?['totalPages'] ?? (data.length / itemsPerPage).ceil();
|
totalPages = pagination?['totalPages'] ?? (data.length / itemsPerPage).ceil();
|
||||||
@ -123,8 +120,7 @@ Future<void> _loadCommandes({int page = 1}) async {
|
|||||||
try {
|
try {
|
||||||
final item = data[i];
|
final item = data[i];
|
||||||
if (item is Map<String, dynamic>) {
|
if (item is Map<String, dynamic>) {
|
||||||
final commandeData = CommandeData.fromJson(item);
|
parsedCommandes.add(CommandeData.fromJson(item));
|
||||||
parsedCommandes.add(commandeData);
|
|
||||||
} else {
|
} else {
|
||||||
print('Item $i invalide: ${item.runtimeType}');
|
print('Item $i invalide: ${item.runtimeType}');
|
||||||
}
|
}
|
||||||
@ -136,8 +132,10 @@ Future<void> _loadCommandes({int page = 1}) async {
|
|||||||
|
|
||||||
print('✅ ${parsedCommandes.length} commandes chargées');
|
print('✅ ${parsedCommandes.length} commandes chargées');
|
||||||
|
|
||||||
|
// ⚡ Mise à jour du state avec toutes les commandes
|
||||||
setState(() {
|
setState(() {
|
||||||
commandes = parsedCommandes;
|
allCommandes = parsedCommandes; // toutes les commandes
|
||||||
|
commandes = parsedCommandes; // commandes affichées (filtrage frontend possible)
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -160,7 +158,34 @@ Future<void> _loadCommandes({int page = 1}) async {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void _filterByDate() {
|
||||||
|
if (_startDate == null && _endDate == null) {
|
||||||
|
setState(() {
|
||||||
|
commandes = allCommandes;
|
||||||
|
currentPage = 1;
|
||||||
|
totalItems = commandes.length;
|
||||||
|
totalPages = (totalItems / itemsPerPage).ceil();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
commandes = allCommandes.where((c) {
|
||||||
|
final date = c.datePaiement ?? c.dateCommande;
|
||||||
|
if (date == null) return false;
|
||||||
|
|
||||||
|
final start = _startDate ?? DateTime(2000);
|
||||||
|
// Fin de journée pour inclure la date complète
|
||||||
|
final end = _endDate?.add(Duration(days: 1)).subtract(Duration(milliseconds: 1)) ?? DateTime(2100);
|
||||||
|
|
||||||
|
return !date.isBefore(start) && !date.isAfter(end);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
currentPage = 1;
|
||||||
|
totalItems = commandes.length;
|
||||||
|
totalPages = (totalItems / itemsPerPage).ceil();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _selectDate(BuildContext context, bool isStart) async {
|
Future<void> _selectDate(BuildContext context, bool isStart) async {
|
||||||
final DateTime? picked = await showDatePicker(
|
final DateTime? picked = await showDatePicker(
|
||||||
@ -337,7 +362,7 @@ Widget _buildHeader() {
|
|||||||
),
|
),
|
||||||
SizedBox(width: 5),
|
SizedBox(width: 5),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () => _loadCommandes(page: 1),
|
onPressed: () => _filterByDate(),
|
||||||
child: Text('Filtrer'),
|
child: Text('Filtrer'),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
@ -370,16 +395,10 @@ Widget _buildHeader() {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_startDate = DateTime(today.year, today.month, today.day);
|
_startDate = DateTime(today.year, today.month, today.day);
|
||||||
_endDate = DateTime(today.year, today.month, today.day, 23, 59, 59);
|
_endDate = DateTime(today.year, today.month, today.day, 23, 59, 59);
|
||||||
|
_filterByDate(); // filtre local sur toutes les commandes déjà chargées
|
||||||
});
|
});
|
||||||
_loadCommandes(page: 1);
|
|
||||||
},
|
},
|
||||||
child: Text('Aujourd’hui'),
|
child: Text('Aujourd’hui'),
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Colors.blue,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
textStyle: TextStyle(fontSize: 10),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user