diff --git a/lib/Views/gestionStock.dart b/lib/Views/gestionStock.dart index 3612775..e8adc25 100644 --- a/lib/Views/gestionStock.dart +++ b/lib/Views/gestionStock.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:get/get_core/src/get_main.dart'; +import 'package:youmazgestion/Models/produit.dart'; +import 'package:youmazgestion/Services/productDatabase.dart'; import 'package:youmazgestion/Components/app_bar.dart'; -import '../Models/produit.dart'; -import '../Services/productDatabase.dart'; +import 'package:youmazgestion/Components/appDrawer.dart'; class GestionStockPage extends StatefulWidget { const GestionStockPage({super.key}); @@ -11,100 +14,404 @@ class GestionStockPage extends StatefulWidget { } class _GestionStockPageState extends State { - late Future> _productsFuture; + final ProductDatabase _database = ProductDatabase.instance; + List _products = []; + List _filteredProducts = []; + String? _selectedCategory; + final TextEditingController _searchController = TextEditingController(); + bool _showLowStockOnly = false; + bool _sortByStockAscending = false; @override void initState() { super.initState(); _loadProducts(); + _searchController.addListener(_filterProducts); } Future _loadProducts() async { - final productDatabase = ProductDatabase.instance; - _productsFuture = productDatabase.getProducts(); + final products = await _database.getProducts(); + setState(() { + _products = products; + _filterProducts(); + }); } - Future _refreshProducts() async { - final productDatabase = ProductDatabase.instance; - _productsFuture = productDatabase.getProducts(); - setState(() {}); + void _filterProducts() { + final query = _searchController.text.toLowerCase(); + setState(() { + _filteredProducts = _products.where((product) { + final matchesSearch = product.name.toLowerCase().contains(query) || + (product.reference?.toLowerCase().contains(query) ?? false); + final matchesCategory = _selectedCategory == null || + product.category == _selectedCategory; + final matchesStockFilter = !_showLowStockOnly || + (product.stock ?? 0) <= 5; // Seuil pour stock faible + + return matchesSearch && matchesCategory && matchesStockFilter; + }).toList(); + + // Trier les produits + _filteredProducts.sort((a, b) { + if (_sortByStockAscending) { + return (a.stock ?? 0).compareTo(b.stock ?? 0); + } else { + return (b.stock ?? 0).compareTo(a.stock ?? 0); + } + }); + }); + } + + Future _updateStock(Product product, int newStock) async { + await _database.updateStock(product.id!, newStock); + await _loadProducts(); + + Get.snackbar( + 'Succès', + 'Stock mis à jour pour ${product.name}', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green, + colorText: Colors.white, + duration: const Duration(seconds: 2), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: const CustomAppBar(title: 'Gestion des Stocks'), + drawer: CustomDrawer(), + body: Column( + children: [ + // Filtres et recherche + Container( + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + children: [ + // Barre de recherche + TextField( + controller: _searchController, + decoration: InputDecoration( + labelText: 'Rechercher un produit', + prefixIcon: const Icon(Icons.search), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + const SizedBox(height: 12), + + // Filtres + Row( + children: [ + // Filtre par catégorie + Expanded( + child: FutureBuilder>( + future: _database.getCategories(), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const SizedBox(); + } + final categories = snapshot.data!; + return DropdownButtonFormField( + value: _selectedCategory, + decoration: InputDecoration( + labelText: 'Catégorie', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + items: [ + const DropdownMenuItem( + value: null, + child: Text('Toutes les catégories'), + ), + ...categories.map((category) { + return DropdownMenuItem( + value: category, + child: Text(category), + ); + }), + ], + onChanged: (value) { + setState(() { + _selectedCategory = value; + _filterProducts(); + }); + }, + ); + }, + ), + ), + const SizedBox(width: 12), + + // Toggle pour stock faible seulement + Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.warning_amber, size: 20), + const SizedBox(width: 8), + const Text('Stock faible'), + Switch( + value: _showLowStockOnly, + onChanged: (value) { + setState(() { + _showLowStockOnly = value; + _filterProducts(); + }); + }, + activeColor: Colors.orange, + ), + ], + ), + ), + ], + ), + + // Tri + const SizedBox(height: 12), + Row( + children: [ + const Text('Trier par stock:'), + const SizedBox(width: 8), + ChoiceChip( + label: const Text('Croissant'), + selected: _sortByStockAscending, + onSelected: (selected) { + setState(() { + _sortByStockAscending = true; + _filterProducts(); + }); + }, + ), + const SizedBox(width: 8), + ChoiceChip( + label: const Text('Décroissant'), + selected: !_sortByStockAscending, + onSelected: (selected) { + setState(() { + _sortByStockAscending = false; + _filterProducts(); + }); + }, + ), + ], + ), + ], + ), + ), + + // Statistiques rapides + Container( + padding: const EdgeInsets.symmetric(vertical: 8), + color: Colors.grey.shade100, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _buildStatCard( + 'Total', + '${_products.length}', + Icons.inventory, + Colors.blue, + ), + _buildStatCard( + 'En stock', + '${_products.where((p) => (p.stock ?? 0) > 0).length}', + Icons.check_circle, + Colors.green, + ), + _buildStatCard( + 'Stock faible', + '${_products.where((p) => (p.stock ?? 0) <= 5).length}', + Icons.warning, + Colors.orange, + ), + _buildStatCard( + 'Rupture', + '${_products.where((p) => (p.stock ?? 0) == 0).length}', + Icons.cancel, + Colors.red, + ), + ], + ), + ), + + // Liste des produits + Expanded( + child: _filteredProducts.isEmpty + ? const Center( + child: Text('Aucun produit trouvé'), + ) + : ListView.builder( + padding: const EdgeInsets.all(8), + itemCount: _filteredProducts.length, + itemBuilder: (context, index) { + final product = _filteredProducts[index]; + return _buildProductCard(product); + }, + ), + ), + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: () => _showAddStockDialog(null), + child: const Icon(Icons.add), + ), + ); } - Future _updateStock(int id, int stock) async { - final productDatabase = ProductDatabase.instance; - await productDatabase.updateStock(id, stock); - _refreshProducts(); + Widget _buildStatCard(String title, String value, IconData icon, Color color) { + return Column( + children: [ + Icon(icon, color: color), + const SizedBox(height: 4), + Text( + value, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: color, + ), + ), + Text( + title, + style: const TextStyle(fontSize: 12), + ), + ], + ); } - //popup pour modifier le stock + Widget _buildProductCard(Product product) { + final stock = product.stock ?? 0; + Color stockColor; + IconData stockIcon; + + if (stock == 0) { + stockColor = Colors.red; + stockIcon = Icons.block; + } else if (stock <= 5) { + stockColor = Colors.orange; + stockIcon = Icons.warning; + } else { + stockColor = Colors.green; + stockIcon = Icons.check_circle; + } + + return Card( + margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + child: ListTile( + leading: product.image != null && product.image!.isNotEmpty + ? CircleAvatar( + backgroundImage: NetworkImage(product.image!), + ) + : CircleAvatar( + backgroundColor: Colors.blue.shade100, + child: Text(product.name[0]), + ), + title: Text(product.name), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Catégorie: ${product.category}'), + if (product.reference != null && product.reference!.isNotEmpty) + Text('Réf: ${product.reference!}'), + ], + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Icon(stockIcon, size: 16, color: stockColor), + const SizedBox(width: 4), + Text( + '$stock', + style: TextStyle( + fontWeight: FontWeight.bold, + color: stockColor, + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + '${product.price.toStringAsFixed(2)} DA', + style: const TextStyle(fontSize: 12), + ), + ], + ), + IconButton( + icon: const Icon(Icons.edit), + onPressed: () => _showAddStockDialog(product), + ), + ], + ), + ), + ); + } - Future _showStockDialog(Product product) async { - int stock = product.stock ?? 0; - final quantityController = TextEditingController(text: stock.toString()); + void _showAddStockDialog(Product? product) { + final isNew = product == null; + final controller = TextEditingController( + text: isNew ? '' : product.stock?.toString() ?? '0', + ); - await showDialog( + showDialog( context: context, builder: (context) { return AlertDialog( - title: const Text('Modifier le stock'), + title: Text(isNew ? 'Ajouter un produit' : 'Modifier le stock'), content: Column( mainAxisSize: MainAxisSize.min, children: [ - Text(product.name), - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - IconButton( - icon: const Icon(Icons.remove), - onPressed: () { - setState(() { - if (stock > 0) { - stock--; - quantityController.text = stock.toString(); - } - }); - }, - ), - Expanded( - child: TextField( - controller: quantityController, - textAlign: TextAlign.center, - keyboardType: TextInputType.number, - onChanged: (value) { - setState(() { - stock = int.parse(value); - }); - }, - ), - ), - IconButton( - icon: const Icon(Icons.add), - onPressed: () { - setState(() { - stock++; - quantityController.text = stock.toString(); - }); - }, - ), - ], + if (isNew) + TextField( + decoration: const InputDecoration(labelText: 'Nom du produit'), + ), + if (isNew) + const SizedBox(height: 12), + TextField( + controller: controller, + decoration: const InputDecoration(labelText: 'Quantité en stock'), + keyboardType: TextInputType.number, ), ], ), actions: [ TextButton( + onPressed: () => Navigator.pop(context), child: const Text('Annuler'), - onPressed: () { - Navigator.of(context).pop(); - }, ), ElevatedButton( - child: const Text('Enregistrer'), - onPressed: () { - // Enregistrer la nouvelle quantité dans la base de données - _updateStock(product.id!, stock); - Navigator.of(context).pop(); + onPressed: () async { + final newStock = int.tryParse(controller.text) ?? 0; + if (isNew) { + // TODO: Implémenter l'ajout d'un nouveau produit + } else { + await _updateStock(product, newStock); + } + Navigator.pop(context); }, + child: Text(isNew ? 'Ajouter' : 'Mettre à jour'), ), ], ); @@ -113,80 +420,8 @@ class _GestionStockPageState extends State { } @override - Widget build(BuildContext context) { - return Scaffold( - appBar: const CustomAppBar(title: 'Gestion du stock'), - body: FutureBuilder>( - future: _productsFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center( - child: CircularProgressIndicator(), - ); - } else if (snapshot.hasError) { - return const Center( - child: Text('Une erreur s\'est produite'), - ); - } else if (!snapshot.hasData || snapshot.data!.isEmpty) { - return const Center( - child: Text('Aucun produit trouvé'), - ); - } else { - final products = snapshot.data!; - return ListView.builder( - itemCount: products.length, - itemBuilder: (context, index) { - final product = products[index]; - Color stockColor; - if (product.stock != null) { - if (product.stock! > 30) { - stockColor = Colors.green; - } else if (product.stock! > 10) { - stockColor = Colors.red; - } else { - stockColor = Colors.red; - } - } else { - stockColor = Colors.red; - } - - return Card( - margin: - const EdgeInsets.symmetric(vertical: 8, horizontal: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - elevation: 4, - shadowColor: Colors.deepOrangeAccent, - child: ListTile( - leading: const Icon(Icons.shopping_basket), - title: Text( - product.name, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - subtitle: Text( - 'Stock: ${product.stock ?? 'Non disponible'}', - style: TextStyle( - fontSize: 16, - color: stockColor, - ), - ), - trailing: IconButton( - icon: const Icon(Icons.edit), - onPressed: () { - _showStockDialog(product); - }, - ), - ), - ); - }, - ); - } - }, - ), - ); + void dispose() { + _searchController.dispose(); + super.dispose(); } -} +} \ No newline at end of file