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.
623 lines
21 KiB
623 lines
21 KiB
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:youmazgestion/Components/app_bar.dart';
|
|
import 'package:youmazgestion/Components/appDrawer.dart';
|
|
import 'package:youmazgestion/Models/client.dart';
|
|
import 'package:youmazgestion/Models/produit.dart';
|
|
import 'package:youmazgestion/Services/productDatabase.dart';
|
|
|
|
class NouvelleCommandePage extends StatefulWidget {
|
|
const NouvelleCommandePage({super.key});
|
|
|
|
@override
|
|
_NouvelleCommandePageState createState() => _NouvelleCommandePageState();
|
|
}
|
|
|
|
class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
|
|
final ProductDatabase _database = ProductDatabase.instance;
|
|
final _formKey = GlobalKey<FormState>();
|
|
|
|
// Informations client
|
|
final TextEditingController _nomController = TextEditingController();
|
|
final TextEditingController _prenomController = TextEditingController();
|
|
final TextEditingController _emailController = TextEditingController();
|
|
final TextEditingController _telephoneController = TextEditingController();
|
|
final TextEditingController _adresseController = TextEditingController();
|
|
|
|
// Panier
|
|
final List<Product> _products = [];
|
|
final Map<int, int> _quantites = {}; // productId -> quantity
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadProducts();
|
|
}
|
|
|
|
Future<void> _loadProducts() async {
|
|
final products = await _database.getProducts();
|
|
setState(() {
|
|
_products.addAll(products);
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: const CustomAppBar(title: 'Nouvelle Commande'),
|
|
drawer: CustomDrawer(),
|
|
body: Column(
|
|
children: [
|
|
// Header avec logo et titre
|
|
Container(
|
|
padding: const EdgeInsets.all(16.0),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [Colors.blue.shade50, Colors.white],
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
// Logo et titre
|
|
Row(
|
|
children: [
|
|
// Logo de l'entreprise
|
|
Container(
|
|
width: 50,
|
|
height: 50,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(8),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 4,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(8),
|
|
child: Image.asset(
|
|
'assets/logo.png',
|
|
fit: BoxFit.cover,
|
|
errorBuilder: (context, error, stackTrace) {
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade800,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: const Icon(
|
|
Icons.shopping_cart,
|
|
color: Colors.white,
|
|
size: 30,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
const Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Nouvelle Commande',
|
|
style: TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
Text(
|
|
'Créez une nouvelle commande pour un client',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: Colors.grey,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Contenu principal
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
_buildClientForm(),
|
|
const SizedBox(height: 20),
|
|
_buildProductList(),
|
|
const SizedBox(height: 20),
|
|
_buildCartSection(),
|
|
const SizedBox(height: 20),
|
|
_buildTotalSection(),
|
|
const SizedBox(height: 20),
|
|
_buildSubmitButton(),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildClientForm() {
|
|
return Card(
|
|
elevation: 4,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'Informations Client',
|
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(height: 16),
|
|
TextFormField(
|
|
controller: _nomController,
|
|
decoration: InputDecoration(
|
|
labelText: 'Nom',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.white,
|
|
),
|
|
validator: (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Veuillez entrer un nom';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 12),
|
|
TextFormField(
|
|
controller: _prenomController,
|
|
decoration: InputDecoration(
|
|
labelText: 'Prénom',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.white,
|
|
),
|
|
validator: (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Veuillez entrer un prénom';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 12),
|
|
TextFormField(
|
|
controller: _emailController,
|
|
decoration: InputDecoration(
|
|
labelText: 'Email',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.white,
|
|
),
|
|
keyboardType: TextInputType.emailAddress,
|
|
validator: (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Veuillez entrer un email';
|
|
}
|
|
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
|
|
return 'Veuillez entrer un email valide';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 12),
|
|
TextFormField(
|
|
controller: _telephoneController,
|
|
decoration: InputDecoration(
|
|
labelText: 'Téléphone',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.white,
|
|
),
|
|
keyboardType: TextInputType.phone,
|
|
validator: (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Veuillez entrer un numéro de téléphone';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 12),
|
|
TextFormField(
|
|
controller: _adresseController,
|
|
decoration: InputDecoration(
|
|
labelText: 'Adresse de livraison',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.white,
|
|
),
|
|
maxLines: 2,
|
|
validator: (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Veuillez entrer une adresse';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildProductList() {
|
|
return Card(
|
|
elevation: 4,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'Produits Disponibles',
|
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(height: 16),
|
|
_products.isEmpty
|
|
? const Center(child: CircularProgressIndicator())
|
|
: ListView.builder(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
itemCount: _products.length,
|
|
itemBuilder: (context, index) {
|
|
final product = _products[index];
|
|
final quantity = _quantites[product.id] ?? 0;
|
|
|
|
return Card(
|
|
margin: const EdgeInsets.symmetric(vertical: 8),
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: ListTile(
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 8,
|
|
),
|
|
leading: Container(
|
|
width: 50,
|
|
height: 50,
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade50,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: const Icon(Icons.shopping_bag,
|
|
color: Colors.blue),
|
|
),
|
|
title: Text(
|
|
product.name,
|
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
subtitle: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'${product.price.toStringAsFixed(2)} DA',
|
|
style: TextStyle(
|
|
color: Colors.green.shade700,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
if (product.stock != null)
|
|
Text(
|
|
'Stock: ${product.stock}',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
trailing: Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade50,
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
IconButton(
|
|
icon: const Icon(Icons.remove, size: 18),
|
|
onPressed: () {
|
|
if (quantity > 0) {
|
|
setState(() {
|
|
_quantites[product.id!] = quantity - 1;
|
|
});
|
|
}
|
|
},
|
|
),
|
|
Text(
|
|
quantity.toString(),
|
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.add, size: 18),
|
|
onPressed: () {
|
|
if (product.stock == null || quantity < product.stock!) {
|
|
setState(() {
|
|
_quantites[product.id!] = quantity + 1;
|
|
});
|
|
} else {
|
|
Get.snackbar(
|
|
'Stock insuffisant',
|
|
'Quantité demandée non disponible',
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
);
|
|
}
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCartSection() {
|
|
final itemsInCart = _quantites.entries.where((e) => e.value > 0).toList();
|
|
|
|
if (itemsInCart.isEmpty) {
|
|
return Card(
|
|
elevation: 4,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: const Padding(
|
|
padding: EdgeInsets.all(16.0),
|
|
child: Center(
|
|
child: Text(
|
|
'Votre panier est vide',
|
|
style: TextStyle(color: Colors.grey),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return Card(
|
|
elevation: 4,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'Votre Panier',
|
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(height: 16),
|
|
...itemsInCart.map((entry) {
|
|
final product = _products.firstWhere((p) => p.id == entry.key);
|
|
return Card(
|
|
margin: const EdgeInsets.only(bottom: 8),
|
|
elevation: 1,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: ListTile(
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 8,
|
|
),
|
|
leading: Container(
|
|
width: 40,
|
|
height: 40,
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade50,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: const Icon(Icons.shopping_bag, size: 20),
|
|
),
|
|
title: Text(product.name),
|
|
subtitle: Text('${entry.value} x ${product.price.toStringAsFixed(2)} DA'),
|
|
trailing: Text(
|
|
'${(entry.value * product.price).toStringAsFixed(2)} DA',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.blue.shade800,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTotalSection() {
|
|
double total = 0;
|
|
_quantites.forEach((productId, quantity) {
|
|
final product = _products.firstWhere((p) => p.id == productId);
|
|
total += quantity * product.price;
|
|
});
|
|
|
|
return Card(
|
|
elevation: 4,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade50,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
padding: const EdgeInsets.all(12),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
const Text(
|
|
'Total:',
|
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
),
|
|
Text(
|
|
'${total.toStringAsFixed(2)} DA',
|
|
style: const TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.green,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSubmitButton() {
|
|
return ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
backgroundColor: Colors.blue.shade600,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
elevation: 4,
|
|
),
|
|
onPressed: _submitOrder,
|
|
child: const Text(
|
|
'Valider la Commande',
|
|
style: TextStyle(fontSize: 16, color: Colors.white),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _submitOrder() async {
|
|
if (!_formKey.currentState!.validate()) {
|
|
return;
|
|
}
|
|
|
|
final itemsInCart = _quantites.entries.where((e) => e.value > 0).toList();
|
|
if (itemsInCart.isEmpty) {
|
|
Get.snackbar(
|
|
'Panier vide',
|
|
'Veuillez ajouter des produits à votre commande',
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Créer le client
|
|
final client = Client(
|
|
nom: _nomController.text,
|
|
prenom: _prenomController.text,
|
|
email: _emailController.text,
|
|
telephone: _telephoneController.text,
|
|
adresse: _adresseController.text,
|
|
dateCreation: DateTime.now(),
|
|
);
|
|
|
|
// Calculer le total et préparer les détails
|
|
double total = 0;
|
|
final details = <DetailCommande>[];
|
|
|
|
for (final entry in itemsInCart) {
|
|
final product = _products.firstWhere((p) => p.id == entry.key);
|
|
total += entry.value * product.price;
|
|
|
|
details.add(DetailCommande(
|
|
commandeId: 0, // Valeur temporaire, sera remplacée dans la transaction
|
|
produitId: product.id!,
|
|
quantite: entry.value,
|
|
prixUnitaire: product.price,
|
|
sousTotal: entry.value * product.price,
|
|
));
|
|
}
|
|
|
|
// Créer la commande
|
|
final commande = Commande(
|
|
clientId: 0, // sera mis à jour après création du client
|
|
dateCommande: DateTime.now(),
|
|
statut: StatutCommande.enAttente,
|
|
montantTotal: total,
|
|
notes: 'Commande passée via l\'application',
|
|
);
|
|
|
|
try {
|
|
// Enregistrer la commande dans la base de données
|
|
await _database.createCommandeComplete(client, commande, details);
|
|
|
|
Get.snackbar(
|
|
'Succès',
|
|
'Votre commande a été enregistrée',
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.green,
|
|
colorText: Colors.white,
|
|
);
|
|
|
|
// Réinitialiser le formulaire
|
|
_formKey.currentState!.reset();
|
|
setState(() {
|
|
_quantites.clear();
|
|
});
|
|
|
|
} catch (e) {
|
|
Get.snackbar(
|
|
'Erreur',
|
|
'Une erreur est survenue lors de l\'enregistrement de la commande: ${e.toString()}',
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.red,
|
|
colorText: Colors.white,
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_nomController.dispose();
|
|
_prenomController.dispose();
|
|
_emailController.dispose();
|
|
_telephoneController.dispose();
|
|
_adresseController.dispose();
|
|
super.dispose();
|
|
}
|
|
}
|