push 29072025_01
This commit is contained in:
parent
c5ef3ca0cb
commit
ab97716dd2
@ -45,6 +45,11 @@ class AppDatabase {
|
||||
await insertDefaultPointsDeVente();
|
||||
}
|
||||
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
return DateFormat('yyyy-MM-dd HH:mm:ss').format(date);
|
||||
}
|
||||
|
||||
Future<MySqlConnection> _initDB() async {
|
||||
try {
|
||||
final config = await DatabaseConfig.getSmartConfig();
|
||||
@ -2395,44 +2400,94 @@ Future<double> getValeurTotaleStock() async {
|
||||
return erreurs;
|
||||
}
|
||||
// --- MÉTHODES POUR LES VENTES PAR POINT DE VENTE ---
|
||||
|
||||
Future<List<Map<String, dynamic>>> getVentesParPointDeVente() async {
|
||||
Future<List<Map<String, dynamic>>> getVentesParPointDeVente({
|
||||
DateTime? dateDebut,
|
||||
DateTime? dateFin,
|
||||
bool? aujourdHuiSeulement = false,
|
||||
}) async {
|
||||
final db = await database;
|
||||
|
||||
try {
|
||||
String whereClause = 'WHERE c.statut != 5';
|
||||
List<dynamic> whereArgs = [];
|
||||
|
||||
if (aujourdHuiSeulement == true) {
|
||||
final today = DateTime.now();
|
||||
final startOfDay = DateTime(today.year, today.month, today.day);
|
||||
final endOfDay = DateTime(today.year, today.month, today.day, 23, 59, 59);
|
||||
|
||||
whereClause += ' AND c.dateCommande >= ? AND c.dateCommande <= ?';
|
||||
whereArgs.addAll([
|
||||
_formatDate(startOfDay),
|
||||
_formatDate(endOfDay),
|
||||
]);
|
||||
} else if (dateDebut != null && dateFin != null) {
|
||||
final adjustedEndDate = DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59);
|
||||
whereClause += ' AND c.dateCommande >= ? AND c.dateCommande <= ?';
|
||||
whereArgs.addAll([
|
||||
_formatDate(dateDebut),
|
||||
_formatDate(adjustedEndDate),
|
||||
]);
|
||||
}
|
||||
|
||||
final result = await db.query('''
|
||||
SELECT
|
||||
pv.id as point_vente_id,
|
||||
pv.nom as point_vente_nom,
|
||||
COUNT(DISTINCT c.id) as nombre_commandes,
|
||||
COUNT(dc.id) as nombre_articles_vendus,
|
||||
SUM(dc.quantite) as quantite_totale_vendue,
|
||||
SUM(c.montantTotal) as chiffre_affaires,
|
||||
AVG(c.montantTotal) as panier_moyen,
|
||||
MIN(c.dateCommande) as premiere_vente,
|
||||
MAX(c.dateCommande) as derniere_vente
|
||||
pv.id AS point_vente_id,
|
||||
pv.nom AS point_vente_nom,
|
||||
COUNT(DISTINCT c.id) AS nombre_commandes,
|
||||
COUNT(dc.id) AS nombre_articles_vendus,
|
||||
SUM(dc.quantite) AS quantite_totale_vendue,
|
||||
SUM(c.montantTotal) AS chiffre_affaires,
|
||||
AVG(c.montantTotal) AS panier_moyen,
|
||||
MIN(c.dateCommande) AS premiere_vente,
|
||||
MAX(c.dateCommande) AS derniere_vente
|
||||
FROM points_de_vente pv
|
||||
LEFT JOIN products p ON pv.id = p.point_de_vente_id
|
||||
LEFT JOIN details_commandes dc ON p.id = dc.produitId
|
||||
LEFT JOIN commandes c ON dc.commandeId = c.id
|
||||
WHERE c.statut != 5 -- Exclure les commandes annulées
|
||||
LEFT JOIN users u ON u.point_de_vente_id = pv.id
|
||||
LEFT JOIN commandes c ON c.commandeurId = u.id
|
||||
LEFT JOIN details_commandes dc ON dc.commandeId = c.id
|
||||
$whereClause
|
||||
GROUP BY pv.id, pv.nom
|
||||
ORDER BY chiffre_affaires DESC
|
||||
''');
|
||||
ORDER BY chiffre_affaires DESC;
|
||||
''', whereArgs);
|
||||
|
||||
return result.map((row) => row.fields).toList();
|
||||
} catch (e) {
|
||||
print("Erreur getVentesParPointDeVente: $e");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getTopProduitsParPointDeVente(
|
||||
int pointDeVenteId,
|
||||
{int limit = 5}) async {
|
||||
}
|
||||
Future<List<Map<String, dynamic>>> getTopProduitsParPointDeVente(
|
||||
int pointDeVenteId, {
|
||||
int limit = 5,
|
||||
DateTime? dateDebut,
|
||||
DateTime? dateFin,
|
||||
bool? aujourdHuiSeulement = false,
|
||||
}) async {
|
||||
final db = await database;
|
||||
|
||||
try {
|
||||
String whereClause = 'WHERE p.point_de_vente_id = ? AND c.statut != 5';
|
||||
List<dynamic> whereArgs = [pointDeVenteId];
|
||||
|
||||
if (aujourdHuiSeulement == true) {
|
||||
final today = DateTime.now();
|
||||
final startOfDay = DateTime(today.year, today.month, today.day);
|
||||
final endOfDay = DateTime(today.year, today.month, today.day, 23, 59, 59);
|
||||
|
||||
whereClause += ' AND c.dateCommande >= ? AND c.dateCommande <= ?';
|
||||
whereArgs.addAll([
|
||||
_formatDate(startOfDay),
|
||||
_formatDate(endOfDay),
|
||||
]);
|
||||
} else if (dateDebut != null && dateFin != null) {
|
||||
final adjustedEndDate = DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59);
|
||||
whereClause += ' AND c.dateCommande >= ? AND c.dateCommande <= ?';
|
||||
whereArgs.addAll([
|
||||
_formatDate(dateDebut),
|
||||
_formatDate(adjustedEndDate),
|
||||
]);
|
||||
}
|
||||
|
||||
final result = await db.query('''
|
||||
SELECT
|
||||
p.id,
|
||||
@ -2445,18 +2500,18 @@ Future<double> getValeurTotaleStock() async {
|
||||
FROM products p
|
||||
INNER JOIN details_commandes dc ON p.id = dc.produitId
|
||||
INNER JOIN commandes c ON dc.commandeId = c.id
|
||||
WHERE p.point_de_vente_id = ? AND c.statut != 5
|
||||
$whereClause
|
||||
GROUP BY p.id, p.name, p.price, p.category
|
||||
ORDER BY quantite_vendue DESC
|
||||
LIMIT ?
|
||||
''', [pointDeVenteId, limit]);
|
||||
''', [...whereArgs, limit]);
|
||||
|
||||
return result.map((row) => row.fields).toList();
|
||||
} catch (e) {
|
||||
print("Erreur getTopProduitsParPointDeVente: $e");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getVentesParPointDeVenteParMois(
|
||||
int pointDeVenteId) async {
|
||||
@ -2487,7 +2542,7 @@ Future<double> getValeurTotaleStock() async {
|
||||
}
|
||||
}
|
||||
|
||||
// Dans la classe AppDatabase, ajoutez cette méthode :
|
||||
// Dans la classe AppDatabase, ajoutez cette méthode :
|
||||
Future<bool> verifyCurrentUserPassword(String password) async {
|
||||
final db = await database;
|
||||
final userController = Get.find<UserController>();
|
||||
@ -2506,7 +2561,7 @@ Future<double> getValeurTotaleStock() async {
|
||||
}
|
||||
}
|
||||
|
||||
// Dans AppDatabase
|
||||
// Dans AppDatabase
|
||||
Future<int> createDemandeTransfert({
|
||||
required int produitId,
|
||||
required int pointDeVenteSourceId,
|
||||
@ -2689,9 +2744,9 @@ Future<double> getValeurTotaleStock() async {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
// Ajoutez ces méthodes dans votre classe AppDatabase
|
||||
// Ajoutez ces méthodes dans votre classe AppDatabase
|
||||
|
||||
// 1. Méthode pour récupérer les demandes de transfert validées
|
||||
// 1. Méthode pour récupérer les demandes de transfert validées
|
||||
Future<List<Map<String, dynamic>>> getDemandesTransfertValidees() async {
|
||||
final db = await database;
|
||||
try {
|
||||
@ -2722,7 +2777,7 @@ Future<double> getValeurTotaleStock() async {
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Méthode pour récupérer toutes les demandes de transfert
|
||||
// 2. Méthode pour récupérer toutes les demandes de transfert
|
||||
Future<List<Map<String, dynamic>>> getToutesDemandesTransfert() async {
|
||||
final db = await database;
|
||||
try {
|
||||
@ -2758,7 +2813,7 @@ Future<double> getValeurTotaleStock() async {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Méthode pour rejeter une demande de transfert
|
||||
// 3. Méthode pour rejeter une demande de transfert
|
||||
Future<int> rejeterTransfert(
|
||||
int demandeId, int validateurId, String motif) async {
|
||||
final db = await database;
|
||||
@ -2809,7 +2864,7 @@ Future<double> getValeurTotaleStock() async {
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Méthode supplémentaire : récupérer les demandes de transfert refusées
|
||||
// 4. Méthode supplémentaire : récupérer les demandes de transfert refusées
|
||||
Future<List<Map<String, dynamic>>> getDemandesTransfertRefusees() async {
|
||||
final db = await database;
|
||||
try {
|
||||
@ -2840,7 +2895,7 @@ Future<double> getValeurTotaleStock() async {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Méthode pour récupérer les demandes par statut spécifique
|
||||
// 5. Méthode pour récupérer les demandes par statut spécifique
|
||||
Future<List<Map<String, dynamic>>> getDemandesTransfertParStatut(
|
||||
String statut) async {
|
||||
final db = await database;
|
||||
@ -2876,7 +2931,7 @@ Future<double> getValeurTotaleStock() async {
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Méthode pour récupérer les statistiques des transferts
|
||||
// 6. Méthode pour récupérer les statistiques des transferts
|
||||
Future<Map<String, dynamic>> getStatistiquesTransferts() async {
|
||||
final db = await database;
|
||||
|
||||
@ -2944,7 +2999,7 @@ Future<double> getValeurTotaleStock() async {
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Méthode pour récupérer l'historique des transferts d'un produit
|
||||
// 7. Méthode pour récupérer l'historique des transferts d'un produit
|
||||
Future<List<Map<String, dynamic>>> getHistoriqueTransfertsProduit(
|
||||
int produitId) async {
|
||||
final db = await database;
|
||||
@ -2971,7 +3026,7 @@ Future<double> getValeurTotaleStock() async {
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Méthode pour annuler une demande de transfert (si en attente)
|
||||
// 8. Méthode pour annuler une demande de transfert (si en attente)
|
||||
Future<int> annulerDemandeTransfert(int demandeId, int utilisateurId) async {
|
||||
final db = await database;
|
||||
|
||||
@ -2998,7 +3053,7 @@ Future<double> getValeurTotaleStock() async {
|
||||
}
|
||||
}
|
||||
|
||||
// --- MÉTHODES POUR SORTIES STOCK PERSONNELLES ---
|
||||
// --- MÉTHODES POUR SORTIES STOCK PERSONNELLES ---
|
||||
|
||||
Future<int> createSortieStockPersonnelle({
|
||||
required int produitId,
|
||||
@ -3157,12 +3212,12 @@ Future<double> getValeurTotaleStock() async {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Future<List<Map<String, dynamic>>> getHistoriqueSortiesPersonnelles({
|
||||
Future<List<Map<String, dynamic>>> getHistoriqueSortiesPersonnelles({
|
||||
int? adminId,
|
||||
String? statut,
|
||||
int? pointDeVenteId,
|
||||
int limit = 50,
|
||||
}) async {
|
||||
}) async {
|
||||
final db = await database;
|
||||
|
||||
try {
|
||||
@ -3210,7 +3265,7 @@ Future<List<Map<String, dynamic>>> getHistoriqueSortiesPersonnelles({
|
||||
print('Erreur récupération historique sorties: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getStatistiquesSortiesPersonnelles() async {
|
||||
final db = await database;
|
||||
@ -3269,4 +3324,7 @@ Future<List<Map<String, dynamic>>> getHistoriqueSortiesPersonnelles({
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _formatDate {
|
||||
}
|
||||
|
||||
@ -1100,7 +1100,6 @@ Widget _buildMiniStatistics() {
|
||||
|
||||
//widget vente
|
||||
// 2. Ajoutez cette méthode dans la classe _DashboardPageState
|
||||
|
||||
Widget _buildVentesParPointDeVenteCard() {
|
||||
return Card(
|
||||
elevation: 4,
|
||||
@ -1123,13 +1122,91 @@ Widget _buildVentesParPointDeVenteCard() {
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
// Boutons de filtre dans le header
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: _toggleTodayFilter,
|
||||
icon: Icon(
|
||||
_showOnlyToday ? Icons.today : Icons.calendar_today,
|
||||
color: _showOnlyToday ? Colors.green : Colors.grey,
|
||||
),
|
||||
tooltip: _showOnlyToday ? 'Toutes les dates' : 'Aujourd\'hui seulement',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => _selectDateRange(context),
|
||||
icon: Icon(
|
||||
Icons.date_range,
|
||||
color: _dateRange != null ? Colors.orange : Colors.grey,
|
||||
),
|
||||
tooltip: 'Sélectionner une période',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
// Affichage de la période sélectionnée
|
||||
if (_showOnlyToday || _dateRange != null)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 8),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: (_showOnlyToday ? Colors.green : Colors.orange).withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: (_showOnlyToday ? Colors.green : Colors.orange).withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
_showOnlyToday ? Icons.today : Icons.date_range,
|
||||
size: 16,
|
||||
color: _showOnlyToday ? Colors.green : Colors.orange,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
_showOnlyToday
|
||||
? 'Aujourd\'hui'
|
||||
: _dateRange != null
|
||||
? '${DateFormat('dd/MM/yyyy').format(_dateRange!.start)} - ${DateFormat('dd/MM/yyyy').format(_dateRange!.end)}'
|
||||
: 'Toutes les dates',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: _showOnlyToday ? Colors.green : Colors.orange,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_showOnlyToday = false;
|
||||
_dateRange = null;
|
||||
});
|
||||
},
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
size: 16,
|
||||
color: _showOnlyToday ? Colors.green : Colors.orange,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Container(
|
||||
height: 400,
|
||||
child: FutureBuilder<List<Map<String, dynamic>>>(
|
||||
future: _database.getVentesParPointDeVente(),
|
||||
future: _database.getVentesParPointDeVente(
|
||||
dateDebut: _dateRange?.start,
|
||||
dateFin: _dateRange?.end,
|
||||
aujourdHuiSeulement: _showOnlyToday,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
@ -1142,7 +1219,11 @@ Widget _buildVentesParPointDeVenteCard() {
|
||||
children: [
|
||||
Icon(Icons.store_mall_directory_outlined, size: 64, color: Colors.grey),
|
||||
SizedBox(height: 16),
|
||||
Text('Aucune donnée de vente par point de vente', style: TextStyle(color: Colors.grey)),
|
||||
Text(
|
||||
'Aucune donnée de vente${_showOnlyToday ? ' pour aujourd\'hui' : _dateRange != null ? ' pour cette période' : ''}',
|
||||
style: TextStyle(color: Colors.grey),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -1435,19 +1516,23 @@ String _formatCurrency(double value) {
|
||||
}
|
||||
}
|
||||
|
||||
void _toggleTodayFilter() {
|
||||
void _toggleTodayFilter() {
|
||||
setState(() {
|
||||
_showOnlyToday = !_showOnlyToday;
|
||||
});
|
||||
if (_showOnlyToday) {
|
||||
_dateRange = null; // Reset date range when showing only today
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
void _showPointVenteDetails(Map<String, dynamic> pointVenteData) async {
|
||||
final isMobile = MediaQuery.of(context).size.width < 600;
|
||||
final pointVenteId = pointVenteData['point_vente_id'] as int;
|
||||
final pointVenteNom = pointVenteData['point_vente_nom'] as String;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
builder: (context) => StatefulBuilder(
|
||||
builder: (context, setDialogState) => AlertDialog(
|
||||
title: Text('Détails - $pointVenteNom'),
|
||||
content: Container(
|
||||
width: double.maxFinite,
|
||||
@ -1461,7 +1546,12 @@ void _showPointVenteDetails(Map<String, dynamic> pointVenteData) async {
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: _toggleTodayFilter,
|
||||
onPressed: () {
|
||||
setDialogState(() {
|
||||
_showOnlyToday = !_showOnlyToday;
|
||||
if (_showOnlyToday) _dateRange = null;
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
_showOnlyToday ? Icons.today : Icons.calendar_today,
|
||||
size: 20,
|
||||
@ -1485,7 +1575,25 @@ void _showPointVenteDetails(Map<String, dynamic> pointVenteData) async {
|
||||
),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _selectDateRange(context),
|
||||
onPressed: () 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) {
|
||||
setDialogState(() {
|
||||
_dateRange = picked;
|
||||
_showOnlyToday = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.date_range, size: 20),
|
||||
label: Text(_dateRange != null
|
||||
? isMobile
|
||||
@ -1507,30 +1615,53 @@ void _showPointVenteDetails(Map<String, dynamic> pointVenteData) async {
|
||||
),
|
||||
],
|
||||
),
|
||||
// Statistiques générales
|
||||
_buildStatRow('Chiffre d\'affaires:', '${NumberFormat('#,##0.00', 'fr_FR').format((pointVenteData['chiffre_affaires'] as num?)?.toDouble() ?? 0.0)} MGA'),
|
||||
_buildStatRow('Nombre de commandes:', '${pointVenteData['nombre_commandes'] ?? 0}'),
|
||||
_buildStatRow('Articles vendus:', '${pointVenteData['nombre_articles_vendus'] ?? 0}'),
|
||||
_buildStatRow('Quantité totale:', '${pointVenteData['quantite_totale_vendue'] ?? 0}'),
|
||||
_buildStatRow('Panier moyen:', '${NumberFormat('#,##0.00', 'fr_FR').format((pointVenteData['panier_moyen'] as num?)?.toDouble() ?? 0.0)} MGA'),
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
// Statistiques générales avec FutureBuilder pour refresh automatique
|
||||
FutureBuilder<Map<String, dynamic>>(
|
||||
future: _getDetailedPointVenteStats(pointVenteId),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (snapshot.hasError || !snapshot.hasData) {
|
||||
return Text('Erreur de chargement des statistiques');
|
||||
}
|
||||
|
||||
final stats = snapshot.data!;
|
||||
return Column(
|
||||
children: [
|
||||
_buildStatRow('Chiffre d\'affaires:', '${NumberFormat('#,##0.00', 'fr_FR').format((stats['chiffre_affaires'] as num?)?.toDouble() ?? 0.0)} MGA'),
|
||||
_buildStatRow('Nombre de commandes:', '${stats['nombre_commandes'] ?? 0}'),
|
||||
_buildStatRow('Articles vendus:', '${stats['nombre_articles_vendus'] ?? 0}'),
|
||||
_buildStatRow('Quantité totale:', '${stats['quantite_totale_vendue'] ?? 0}'),
|
||||
_buildStatRow('Panier moyen:', '${NumberFormat('#,##0.00', 'fr_FR').format((stats['panier_moyen'] as num?)?.toDouble() ?? 0.0)} MGA'),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
SizedBox(height: 16),
|
||||
Text('Top 5 des produits:', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 8),
|
||||
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
// ✅ Top produits
|
||||
// Top produits avec filtre
|
||||
FutureBuilder<List<Map<String, dynamic>>>(
|
||||
future: _database.getTopProduitsParPointDeVente(pointVenteId),
|
||||
future: _database.getTopProduitsParPointDeVente(
|
||||
pointVenteId,
|
||||
dateDebut: _dateRange?.start,
|
||||
dateFin: _dateRange?.end,
|
||||
aujourdHuiSeulement: _showOnlyToday,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (snapshot.hasError || !snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return Text('Aucun produit vendu', style: TextStyle(color: Colors.grey));
|
||||
return Text('Aucun produit vendu${_showOnlyToday ? ' aujourd\'hui' : _dateRange != null ? ' pour cette période' : ''}', style: TextStyle(color: Colors.grey));
|
||||
}
|
||||
|
||||
final produits = snapshot.data!;
|
||||
@ -1562,13 +1693,45 @@ void _showPointVenteDetails(Map<String, dynamic> pointVenteData) async {
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
if (_showOnlyToday || _dateRange != null)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setDialogState(() {
|
||||
_showOnlyToday = false;
|
||||
_dateRange = null;
|
||||
});
|
||||
},
|
||||
child: Text('Réinitialiser'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Fermer'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _getDetailedPointVenteStats(int pointVenteId) async {
|
||||
final ventesData = await _database.getVentesParPointDeVente(
|
||||
dateDebut: _dateRange?.start,
|
||||
dateFin: _dateRange?.end,
|
||||
aujourdHuiSeulement: _showOnlyToday,
|
||||
);
|
||||
|
||||
final pointVenteStats = ventesData.firstWhere(
|
||||
(data) => data['point_vente_id'] == pointVenteId,
|
||||
orElse: () => {
|
||||
'chiffre_affaires': 0.0,
|
||||
'nombre_commandes': 0,
|
||||
'nombre_articles_vendus': 0,
|
||||
'quantite_totale_vendue': 0,
|
||||
'panier_moyen': 0.0,
|
||||
},
|
||||
);
|
||||
|
||||
return pointVenteStats;
|
||||
}
|
||||
|
||||
Widget _buildStatRow(String label, String value) {
|
||||
|
||||
@ -11,7 +11,8 @@ class DatabaseConfig {
|
||||
static const String localDatabase = 'guycom';
|
||||
|
||||
// Production (public) MySQL settings
|
||||
static const String prodHost = '185.70.105.157';
|
||||
// static const String prodHost = '185.70.105.157';
|
||||
static const String prodHost = '102.17.52.31';
|
||||
static const String prodUsername = 'guycom';
|
||||
static const String prodPassword = '3iV59wjRdbuXAPR';
|
||||
static const String prodDatabase = 'guycom';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user