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: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<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
void initState() {
super.initState();
_loadProducts();
_searchController.addListener(_filterProducts);
}
Future<void> _loadProducts() async {
final productDatabase = ProductDatabase.instance;
_productsFuture = productDatabase.getProducts();
final products = await _database.getProducts();
setState(() {
_products = products;
_filterProducts();
});
}
Future<void> _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();
Future<void> _updateStock(int id, int stock) async {
final productDatabase = ProductDatabase.instance;
await productDatabase.updateStock(id, stock);
_refreshProducts();
// 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);
}
});
});
}
//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 {
int stock = product.stock ?? 0;
final quantityController = TextEditingController(text: stock.toString());
Get.snackbar(
'Succès',
'Stock mis à jour pour ${product.name}',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.green,
colorText: Colors.white,
duration: const Duration(seconds: 2),
);
}
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('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();
}
});
},
@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),
),
),
Expanded(
child: TextField(
controller: quantityController,
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
onChanged: (value) {
),
const SizedBox(height: 12),
// Filtres
Row(
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(() {
stock = int.parse(value);
_sortByStockAscending = true;
_filterProducts();
});
},
),
),
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
setState(() {
stock++;
quantityController.text = stock.toString();
});
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),
),
);
}
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: [
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<GestionStockPage> {
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CustomAppBar(title: 'Gestion du stock'),
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);
},
),
),
);
},
);
}
},
),
);
void dispose() {
_searchController.dispose();
super.dispose();
}
}
Loading…
Cancel
Save