push 29072025_01
This commit is contained in:
parent
c5ef3ca0cb
commit
ab97716dd2
File diff suppressed because it is too large
Load Diff
@ -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,140 +1516,222 @@ String _formatCurrency(double value) {
|
||||
}
|
||||
}
|
||||
|
||||
void _toggleTodayFilter() {
|
||||
setState(() {
|
||||
_showOnlyToday = !_showOnlyToday;
|
||||
});
|
||||
}
|
||||
|
||||
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(
|
||||
title: Text('Détails - $pointVenteNom'),
|
||||
content: Container(
|
||||
width: double.maxFinite,
|
||||
height: 500,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StatefulBuilder(
|
||||
builder: (context, setDialogState) => AlertDialog(
|
||||
title: Text('Détails - $pointVenteNom'),
|
||||
content: Container(
|
||||
width: double.maxFinite,
|
||||
height: 500,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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,
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
setDialogState(() {
|
||||
_showOnlyToday = !_showOnlyToday;
|
||||
if (_showOnlyToday) _dateRange = null;
|
||||
});
|
||||
},
|
||||
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: () 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
|
||||
? '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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
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,
|
||||
),
|
||||
|
||||
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),
|
||||
|
||||
// Top produits avec filtre
|
||||
FutureBuilder<List<Map<String, dynamic>>>(
|
||||
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${_showOnlyToday ? ' aujourd\'hui' : _dateRange != null ? ' pour cette période' : ''}', style: TextStyle(color: Colors.grey));
|
||||
}
|
||||
|
||||
final produits = snapshot.data!;
|
||||
return Column(
|
||||
children: produits.map((produit) => Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
produit['produit_nom'] ?? 'N/A',
|
||||
style: TextStyle(fontSize: 12),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${produit['quantite_vendue'] ?? 0} vendus',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
)).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
// 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),
|
||||
Text('Top 5 des produits:', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 8),
|
||||
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
// ✅ Top produits
|
||||
FutureBuilder<List<Map<String, dynamic>>>(
|
||||
future: _database.getTopProduitsParPointDeVente(pointVenteId),
|
||||
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));
|
||||
}
|
||||
|
||||
final produits = snapshot.data!;
|
||||
return Column(
|
||||
children: produits.map((produit) => Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
produit['produit_nom'] ?? 'N/A',
|
||||
style: TextStyle(fontSize: 12),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${produit['quantite_vendue'] ?? 0} vendus',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
)).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
if (_showOnlyToday || _dateRange != null)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setDialogState(() {
|
||||
_showOnlyToday = false;
|
||||
_dateRange = null;
|
||||
});
|
||||
},
|
||||
child: Text('Réinitialiser'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Fermer'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
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