You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
999 lines
34 KiB
999 lines
34 KiB
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:youmazgestion/Components/app_bar.dart';
|
|
import 'package:youmazgestion/Components/appDrawer.dart';
|
|
import 'package:youmazgestion/Models/client.dart';
|
|
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
|
|
import 'package:youmazgestion/controller/userController.dart';
|
|
|
|
class HistoriquePage extends StatefulWidget {
|
|
const HistoriquePage({super.key});
|
|
|
|
@override
|
|
_HistoriquePageState createState() => _HistoriquePageState();
|
|
}
|
|
|
|
class _HistoriquePageState extends State<HistoriquePage> {
|
|
final AppDatabase _appDatabase = AppDatabase.instance;
|
|
|
|
// Listes pour les commandes
|
|
final List<Commande> _commandes = [];
|
|
final List<Commande> _filteredCommandes = [];
|
|
|
|
List<Map<String, dynamic>> _pointsDeVente = [];
|
|
String? _selectedPointDeVente;
|
|
final UserController _userController = Get.find<UserController>();
|
|
|
|
bool _isLoading = true;
|
|
DateTimeRange? _dateRange;
|
|
|
|
// Contrôleurs pour les filtres
|
|
final TextEditingController _searchController = TextEditingController();
|
|
final TextEditingController _searchClientController = TextEditingController();
|
|
final TextEditingController _searchCommandeIdController =
|
|
TextEditingController();
|
|
|
|
// Variables de filtre
|
|
StatutCommande? _selectedStatut;
|
|
bool _showOnlyToday = false;
|
|
double? _minAmount;
|
|
double? _maxAmount;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadCommandes();
|
|
_loadPointsDeVenteWithDefault();
|
|
|
|
// Listeners pour les filtres
|
|
_searchController.addListener(_filterCommandes);
|
|
_searchClientController.addListener(_filterCommandes);
|
|
_searchCommandeIdController.addListener(_filterCommandes);
|
|
}
|
|
|
|
Future<void> _loadPointsDeVenteWithDefault() async {
|
|
try {
|
|
print(_userController.pointDeVenteId);
|
|
final points = await _appDatabase.getPointsDeVente();
|
|
setState(() {
|
|
_pointsDeVente = points;
|
|
|
|
if (points.isNotEmpty) {
|
|
if (_userController.pointDeVenteId > 0) {
|
|
final userPointDeVente = points.firstWhere(
|
|
(point) => point['id'] == _userController.pointDeVenteId,
|
|
orElse: () => <String, dynamic>{},
|
|
);
|
|
|
|
if (userPointDeVente.isNotEmpty) {
|
|
_selectedPointDeVente = userPointDeVente['nom'] as String;
|
|
} else {
|
|
_selectedPointDeVente = points[0]['nom'] as String;
|
|
}
|
|
} else {
|
|
_selectedPointDeVente = points[0]['nom'] as String;
|
|
}
|
|
}
|
|
});
|
|
} catch (e) {
|
|
Get.snackbar('Erreur', 'Impossible de charger les points de vente: $e');
|
|
print('❌ Erreur chargement points de vente: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> _loadCommandes() async {
|
|
setState(() {
|
|
_isLoading = true;
|
|
});
|
|
|
|
try {
|
|
final allCommandes = await _appDatabase.getCommandes();
|
|
final pointDeVenteId = _userController.pointDeVenteId;
|
|
final commandes = pointDeVenteId == 0
|
|
? allCommandes
|
|
: allCommandes.where((cmd) => cmd.pointDeVenteId == pointDeVenteId).toList();
|
|
|
|
setState(() {
|
|
_commandes.clear();
|
|
_commandes.addAll(commandes);
|
|
_filteredCommandes.clear();
|
|
_filteredCommandes.addAll(commandes);
|
|
_isLoading = false;
|
|
});
|
|
} catch (e) {
|
|
setState(() {
|
|
_isLoading = false;
|
|
});
|
|
Get.snackbar(
|
|
'Erreur',
|
|
'Impossible de charger les commandes: ${e.toString()}',
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.red,
|
|
colorText: Colors.white,
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _selectDateRange(BuildContext context) async {
|
|
final DateTimeRange? picked = await showDateRangePicker(
|
|
context: context,
|
|
firstDate: DateTime(2020),
|
|
lastDate: DateTime.now().add(const Duration(days: 365)),
|
|
initialDateRange: _dateRange ??
|
|
DateTimeRange(
|
|
start: DateTime.now().subtract(const Duration(days: 30)),
|
|
end: DateTime.now(),
|
|
),
|
|
);
|
|
|
|
if (picked != null) {
|
|
setState(() {
|
|
_dateRange = picked;
|
|
});
|
|
_filterCommandes();
|
|
}
|
|
}
|
|
|
|
// Méthode pour filtrer les commandes
|
|
void _filterCommandes() {
|
|
final searchText = _searchController.text.toLowerCase();
|
|
final clientQuery = _searchClientController.text.toLowerCase();
|
|
final commandeIdQuery = _searchCommandeIdController.text.toLowerCase();
|
|
final pointDeVenteId = _userController.pointDeVenteId;
|
|
|
|
setState(() {
|
|
_filteredCommandes.clear();
|
|
|
|
for (var commande in _commandes) {
|
|
if (pointDeVenteId != 0 && commande.pointDeVenteId != pointDeVenteId) continue;
|
|
bool matchesSearch = searchText.isEmpty ||
|
|
commande.clientNom!.toLowerCase().contains(searchText) ||
|
|
commande.clientPrenom!.toLowerCase().contains(searchText) ||
|
|
commande.id.toString().contains(searchText);
|
|
|
|
bool matchesClient = clientQuery.isEmpty ||
|
|
commande.clientNom!.toLowerCase().contains(clientQuery) ||
|
|
commande.clientPrenom!.toLowerCase().contains(clientQuery);
|
|
|
|
bool matchesCommandeId = commandeIdQuery.isEmpty ||
|
|
commande.id.toString().contains(commandeIdQuery);
|
|
|
|
bool matchesStatut =
|
|
_selectedStatut == null || commande.statut == _selectedStatut;
|
|
|
|
bool matchesDate = true;
|
|
if (_dateRange != null) {
|
|
final date = commande.dateCommande;
|
|
matchesDate = date.isAfter(_dateRange!.start) &&
|
|
date.isBefore(_dateRange!.end.add(const Duration(days: 1)));
|
|
}
|
|
|
|
bool matchesToday = !_showOnlyToday || _isToday(commande.dateCommande);
|
|
|
|
bool matchesAmount = true;
|
|
if (_minAmount != null && commande.montantTotal < _minAmount!) {
|
|
matchesAmount = false;
|
|
}
|
|
if (_maxAmount != null && commande.montantTotal > _maxAmount!) {
|
|
matchesAmount = false;
|
|
}
|
|
|
|
if (matchesSearch &&
|
|
matchesClient &&
|
|
matchesCommandeId &&
|
|
matchesStatut &&
|
|
matchesDate &&
|
|
matchesToday &&
|
|
matchesAmount) {
|
|
_filteredCommandes.add(commande);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
bool _isToday(DateTime date) {
|
|
final now = DateTime.now();
|
|
return date.year == now.year &&
|
|
date.month == now.month &&
|
|
date.day == now.day;
|
|
}
|
|
|
|
// Toggle filtre aujourd'hui
|
|
void _toggleTodayFilter() {
|
|
setState(() {
|
|
_showOnlyToday = !_showOnlyToday;
|
|
});
|
|
_filterCommandes();
|
|
}
|
|
|
|
// Réinitialiser les filtres
|
|
void _clearFilters() {
|
|
setState(() {
|
|
_searchController.clear();
|
|
_searchClientController.clear();
|
|
_searchCommandeIdController.clear();
|
|
_selectedStatut = null;
|
|
_dateRange = null;
|
|
_showOnlyToday = false;
|
|
_minAmount = null;
|
|
_maxAmount = null;
|
|
});
|
|
_filterCommandes();
|
|
}
|
|
|
|
// Widget pour la section des filtres (adapté pour mobile)
|
|
Widget _buildFilterSection() {
|
|
final isMobile = MediaQuery.of(context).size.width < 600;
|
|
|
|
return Card(
|
|
elevation: 2,
|
|
margin: const EdgeInsets.only(bottom: 16),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(Icons.filter_list, color: Colors.blue.shade700),
|
|
const SizedBox(width: 8),
|
|
const Text(
|
|
'Filtres',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: Color.fromARGB(255, 9, 56, 95),
|
|
),
|
|
),
|
|
const Spacer(),
|
|
TextButton.icon(
|
|
onPressed: _clearFilters,
|
|
icon: const Icon(Icons.clear, size: 18),
|
|
label:
|
|
isMobile ? const SizedBox() : const Text('Réinitialiser'),
|
|
style: TextButton.styleFrom(
|
|
foregroundColor: Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Champ de recherche générale
|
|
TextField(
|
|
controller: _searchController,
|
|
decoration: InputDecoration(
|
|
labelText: 'Recherche',
|
|
prefixIcon: const Icon(Icons.search),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
if (!isMobile) ...[
|
|
// Version desktop - champs sur la même ligne
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextField(
|
|
controller: _searchClientController,
|
|
decoration: InputDecoration(
|
|
labelText: 'Client',
|
|
prefixIcon: const Icon(Icons.person),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: TextField(
|
|
controller: _searchCommandeIdController,
|
|
decoration: InputDecoration(
|
|
labelText: 'ID Commande',
|
|
prefixIcon: const Icon(Icons.confirmation_number),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
keyboardType: TextInputType.number,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
] else ...[
|
|
// Version mobile - champs empilés
|
|
TextField(
|
|
controller: _searchClientController,
|
|
decoration: InputDecoration(
|
|
labelText: 'Client',
|
|
prefixIcon: const Icon(Icons.person),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
TextField(
|
|
controller: _searchCommandeIdController,
|
|
decoration: InputDecoration(
|
|
labelText: 'ID Commande',
|
|
prefixIcon: const Icon(Icons.confirmation_number),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
keyboardType: TextInputType.number,
|
|
),
|
|
],
|
|
const SizedBox(height: 12),
|
|
|
|
// Dropdown pour le statut
|
|
DropdownButtonFormField<StatutCommande>(
|
|
value: _selectedStatut,
|
|
decoration: InputDecoration(
|
|
labelText: 'Statut',
|
|
prefixIcon: const Icon(Icons.assignment),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
items: [
|
|
const DropdownMenuItem<StatutCommande>(
|
|
value: null,
|
|
child: Text('Tous les statuts'),
|
|
),
|
|
...StatutCommande.values.map((StatutCommande statut) {
|
|
return DropdownMenuItem<StatutCommande>(
|
|
value: statut,
|
|
child: Text(_getStatutText(statut)),
|
|
);
|
|
}).toList(),
|
|
],
|
|
onChanged: (StatutCommande? newValue) {
|
|
setState(() {
|
|
_selectedStatut = newValue;
|
|
});
|
|
_filterCommandes();
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Boutons de filtre rapide - adaptés pour mobile
|
|
Wrap(
|
|
spacing: 8,
|
|
runSpacing: 8,
|
|
children: [
|
|
ElevatedButton.icon(
|
|
onPressed: _toggleTodayFilter,
|
|
icon: Icon(
|
|
_showOnlyToday ? Icons.today : Icons.calendar_today,
|
|
size: 20,
|
|
),
|
|
label: Text(_showOnlyToday
|
|
? isMobile
|
|
? 'Toutes dates'
|
|
: 'Toutes les dates'
|
|
: isMobile
|
|
? 'Aujourd\'hui'
|
|
: 'Aujourd\'hui seulement'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: _showOnlyToday
|
|
? Colors.green.shade600
|
|
: Colors.blue.shade600,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: isMobile ? 12 : 16, vertical: 8),
|
|
),
|
|
),
|
|
ElevatedButton.icon(
|
|
onPressed: () => _selectDateRange(context),
|
|
icon: const Icon(Icons.date_range, size: 20),
|
|
label: Text(_dateRange != null
|
|
? isMobile
|
|
? 'Période'
|
|
: 'Période sélectionnée'
|
|
: isMobile
|
|
? 'Période'
|
|
: 'Choisir période'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: _dateRange != null
|
|
? Colors.orange.shade600
|
|
: Colors.grey.shade600,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: isMobile ? 12 : 16, vertical: 8),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
if (_dateRange != null)
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 8),
|
|
child: Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.orange.shade50,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.date_range,
|
|
size: 16, color: Colors.orange.shade700),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
'${DateFormat('dd/MM/yyyy').format(_dateRange!.start)} - ${DateFormat('dd/MM/yyyy').format(_dateRange!.end)}',
|
|
style: TextStyle(
|
|
fontSize: isMobile ? 10 : 12,
|
|
color: Colors.orange.shade700,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
const SizedBox(width: 4),
|
|
GestureDetector(
|
|
onTap: () {
|
|
setState(() {
|
|
_dateRange = null;
|
|
});
|
|
_filterCommandes();
|
|
},
|
|
child: Icon(Icons.close,
|
|
size: 16, color: Colors.orange.shade700),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
// Compteur de résultats
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade50,
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Text(
|
|
'${_filteredCommandes.length} commande(s)',
|
|
style: TextStyle(
|
|
color: Colors.blue.shade700,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: isMobile ? 12 : 14,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showCommandeDetails(Commande commande) async {
|
|
final details = await _appDatabase.getDetailsCommande(commande.id!);
|
|
final client = await _appDatabase.getClientById(commande.clientId);
|
|
|
|
Get.bottomSheet(
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
height:
|
|
MediaQuery.of(context).size.height * 0.85, // Plus grand sur mobile
|
|
decoration: const BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade100,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child:
|
|
Icon(Icons.receipt_long, color: Colors.blue.shade700),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
'Commande #${commande.id}',
|
|
style: const TextStyle(
|
|
fontSize: 18, // Taille réduite pour mobile
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.close),
|
|
onPressed: () => Get.back(),
|
|
),
|
|
],
|
|
),
|
|
const Divider(),
|
|
|
|
// Informations de la commande - version compacte pour mobile
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade50,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildDetailRow('Client', '${client?.nom} ${client?.prenom}',
|
|
Icons.person),
|
|
_buildDetailRow(
|
|
'Date',
|
|
DateFormat('dd/MM/yyyy à HH:mm')
|
|
.format(commande.dateCommande),
|
|
Icons.calendar_today),
|
|
_buildDetailRow(
|
|
'PV', '${commande.pointDeVenteDesign} ', Icons.person),
|
|
Row(
|
|
children: [
|
|
Icon(Icons.assignment,
|
|
size: 16, color: Colors.grey.shade600),
|
|
const SizedBox(width: 8),
|
|
const Text('Statut: ',
|
|
style: TextStyle(fontWeight: FontWeight.w500)),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color:
|
|
_getStatutColor(commande.statut).withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Text(
|
|
_getStatutText(commande.statut),
|
|
style: TextStyle(
|
|
color: _getStatutColor(commande.statut),
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Total:',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.grey.shade700,
|
|
),
|
|
),
|
|
Text(
|
|
'${NumberFormat('#,##0.00', 'fr_FR').format(commande.montantTotal)} MGA',
|
|
style: const TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.green,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 12),
|
|
const Text(
|
|
'Articles:',
|
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
|
),
|
|
|
|
Expanded(
|
|
child: details.isEmpty
|
|
? const Center(
|
|
child: Text('Aucun détail disponible'),
|
|
)
|
|
: ListView.builder(
|
|
itemCount: details.length,
|
|
itemBuilder: (context, index) {
|
|
final detail = details[index];
|
|
return Card(
|
|
margin: const EdgeInsets.symmetric(vertical: 4),
|
|
elevation: 1,
|
|
child: ListTile(
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 12,
|
|
vertical: 8,
|
|
),
|
|
leading: Container(
|
|
width: 40,
|
|
height: 40,
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade50,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: const Icon(Icons.shopping_bag, size: 20),
|
|
),
|
|
title: Text(
|
|
detail.produitNom ?? 'Produit inconnu',
|
|
style:
|
|
const TextStyle(fontWeight: FontWeight.w500),
|
|
),
|
|
subtitle: Text(
|
|
'${detail.quantite} x ${NumberFormat('#,##0.00', 'fr_FR').format(detail.prixUnitaire)} MGA',
|
|
),
|
|
trailing: Text(
|
|
'${NumberFormat('#,##0.00', 'fr_FR').format(detail.sousTotal)} MGA',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.blue.shade800,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
|
|
if (commande.statut == StatutCommande.enAttente)
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blue.shade800,
|
|
foregroundColor: Colors.white,
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 14), // Plus compact
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
onPressed: () => _updateStatutCommande(commande.id!),
|
|
child: const Text('Marquer comme confirmé'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
isScrollControlled: true,
|
|
);
|
|
}
|
|
|
|
Widget _buildDetailRow(String label, String value, IconData icon) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
child: Row(
|
|
children: [
|
|
Icon(icon, size: 16, color: Colors.grey.shade600),
|
|
const SizedBox(width: 8),
|
|
Text('$label: ', style: const TextStyle(fontWeight: FontWeight.w500)),
|
|
Expanded(
|
|
child: Text(
|
|
value,
|
|
overflow: TextOverflow.ellipsis,
|
|
maxLines: 1,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
String _getStatutText(StatutCommande statut) {
|
|
switch (statut) {
|
|
case StatutCommande.enAttente:
|
|
return 'En attente';
|
|
case StatutCommande.confirmee:
|
|
return 'Confirmée';
|
|
case StatutCommande.annulee:
|
|
return 'Annulée';
|
|
default:
|
|
return 'Inconnu';
|
|
}
|
|
}
|
|
|
|
Color _getStatutColor(StatutCommande statut) {
|
|
switch (statut) {
|
|
case StatutCommande.enAttente:
|
|
return Colors.orange;
|
|
case StatutCommande.confirmee:
|
|
return Colors.green;
|
|
case StatutCommande.annulee:
|
|
return Colors.red;
|
|
default:
|
|
return Colors.grey;
|
|
}
|
|
}
|
|
|
|
Future<void> _updateStatutCommande(int commandeId) async {
|
|
try {
|
|
await _appDatabase.updateStatutCommande(
|
|
commandeId, StatutCommande.confirmee);
|
|
Get.back(); // Ferme le bottom sheet
|
|
_loadCommandes();
|
|
Get.snackbar(
|
|
'Succès',
|
|
'Statut de la commande mis à jour',
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.green,
|
|
colorText: Colors.white,
|
|
);
|
|
} catch (e) {
|
|
Get.snackbar(
|
|
'Erreur',
|
|
'Impossible de mettre à jour le statut: ${e.toString()}',
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.red,
|
|
colorText: Colors.white,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Widget pour l'état vide
|
|
Widget _buildEmptyState() {
|
|
return Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(32.0),
|
|
child: Column(
|
|
children: [
|
|
Icon(
|
|
Icons.receipt_long_outlined,
|
|
size: 64,
|
|
color: Colors.grey.shade400,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'Aucune commande trouvée',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Modifiez vos critères de recherche',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: Colors.grey.shade500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Widget pour l'item de commande (adapté pour mobile)
|
|
Widget _buildCommandeListItem(Commande commande) {
|
|
final isMobile = MediaQuery.of(context).size.width < 600;
|
|
// print(commande.commandeurId);
|
|
print(_userController.pointDeVenteId);
|
|
|
|
return Card(
|
|
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(8),
|
|
onTap: () => _showCommandeDetails(commande),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(12.0),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: isMobile ? 40 : 50,
|
|
height: isMobile ? 40 : 50,
|
|
decoration: BoxDecoration(
|
|
color: _getStatutColor(commande.statut).withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Icon(
|
|
Icons.shopping_cart,
|
|
size: isMobile ? 20 : 24,
|
|
color: _getStatutColor(commande.statut),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Commande #${commande.id}',
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'${commande.clientNom} ${commande.clientPrenom}',
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
Text(
|
|
'PV: ${commande.pointDeVenteDesign}',
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
Text(
|
|
DateFormat('dd/MM/yyyy').format(commande.dateCommande),
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Text(
|
|
'${NumberFormat('#,##0.00', 'fr_FR').format(commande.montantTotal)} MGA',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.green.shade700,
|
|
fontSize: isMobile ? 14 : 16,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Container(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: _getStatutColor(commande.statut).withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Text(
|
|
_getStatutText(commande.statut),
|
|
style: TextStyle(
|
|
color: _getStatutColor(commande.statut),
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: isMobile ? 10 : 12,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isMobile = MediaQuery.of(context).size.width < 600;
|
|
|
|
return Scaffold(
|
|
appBar: CustomAppBar(
|
|
title: 'Historique',
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.refresh),
|
|
onPressed: _loadCommandes,
|
|
),
|
|
],
|
|
),
|
|
drawer: CustomDrawer(),
|
|
body: Column(
|
|
children: [
|
|
// Section des filtres - toujours visible mais plus compacte sur mobile
|
|
if (!isMobile) _buildFilterSection(),
|
|
|
|
// Sur mobile, on ajoute un bouton pour afficher les filtres dans un modal
|
|
if (isMobile) ...[
|
|
Padding(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
|
child: ElevatedButton.icon(
|
|
icon: const Icon(Icons.filter_alt),
|
|
label: const Text('Filtres'),
|
|
onPressed: () {
|
|
showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
builder: (context) => SingleChildScrollView(
|
|
padding: EdgeInsets.only(
|
|
bottom: MediaQuery.of(context).viewInsets.bottom),
|
|
child: _buildFilterSection(),
|
|
),
|
|
);
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blue.shade700,
|
|
foregroundColor: Colors.white,
|
|
minimumSize: const Size(double.infinity, 48),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
// Compteur de résultats visible en haut sur mobile
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
child: Container(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade50,
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Text(
|
|
'${_filteredCommandes.length} commande(s)',
|
|
style: TextStyle(
|
|
color: Colors.blue.shade700,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
|
|
// Liste des commandes
|
|
Expanded(
|
|
child: _isLoading
|
|
? const Center(
|
|
child: CircularProgressIndicator(),
|
|
)
|
|
: _filteredCommandes.isEmpty
|
|
? _buildEmptyState()
|
|
: ListView.builder(
|
|
padding: const EdgeInsets.all(16.0),
|
|
itemCount: _filteredCommandes.length,
|
|
itemBuilder: (context, index) {
|
|
final commande = _filteredCommandes[index];
|
|
return _buildCommandeListItem(commande);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_searchController.dispose();
|
|
_searchClientController.dispose();
|
|
_searchCommandeIdController.dispose();
|
|
super.dispose();
|
|
}
|
|
}
|
|
|