Browse Source

push 29072025_01

28062025_02
andrymodeste 4 months ago
parent
commit
ab97716dd2
  1. 1626
      lib/Services/stock_managementDatabase.dart
  2. 401
      lib/Views/Dashboard.dart
  3. 3
      lib/config/DatabaseConfig.dart

1626
lib/Services/stock_managementDatabase.dart

File diff suppressed because it is too large

401
lib/Views/Dashboard.dart

@ -1100,7 +1100,6 @@ Widget _buildMiniStatistics() {
//widget vente //widget vente
// 2. Ajoutez cette méthode dans la classe _DashboardPageState // 2. Ajoutez cette méthode dans la classe _DashboardPageState
Widget _buildVentesParPointDeVenteCard() { Widget _buildVentesParPointDeVenteCard() {
return Card( return Card(
elevation: 4, elevation: 4,
@ -1123,13 +1122,91 @@ Widget _buildVentesParPointDeVenteCard() {
fontWeight: FontWeight.bold, 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), SizedBox(height: 16),
Container( Container(
height: 400, height: 400,
child: FutureBuilder<List<Map<String, dynamic>>>( child: FutureBuilder<List<Map<String, dynamic>>>(
future: _database.getVentesParPointDeVente(), future: _database.getVentesParPointDeVente(
dateDebut: _dateRange?.start,
dateFin: _dateRange?.end,
aujourdHuiSeulement: _showOnlyToday,
),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator()); return Center(child: CircularProgressIndicator());
@ -1142,7 +1219,11 @@ Widget _buildVentesParPointDeVenteCard() {
children: [ children: [
Icon(Icons.store_mall_directory_outlined, size: 64, color: Colors.grey), Icon(Icons.store_mall_directory_outlined, size: 64, color: Colors.grey),
SizedBox(height: 16), 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() { void _toggleTodayFilter() {
setState(() { setState(() {
_showOnlyToday = !_showOnlyToday; _showOnlyToday = !_showOnlyToday;
}); if (_showOnlyToday) {
} _dateRange = null; // Reset date range when showing only today
}
});
}
void _showPointVenteDetails(Map<String, dynamic> pointVenteData) async { void _showPointVenteDetails(Map<String, dynamic> pointVenteData) async {
final isMobile = MediaQuery.of(context).size.width < 600; final isMobile = MediaQuery.of(context).size.width < 600;
final pointVenteId = pointVenteData['point_vente_id'] as int; final pointVenteId = pointVenteData['point_vente_id'] as int;
final pointVenteNom = pointVenteData['point_vente_nom'] as String; final pointVenteNom = pointVenteData['point_vente_nom'] as String;
showDialog(
context: context, showDialog(
builder: (context) => AlertDialog( context: context,
title: Text('Détails - $pointVenteNom'), builder: (context) => StatefulBuilder(
content: Container( builder: (context, setDialogState) => AlertDialog(
width: double.maxFinite, title: Text('Détails - $pointVenteNom'),
height: 500, content: Container(
child: SingleChildScrollView( width: double.maxFinite,
child: Column( height: 500,
crossAxisAlignment: CrossAxisAlignment.start, child: SingleChildScrollView(
children: [ child: Column(
Wrap( crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
runSpacing: 8,
children: [ children: [
ElevatedButton.icon( Wrap(
onPressed: _toggleTodayFilter, spacing: 8,
icon: Icon( runSpacing: 8,
_showOnlyToday ? Icons.today : Icons.calendar_today, children: [
size: 20, ElevatedButton.icon(
), onPressed: () {
label: Text(_showOnlyToday setDialogState(() {
? isMobile _showOnlyToday = !_showOnlyToday;
? 'Toutes dates' if (_showOnlyToday) _dateRange = null;
: 'Toutes les dates' });
: isMobile },
? 'Aujourd\'hui' icon: Icon(
: 'Aujourd\'hui seulement'), _showOnlyToday ? Icons.today : Icons.calendar_today,
style: ElevatedButton.styleFrom( size: 20,
backgroundColor: _showOnlyToday ),
? Colors.green.shade600 label: Text(_showOnlyToday
: Colors.blue.shade600, ? isMobile
foregroundColor: Colors.white, ? 'Toutes dates'
padding: EdgeInsets.symmetric( : 'Toutes les dates'
horizontal: isMobile ? 12 : 16, : isMobile
vertical: 8, ? '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 {
ElevatedButton.icon( final DateTimeRange? picked = await showDateRangePicker(
onPressed: () => _selectDateRange(context), context: context,
icon: const Icon(Icons.date_range, size: 20), firstDate: DateTime(2020),
label: Text(_dateRange != null lastDate: DateTime.now().add(const Duration(days: 365)),
? isMobile initialDateRange: _dateRange ??
? 'Période' DateTimeRange(
: 'Période sélectionnée' start: DateTime.now().subtract(const Duration(days: 30)),
: isMobile end: DateTime.now(),
? 'Période' ),
: 'Choisir période'), );
style: ElevatedButton.styleFrom(
backgroundColor: _dateRange != null if (picked != null) {
? Colors.orange.shade600 setDialogState(() {
: Colors.grey.shade600, _dateRange = picked;
foregroundColor: Colors.white, _showOnlyToday = false;
padding: EdgeInsets.symmetric( });
horizontal: isMobile ? 12 : 16, }
vertical: 8, },
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,
),
),
), ),
), ],
), ),
],
),
// 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), SizedBox(height: 16),
// Top produits // Statistiques générales avec FutureBuilder pour refresh automatique
FutureBuilder<List<Map<String, dynamic>>>( FutureBuilder<Map<String, dynamic>>(
future: _database.getTopProduitsParPointDeVente(pointVenteId), future: _getDetailedPointVenteStats(pointVenteId),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator()); return Center(child: CircularProgressIndicator());
} }
if (snapshot.hasError || !snapshot.hasData || snapshot.data!.isEmpty) { if (snapshot.hasError || !snapshot.hasData) {
return Text('Aucun produit vendu', style: TextStyle(color: Colors.grey)); return Text('Erreur de chargement des statistiques');
} }
final produits = snapshot.data!; final stats = snapshot.data!;
return Column( return Column(
children: produits.map((produit) => Padding(
padding: EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( _buildStatRow('Chiffre d\'affaires:', '${NumberFormat('#,##0.00', 'fr_FR').format((stats['chiffre_affaires'] as num?)?.toDouble() ?? 0.0)} MGA'),
child: Text( _buildStatRow('Nombre de commandes:', '${stats['nombre_commandes'] ?? 0}'),
produit['produit_nom'] ?? 'N/A', _buildStatRow('Articles vendus:', '${stats['nombre_articles_vendus'] ?? 0}'),
style: TextStyle(fontSize: 12), _buildStatRow('Quantité totale:', '${stats['quantite_totale_vendue'] ?? 0}'),
overflow: TextOverflow.ellipsis, _buildStatRow('Panier moyen:', '${NumberFormat('#,##0.00', 'fr_FR').format((stats['panier_moyen'] as num?)?.toDouble() ?? 0.0)} MGA'),
),
),
Text(
'${produit['quantite_vendue'] ?? 0} vendus',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
], ],
), );
)).toList(), },
); ),
},
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(),
);
},
),
],
), ),
], ),
), ),
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) { Widget _buildStatRow(String label, String value) {

3
lib/config/DatabaseConfig.dart

@ -11,7 +11,8 @@ class DatabaseConfig {
static const String localDatabase = 'guycom'; static const String localDatabase = 'guycom';
// Production (public) MySQL settings // 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 prodUsername = 'guycom';
static const String prodPassword = '3iV59wjRdbuXAPR'; static const String prodPassword = '3iV59wjRdbuXAPR';
static const String prodDatabase = 'guycom'; static const String prodDatabase = 'guycom';

Loading…
Cancel
Save