From c8fedd08e506adad75376086c734a7a31a1c075b Mon Sep 17 00:00:00 2001 From: "b.razafimandimbihery" Date: Sat, 31 May 2025 14:24:47 +0300 Subject: [PATCH] last update --- lib/Components/appDrawer.dart | 7 +- lib/Services/stock_managementDatabase.dart | 14 +- lib/Views/Dashboard.dart | 1151 ++++++++++++++++++++ lib/Views/loginPage.dart | 8 +- pubspec.lock | 16 +- pubspec.yaml | 4 +- 6 files changed, 1186 insertions(+), 14 deletions(-) create mode 100644 lib/Views/Dashboard.dart diff --git a/lib/Components/appDrawer.dart b/lib/Components/appDrawer.dart index 158b890..58ed13e 100644 --- a/lib/Components/appDrawer.dart +++ b/lib/Components/appDrawer.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:youmazgestion/Views/Dashboard.dart'; import 'package:youmazgestion/Views/HandleProduct.dart'; import 'package:youmazgestion/Views/RoleListPage.dart'; import 'package:youmazgestion/Views/commandManagement.dart'; @@ -105,7 +106,7 @@ class CustomDrawer extends StatelessWidget { color: Colors.blue, permissionAction: 'view', permissionRoute: '/accueil', - onTap: () => Get.to(const AccueilPage()), + onTap: () => Get.to( DashboardPage()), ), ); @@ -228,11 +229,11 @@ class CustomDrawer extends StatelessWidget { List rapportsItems = [ await _buildDrawerItem( icon: Icons.bar_chart, - title: "Bilan mensuel", + title: "Bilan ", color: Colors.teal, permissionAction: 'read', permissionRoute: '/bilan', - onTap: () => Get.to(const BilanMois()), + onTap: () => Get.to( DashboardPage()), ), await _buildDrawerItem( icon: Icons.history, diff --git a/lib/Services/stock_managementDatabase.dart b/lib/Services/stock_managementDatabase.dart index 67e1e54..26fa240 100644 --- a/lib/Services/stock_managementDatabase.dart +++ b/lib/Services/stock_managementDatabase.dart @@ -1126,7 +1126,19 @@ Future deletePointDeVente(int id) async { whereArgs: [id], ); } - +// Dans AppDatabase +Future> getProductCountByCategory() async { + final db = await database; + final result = await db.rawQuery(''' + SELECT category, COUNT(*) as count + FROM products + GROUP BY category + ORDER BY count DESC + '''); + + return Map.fromEntries(result.map((e) => + MapEntry(e['category'] as String, e['count'] as int))); +} Future?> getPointDeVenteById(int id) async { final db = await database; final result = await db.query( diff --git a/lib/Views/Dashboard.dart b/lib/Views/Dashboard.dart new file mode 100644 index 0000000..4b59991 --- /dev/null +++ b/lib/Views/Dashboard.dart @@ -0,0 +1,1151 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:youmazgestion/Components/appDrawer.dart'; +import 'package:youmazgestion/Services/stock_managementDatabase.dart'; +import 'package:youmazgestion/controller/userController.dart'; +import 'package:youmazgestion/Models/users.dart'; +import 'package:youmazgestion/Models/client.dart'; +import 'package:youmazgestion/Models/produit.dart'; // Ajout de l'import manquant + +class DashboardPage extends StatefulWidget { + @override + _DashboardPageState createState() => _DashboardPageState(); +} + +class _DashboardPageState extends State with SingleTickerProviderStateMixin { + final AppDatabase _database = AppDatabase.instance; + final UserController _userController = Get.find(); + final GlobalKey _recentClientsKey = GlobalKey(); +final GlobalKey _recentOrdersKey = GlobalKey(); +final GlobalKey _lowStockKey = GlobalKey(); +final GlobalKey _salesChartKey = GlobalKey(); + late Future> _statsFuture; + late Future> _recentOrdersFuture; + late Future> _lowStockProductsFuture; + late Future> _recentClientsFuture; + late Future> _allOrdersFuture; + late Future> _productsByCategoryFuture; + + late AnimationController _animationController; + late Animation _fadeAnimation; + + @override + void initState() { + super.initState(); + _loadData(); + + _animationController = AnimationController( + vsync: this, + duration: Duration(milliseconds: 800), + ); + _fadeAnimation = Tween(begin: 0, end: 1).animate( + CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + ), + ); + + _animationController.forward(); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + void _loadData() { + _statsFuture = _database.getStatistiques(); + _recentOrdersFuture = _database.getCommandes().then((orders) => orders.take(5).toList()); + _lowStockProductsFuture = _database.getProducts().then((products) { + return products.where((p) => (p.stock ?? 0) < 10).toList(); + }); + _recentClientsFuture = _database.getClients().then((clients) => clients.take(5).toList()); + _allOrdersFuture = _database.getCommandes(); + _productsByCategoryFuture = _database.getProductCountByCategory(); + } +Future _showCategoryProductsDialog(String category) async { + final products = await _database.getProductsByCategory(category); + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('Produits dans $category'), + content: Container( + width: double.maxFinite, + child: ListView.builder( + shrinkWrap: true, + itemCount: products.length, + itemBuilder: (context, index) { + final product = products[index]; + return ListTile( + leading: product.image != null && product.image!.isNotEmpty + ? CircleAvatar(backgroundImage: NetworkImage(product.image!)) + : CircleAvatar(child: Icon(Icons.inventory)), + title: Text(product.name), + subtitle: Text('Stock: ${product.stock}'), + trailing: Text('${product.price.toStringAsFixed(2)} MGA'), + ); + }, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text('Fermer'), + ), + ], + ), + ); +} + List> _groupOrdersByMonth(List orders) { + final Map monthlySales = {}; + + for (final order in orders) { + final monthYear = '${order.dateCommande.year}-${order.dateCommande.month.toString().padLeft(2, '0')}'; + monthlySales.update( + monthYear, + (value) => value + order.montantTotal, + ifAbsent: () => order.montantTotal, + ); + } + + return monthlySales.entries.map((entry) { + return { + 'month': entry.key, + 'total': entry.value, + }; + }).toList(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Image.asset( + 'assets/youmaz2.png', + height: 40, // Ajustez la hauteur selon vos besoins + ), + centerTitle: true, + elevation: 0, + flexibleSpace: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [const Color.fromARGB(255, 15, 83, 160), const Color.fromARGB(255, 79, 165, 239)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + ), + actions: [ + IconButton( + icon: Icon(Icons.refresh, color: Colors.white), + onPressed: () { + _animationController.reset(); + _loadData(); + _animationController.forward(); + }, + ), + ], + ), + drawer: CustomDrawer(), + body: SingleChildScrollView( + padding: EdgeInsets.all(16), + child: FadeTransition( + opacity: _fadeAnimation, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildUserInfo(), + SizedBox(height: 20), + + _buildMiniStatistics(), + SizedBox(height: 20), + + // Graphiques en ligne + Row( + children: [ + Expanded( + child: _buildSalesChart(), + ), + SizedBox(width: 16), + Expanded( + child: _buildStockChart(), + ), + ], + ), + SizedBox(height: 20), + + // Histogramme des catégories de produits + _buildCategoryHistogram(), + SizedBox(height: 20), + + // Section des données récentes + _buildRecentDataSection(), + ], + ), + ), + ), + ); + } + + Widget _buildCategoryHistogram() { + return Card( + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.category, color: Colors.blue), + SizedBox(width: 8), + Text( + 'Produits par Catégorie', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + SizedBox(height: 16), + Container( + height: 200, + child: FutureBuilder>( + future: _productsByCategoryFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } + + if (snapshot.hasError || !snapshot.hasData || snapshot.data!.isEmpty) { + return Center(child: Text('Aucune donnée disponible')); + } + + final data = snapshot.data!; + final categories = data.keys.toList(); + final counts = data.values.toList(); + + return BarChart( + BarChartData( + alignment: BarChartAlignment.spaceAround, + maxY: counts.reduce((a, b) => a > b ? a : b).toDouble() * 1.2, + barTouchData: BarTouchData( + enabled: true, + touchCallback: (FlTouchEvent event, response) { + if (response != null && response.spot != null && event is FlTapUpEvent) { + final category = categories[response.spot!.touchedBarGroupIndex]; + _showCategoryProductsDialog(category); + } + }, + touchTooltipData: BarTouchTooltipData( + tooltipBgColor: Colors.blueGrey, + getTooltipItem: (group, groupIndex, rod, rodIndex) { + final category = categories[groupIndex]; + final count = counts[groupIndex]; + return BarTooltipItem( + '$category\n$count produits', + TextStyle(color: Colors.white), + ); + }, + ), + ), + titlesData: FlTitlesData( + show: true, + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + final index = value.toInt(); + if (index >= 0 && index < categories.length) { + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + categories[index].substring(0, 3).toUpperCase(), + style: TextStyle( + fontSize: 10, + color: Colors.grey, + ), + ), + ); + } + return Text(''); + }, + reservedSize: 40, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + return Text( + value.toInt().toString(), + style: TextStyle( + fontSize: 10, + color: Colors.grey, + ), + ); + }, + reservedSize: 40, + ), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + ), + borderData: FlBorderData( + show: true, + border: Border.all( + color: Colors.grey.withOpacity(0.3), + width: 1, + ), + ), + barGroups: categories.asMap().entries.map((entry) { + final index = entry.key; + return BarChartGroupData( + x: index, + barRods: [ + BarChartRodData( + toY: counts[index].toDouble(), + color: _getCategoryColor(index), + width: 16, + borderRadius: BorderRadius.circular(4), + backDrawRodData: BackgroundBarChartRodData( + show: true, + toY: counts.reduce((a, b) => a > b ? a : b).toDouble() * 1.2, + color: Colors.grey.withOpacity(0.1), + ), + ), + ], + showingTooltipIndicators: [0], + ); + }).toList(), + ), + ); + }, + ), + ), + ], + ), + ), + ); +} + + Color _getCategoryColor(int index) { + final colors = [ + Colors.blue, + Colors.green, + Colors.orange, + Colors.purple, + Colors.teal, + Colors.pink, + Colors.indigo, + ]; + return colors[index % colors.length]; + } + + Widget _buildSalesChart() { + key: _salesChartKey; + return Card( + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.trending_up, color: Colors.blue), + SizedBox(width: 8), + Text( + 'Ventes par mois', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + SizedBox(height: 16), + Container( + height: 200, + child: FutureBuilder>( + future: _allOrdersFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } + + if (snapshot.hasError || !snapshot.hasData || snapshot.data!.isEmpty) { + return Center(child: Text('Aucune donnée disponible')); + } + + final salesData = _groupOrdersByMonth(snapshot.data!); + + return BarChart( + BarChartData( + alignment: BarChartAlignment.spaceAround, + maxY: salesData.map((e) => e['total']).reduce((a, b) => a > b ? a : b) * 1.2, + barTouchData: BarTouchData( + enabled: true, + touchTooltipData: BarTouchTooltipData( + tooltipBgColor: Colors.blueGrey, + getTooltipItem: (group, groupIndex, rod, rodIndex) { + final month = salesData[groupIndex]['month']; + final total = salesData[groupIndex]['total']; + return BarTooltipItem( + '$month\n${total.toStringAsFixed(2)} MGA', + TextStyle(color: Colors.white), + ); + }, + ), + ), + titlesData: FlTitlesData( + show: true, + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + final index = value.toInt(); + if (index >= 0 && index < salesData.length) { + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + salesData[index]['month'].toString().split('-')[1], + style: TextStyle( + fontSize: 10, + color: Colors.grey, + ), + ), + ); + } + return Text(''); + }, + reservedSize: 40, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + return Text( + value.toInt().toString(), + style: TextStyle( + fontSize: 10, + color: Colors.grey, + ), + ); + }, + reservedSize: 40, + ), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + ), + borderData: FlBorderData( + show: true, + border: Border.all( + color: Colors.grey.withOpacity(0.3), + width: 1, + ), + ), + barGroups: salesData.asMap().entries.map((entry) { + final index = entry.key; + final data = entry.value; + return BarChartGroupData( + x: index, + barRods: [ + BarChartRodData( + toY: data['total'], + color: Colors.blue, + width: 16, + borderRadius: BorderRadius.circular(4), + backDrawRodData: BackgroundBarChartRodData( + show: true, + toY: salesData.map((e) => e['total']).reduce((a, b) => a > b ? a : b) * 1.2, + color: Colors.grey.withOpacity(0.1), + ), + ), + ], + showingTooltipIndicators: [0], + ); + }).toList(), + ), + ); + }, + ), + ), + ], + ), + ), + ); + } + + Widget _buildStockChart() { + return Card( + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.inventory, color: Colors.blue), + SizedBox(width: 8), + Text( + 'État du stock', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + SizedBox(height: 16), + Container( + height: 200, + child: FutureBuilder>( + future: _database.getProducts(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } + + if (snapshot.hasError || !snapshot.hasData) { + return Center(child: Text('Aucune donnée disponible')); + } + + final products = snapshot.data!; + final lowStock = products.where((p) => (p.stock ?? 0) < 10).length; + final inStock = products.length - lowStock; + + return PieChart( + PieChartData( + sectionsSpace: 0, + centerSpaceRadius: 40, + sections: [ + PieChartSectionData( + color: Colors.orange, + value: lowStock.toDouble(), + title: '$lowStock', + radius: 20, + titleStyle: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + PieChartSectionData( + color: Colors.green, + value: inStock.toDouble(), + title: '$inStock', + radius: 20, + titleStyle: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ], + pieTouchData: PieTouchData( + touchCallback: (FlTouchEvent event, pieTouchResponse) {}, + ), + startDegreeOffset: 180, + borderData: FlBorderData(show: false), + ), + ); + }, + ), + ), + SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildLegendItem(Colors.orange, 'Stock faible'), + SizedBox(width: 16), + _buildLegendItem(Colors.green, 'En stock'), + ], + ), + ], + ), + ), + ); + } + + Widget _buildLegendItem(Color color, String text) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 12, + height: 12, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: color, + ), + ), + SizedBox(width: 4), + Text( + text, + style: TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ), + ], + ); + } + + Widget _buildUserInfo() { + return FutureBuilder( + future: _database.getUserById(_userController.userId), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } + + if (snapshot.hasError || !snapshot.hasData) { + return Text('Bienvenue'); + } + + final user = snapshot.data!; + return Row( + children: [ + CircleAvatar( + radius: 30, + backgroundColor: Colors.blue.shade100, + child: Icon(Icons.person, size: 30, color: Colors.blue), + ), + SizedBox(width: 16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Bienvenue, ${user.name}', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + SizedBox(height: 4), + Text( + 'Rôle: ${user.roleName ?? 'Utilisateur'}', + style: TextStyle(fontSize: 14, color: Colors.grey.shade600), + ), + ], + ), + ], + ); + }, + ); + } + + Widget _buildMiniStatistics() { + return FutureBuilder>( + future: _statsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } + + if (snapshot.hasError) { + return Text('Erreur de chargement des statistiques'); + } + + final stats = snapshot.data ?? {}; + + return Wrap( + spacing: 12, + runSpacing: 12, + children: [ + _buildMiniStatCard( + title: 'Clients', + value: '${stats['totalClients'] ?? 0}', + icon: Icons.people, + color: Colors.blue, + ), + _buildMiniStatCard( + title: 'Commandes', + value: '${stats['totalCommandes'] ?? 0}', + icon: Icons.shopping_cart, + color: Colors.green, + ), + _buildMiniStatCard( + title: 'Produits', + value: '${stats['totalProduits'] ?? 0}', + icon: Icons.inventory, + color: Colors.orange, + ), + _buildMiniStatCard( + title: 'CA (MGA)', + value: '${(stats['chiffreAffaires'] ?? 0.0).toStringAsFixed(2)}', + icon: Icons.euro_symbol, + color: Colors.purple, + ), + ], + ); + }, + ); + } + + + + Widget _buildMiniStatCard({required String title, required String value, required IconData icon, required Color color}) { + + return InkWell( + onTap: () { + // Animation au clic + _animationController.reset(); + _animationController.forward(); + + // Navigation based on the card type + switch(title) { + case 'Clients': + // Scroll to recent clients section + Scrollable.ensureVisible( + _recentClientsKey.currentContext!, + duration: Duration(milliseconds: 500), + curve: Curves.easeInOut, + ); + break; + case 'Commandes': + // Scroll to recent orders section + Scrollable.ensureVisible( + _recentOrdersKey.currentContext!, + duration: Duration(milliseconds: 500), + curve: Curves.easeInOut, + ); + break; + case 'Produits': + // Scroll to low stock products section + Scrollable.ensureVisible( + _lowStockKey.currentContext!, + duration: Duration(milliseconds: 500), + curve: Curves.easeInOut, + ); + break; + case 'CA (MGA)': + // Scroll to sales chart + Scrollable.ensureVisible( + _salesChartKey.currentContext!, + duration: Duration(milliseconds: 500), + curve: Curves.easeInOut, + ); + break; + } + }, + borderRadius: BorderRadius.circular(12), + child: Container( + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: color.withOpacity(0.3)), + ), + child: Padding( + padding: EdgeInsets.all(12), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, size: 32, color: color), + SizedBox(height: 8), + Text( + value, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: color, + ), + ), + SizedBox(height: 4), + Text( + title, + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + ], + ), + ), + ), + ); +} + Widget _buildRecentDataSection() { + return Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildRecentOrdersCard()), + SizedBox(width: 16), + Expanded(child: _buildRecentClientsCard()), + ], + ), + SizedBox(height: 16), + _buildLowStockCard(), + ], + ); + } + + Widget _buildRecentOrdersCard() { + key: _recentOrdersKey; + return Card( + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.shopping_cart, color: Colors.green), + SizedBox(width: 8), + Text( + 'Commandes récentes', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + SizedBox(height: 8), + FutureBuilder>( + future: _recentOrdersFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } + + if (snapshot.hasError || !snapshot.hasData || snapshot.data!.isEmpty) { + return Padding( + padding: EdgeInsets.all(8), + child: Text('Aucune commande récente'), + ); + } + + final orders = snapshot.data!; + + return Column( + children: orders.map((order) => FutureBuilder>( + future: _database.getDetailsCommande(order.id!), + builder: (context, detailsSnapshot) { + if (detailsSnapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } + + if (detailsSnapshot.hasError || !detailsSnapshot.hasData || detailsSnapshot.data!.isEmpty) { + return Padding( + padding: EdgeInsets.all(8), + child: Text('Aucun détail de commande disponible'), + ); + } + + final details = detailsSnapshot.data!; + + return InkWell( + onTap: () { + _animationController.reset(); + _animationController.forward(); + }, + borderRadius: BorderRadius.circular(8), + child: Container( + margin: EdgeInsets.only(bottom: 8), + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.grey.shade50, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + ListTile( + contentPadding: EdgeInsets.zero, + leading: CircleAvatar( + backgroundColor: _getStatusColor(order.statut).withOpacity(0.2), + child: Icon(Icons.receipt, color: _getStatusColor(order.statut)), + ), + title: Text( + '${order.clientNomComplet}', + style: TextStyle(fontSize: 14), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${order.montantTotal.toStringAsFixed(2)} MGA', + style: TextStyle(fontSize: 12), + ), + Text( + '${order.dateCommande.toString().substring(0, 10)}', + style: TextStyle(fontSize: 10, color: Colors.grey), + ), + ], + ), + trailing: Container( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: _getStatusColor(order.statut).withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + order.statutLibelle, + style: TextStyle( + fontSize: 10, + color: _getStatusColor(order.statut), + ), + ), + ), + ), + // Affichage des produits commandés + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: details.map((detail) => Padding( + padding: EdgeInsets.only(left: 16, top: 4), + child: Text( + 'Produit: ${detail.produitNom}', + style: TextStyle(fontSize: 12), + ), + )).toList(), + ), + ], + ), + ), + ); + }, + )).toList(), + ); + }, + ), + ], + ), + ), + ); +} + + + Widget _buildRecentClientsCard() { + key: _recentClientsKey; + return Card( + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.people, color: Colors.blue), + SizedBox(width: 8), + Text( + 'Clients récents', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + SizedBox(height: 8), + FutureBuilder>( + future: _recentClientsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } + + if (snapshot.hasError || !snapshot.hasData || snapshot.data!.isEmpty) { + return Padding( + padding: EdgeInsets.all(8), + child: Text('Aucun client récent'), + ); + } + + final clients = snapshot.data!; + + return Column( + children: clients.map((client) => InkWell( + onTap: () { + // Animation et action au clic + _animationController.reset(); + _animationController.forward(); + // Vous pouvez ajouter une navigation vers le client ici + }, + borderRadius: BorderRadius.circular(8), + child: Container( + margin: EdgeInsets.only(bottom: 8), + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.grey.shade50, + borderRadius: BorderRadius.circular(8), + ), + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: CircleAvatar( + backgroundColor: Colors.blue.shade100, + child: Icon(Icons.person, color: Colors.blue), + ), + title: Text( + client.nomComplet.split(' ').first, + style: TextStyle(fontSize: 14), + ), + subtitle: Text( + client.email, + style: TextStyle(fontSize: 12), + overflow: TextOverflow.ellipsis, + ), + ), + ), + )).toList(), + ); + }, + ), + ], + ), + ), + ); + } + + Widget _buildLowStockCard() { + key: _lowStockKey; + return Card( + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.warning, color: Colors.orange), + SizedBox(width: 8), + Text( + 'Produits en rupture', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + SizedBox(height: 8), + FutureBuilder>( + future: _lowStockProductsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } + + if (snapshot.hasError || !snapshot.hasData || snapshot.data!.isEmpty) { + return Padding( + padding: EdgeInsets.all(8), + child: Text('Aucun produit en rupture de stock'), + ); + } + + final products = snapshot.data!; + + return Column( + children: products.map((product) => InkWell( + onTap: () { + // Animation et action au clic + _animationController.reset(); + _animationController.forward(); + // Vous pouvez ajouter une navigation vers le produit ici + }, + borderRadius: BorderRadius.circular(8), + child: Container( + margin: EdgeInsets.only(bottom: 8), + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.orange.shade50, + borderRadius: BorderRadius.circular(8), + ), + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: product.image != null && product.image!.isNotEmpty + ? CircleAvatar( + backgroundImage: NetworkImage(product.image!), + radius: 20, + ) + : CircleAvatar( + backgroundColor: Colors.orange.shade100, + child: Icon(Icons.inventory, color: Colors.orange), + ), + title: Text( + product.name, + style: TextStyle(fontSize: 14), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Stock: ${product.stock ?? 0}', + style: TextStyle(fontSize: 12), + ), + Text( + 'Catégorie: ${product.category}', + style: TextStyle(fontSize: 10, color: Colors.grey), + ), + ], + ), + trailing: Text( + '${product.price.toStringAsFixed(2)} MGA', + style: TextStyle(fontSize: 12), + ), + ), + ), + )).toList(), + ); + }, + ), + ], + ), + ), + ); + } + + Color _getStatusColor(StatutCommande status) { + switch (status) { + case StatutCommande.enAttente: + return Colors.orange; + case StatutCommande.confirmee: + return Colors.blue; + case StatutCommande.enPreparation: + return Colors.purple; + case StatutCommande.expediee: + return Colors.teal; + case StatutCommande.livree: + return Colors.green; + case StatutCommande.annulee: + return Colors.red; + default: + return Colors.grey; + } + } +} \ No newline at end of file diff --git a/lib/Views/loginPage.dart b/lib/Views/loginPage.dart index c132b74..9b1b921 100644 --- a/lib/Views/loginPage.dart +++ b/lib/Views/loginPage.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart'; +import 'package:youmazgestion/Views/Dashboard.dart'; import 'package:youmazgestion/Views/mobilepage.dart'; import 'package:youmazgestion/Views/particles.dart' show ParticleBackground; import 'package:youmazgestion/accueil.dart'; @@ -125,11 +126,10 @@ class _LoginPageState extends State { context, MaterialPageRoute(builder: (context) => const MainLayout()), ); - } else { - // Redirection vers AccueilPage pour les autres rôles - Navigator.pushReplacement( + }else{ + Navigator.pushReplacement( context, - MaterialPageRoute(builder: (context) => const AccueilPage()), + MaterialPageRoute(builder: (context) => DashboardPage()), ); } } diff --git a/pubspec.lock b/pubspec.lock index 443bfae..2162a76 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -241,6 +241,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.3+4" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: "5a74434cc83bf64346efb562f1a06eefaf1bcb530dc3d96a104f631a1eff8d79" + url: "https://pub.dev" + source: hosted + version: "0.65.0" flutter: dependency: "direct main" description: flutter @@ -1049,18 +1057,18 @@ packages: dependency: "direct main" description: name: syncfusion_flutter_charts - sha256: bdb7cc5814ceb187793cea587f4a5946afcffd96726b219cee79df8460f44b7b + sha256: "0222ac9d8cb6c671f014effe9bd5c0aef35eadb16471355345ba87cc0ac007b3" url: "https://pub.dev" source: hosted - version: "21.2.4" + version: "20.4.54" syncfusion_flutter_core: dependency: transitive description: name: syncfusion_flutter_core - sha256: "8db8f55c77f56968681447d3837c10f27a9e861e238a898fda116c7531def979" + sha256: "3979f0b1c5a97422cadae52d476c21fa3e0fb671ef51de6cae1d646d8b99fe1f" url: "https://pub.dev" source: hosted - version: "21.2.10" + version: "20.4.54" synchronized: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 80c52e5..91bff44 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,7 +50,7 @@ dependencies: logging: ^1.2.0 msix: ^3.7.0 flutter_charts: ^0.5.1 - syncfusion_flutter_charts: ^21.2.4 + syncfusion_flutter_charts: ^20.4.48 shelf: ^1.4.1 shelf_router: ^1.1.4 pdf: ^3.8.4 @@ -63,7 +63,7 @@ dependencies: shared_preferences: ^2.2.2 excel: ^2.0.1 mobile_scanner: ^5.0.0 # ou la version la plus récente - + fl_chart: ^0.65.0 # Version la plus récente au moment de cette répons