Browse Source

last commit gestion commande

31052025_01
b.razafimandimbihery 6 months ago
parent
commit
435baa3b4f
  1. 399
      lib/Components/appDrawer.dart
  2. 191
      lib/Models/Client.dart
  3. 230
      lib/Services/app_database.dart
  4. 515
      lib/Services/productDatabase.dart
  5. 812
      lib/Views/addProduct.dart
  6. 1329
      lib/Views/commandManagement.dart
  7. 623
      lib/Views/newCommand.dart
  8. 140
      lib/Views/produitsCard.dart
  9. 455
      lib/accueil.dart
  10. 1
      lib/main.dart

399
lib/Components/appDrawer.dart

@ -3,15 +3,14 @@ import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:youmazgestion/Views/HandleProduct.dart';
import 'package:youmazgestion/Views/RoleListPage.dart';
import 'package:youmazgestion/Views/commandManagement.dart';
import 'package:youmazgestion/Views/historique.dart';
import 'package:youmazgestion/Views/addProduct.dart';
import 'package:youmazgestion/Views/bilanMois.dart';
import 'package:youmazgestion/Views/gestionProduct.dart';
import 'package:youmazgestion/Views/gestionStock.dart';
import 'package:youmazgestion/Views/listUser.dart';
import 'package:youmazgestion/Views/loginPage.dart';
import 'package:youmazgestion/Views/newCommand.dart';
import 'package:youmazgestion/Views/registrationPage.dart';
import 'package:youmazgestion/Views/gestionRole.dart';
import 'package:youmazgestion/accueil.dart';
import 'package:youmazgestion/controller/userController.dart';
@ -31,201 +30,230 @@ class CustomDrawer extends StatelessWidget {
return Drawer(
backgroundColor: Colors.white,
child: ListView(
padding: EdgeInsets.zero,
children: [
// Header avec informations utilisateur
GetBuilder<UserController>(
builder: (controller) => UserAccountsDrawerHeader(
accountEmail: Text(controller.email),
accountName: Text(controller.name),
currentAccountPicture: const CircleAvatar(
backgroundImage: AssetImage("assets/youmaz2.png"),
),
builder: (controller) => Container(
padding: const EdgeInsets.only(top: 50, left: 20, bottom: 20),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Colors.white, Color.fromARGB(255, 4, 54, 95)],
colors: [Color.fromARGB(255, 4, 54, 95), Colors.blue],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Row(
children: [
const CircleAvatar(
radius: 30,
backgroundImage: AssetImage("assets/youmaz2.png"),
),
const SizedBox(width: 15),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
controller.name,
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
controller.email,
style: const TextStyle(
color: Colors.white70,
fontSize: 14,
),
),
Text(
controller.role,
style: const TextStyle(
color: Colors.white70,
fontSize: 12,
),
),
],
),
],
),
),
),
ListTile(
leading: const Icon(Icons.home),
iconColor: Colors.lightBlueAccent,
title: const Text("Accueil"),
onTap: () async {
bool hasPermission = await userController.hasPermission('view', '/accueil');
if (hasPermission) {
Get.to(const AccueilPage());
} else {
Get.snackbar(
"Accès refusé",
"Vous n'avez pas les droits pour accéder à cette page",
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(Icons.error),
duration: const Duration(seconds: 3),
snackPosition: SnackPosition.TOP,
);
}
},
// Section Accueil
_buildDrawerItem(
icon: Icons.home,
title: "Accueil",
color: Colors.blue,
permissionAction: 'view',
permissionRoute: '/accueil',
onTap: () => Get.to(const AccueilPage()),
),
ListTile(
leading: const Icon(Icons.person_add),
iconColor: Colors.green,
title: const Text("Ajouter un utilisateur"),
onTap: () async {
bool hasPermission = await userController.hasPermission('create', '/ajouter-utilisateur');
if (hasPermission) {
Get.to(const RegistrationPage());
} else {
Get.snackbar(
"Accès refusé",
"Vous n'avez pas les droits pour ajouter un utilisateur",
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(Icons.error),
duration: const Duration(seconds: 3),
snackPosition: SnackPosition.TOP,
);
}
},
// Section Utilisateurs
const Padding(
padding: EdgeInsets.only(left: 20, top: 15, bottom: 5),
child: Text(
"GESTION UTILISATEURS",
style: TextStyle(
color: Colors.grey,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
ListTile(
leading: const Icon(Icons.supervised_user_circle),
iconColor: const Color.fromARGB(255, 4, 54, 95),
title: const Text("Modifier/Supprimer un utilisateur"),
onTap: () async {
bool hasPermission = await userController.hasPermission('update', '/modifier-utilisateur');
if (hasPermission) {
Get.to(const ListUserPage());
} else {
Get.snackbar(
"Accès refusé",
"Vous n'avez pas les droits pour modifier/supprimer un utilisateur",
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(Icons.error),
duration: const Duration(seconds: 3),
snackPosition: SnackPosition.TOP,
);
}
},
_buildDrawerItem(
icon: Icons.person_add,
title: "Ajouter un utilisateur",
color: Colors.green,
permissionAction: 'create',
permissionRoute: '/ajouter-utilisateur',
onTap: () => Get.to(const RegistrationPage()),
),
ListTile(
leading: const Icon(Icons.add),
iconColor: Colors.indigoAccent,
title: const Text("Gestion des produit"),
onTap: () async {
bool hasPermission = await userController.hasPermission('create', '/ajouter-produit');
if (hasPermission) {
Get.to(() => const ProductManagementPage());
} else {
Get.snackbar(
"Accès refusé",
"Vous n'avez pas les droits pour ajouter un produit",
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(Icons.error),
duration: const Duration(seconds: 3),
snackPosition: SnackPosition.TOP,
);
}
},
_buildDrawerItem(
icon: Icons.supervised_user_circle,
title: "Gérer les utilisateurs",
color: Color.fromARGB(255, 4, 54, 95),
permissionAction: 'update',
permissionRoute: '/modifier-utilisateur',
onTap: () => Get.to(const ListUserPage()),
),
ListTile(
leading: const Icon(Icons.bar_chart),
title: const Text("Bilan"),
onTap: () async {
bool hasPermission = await userController.hasPermission('read', '/bilan');
if (hasPermission) {
Get.to(const BilanMois());
} else {
Get.snackbar(
"Accès refusé",
"Vous n'avez pas les droits pour accéder au bilan",
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(Icons.error_outline_outlined),
duration: const Duration(seconds: 3),
snackPosition: SnackPosition.TOP,
);
}
},
// Section Produits
const Padding(
padding: EdgeInsets.only(left: 20, top: 15, bottom: 5),
child: Text(
"GESTION PRODUITS",
style: TextStyle(
color: Colors.grey,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
ListTile(
leading: const Icon(Icons.warning_amber),
title: const Text("Gérer les rôles"),
onTap: () async {
bool hasPermission = await userController.hasPermission('admin', '/gerer-roles');
if (hasPermission) {
Get.to(const RoleListPage());
print("permission accepted");
} else {
print("permission not accepted for" +userController.username);
Get.snackbar(
"Accès refusé ",
"Vous n'avez pas les droits pour gérer les rôles",
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(Icons.error_outline_outlined),
duration: const Duration(seconds: 3),
snackPosition: SnackPosition.TOP,
);
}
},
_buildDrawerItem(
icon: Icons.inventory,
title: "Gestion des produits",
color: Colors.indigoAccent,
permissionAction: 'create',
permissionRoute: '/ajouter-produit',
onTap: () => Get.to(const ProductManagementPage()),
),
ListTile(
leading: const Icon(Icons.inventory),
iconColor: Colors.blueAccent,
title: const Text("Gestion de stock"),
onTap: () async {
bool hasPermission = await userController.hasPermission('update', '/gestion-stock');
if (hasPermission) {
Get.to(const GestionStockPage());
} else {
Get.snackbar(
"Accès refusé",
"Vous n'avez pas les droits pour accéder à la gestion de stock",
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(Icons.error),
duration: const Duration(seconds: 3),
snackPosition: SnackPosition.TOP,
);
}
},
_buildDrawerItem(
icon: Icons.storage,
title: "Gestion de stock",
color: Colors.blueAccent,
permissionAction: 'update',
permissionRoute: '/gestion-stock',
onTap: () => Get.to(const GestionStockPage()),
),
ListTile(
leading: const Icon(Icons.history),
iconColor: Colors.blue,
title: const Text("Historique"),
onTap: () {
Get.to(HistoryPage());
},
// Section Commandes
const Padding(
padding: EdgeInsets.only(left: 20, top: 15, bottom: 5),
child: Text(
"GESTION COMMANDES",
style: TextStyle(
color: Colors.grey,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
_buildDrawerItem(
icon: Icons.add_shopping_cart,
title: "Nouvelle commande",
color: Colors.orange,
permissionAction: 'create',
permissionRoute: '/nouvelle-commande',
onTap: () => Get.to(const NouvelleCommandePage()),
),
ListTile(
leading: const Icon(Icons.logout),
iconColor: Colors.red,
title: const Text("Déconnexion"),
_buildDrawerItem(
icon: Icons.list_alt,
title: "Gérer les commandes",
color: Colors.deepPurple,
permissionAction: 'manage',
permissionRoute: '/gerer-commandes',
onTap: () => Get.to(const GestionCommandesPage()),
),
// Section Rapports
const Padding(
padding: EdgeInsets.only(left: 20, top: 15, bottom: 5),
child: Text(
"RAPPORTS",
style: TextStyle(
color: Colors.grey,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
_buildDrawerItem(
icon: Icons.bar_chart,
title: "Bilan mensuel",
color: Colors.teal,
permissionAction: 'read',
permissionRoute: '/bilan',
onTap: () => Get.to(const BilanMois()),
),
_buildDrawerItem(
icon: Icons.history,
title: "Historique",
color: Colors.blue,
permissionAction: 'read',
permissionRoute: '/historique',
onTap: () => Get.to(HistoryPage()),
),
// Section Administration
const Padding(
padding: EdgeInsets.only(left: 20, top: 15, bottom: 5),
child: Text(
"ADMINISTRATION",
style: TextStyle(
color: Colors.grey,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
_buildDrawerItem(
icon: Icons.admin_panel_settings,
title: "Gérer les rôles",
color: Colors.redAccent,
permissionAction: 'admin',
permissionRoute: '/gerer-roles',
onTap: () => Get.to(const RoleListPage()),
),
// Déconnexion
const Divider(),
_buildDrawerItem(
icon: Icons.logout,
title: "Déconnexion",
color: Colors.red,
onTap: () {
Get.defaultDialog(
title: "Déconnexion",
content: const Text("Voulez-vous vraiment vous déconnecter ?"),
actions: [
TextButton(
child: const Text("Non"),
onPressed: () => Get.back(),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
child: const Text("Oui"),
onPressed: () {
clearUserData();
Get.offAll(const LoginPage());
},
),
ElevatedButton(
child: const Text("Non"),
onPressed: () {
Get.back();
},
),
],
);
},
@ -234,4 +262,41 @@ class CustomDrawer extends StatelessWidget {
),
);
}
Widget _buildDrawerItem({
required IconData icon,
required String title,
required Color color,
String? permissionAction,
String? permissionRoute,
required VoidCallback onTap,
}) {
return ListTile(
leading: Icon(icon, color: color),
title: Text(title),
trailing: permissionAction != null
? const Icon(Icons.chevron_right, color: Colors.grey)
: null,
onTap: () async {
if (permissionAction != null && permissionRoute != null) {
bool hasPermission = await userController.hasPermission(permissionAction, permissionRoute);
if (hasPermission) {
onTap();
} else {
Get.snackbar(
"Accès refusé",
"Vous n'avez pas les droits pour accéder à cette page",
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(Icons.error),
duration: const Duration(seconds: 3),
snackPosition: SnackPosition.TOP,
);
}
} else {
onTap();
}
},
);
}
}

191
lib/Models/Client.dart

@ -0,0 +1,191 @@
// Models/client.dart
class Client {
final int? id;
final String nom;
final String prenom;
final String email;
final String telephone;
final String? adresse;
final DateTime dateCreation;
final bool actif;
Client({
this.id,
required this.nom,
required this.prenom,
required this.email,
required this.telephone,
this.adresse,
required this.dateCreation,
this.actif = true,
});
Map<String, dynamic> toMap() {
return {
'id': id,
'nom': nom,
'prenom': prenom,
'email': email,
'telephone': telephone,
'adresse': adresse,
'dateCreation': dateCreation.toIso8601String(),
'actif': actif ? 1 : 0,
};
}
factory Client.fromMap(Map<String, dynamic> map) {
return Client(
id: map['id'],
nom: map['nom'],
prenom: map['prenom'],
email: map['email'],
telephone: map['telephone'],
adresse: map['adresse'],
dateCreation: DateTime.parse(map['dateCreation']),
actif: map['actif'] == 1,
);
}
String get nomComplet => '$prenom $nom';
}
// Models/commande.dart
enum StatutCommande {
enAttente,
confirmee,
enPreparation,
expediee,
livree,
annulee
}
class Commande {
final int? id;
final int clientId;
final DateTime dateCommande;
final StatutCommande statut;
final double montantTotal;
final String? notes;
final DateTime? dateLivraison;
// Données du client (pour les jointures)
final String? clientNom;
final String? clientPrenom;
final String? clientEmail;
Commande({
this.id,
required this.clientId,
required this.dateCommande,
this.statut = StatutCommande.enAttente,
required this.montantTotal,
this.notes,
this.dateLivraison,
this.clientNom,
this.clientPrenom,
this.clientEmail,
});
Map<String, dynamic> toMap() {
return {
'id': id,
'clientId': clientId,
'dateCommande': dateCommande.toIso8601String(),
'statut': statut.index,
'montantTotal': montantTotal,
'notes': notes,
'dateLivraison': dateLivraison?.toIso8601String(),
};
}
factory Commande.fromMap(Map<String, dynamic> map) {
return Commande(
id: map['id'],
clientId: map['clientId'],
dateCommande: DateTime.parse(map['dateCommande']),
statut: StatutCommande.values[map['statut']],
montantTotal: map['montantTotal'].toDouble(),
notes: map['notes'],
dateLivraison: map['dateLivraison'] != null
? DateTime.parse(map['dateLivraison'])
: null,
clientNom: map['clientNom'],
clientPrenom: map['clientPrenom'],
clientEmail: map['clientEmail'],
);
}
String get statutLibelle {
switch (statut) {
case StatutCommande.enAttente:
return 'En attente';
case StatutCommande.confirmee:
return 'Confirmée';
case StatutCommande.enPreparation:
return 'En préparation';
case StatutCommande.expediee:
return 'Expédiée';
case StatutCommande.livree:
return 'Livrée';
case StatutCommande.annulee:
return 'Annulée';
}
}
String get clientNomComplet =>
clientPrenom != null && clientNom != null
? '$clientPrenom $clientNom'
: 'Client inconnu';
}
// Models/detail_commande.dart
class DetailCommande {
final int? id;
final int commandeId;
final int produitId;
final int quantite;
final double prixUnitaire;
final double sousTotal;
// Données du produit (pour les jointures)
final String? produitNom;
final String? produitImage;
final String? produitReference;
DetailCommande({
this.id,
required this.commandeId,
required this.produitId,
required this.quantite,
required this.prixUnitaire,
required this.sousTotal,
this.produitNom,
this.produitImage,
this.produitReference,
});
Map<String, dynamic> toMap() {
return {
'id': id,
'commandeId': commandeId,
'produitId': produitId,
'quantite': quantite,
'prixUnitaire': prixUnitaire,
'sousTotal': sousTotal,
};
}
factory DetailCommande.fromMap(Map<String, dynamic> map) {
return DetailCommande(
id: map['id'],
commandeId: map['commandeId'],
produitId: map['produitId'],
quantite: map['quantite'],
prixUnitaire: map['prixUnitaire'].toDouble(),
sousTotal: map['sousTotal'].toDouble(),
produitNom: map['produitNom'],
produitImage: map['produitImage'],
produitReference: map['produitReference'],
);
}
}

230
lib/Services/app_database.dart

@ -142,37 +142,78 @@ class AppDatabase {
}
Future<void> insertDefaultPermissions() async {
final db = await database;
final existing = await db.query('permissions');
if (existing.isEmpty) {
await db.insert('permissions', {'name': 'view'});
await db.insert('permissions', {'name': 'create'});
await db.insert('permissions', {'name': 'update'});
await db.insert('permissions', {'name': 'delete'});
await db.insert('permissions', {'name': 'admin'});
print("Permissions par défaut insérées");
Future<void> insertDefaultPermissions() async {
final db = await database;
final existing = await db.query('permissions');
if (existing.isEmpty) {
await db.insert('permissions', {'name': 'view'});
await db.insert('permissions', {'name': 'create'});
await db.insert('permissions', {'name': 'update'});
await db.insert('permissions', {'name': 'delete'});
await db.insert('permissions', {'name': 'admin'});
await db.insert('permissions', {'name': 'manage'}); // Nouvelle permission
await db.insert('permissions', {'name': 'read'}); // Nouvelle permission
print("Permissions par défaut insérées");
} else {
// Vérifier et ajouter les nouvelles permissions si elles n'existent pas
final newPermissions = ['manage', 'read'];
for (var permission in newPermissions) {
final existingPermission = await db.query('permissions', where: 'name = ?', whereArgs: [permission]);
if (existingPermission.isEmpty) {
await db.insert('permissions', {'name': permission});
print("Permission ajoutée: $permission");
}
}
}
}
Future<void> insertDefaultMenus() async {
final db = await database;
final existingMenus = await db.query('menu');
if (existingMenus.isEmpty) {
await db.insert('menu', {'name': 'Accueil', 'route': '/accueil'});
await db.insert('menu', {'name': 'Ajouter un utilisateur', 'route': '/ajouter-utilisateur'});
await db.insert('menu', {'name': 'Modifier/Supprimer un utilisateur', 'route': '/modifier-utilisateur'});
await db.insert('menu', {'name': 'Ajouter un produit', 'route': '/ajouter-produit'});
await db.insert('menu', {'name': 'Modifier/Supprimer un produit', 'route': '/modifier-produit'});
await db.insert('menu', {'name': 'Bilan', 'route': '/bilan'});
await db.insert('menu', {'name': 'Gérer les rôles', 'route': '/gerer-roles'});
await db.insert('menu', {'name': 'Gestion de stock', 'route': '/gestion-stock'});
await db.insert('menu', {'name': 'Historique', 'route': '/historique'});
await db.insert('menu', {'name': 'Déconnexion', 'route': '/deconnexion'});
print("Menus par défaut insérés");
final db = await database;
final existingMenus = await db.query('menu');
if (existingMenus.isEmpty) {
// Menus existants
await db.insert('menu', {'name': 'Accueil', 'route': '/accueil'});
await db.insert('menu', {'name': 'Ajouter un utilisateur', 'route': '/ajouter-utilisateur'});
await db.insert('menu', {'name': 'Modifier/Supprimer un utilisateur', 'route': '/modifier-utilisateur'});
await db.insert('menu', {'name': 'Ajouter un produit', 'route': '/ajouter-produit'});
await db.insert('menu', {'name': 'Modifier/Supprimer un produit', 'route': '/modifier-produit'});
await db.insert('menu', {'name': 'Bilan', 'route': '/bilan'});
await db.insert('menu', {'name': 'Gérer les rôles', 'route': '/gerer-roles'});
await db.insert('menu', {'name': 'Gestion de stock', 'route': '/gestion-stock'});
await db.insert('menu', {'name': 'Historique', 'route': '/historique'});
await db.insert('menu', {'name': 'Déconnexion', 'route': '/deconnexion'});
// Nouveaux menus ajoutés
await db.insert('menu', {'name': 'Nouvelle commande', 'route': '/nouvelle-commande'});
await db.insert('menu', {'name': 'Gérer les commandes', 'route': '/gerer-commandes'});
print("Menus par défaut insérés");
} else {
// Si des menus existent déjà, vérifier et ajouter les nouveaux menus manquants
await _addMissingMenus(db);
}
}
Future<void> _addMissingMenus(Database db) async {
final menusToAdd = [
{'name': 'Nouvelle commande', 'route': '/nouvelle-commande'},
{'name': 'Gérer les commandes', 'route': '/gerer-commandes'},
];
for (var menu in menusToAdd) {
final existing = await db.query(
'menu',
where: 'route = ?',
whereArgs: [menu['route']],
);
if (existing.isEmpty) {
await db.insert('menu', menu);
print("Menu ajouté: ${menu['name']}");
}
}
}
Future<void> insertDefaultRoles() async {
final db = await database;
@ -197,69 +238,94 @@ class AppDatabase {
}
}
// Assigner quelques permissions à l'Admin et à l'User
final viewPermission = await db.query('permissions', where: 'name = ?', whereArgs: ['view']);
final createPermission = await db.query('permissions', where: 'name = ?', whereArgs: ['create']);
final updatePermission = await db.query('permissions', where: 'name = ?', whereArgs: ['update']);
if (viewPermission.isNotEmpty) {
await db.insert('role_menu_permissions', {
'role_id': adminRoleId,
'menu_id': 1, // Assurez-vous que l'ID du menu est correct
'permission_id': viewPermission.first['id'],
});
await db.insert('role_menu_permissions', {
'role_id': userRoleId,
'menu_id': 1, // Assurez-vous que l'ID du menu est correct
'permission_id': viewPermission.first['id'],
});
}
if (createPermission.isNotEmpty) {
await db.insert('role_menu_permissions', {
'role_id': adminRoleId,
'menu_id': 1, // Assurez-vous que l'ID du menu est correct
'permission_id': createPermission.first['id'],
});
}
if (updatePermission.isNotEmpty) {
await db.insert('role_menu_permissions', {
'role_id': adminRoleId,
'menu_id': 1, // Assurez-vous que l'ID du menu est correct
'permission_id': updatePermission.first['id'],
});
}
// Assigner quelques permissions à l'Admin et à l'User pour les nouveaux menus
await _assignBasicPermissionsToRoles(db, adminRoleId, userRoleId);
print("Rôles par défaut créés et permissions assignées");
} else {
// Si les rôles existent déjà, vérifier et ajouter les permissions manquantes pour le Super Admin
final superAdminRole = await db.query('roles', where: 'designation = ?', whereArgs: ['Super Admin']);
if (superAdminRole.isNotEmpty) {
final superAdminRoleId = superAdminRole.first['id'] as int;
final permissions = await db.query('permissions');
final menus = await db.query('menu');
// Vérifier et ajouter les permissions manquantes pour le Super Admin
for (var menu in menus) {
for (var permission in permissions) {
final existingPermission = await db.query(
'role_menu_permissions',
where: 'role_id = ? AND menu_id = ? AND permission_id = ?',
whereArgs: [superAdminRoleId, menu['id'], permission['id']],
);
if (existingPermission.isEmpty) {
await db.insert('role_menu_permissions', {
'role_id': superAdminRoleId,
'menu_id': menu['id'],
'permission_id': permission['id'],
});
}
// Si les rôles existent déjà, vérifier et ajouter les permissions manquantes
await _updateExistingRolePermissions(db);
}
}
// Nouvelle méthode pour assigner les permissions de base aux nouveaux menus
Future<void> _assignBasicPermissionsToRoles(Database db, int adminRoleId, int userRoleId) async {
final viewPermission = await db.query('permissions', where: 'name = ?', whereArgs: ['view']);
final createPermission = await db.query('permissions', where: 'name = ?', whereArgs: ['create']);
final updatePermission = await db.query('permissions', where: 'name = ?', whereArgs: ['update']);
final managePermission = await db.query('permissions', where: 'name = ?', whereArgs: ['manage']);
// Récupérer les IDs des nouveaux menus
final nouvelleCommandeMenu = await db.query('menu', where: 'route = ?', whereArgs: ['/nouvelle-commande']);
final gererCommandesMenu = await db.query('menu', where: 'route = ?', whereArgs: ['/gerer-commandes']);
if (nouvelleCommandeMenu.isNotEmpty && createPermission.isNotEmpty) {
// Admin peut créer de nouvelles commandes
await db.insert('role_menu_permissions', {
'role_id': adminRoleId,
'menu_id': nouvelleCommandeMenu.first['id'],
'permission_id': createPermission.first['id'],
});
// User peut aussi créer de nouvelles commandes
await db.insert('role_menu_permissions', {
'role_id': userRoleId,
'menu_id': nouvelleCommandeMenu.first['id'],
'permission_id': createPermission.first['id'],
});
}
if (gererCommandesMenu.isNotEmpty && managePermission.isNotEmpty) {
// Admin peut gérer les commandes
await db.insert('role_menu_permissions', {
'role_id': adminRoleId,
'menu_id': gererCommandesMenu.first['id'],
'permission_id': managePermission.first['id'],
});
}
if (gererCommandesMenu.isNotEmpty && viewPermission.isNotEmpty) {
// User peut voir les commandes
await db.insert('role_menu_permissions', {
'role_id': userRoleId,
'menu_id': gererCommandesMenu.first['id'],
'permission_id': viewPermission.first['id'],
});
}
}
Future<void> _updateExistingRolePermissions(Database db) async {
final superAdminRole = await db.query('roles', where: 'designation = ?', whereArgs: ['Super Admin']);
if (superAdminRole.isNotEmpty) {
final superAdminRoleId = superAdminRole.first['id'] as int;
final permissions = await db.query('permissions');
final menus = await db.query('menu');
// Vérifier et ajouter les permissions manquantes pour le Super Admin sur tous les menus
for (var menu in menus) {
for (var permission in permissions) {
final existingPermission = await db.query(
'role_menu_permissions',
where: 'role_id = ? AND menu_id = ? AND permission_id = ?',
whereArgs: [superAdminRoleId, menu['id'], permission['id']],
);
if (existingPermission.isEmpty) {
await db.insert('role_menu_permissions', {
'role_id': superAdminRoleId,
'menu_id': menu['id'],
'permission_id': permission['id'],
});
}
}
}
print("Permissions manquantes ajoutées pour le Super Admin");
// Assigner les permissions de base aux autres rôles pour les nouveaux menus
final adminRole = await db.query('roles', where: 'designation = ?', whereArgs: ['Admin']);
final userRole = await db.query('roles', where: 'designation = ?', whereArgs: ['User']);
if (adminRole.isNotEmpty && userRole.isNotEmpty) {
await _assignBasicPermissionsToRoles(db, adminRole.first['id'] as int, userRole.first['id'] as int);
}
print("Permissions mises à jour pour tous les rôles");
}
}
@ -576,7 +642,7 @@ Future<void> deleteDatabaseFile() async {
final file = File(path);
if (await file.exists()) {
await file.delete();
print("Base de données supprimée");
print("Base de données utilisateur supprimée");
}
}
Future<void> assignRoleMenuPermission(int roleId, int menuId, int permissionId) async {

515
lib/Services/productDatabase.dart

@ -5,6 +5,8 @@ import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import '../Models/produit.dart';
import '../Models/client.dart';
class ProductDatabase {
static final ProductDatabase instance = ProductDatabase._init();
@ -25,6 +27,8 @@ class ProductDatabase {
Future<void> initDatabase() async {
_database = await _initDB('products2.db');
await _createDB(_database, 1);
await _insertDefaultClients();
await _insertDefaultCommandes();
}
Future<Database> _initDB(String filePath) async {
@ -33,74 +37,129 @@ class ProductDatabase {
bool dbExists = await File(path).exists();
if (!dbExists) {
ByteData data = await rootBundle.load('assets/database/$filePath');
List<int> bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await File(path).writeAsBytes(bytes);
try {
ByteData data = await rootBundle.load('assets/database/$filePath');
List<int> bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await File(path).writeAsBytes(bytes);
} catch (e) {
print('Pas de fichier DB dans assets, création nouvelle DB');
}
}
return await databaseFactoryFfi.openDatabase(path);
}
Future<void> _createDB(Database db, int version) async {
// Récupère la liste des colonnes de la table "products"
final tables = await db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'");
final tableNames = tables.map((row) => row['name'] as String).toList();
// Si la table "products" n'existe pas encore, on la crée entièrement
if (!tableNames.contains('products')) {
await db.execute('''
CREATE TABLE products(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
price REAL,
image TEXT,
category TEXT,
stock INTEGER,
description TEXT,
qrCode TEXT,
reference TEXT
)
''');
print("Table 'products' créée avec toutes les colonnes.");
} else {
// Vérifie si les colonnes "description", "qrCode" et "reference" existent déjà
Future<void> _createDB(Database db, int version) async {
final tables = await db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'");
final tableNames = tables.map((row) => row['name'] as String).toList();
// Table products (existante avec améliorations)
if (!tableNames.contains('products')) {
await db.execute('''
CREATE TABLE products(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price REAL NOT NULL,
image TEXT,
category TEXT NOT NULL,
stock INTEGER NOT NULL DEFAULT 0,
description TEXT,
qrCode TEXT,
reference TEXT UNIQUE
)
''');
print("Table 'products' créée.");
} else {
// Vérifier et ajouter les colonnes manquantes
await _updateProductsTable(db);
}
// Table clients
if (!tableNames.contains('clients')) {
await db.execute('''
CREATE TABLE clients(
id INTEGER PRIMARY KEY AUTOINCREMENT,
nom TEXT NOT NULL,
prenom TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
telephone TEXT NOT NULL,
adresse TEXT,
dateCreation TEXT NOT NULL,
actif INTEGER NOT NULL DEFAULT 1
)
''');
print("Table 'clients' créée.");
}
// Table commandes
if (!tableNames.contains('commandes')) {
await db.execute('''
CREATE TABLE commandes(
id INTEGER PRIMARY KEY AUTOINCREMENT,
clientId INTEGER NOT NULL,
dateCommande TEXT NOT NULL,
statut INTEGER NOT NULL DEFAULT 0,
montantTotal REAL NOT NULL,
notes TEXT,
dateLivraison TEXT,
FOREIGN KEY (clientId) REFERENCES clients(id) ON DELETE CASCADE
)
''');
print("Table 'commandes' créée.");
}
// Table détails commandes
if (!tableNames.contains('details_commandes')) {
await db.execute('''
CREATE TABLE details_commandes(
id INTEGER PRIMARY KEY AUTOINCREMENT,
commandeId INTEGER NOT NULL,
produitId INTEGER NOT NULL,
quantite INTEGER NOT NULL,
prixUnitaire REAL NOT NULL,
sousTotal REAL NOT NULL,
FOREIGN KEY (commandeId) REFERENCES commandes(id) ON DELETE CASCADE,
FOREIGN KEY (produitId) REFERENCES products(id) ON DELETE CASCADE
)
''');
print("Table 'details_commandes' créée.");
}
// Créer les index pour optimiser les performances
await _createIndexes(db);
}
Future<void> _updateProductsTable(Database db) async {
final columns = await db.rawQuery('PRAGMA table_info(products)');
final columnNames = columns.map((e) => e['name'] as String).toList();
// Ajoute la colonne "description" si elle n'existe pas
if (!columnNames.contains('description')) {
try {
await db.execute("ALTER TABLE products ADD COLUMN description TEXT");
print("Colonne 'description' ajoutée.");
} catch (e) {
print("Erreur ajout colonne description : $e");
}
await db.execute("ALTER TABLE products ADD COLUMN description TEXT");
print("Colonne 'description' ajoutée.");
}
// Ajoute la colonne "qrCode" si elle n'existe pas
if (!columnNames.contains('qrCode')) {
try {
await db.execute("ALTER TABLE products ADD COLUMN qrCode TEXT");
print("Colonne 'qrCode' ajoutée.");
} catch (e) {
print("Erreur ajout colonne qrCode : $e");
}
await db.execute("ALTER TABLE products ADD COLUMN qrCode TEXT");
print("Colonne 'qrCode' ajoutée.");
}
// Ajoute la colonne "reference" si elle n'existe pas
if (!columnNames.contains('reference')) {
try {
await db.execute("ALTER TABLE products ADD COLUMN reference TEXT");
print("Colonne 'reference' ajoutée.");
} catch (e) {
print("Erreur ajout colonne reference : $e");
}
await db.execute("ALTER TABLE products ADD COLUMN reference TEXT");
print("Colonne 'reference' ajoutée.");
}
}
}
Future<void> _createIndexes(Database db) async {
await db.execute('CREATE INDEX IF NOT EXISTS idx_products_category ON products(category)');
await db.execute('CREATE INDEX IF NOT EXISTS idx_products_reference ON products(reference)');
await db.execute('CREATE INDEX IF NOT EXISTS idx_commandes_client ON commandes(clientId)');
await db.execute('CREATE INDEX IF NOT EXISTS idx_commandes_date ON commandes(dateCommande)');
await db.execute('CREATE INDEX IF NOT EXISTS idx_details_commande ON details_commandes(commandeId)');
print("Index créés pour optimiser les performances.");
}
// =========================
// MÉTHODES PRODUCTS (existantes)
// =========================
Future<int> createProduct(Product product) async {
final db = await database;
return await db.insert('products', product.toMap());
@ -108,7 +167,7 @@ class ProductDatabase {
Future<List<Product>> getProducts() async {
final db = await database;
final maps = await db.query('products');
final maps = await db.query('products', orderBy: 'name ASC');
return List.generate(maps.length, (i) {
return Product.fromMap(maps[i]);
});
@ -135,7 +194,7 @@ class ProductDatabase {
Future<List<String>> getCategories() async {
final db = await database;
final result = await db.rawQuery('SELECT DISTINCT category FROM products');
final result = await db.rawQuery('SELECT DISTINCT category FROM products ORDER BY category');
return List.generate(
result.length, (index) => result[index]['category'] as String);
}
@ -143,7 +202,7 @@ class ProductDatabase {
Future<List<Product>> getProductsByCategory(String category) async {
final db = await database;
final maps = await db
.query('products', where: 'category = ?', whereArgs: [category]);
.query('products', where: 'category = ?', whereArgs: [category], orderBy: 'name ASC');
return List.generate(maps.length, (i) {
return Product.fromMap(maps[i]);
});
@ -154,19 +213,347 @@ class ProductDatabase {
return await db
.rawUpdate('UPDATE products SET stock = ? WHERE id = ?', [stock, id]);
}
// Ajouter cette méthode dans la classe ProductDatabase
Future<Product?> getProductByReference(String reference) async {
Future<Product?> getProductByReference(String reference) async {
final db = await database;
final maps = await db.query(
'products',
where: 'reference = ?',
whereArgs: [reference],
);
if (maps.isNotEmpty) {
return Product.fromMap(maps.first);
}
return null;
}
// =========================
// MÉTHODES CLIENTS
// =========================
Future<int> createClient(Client client) async {
final db = await database;
return await db.insert('clients', client.toMap());
}
Future<List<Client>> getClients() async {
final db = await database;
final maps = await db.query('clients', where: 'actif = 1', orderBy: 'nom ASC, prenom ASC');
return List.generate(maps.length, (i) {
return Client.fromMap(maps[i]);
});
}
Future<Client?> getClientById(int id) async {
final db = await database;
final maps = await db.query('clients', where: 'id = ?', whereArgs: [id]);
if (maps.isNotEmpty) {
return Client.fromMap(maps.first);
}
return null;
}
Future<int> updateClient(Client client) async {
final db = await database;
return await db.update(
'clients',
client.toMap(),
where: 'id = ?',
whereArgs: [client.id],
);
}
Future<int> deleteClient(int id) async {
final db = await database;
// Soft delete
return await db.update(
'clients',
{'actif': 0},
where: 'id = ?',
whereArgs: [id],
);
}
Future<List<Client>> searchClients(String query) async {
final db = await database;
final maps = await db.query(
'clients',
where: 'actif = 1 AND (nom LIKE ? OR prenom LIKE ? OR email LIKE ?)',
whereArgs: ['%$query%', '%$query%', '%$query%'],
orderBy: 'nom ASC, prenom ASC',
);
return List.generate(maps.length, (i) {
return Client.fromMap(maps[i]);
});
}
// =========================
// MÉTHODES COMMANDES
// =========================
Future<int> createCommande(Commande commande) async {
final db = await database;
return await db.insert('commandes', commande.toMap());
}
Future<List<Commande>> getCommandes() async {
final db = await database;
final maps = await db.rawQuery('''
SELECT c.*, cl.nom as clientNom, cl.prenom as clientPrenom, cl.email as clientEmail
FROM commandes c
LEFT JOIN clients cl ON c.clientId = cl.id
ORDER BY c.dateCommande DESC
''');
return List.generate(maps.length, (i) {
return Commande.fromMap(maps[i]);
});
}
Future<List<Commande>> getCommandesByClient(int clientId) async {
final db = await database;
final maps = await db.rawQuery('''
SELECT c.*, cl.nom as clientNom, cl.prenom as clientPrenom, cl.email as clientEmail
FROM commandes c
LEFT JOIN clients cl ON c.clientId = cl.id
WHERE c.clientId = ?
ORDER BY c.dateCommande DESC
''', [clientId]);
return List.generate(maps.length, (i) {
return Commande.fromMap(maps[i]);
});
}
Future<Commande?> getCommandeById(int id) async {
final db = await database;
final maps = await db.rawQuery('''
SELECT c.*, cl.nom as clientNom, cl.prenom as clientPrenom, cl.email as clientEmail
FROM commandes c
LEFT JOIN clients cl ON c.clientId = cl.id
WHERE c.id = ?
''', [id]);
if (maps.isNotEmpty) {
return Commande.fromMap(maps.first);
}
return null;
}
Future<int> updateCommande(Commande commande) async {
final db = await database;
return await db.update(
'commandes',
commande.toMap(),
where: 'id = ?',
whereArgs: [commande.id],
);
}
Future<int> updateStatutCommande(int commandeId, StatutCommande statut) async {
final db = await database;
return await db.update(
'commandes',
{'statut': statut.index},
where: 'id = ?',
whereArgs: [commandeId],
);
}
// =========================
// MÉTHODES DÉTAILS COMMANDES
// =========================
Future<int> createDetailCommande(DetailCommande detail) async {
final db = await database;
return await db.insert('details_commandes', detail.toMap());
}
Future<List<DetailCommande>> getDetailsCommande(int commandeId) async {
final db = await database;
final maps = await db.rawQuery('''
SELECT dc.*, p.name as produitNom, p.image as produitImage, p.reference as produitReference
FROM details_commandes dc
LEFT JOIN products p ON dc.produitId = p.id
WHERE dc.commandeId = ?
ORDER BY dc.id
''', [commandeId]);
return List.generate(maps.length, (i) {
return DetailCommande.fromMap(maps[i]);
});
}
// =========================
// MÉTHODES TRANSACTION COMPLÈTE
// =========================
Future<int> createCommandeComplete(Client client, Commande commande, List<DetailCommande> details) async {
final db = await database;
final maps = await db.query(
'products',
where: 'reference = ?',
whereArgs: [reference],
);
if (maps.isNotEmpty) {
return Product.fromMap(maps.first);
return await db.transaction((txn) async {
// Créer le client
final clientId = await txn.insert('clients', client.toMap());
// Créer la commande
final commandeMap = commande.toMap();
commandeMap['clientId'] = clientId;
final commandeId = await txn.insert('commandes', commandeMap);
// Créer les détails et mettre à jour le stock
for (var detail in details) {
final detailMap = detail.toMap();
detailMap['commandeId'] = commandeId; // Ajoute l'ID de la commande
await txn.insert('details_commandes', detailMap);
// Mettre à jour le stock du produit
await txn.rawUpdate(
'UPDATE products SET stock = stock - ? WHERE id = ?',
[detail.quantite, detail.produitId],
);
}
return commandeId;
});
}
// =========================
// STATISTIQUES
// =========================
Future<Map<String, dynamic>> getStatistiques() async {
final db = await database;
final totalClients = await db.rawQuery('SELECT COUNT(*) as count FROM clients WHERE actif = 1');
final totalCommandes = await db.rawQuery('SELECT COUNT(*) as count FROM commandes');
final totalProduits = await db.rawQuery('SELECT COUNT(*) as count FROM products');
final chiffreAffaires = await db.rawQuery('SELECT SUM(montantTotal) as total FROM commandes WHERE statut != 5'); // 5 = annulée
return {
'totalClients': totalClients.first['count'],
'totalCommandes': totalCommandes.first['count'],
'totalProduits': totalProduits.first['count'],
'chiffreAffaires': chiffreAffaires.first['total'] ?? 0.0,
};
}
// =========================
// DONNÉES PAR DÉFAUT
// =========================
Future<void> _insertDefaultClients() async {
final db = await database;
final existingClients = await db.query('clients');
if (existingClients.isEmpty) {
final defaultClients = [
Client(
nom: 'Dupont',
prenom: 'Jean',
email: 'jean.dupont@email.com',
telephone: '0123456789',
adresse: '123 Rue de la Paix, Paris',
dateCreation: DateTime.now(),
),
Client(
nom: 'Martin',
prenom: 'Marie',
email: 'marie.martin@email.com',
telephone: '0987654321',
adresse: '456 Avenue des Champs, Lyon',
dateCreation: DateTime.now(),
),
Client(
nom: 'Bernard',
prenom: 'Pierre',
email: 'pierre.bernard@email.com',
telephone: '0456789123',
adresse: '789 Boulevard Saint-Michel, Marseille',
dateCreation: DateTime.now(),
),
];
for (var client in defaultClients) {
await db.insert('clients', client.toMap());
}
print("Clients par défaut insérés");
}
}
Future<void> _insertDefaultCommandes() async {
final db = await database;
final existingCommandes = await db.query('commandes');
if (existingCommandes.isEmpty) {
// Récupérer quelques produits pour créer des commandes
final produits = await db.query('products', limit: 3);
final clients = await db.query('clients', limit: 3);
if (produits.isNotEmpty && clients.isNotEmpty) {
// Commande 1
final commande1Id = await db.insert('commandes', {
'clientId': clients[0]['id'],
'dateCommande': DateTime.now().subtract(Duration(days: 5)).toIso8601String(),
'statut': StatutCommande.livree.index,
'montantTotal': 150.0,
'notes': 'Commande urgente',
});
await db.insert('details_commandes', {
'commandeId': commande1Id,
'produitId': produits[0]['id'],
'quantite': 2,
'prixUnitaire': 75.0,
'sousTotal': 150.0,
});
// Commande 2
final commande2Id = await db.insert('commandes', {
'clientId': clients[1]['id'],
'dateCommande': DateTime.now().subtract(Duration(days: 2)).toIso8601String(),
'statut': StatutCommande.enPreparation.index,
'montantTotal': 225.0,
'notes': 'Livraison prévue demain',
});
if (produits.length > 1) {
await db.insert('details_commandes', {
'commandeId': commande2Id,
'produitId': produits[1]['id'],
'quantite': 3,
'prixUnitaire': 75.0,
'sousTotal': 225.0,
});
}
// Commande 3
final commande3Id = await db.insert('commandes', {
'clientId': clients[2]['id'],
'dateCommande': DateTime.now().subtract(Duration(hours: 6)).toIso8601String(),
'statut': StatutCommande.confirmee.index,
'montantTotal': 300.0,
'notes': 'Commande standard',
});
if (produits.length > 2) {
await db.insert('details_commandes', {
'commandeId': commande3Id,
'produitId': produits[2]['id'],
'quantite': 4,
'prixUnitaire': 75.0,
'sousTotal': 300.0,
});
}
print("Commandes par défaut insérées");
}
}
}
Future<void> close() async {
if (_database.isOpen) {
await _database.close();
}
}
// Ajoutez cette méthode temporaire pour supprimer la DB corrompue
Future<void> deleteDatabaseFile() async {
final documentsDirectory = await getApplicationDocumentsDirectory();
final path = join(documentsDirectory.path, 'products2.db');
final file = File(path);
if (await file.exists()) {
await file.delete();
print("Base de données product supprimée");
}
return null;
}
}

812
lib/Views/addProduct.dart

@ -1,812 +0,0 @@
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:file_picker/file_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:excel/excel.dart' hide Border;
import 'package:flutter/services.dart';
import '../Components/appDrawer.dart';
import '../Components/app_bar.dart';
import '../Models/produit.dart';
import '../Services/productDatabase.dart';
class AddProductPage extends StatefulWidget {
const AddProductPage({super.key});
@override
_AddProductPageState createState() => _AddProductPageState();
}
class _AddProductPageState extends State<AddProductPage> {
final TextEditingController _nameController = TextEditingController();
final TextEditingController _priceController = TextEditingController();
final TextEditingController _imageController = TextEditingController();
final TextEditingController _descriptionController = TextEditingController();
final TextEditingController _stockController = TextEditingController();
final List<String> _categories = ['Sucré', 'Salé', 'Jus', 'Gateaux', 'Non catégorisé'];
String? _selectedCategory;
File? _pickedImage;
String? _qrData;
String? _currentReference; // Ajout pour stocker la référence actuelle
late ProductDatabase _productDatabase;
// Variables pour la barre de progression
bool _isImporting = false;
double _importProgress = 0.0;
String _importStatusText = '';
@override
void initState() {
super.initState();
_productDatabase = ProductDatabase.instance;
_productDatabase.initDatabase();
_nameController.addListener(_updateQrData);
}
@override
void dispose() {
_nameController.removeListener(_updateQrData);
_nameController.dispose();
_priceController.dispose();
_imageController.dispose();
_descriptionController.dispose();
_stockController.dispose();
super.dispose();
}
// Méthode pour générer une référence unique
String _generateUniqueReference() {
final timestamp = DateTime.now().millisecondsSinceEpoch;
final randomSuffix = DateTime.now().microsecond.toString().padLeft(6, '0');
return 'PROD_${timestamp}${randomSuffix}';
}
void _updateQrData() {
if (_nameController.text.isNotEmpty) {
// Générer une nouvelle référence si elle n'existe pas encore
if (_currentReference == null) {
_currentReference = _generateUniqueReference();
}
setState(() {
// Utiliser la référence courante dans l'URL du QR code
_qrData = 'https://stock.guycom.mg/$_currentReference';
});
} else {
setState(() {
_currentReference = null;
_qrData = null;
});
}
}
Future<void> _selectImage() async {
final result = await FilePicker.platform.pickFiles(type: FileType.image);
if (result != null && result.files.single.path != null) {
setState(() {
_pickedImage = File(result.files.single.path!);
_imageController.text = _pickedImage!.path;
});
}
}
// Assurez-vous aussi que _generateAndSaveQRCode utilise bien la référence passée :
Future<String> _generateAndSaveQRCode(String reference) async {
final qrUrl = 'https://stock.guycom.mg/$reference'; // Utilise le paramètre reference
final validation = QrValidator.validate(
data: qrUrl,
version: QrVersions.auto,
errorCorrectionLevel: QrErrorCorrectLevel.L,
);
if (validation.status != QrValidationStatus.valid) {
throw Exception('Données QR invalides: ${validation.error}');
}
final qrCode = validation.qrCode!;
final painter = QrPainter.withQr(
qr: qrCode,
color: Colors.black,
emptyColor: Colors.white,
gapless: true,
);
final directory = await getApplicationDocumentsDirectory();
final path = '${directory.path}/$reference.png'; // Utilise le paramètre reference
try {
final picData = await painter.toImageData(2048, format: ImageByteFormat.png);
if (picData != null) {
await File(path).writeAsBytes(picData.buffer.asUint8List());
} else {
throw Exception('Impossible de générer l\'image QR');
}
} catch (e) {
throw Exception('Erreur lors de la génération du QR code: $e');
}
return path;
}
void _addProduct() async {
final name = _nameController.text.trim();
final price = double.tryParse(_priceController.text.trim()) ?? 0.0;
final image = _imageController.text.trim();
final category = _selectedCategory ?? 'Non catégorisé';
final description = _descriptionController.text.trim();
final stock = int.tryParse(_stockController.text.trim()) ?? 0;
if (name.isEmpty || price <= 0) {
Get.snackbar('Erreur', 'Nom et prix sont obligatoires');
return;
}
// Utiliser la référence générée ou en créer une nouvelle
String finalReference = _currentReference ?? _generateUniqueReference();
// Vérifier l'unicité de la référence en base
var existingProduct = await _productDatabase.getProductByReference(finalReference);
// Si la référence existe déjà, en générer une nouvelle
while (existingProduct != null) {
finalReference = _generateUniqueReference();
existingProduct = await _productDatabase.getProductByReference(finalReference);
}
// Mettre à jour la référence courante avec la référence finale
_currentReference = finalReference;
// Générer le QR code avec la référence finale
final qrPath = await _generateAndSaveQRCode(finalReference);
final product = Product(
name: name,
price: price,
image: image,
category: category,
description: description,
qrCode: qrPath,
reference: finalReference, // Utiliser la référence finale
stock: stock,
);
try {
await _productDatabase.createProduct(product);
Get.snackbar('Succès', 'Produit ajouté avec succès\nRéférence: $finalReference');
setState(() {
_nameController.clear();
_priceController.clear();
_imageController.clear();
_descriptionController.clear();
_stockController.clear();
_selectedCategory = null;
_pickedImage = null;
_qrData = null;
_currentReference = null; // Reset de la référence
});
} catch (e) {
Get.snackbar('Erreur', 'Ajout du produit échoué : $e');
print(e);
}
}
// Méthode pour réinitialiser l'état d'importation
void _resetImportState() {
setState(() {
_isImporting = false;
_importProgress = 0.0;
_importStatusText = '';
});
}
Future<void> _importFromExcel() async {
try {
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['xlsx', 'xls','csv'],
allowMultiple: false,
);
if (result == null || result.files.isEmpty) {
Get.snackbar('Annulé', 'Aucun fichier sélectionné');
return;
}
// Démarrer la progression
setState(() {
_isImporting = true;
_importProgress = 0.0;
_importStatusText = 'Lecture du fichier...';
});
final file = File(result.files.single.path!);
// Vérifier que le fichier existe
if (!await file.exists()) {
_resetImportState();
Get.snackbar('Erreur', 'Le fichier sélectionné n\'existe pas');
return;
}
setState(() {
_importProgress = 0.1;
_importStatusText = 'Vérification du fichier...';
});
final bytes = await file.readAsBytes();
// Vérifier que le fichier n'est pas vide
if (bytes.isEmpty) {
_resetImportState();
Get.snackbar('Erreur', 'Le fichier Excel est vide');
return;
}
setState(() {
_importProgress = 0.2;
_importStatusText = 'Décodage du fichier Excel...';
});
Excel excel;
try {
// Initialisation
setState(() {
_isImporting = true;
_importProgress = 0.0;
_importStatusText = 'Initialisation...';
});
// Petit délai pour permettre au build de s'exécuter
await Future.delayed(Duration(milliseconds: 50));
excel = Excel.decodeBytes(bytes);
} catch (e) {
_resetImportState();
debugPrint('Erreur décodage Excel: $e');
if (e.toString().contains('styles') || e.toString().contains('Damaged')) {
_showExcelCompatibilityError();
return;
} else {
Get.snackbar('Erreur', 'Impossible de lire le fichier Excel. Format non supporté.');
return;
}
}
if (excel.tables.isEmpty) {
_resetImportState();
Get.snackbar('Erreur', 'Le fichier Excel ne contient aucune feuille');
return;
}
setState(() {
_importProgress = 0.3;
_importStatusText = 'Analyse des données...';
});
int successCount = 0;
int errorCount = 0;
List<String> errorMessages = [];
// Prendre la première feuille disponible
final sheetName = excel.tables.keys.first;
final sheet = excel.tables[sheetName]!;
if (sheet.rows.isEmpty) {
_resetImportState();
Get.snackbar('Erreur', 'La feuille Excel est vide');
return;
}
final totalRows = sheet.rows.length - 1; // -1 pour exclure l'en-tête
setState(() {
_importStatusText = 'Importation en cours... (0/$totalRows)';
});
// Ignorer la première ligne (en-têtes) et traiter les données
for (var i = 1; i < sheet.rows.length; i++) {
try {
// Mettre à jour la progression
final currentProgress = 0.3 + (0.6 * (i - 1) / totalRows);
setState(() {
_importProgress = currentProgress;
_importStatusText = 'Importation en cours... (${i - 1}/$totalRows)';
});
// Petite pause pour permettre à l'UI de se mettre à jour
await Future.delayed(const Duration(milliseconds: 10));
final row = sheet.rows[i];
// Vérifier que la ligne a au moins les colonnes obligatoires (nom et prix)
if (row.isEmpty || row.length < 2) {
errorCount++;
errorMessages.add('Ligne ${i + 1}: Données insuffisantes');
continue;
}
// Extraire les valeurs avec vérifications sécurisées
final nameCell = row[0];
final priceCell = row[1];
// Extraction sécurisée des valeurs
String? nameValue;
String? priceValue;
if (nameCell?.value != null) {
nameValue = nameCell!.value.toString().trim();
}
if (priceCell?.value != null) {
priceValue = priceCell!.value.toString().trim();
}
if (nameValue == null || nameValue.isEmpty) {
errorCount++;
errorMessages.add('Ligne ${i + 1}: Nom du produit manquant');
continue;
}
if (priceValue == null || priceValue.isEmpty) {
errorCount++;
errorMessages.add('Ligne ${i + 1}: Prix manquant');
continue;
}
final name = nameValue;
// Remplacer les virgules par des points pour les décimaux
final price = double.tryParse(priceValue.replaceAll(',', '.'));
if (price == null || price <= 0) {
errorCount++;
errorMessages.add('Ligne ${i + 1}: Prix invalide ($priceValue)');
continue;
}
// Extraire les autres colonnes optionnelles de manière sécurisée
String category = 'Non catégorisé';
if (row.length > 2 && row[2]?.value != null) {
final categoryValue = row[2]!.value.toString().trim();
if (categoryValue.isNotEmpty) {
category = categoryValue;
}
}
String description = '';
if (row.length > 3 && row[3]?.value != null) {
description = row[3]!.value.toString().trim();
}
int stock = 0;
if (row.length > 4 && row[4]?.value != null) {
final stockStr = row[4]!.value.toString().trim();
stock = int.tryParse(stockStr) ?? 0;
}
// Générer une référence unique et vérifier son unicité
String reference = _generateUniqueReference();
// Vérifier l'unicité en base de données
var existingProduct = await _productDatabase.getProductByReference(reference);
while (existingProduct != null) {
reference = _generateUniqueReference();
existingProduct = await _productDatabase.getProductByReference(reference);
}
// Créer le produit
final product = Product(
name: name,
price: price,
image: '', // Pas d'image lors de l'import
category: category,
description: description,
stock: stock,
qrCode: '', // Sera généré après
reference: reference,
);
// Générer et sauvegarder le QR code avec la nouvelle URL
setState(() {
_importStatusText = 'Génération QR Code... (${i - 1}/$totalRows)';
});
final qrPath = await _generateAndSaveQRCode(reference);
product.qrCode = qrPath;
// Sauvegarder en base de données
await _productDatabase.createProduct(product);
successCount++;
} catch (e) {
errorCount++;
errorMessages.add('Ligne ${i + 1}: Erreur de traitement - $e');
debugPrint('Erreur ligne ${i + 1}: $e');
}
}
// Finalisation
setState(() {
_importProgress = 1.0;
_importStatusText = 'Finalisation...';
});
await Future.delayed(const Duration(milliseconds: 500));
// Réinitialiser l'état d'importation
_resetImportState();
// Afficher le résultat
String message = '$successCount produits importés avec succès';
if (errorCount > 0) {
message += ', $errorCount erreurs';
// Afficher les détails des erreurs si pas trop nombreuses
if (errorMessages.length <= 5) {
message += ':\n${errorMessages.join('\n')}';
}
}
Get.snackbar(
'Importation terminée',
message,
duration: const Duration(seconds: 6),
colorText: Colors.white,
backgroundColor: successCount > 0 ? Colors.green : Colors.orange,
);
} catch (e) {
_resetImportState();
Get.snackbar('Erreur', 'Erreur lors de l\'importation Excel: $e');
debugPrint('Erreur générale import Excel: $e');
}
}
void _showExcelCompatibilityError() {
Get.dialog(
AlertDialog(
title: const Text('Fichier Excel incompatible'),
content: const Text(
'Ce fichier Excel contient des éléments qui ne sont pas compatibles avec notre système d\'importation.\n\n'
'Solutions recommandées :\n'
'• Téléchargez notre modèle Excel et copiez-y vos données\n'
'• Ou exportez votre fichier en format simple: Classeur Excel .xlsx depuis Excel\n'
'• Ou créez un nouveau fichier Excel simple sans formatage complexe'
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: const Text('Annuler'),
),
TextButton(
onPressed: () {
Get.back();
_downloadExcelTemplate();
},
child: const Text('Télécharger modèle'),
style: TextButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
),
),
],
),
);
}
Future<void> _downloadExcelTemplate() async {
try {
// Créer un fichier Excel temporaire comme modèle
final excel = Excel.createExcel();
// Supprimer la feuille par défaut et créer une nouvelle
excel.delete('Sheet1');
excel.copy('Sheet1', 'Produits');
excel.delete('Sheet1');
final sheet = excel['Produits'];
// Ajouter les en-têtes avec du style
final headers = ['Nom', 'Prix', 'Catégorie', 'Description', 'Stock'];
for (int i = 0; i < headers.length; i++) {
final cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: 0));
cell.value = headers[i];
cell.cellStyle = CellStyle(
bold: true,
backgroundColorHex: '#E8F4FD',
);
}
// Ajouter des exemples
final examples = [
['Croissant', '1.50', 'Sucré', 'Délicieux croissant beurré', '20'],
['Sandwich jambon', '4.00', 'Salé', 'Sandwich fait maison', '15'],
['Jus d\'orange', '2.50', 'Jus', 'Jus d\'orange frais', '30'],
['Gâteau chocolat', '18.00', 'Gateaux', 'Gâteau au chocolat portion 8 personnes', '5'],
];
for (int row = 0; row < examples.length; row++) {
for (int col = 0; col < examples[row].length; col++) {
final cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: col, rowIndex: row + 1));
cell.value = examples[row][col];
}
}
// Ajuster la largeur des colonnes
sheet.setColWidth(0, 20); // Nom
sheet.setColWidth(1, 10); // Prix
sheet.setColWidth(2, 15); // Catégorie
sheet.setColWidth(3, 30); // Description
sheet.setColWidth(4, 10); // Stock
// Sauvegarder en mémoire
final bytes = excel.save();
if (bytes == null) {
Get.snackbar('Erreur', 'Impossible de créer le fichier modèle');
return;
}
// Demander sauvegarder
final String? outputFile = await FilePicker.platform.saveFile(
fileName: 'modele_import_produits.xlsx',
allowedExtensions: ['xlsx'],
type: FileType.custom,
);
if (outputFile != null) {
try {
await File(outputFile).writeAsBytes(bytes);
Get.snackbar(
'Succès',
'Modèle téléchargé avec succès\n$outputFile',
duration: const Duration(seconds: 4),
backgroundColor: Colors.green,
colorText: Colors.white,
);
} catch (e) {
Get.snackbar('Erreur', 'Impossible d\'écrire le fichier: $e');
}
}
} catch (e) {
Get.snackbar('Erreur', 'Erreur lors de la création du modèle: $e');
debugPrint('Erreur création modèle Excel: $e');
}
}
Widget _displayImage() {
if (_pickedImage != null) {
return ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Image.file(
_pickedImage!,
width: 100,
height: 100,
fit: BoxFit.cover,
),
);
} else {
return Container(
width: 100,
height: 100,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.image, size: 32, color: Colors.grey),
Text('Aucune image', style: TextStyle(color: Colors.grey)),
],
),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(8.0),
));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CustomAppBar(title: 'Ajouter un produit'),
drawer: CustomDrawer(),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Ajouter un produit', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
// Boutons d'importation
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _isImporting ? null : _importFromExcel,
icon: const Icon(Icons.upload),
label: const Text('Importer depuis Excel'),
),
),
const SizedBox(width: 10),
TextButton(
onPressed: _isImporting ? null : _downloadExcelTemplate,
child: const Text('Modèle'),
),
],
),
const SizedBox(height: 16),
// Barre de progression
if (_isImporting) ...[
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue.shade200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Importation en cours...',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.blue.shade800,
),
),
const SizedBox(height: 8),
LinearProgressIndicator(
value: _importProgress,
backgroundColor: Colors.blue.shade100,
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue.shade600),
),
const SizedBox(height: 8),
Text(
_importStatusText,
style: TextStyle(
fontSize: 14,
color: Colors.blue.shade700,
),
),
const SizedBox(height: 8),
Text(
'${(_importProgress * 100).round()}%',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.blue.shade600,
),
),
],
),
),
const SizedBox(height: 16),
],
const Divider(),
const SizedBox(height: 16),
// Formulaire d'ajout manuel
TextField(
controller: _nameController,
enabled: !_isImporting,
decoration: const InputDecoration(
labelText: 'Nom du produit*',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextField(
controller: _priceController,
enabled: !_isImporting,
keyboardType: TextInputType.numberWithOptions(decimal: true),
decoration: const InputDecoration(
labelText: 'Prix*',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextField(
controller: _stockController,
enabled: !_isImporting,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: 'Stock',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
// Section image (optionnelle)
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: TextField(
controller: _imageController,
enabled: !_isImporting,
decoration: const InputDecoration(
labelText: 'Chemin de l\'image (optionnel)',
border: OutlineInputBorder(),
),
readOnly: true,
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: _isImporting ? null : _selectImage,
child: const Text('Sélectionner'),
),
],
),
const SizedBox(height: 16),
_displayImage(),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
value: _selectedCategory,
items: _categories
.map((c) => DropdownMenuItem(value: c, child: Text(c)))
.toList(),
onChanged: _isImporting ? null : (value) => setState(() => _selectedCategory = value),
decoration: const InputDecoration(
labelText: 'Catégorie',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
TextField(
controller: _descriptionController,
enabled: !_isImporting,
maxLines: 3,
decoration: const InputDecoration(
labelText: 'Description (optionnel)',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
if (_qrData != null) ...[
const Text('Aperçu du QR Code :'),
const SizedBox(height: 8),
Center(
child: QrImageView(
data: _qrData!,
version: QrVersions.auto,
size: 120,
),
),
const SizedBox(height: 8),
Center(
child: Text(
_qrData!,
style: const TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
),
),
],
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isImporting ? null : _addProduct,
child: const Text('Ajouter le produit'),
),
),
],
),
),
);
}
}

1329
lib/Views/commandManagement.dart

File diff suppressed because it is too large

623
lib/Views/newCommand.dart

@ -0,0 +1,623 @@
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();
}
}

140
lib/Views/produitsCard.dart

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:quantity_input/quantity_input.dart';
import 'package:youmazgestion/Models/produit.dart';
class ProductCard extends StatefulWidget {
@ -163,7 +162,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
),
],
),
child: Row(
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
@ -300,81 +299,84 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
const SizedBox(width: 8),
Expanded(
child: GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onTapCancel: _onTapCancel,
onTap: () {
widget.onAddToCart(widget.product, selectedQuantity);
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onTapCancel: _onTapCancel,
onTap: () {
widget.onAddToCart(widget.product, selectedQuantity);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(
Icons.shopping_cart,
color: Colors.white,
),
const SizedBox(width: 8),
Expanded(
child: Text(
'${widget.product.name} (x$selectedQuantity) ajouté au panier',
overflow: TextOverflow.ellipsis,
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(
Icons.shopping_cart,
color: Colors.white,
),
),
],
),
backgroundColor: Colors.green,
duration: const Duration(seconds: 1),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
const SizedBox(width: 8),
Expanded(
child: Text(
'${widget.product.name} (x$selectedQuantity) ajouté au panier',
overflow: TextOverflow.ellipsis,
),
),
],
),
backgroundColor: Colors.green,
duration: const Duration(seconds: 1),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
},
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 12,
),
);
},
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 12,
),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [
Color.fromARGB(255, 4, 54, 95),
Color.fromARGB(255, 6, 80, 140),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [
Color.fromARGB(255, 4, 54, 95),
Color.fromARGB(255, 6, 80, 140),
],
),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: const Color.fromARGB(255, 4, 54, 95).withOpacity(0.3),
blurRadius: 6,
offset: const Offset(0, 3),
),
],
),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: const Color.fromARGB(255, 4, 54, 95).withOpacity(0.3),
blurRadius: 6,
offset: const Offset(0, 3),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.add_shopping_cart,
color: Colors.white,
size: 16,
),
const SizedBox(width: 6),
const Flexible(
child: Text(
'Ajouter',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 12,
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.add_shopping_cart,
color: Colors.white,
size: 16,
),
const SizedBox(width: 6),
const Flexible(
child: Text(
'Ajouter',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 12,
),
overflow: TextOverflow.ellipsis,
),
overflow: TextOverflow.ellipsis,
),
),
],
],
),
),
),
),

