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.
 
 
 
 
 
 

626 lines
22 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)} MGA',
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)} MGA'),
trailing: Text(
'${(entry.value * product.price).toStringAsFixed(2)} MGA',
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)} MGA',
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();
}
}