You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

478 lines
16 KiB

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/appDrawer.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
class GestionStockPage extends StatefulWidget {
const GestionStockPage({super.key});
@override
_GestionStockPageState createState() => _GestionStockPageState();
}
class _GestionStockPageState extends State<GestionStockPage> {
final AppDatabase _database = AppDatabase.instance;
List<Product> _products = [];
List<Product> _filteredProducts = [];
String? _selectedCategory;
int? _selectedIdPointDeVente; // Nouveau filtre
final TextEditingController _searchController = TextEditingController();
bool _showLowStockOnly = false;
bool _sortByStockAscending = false;
@override
void initState() {
super.initState();
_loadProducts();
_searchController.addListener(_filterProducts);
}
Future<void> _loadProducts() async {
final products = await _database.getProducts();
setState(() {
_products = products;
_filterProducts();
});
}
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 matchesPointDeVente = _selectedIdPointDeVente == null ||
product.pointDeVenteId == _selectedIdPointDeVente; // Nouveau filtre
final matchesStockFilter = !_showLowStockOnly ||
(product.stock ?? 0) <= 5; // Seuil pour stock faible
return matchesSearch && matchesCategory && matchesPointDeVente && 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<void> _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: 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 - Première ligne
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),
// Filtre par point de vente
Expanded(
child: FutureBuilder<List<Map<String, dynamic>>>(
future: _database.getPointsDeVente(), // Vous devez implémenter cette méthode
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
}
final pointsDeVente = snapshot.data!;
return DropdownButtonFormField<int>(
value: _selectedIdPointDeVente,
decoration: InputDecoration(
labelText: 'Point de vente',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
items: [
const DropdownMenuItem<int>(
value: null,
child: Text('Tous les points de vente'),
),
...pointsDeVente.map((point) {
return DropdownMenuItem<int>(
value: point['id'],
child: Text(point['nom'] ?? 'Point ${point['id']}'),
);
}),
],
onChanged: (value) {
setState(() {
_selectedIdPointDeVente = value;
_filterProducts();
});
},
);
},
),
),
],
),
const SizedBox(height: 12),
// Filtres - Deuxième ligne
Row(
children: [
// 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),
),
);
}
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.pointDeVentelib != null)
Text('Point de vente: ${product.pointDeVentelib}'),
],
),
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'),
),
ElevatedButton(
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'),
),
],
);
},
);
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
}