455
lib/accueil.dart

@ -2,7 +2,6 @@ import 'dart:io';
import 'package:intl/intl.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:quantity_input/quantity_input.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:youmazgestion/Views/particles.dart' show ParticleBackground;
import 'package:youmazgestion/Views/produitsCard.dart';
@ -39,27 +38,21 @@ class _AccueilPageState extends State<AccueilPage> {
int selectedQuantity = 1;
double totalCartPrice = 0;
double amountPaid = 0;
final TextEditingController _amountController = TextEditingController();
@override
void initState() {
super.initState();
initorder();
initwork();
_initializeDatabases();
loadUserData();
productsFuture = _initDatabaseAndFetchProducts();
_initializeRegister();
super.initState();
_initializeDatabases();
loadUserData();
productsFuture = _initDatabaseAndFetchProducts();
}
Future<void> _initializeDatabases() async {
await orderDatabase.initDatabase();
await workDatabase.initDatabase(); // Attendre l'initialisation complète
await _initializeRegister();
}
Future<void> _initializeDatabases() async {
await orderDatabase.initDatabase();
await workDatabase.initDatabase();
await _initializeRegister();
}
Future<void> _initializeRegister() async {
if (!MyApp.isRegisterOpen) {
@ -86,6 +79,30 @@ Future<void> _initializeDatabases() async {
final dateTime = DateTime.now().toString();
String user = userController.username;
if (selectedProducts.isEmpty) {
Get.snackbar(
'Panier vide',
'Ajoutez des produits avant de passer commande.',
snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 3),
backgroundColor: Colors.red,
colorText: Colors.white,
);
return;
}
if (amountPaid < totalPrice) {
Get.snackbar(
'Paiement incomplet',
'Le montant payé est insuffisant.',
snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 3),
backgroundColor: Colors.red,
colorText: Colors.white,
);
return;
}
orderId = await orderDatabase.insertOrder(
totalPrice, dateTime, MyApp.startDate!, user);
@ -100,7 +117,14 @@ Future<void> _initializeDatabases() async {
final updatedStock = product.stock! - quantity;
await productDatabase.updateStock(product.id!, updatedStock);
}
// Afficher le ticket et réinitialiser le panier
showTicketPage();
setState(() {
selectedProducts.clear();
_amountController.clear();
amountPaid = 0;
});
}
Future<Map<String, List<Product>>> _initDatabaseAndFetchProducts() async {
@ -119,14 +143,6 @@ Future<void> _initializeDatabases() async {
return productsByCategory;
}
void initorder() async {
await orderDatabase.initDatabase();
}
void initwork() async {
await workDatabase.initDatabase();
}
double calculateTotalPrice() {
double totalPrice = 0;
for (final cartItem in selectedProducts) {
@ -159,49 +175,24 @@ Future<void> _initializeDatabases() async {
}
void showTicketPage() {
final double totalCartPrice = calculateTotalPrice();
if (selectedProducts.isNotEmpty) {
if (amountPaid >= totalCartPrice) {
Get.offAll(TicketPage(
businessName: 'Youmaz',
businessAddress:
'quartier escale, Diourbel, Sénégal, en face de Sonatel',
businessPhoneNumber: '77 446 92 68',
cartItems: selectedProducts,
totalCartPrice: totalCartPrice,
amountPaid: amountPaid,
));
} else {
Get.snackbar(
'Paiement incomplet',
'Le montant payé est insuffisant.',
snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 3),
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
} else {
Get.snackbar(
'Panier vide',
'Ajoutez des produits avant de passer commande.',
snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 3),
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
Get.offAll(TicketPage(
businessName: 'Youmaz',
businessAddress:
'quartier escale, Diourbel, Sénégal, en face de Sonatel',
businessPhoneNumber: '77 446 92 68',
cartItems: selectedProducts,
totalCartPrice: calculateTotalPrice(),
amountPaid: amountPaid,
));
}
@override
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(
title: "Accueil",
subtitle: Text('Bienvenue $username ! (Rôle: $role)'),
subtitle: Text('Bienvenue $username ! (Rôle: $role)',
style: const TextStyle(color: Colors.white70, fontSize: 14)),
),
drawer: CustomDrawer(),
body: ParticleBackground(
@ -216,98 +207,159 @@ Future<void> _initializeDatabases() async {
future: productsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
return const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
Color.fromARGB(255, 4, 54, 95),),
));
} else if (snapshot.hasError) {
return const Center(child: Text("Erreur de chargement"));
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error, color: Colors.red, size: 48),
SizedBox(height: 16),
Text("Erreur de chargement des produits",
style: TextStyle(fontSize: 16, color: Colors.white)),
],
));
} else if (snapshot.hasData) {
final productsByCategory = snapshot.data!;
final categories = productsByCategory.keys.toList();
return Row(
children: [
// Section produits
Expanded(
flex: 3,
child: ListView.builder(
itemCount: categories.length,
itemBuilder: (context, index) {
final category = categories[index];
final products = productsByCategory[category]!;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
category,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
child: Container(
padding:const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.9),
borderRadius: const BorderRadius.only(
topRight: Radius.circular(20),
),
),
child: ListView.builder(
itemCount: categories.length,
itemBuilder: (context, index) {
final category = categories[index];
final products = productsByCategory[category]!;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.symmetric(vertical: 8),
padding:const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Color.fromARGB(255, 4, 54, 95),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: Offset(0, 2),)
],
),
child: Center(
child: Text(
category,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
),
GridView.builder(
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
childAspectRatio: 1,
GridView.builder(
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
childAspectRatio: 0.9,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ProductCard(
product: product,
onAddToCart: (product, quantity) {
addToCartWithDetails(product, quantity);
},
);
},
),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ProductCard(
product: product,
onAddToCart: (product, quantity) {
addToCartWithDetails(product, quantity);
},
);
},
),
],
);
},
),
],
);
},
),
),
Expanded(
flex: 1,
// Section panier
),
Expanded(flex: 1,
child: Container(
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.only(
borderRadius:const BorderRadius.only(
topLeft: Radius.circular(20),
bottomLeft: Radius.circular(20),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
spreadRadius: 2,
),
],
),
padding: const EdgeInsets.all(16),
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'Panier',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Color.fromARGB(255, 4, 54, 95),
borderRadius: BorderRadius.circular(12),
),
child:const Text(
'Panier',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
SizedBox(height: 16),
// Liste des produits dans le panier
Expanded(
child: selectedProducts.isEmpty
? const Center(
child: Text(
"Votre panier est vide",
style: TextStyle(fontSize: 16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.shopping_cart,
size: 48, color: Colors.grey),
SizedBox(height: 16),
Text(
"Votre panier est vide",
style: TextStyle(
fontSize: 16,
color: Colors.grey),
),
],
),
)
: ListView.builder(
@ -315,86 +367,135 @@ Future<void> _initializeDatabases() async {
itemBuilder: (context, index) {
final cartItem = selectedProducts[index];
return Card(
margin: const EdgeInsets.symmetric(
vertical: 4),
margin: EdgeInsets.symmetric(vertical: 4),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 2,
child: ListTile(
contentPadding: EdgeInsets.symmetric(
horizontal: 12, vertical: 4),
leading: Icon(Icons.shopping_basket,
color: Color.fromARGB(255, 4, 54, 95),),
title: Text(
cartItem.product.name,
style: const TextStyle(
fontWeight: FontWeight.bold),
fontWeight: FontWeight.bold),
),
subtitle: Text(
'${NumberFormat('#,##0').format(cartItem.product.price)} FCFA x ${cartItem.quantity}',
style: const TextStyle(
fontSize: 14),
style:const TextStyle(fontSize: 14),
),
trailing: IconButton(
icon: const Icon(Icons.delete,
color: Colors.red),
onPressed: () {
setState(() {
selectedProducts
.removeAt(index);
});
},
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${NumberFormat('#,##0').format(cartItem.product.price * cartItem.quantity)}',
style:const TextStyle(
fontWeight: FontWeight.bold),
),
const SizedBox(width: 8),
IconButton(
icon: const Icon(Icons.delete,
color: Colors.red),
onPressed: () {
setState(() {
selectedProducts
.removeAt(index);
});
},
),
],
),
),
);
},
),
),
const Divider(thickness: 1),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text(
'Total: ${NumberFormat('#,##0.00').format(calculateTotalPrice())} FCFA',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
TextField(
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: 'Montant payé',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
filled: true,
fillColor: Colors.white,
),
onChanged: (value) {
amountPaid = double.tryParse(value) ?? 0;
},
),
const SizedBox(height: 16),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
// Total et paiement
Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: Offset(0, 2),
)
],
),
onPressed: saveOrderToDatabase,
child: const Text(
'Valider la commande',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Total:',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold)),
Text(
'${NumberFormat('#,##0.00').format(calculateTotalPrice())} FCFA',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 4, 54, 95),),
),
],
),
const SizedBox(height: 12),
TextField(
controller: _amountController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: 'Montant payé',
prefixIcon: Icon(Icons.attach_money),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
filled: true,
fillColor: Colors.grey[100],
),
onChanged: (value) {
setState(() {
amountPaid = double.tryParse(value) ?? 0;
});
},
),
SizedBox(height: 16),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
padding: EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: saveOrderToDatabase,
icon:const Icon(Icons.check_circle),
label:const Text(
'Valider la commande',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
],
),
),
],
),
),
),
),)
],
);
} else {
return const Center(child: Text("Aucun produit disponible"));
return const Center(
child: Text("Aucun produit disponible",
style: TextStyle(color: Colors.white)),
);
}
},
),
@ -402,4 +503,10 @@ Future<void> _initializeDatabases() async {
),
);
}
@override
void dispose() {
_amountController.dispose();
super.dispose();
}
}

1
lib/main.dart

@ -12,6 +12,7 @@ void main() async {
try {
// Initialiser les bases de données une seule fois
// await AppDatabase.instance.deleteDatabaseFile();
//await ProductDatabase.instance.deleteDatabaseFile();
await ProductDatabase.instance.initDatabase();
await AppDatabase.instance.initDatabase();

Loading…
Cancel
Save