Browse Source

maj page stock

31052025_01
b.razafimandimbihery 6 months ago
parent
commit
37e6ae88a2
  1. 515
      lib/Views/gestionStock.dart

515
lib/Views/gestionStock.dart

@ -1,7 +1,10 @@
import 'package:flutter/material.dart'; 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 'package:youmazgestion/Components/app_bar.dart';
import '../Models/produit.dart'; import 'package:youmazgestion/Components/appDrawer.dart';
import '../Services/productDatabase.dart';
class GestionStockPage extends StatefulWidget { class GestionStockPage extends StatefulWidget {
const GestionStockPage({super.key}); const GestionStockPage({super.key});
@ -11,100 +14,404 @@ class GestionStockPage extends StatefulWidget {
} }
class _GestionStockPageState extends State<GestionStockPage> { class _GestionStockPageState extends State<GestionStockPage> {
late Future<List<Product>> _productsFuture; final ProductDatabase _database = ProductDatabase.instance;
List<Product> _products = [];
List<Product> _filteredProducts = [];
String? _selectedCategory;
final TextEditingController _searchController = TextEditingController();
bool _showLowStockOnly = false;
bool _sortByStockAscending = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loadProducts(); _loadProducts();
_searchController.addListener(_filterProducts);
} }
Future<void> _loadProducts() async { Future<void> _loadProducts() async {
final productDatabase = ProductDatabase.instance; final products = await _database.getProducts();
_productsFuture = productDatabase.getProducts(); setState(() {
_products = products;
_filterProducts();
});
} }
Future<void> _refreshProducts() async { void _filterProducts() {
final productDatabase = ProductDatabase.instance; final query = _searchController.text.toLowerCase();
_productsFuture = productDatabase.getProducts(); setState(() {
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();
Future<void> _updateStock(int id, int stock) async { // Trier les produits
final productDatabase = ProductDatabase.instance; _filteredProducts.sort((a, b) {
await productDatabase.updateStock(id, stock); if (_sortByStockAscending) {
_refreshProducts(); return (a.stock ?? 0).compareTo(b.stock ?? 0);
} else {
return (b.stock ?? 0).compareTo(a.stock ?? 0);
}
});
});
} }
//popup pour modifier le stock Future<void> _updateStock(Product product, int newStock) async {
await _database.updateStock(product.id!, newStock);
await _loadProducts();
Future<void> _showStockDialog(Product product) async { Get.snackbar(
int stock = product.stock ?? 0; 'Succès',
final quantityController = TextEditingController(text: stock.toString()); 'Stock mis à jour pour ${product.name}',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.green,
colorText: Colors.white,
duration: const Duration(seconds: 2),
);
}
await showDialog( @override
context: context, Widget build(BuildContext context) {
builder: (context) { return Scaffold(
return AlertDialog( appBar: const CustomAppBar(title: 'Gestion des Stocks'),
title: const Text('Modifier le stock'), drawer: CustomDrawer(),
content: Column( body: Column(
mainAxisSize: MainAxisSize.min, children: [
children: [ // Filtres et recherche
Text(product.name), Container(
const SizedBox(height: 16), padding: const EdgeInsets.all(16.0),
Row( decoration: BoxDecoration(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, color: Colors.white,
children: [ boxShadow: [
IconButton( BoxShadow(
icon: const Icon(Icons.remove), color: Colors.black.withOpacity(0.1),
onPressed: () { blurRadius: 4,
setState(() { offset: const Offset(0, 2),
if (stock > 0) { ),
stock--; ],
quantityController.text = stock.toString(); ),
} 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),
),
), ),
Expanded( ),
child: TextField( const SizedBox(height: 12),
controller: quantityController,
textAlign: TextAlign.center, // Filtres
keyboardType: TextInputType.number, Row(
onChanged: (value) { children: [
// Filtre par catégorie
Expanded(
child: FutureBuilder<List<String>>(
future: _database.getCategories(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
}
final categories = snapshot.data!;
return DropdownButtonFormField<String>(
value: _selectedCategory,
decoration: InputDecoration(
labelText: 'Catégorie',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
items: [
const DropdownMenuItem<String>(
value: null,
child: Text('Toutes les catégories'),
),
...categories.map((category) {
return DropdownMenuItem<String>(
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(() { setState(() {
stock = int.parse(value); _sortByStockAscending = true;
_filterProducts();
}); });
}, },
), ),
), const SizedBox(width: 8),
IconButton( ChoiceChip(
icon: const Icon(Icons.add), label: const Text('Décroissant'),
onPressed: () { selected: !_sortByStockAscending,
setState(() { onSelected: (selected) {
stock++; setState(() {
quantityController.text = stock.toString(); _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),
),
);
}
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),
),
],
);
}
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),
),
],
),
),
);
}
void _showAddStockDialog(Product? product) {
final isNew = product == null;
final controller = TextEditingController(
text: isNew ? '' : product.stock?.toString() ?? '0',
);
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(isNew ? 'Ajouter un produit' : 'Modifier le stock'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
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: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Annuler'), child: const Text('Annuler'),
onPressed: () {
Navigator.of(context).pop();
},
), ),
ElevatedButton( ElevatedButton(
child: const Text('Enregistrer'), onPressed: () async {
onPressed: () { final newStock = int.tryParse(controller.text) ?? 0;
// Enregistrer la nouvelle quantité dans la base de données if (isNew) {
_updateStock(product.id!, stock); // TODO: Implémenter l'ajout d'un nouveau produit
Navigator.of(context).pop(); } else {
await _updateStock(product, newStock);
}
Navigator.pop(context);
}, },
child: Text(isNew ? 'Ajouter' : 'Mettre à jour'),
), ),
], ],
); );
@ -113,80 +420,8 @@ class _GestionStockPageState extends State<GestionStockPage> {
} }
@override @override
Widget build(BuildContext context) { void dispose() {
return Scaffold( _searchController.dispose();
appBar: const CustomAppBar(title: 'Gestion du stock'), super.dispose();
body: FutureBuilder<List<Product>>(
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);
},
),
),
);
},
);
}
},
),
);
} }
} }
Loading…
Cancel
Save