From e739df38113e924ea5d177112b74b886e4937c51 Mon Sep 17 00:00:00 2001 From: "b.razafimandimbihery" Date: Tue, 27 May 2025 00:01:00 +0300 Subject: [PATCH] dernier mis a jour --- assets/database/roles.db | 0 lib/Components/appDrawer.dart | 207 ++++++------ lib/Models/Permission.dart | 16 + lib/Models/Role.dart | 20 ++ lib/Models/users.dart | 26 +- lib/Services/app_database.dart | 477 +++++++++++++++++++++++++++ lib/Services/authDatabase.dart | 147 --------- lib/Views/addProduct.dart | 288 ++++++----------- lib/Views/editUser.dart | 352 ++++++++++++-------- lib/Views/gestionProduct.dart | 2 +- lib/Views/gestionRole.dart | 147 +++++++++ lib/Views/listUser.dart | 8 +- lib/Views/loginPage.dart | 212 +++++++++--- lib/Views/produitsCard.dart | 449 ++++++++++++++++++++++++++ lib/Views/registrationPage.dart | 496 +++++++++++++++++++++-------- lib/accueil.dart | 291 ++++------------- lib/controller/userController.dart | 15 + lib/main.dart | 40 ++- pubspec.lock | 16 +- pubspec.yaml | 1 + 20 files changed, 2207 insertions(+), 1003 deletions(-) create mode 100644 assets/database/roles.db create mode 100644 lib/Models/Permission.dart create mode 100644 lib/Models/Role.dart create mode 100644 lib/Services/app_database.dart delete mode 100644 lib/Services/authDatabase.dart create mode 100644 lib/Views/gestionRole.dart create mode 100644 lib/Views/produitsCard.dart diff --git a/assets/database/roles.db b/assets/database/roles.db new file mode 100644 index 0000000..e69de29 diff --git a/lib/Components/appDrawer.dart b/lib/Components/appDrawer.dart index f9f9c43..1fce330 100644 --- a/lib/Components/appDrawer.dart +++ b/lib/Components/appDrawer.dart @@ -2,24 +2,26 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:youmazgestion/Views/historique.dart'; - -import '../Views/addProduct.dart'; -import '../Views/bilanMois.dart'; -import '../Views/gestionProduct.dart'; -import '../Views/gestionStock.dart'; -import '../Views/listUser.dart'; -import '../Views/loginPage.dart'; -import '../Views/registrationPage.dart'; -import '../accueil.dart'; -import '../controller/userController.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/registrationPage.dart'; +import 'package:youmazgestion/Views/gestionRole.dart'; +import 'package:youmazgestion/accueil.dart'; +import 'package:youmazgestion/controller/userController.dart'; class CustomDrawer extends StatelessWidget { final UserController userController = Get.find(); -Future clearUserData() async { - final prefs = await SharedPreferences.getInstance(); - await prefs.remove('username'); - await prefs.remove('role'); -} + + Future clearUserData() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('username'); + await prefs.remove('role'); + } + CustomDrawer({super.key}); @override @@ -30,25 +32,25 @@ Future clearUserData() async { children: [ GetBuilder( builder: (controller) => UserAccountsDrawerHeader( - accountEmail: Text(controller.email), - accountName: Text(controller.name), - currentAccountPicture: const CircleAvatar( - backgroundImage: AssetImage("assets/youmaz2.png"), + accountEmail: Text(controller.email), + accountName: Text(controller.name), + currentAccountPicture: const CircleAvatar( + backgroundImage: AssetImage("assets/youmaz2.png"), + ), + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [Colors.white, Color.fromARGB(255, 4, 54, 95)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, ), - decoration: const BoxDecoration( - gradient: LinearGradient( - colors: [Colors.white, const Color.fromARGB(255, 4, 54, 95)], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - )), + ), + ), ), ListTile( leading: const Icon(Icons.home), iconColor: Colors.lightBlueAccent, title: const Text("Accueil"), onTap: () { - // Action lorsque l'utilisateur clique sur "Accueil" Get.to(const AccueilPage()); }, ), @@ -56,18 +58,20 @@ Future clearUserData() async { leading: const Icon(Icons.person_add), iconColor: Colors.green, title: const Text("Ajouter un utilisateur"), - onTap: () { - if (userController.role == "admin") { + onTap: () async { + bool hasPermission = await userController.hasAnyPermission(['create']); + if (hasPermission) { Get.to(const RegistrationPage()); } else { Get.snackbar( - "Accés refusé", - backgroundColor: Colors.red, - colorText: Colors.white, - icon: const Icon(Icons.error), - duration: const Duration(seconds: 3), - snackPosition: SnackPosition.TOP, - "Vous n'avez pas les droits pour ajouter un utilisateur"); + "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, + ); } }, ), @@ -75,19 +79,20 @@ Future clearUserData() async { leading: const Icon(Icons.supervised_user_circle), iconColor: const Color.fromARGB(255, 4, 54, 95), title: const Text("Modifier/Supprimer un utilisateur"), - onTap: () { - // Action lorsque l'utilisateur clique sur "Modifier/Supprimer un utilisateur" - if (userController.role == "admin") { + onTap: () async { + bool hasPermission = await userController.hasAnyPermission(['update', 'delete']); + if (hasPermission) { Get.to(const ListUserPage()); } else { Get.snackbar( - "Accés refusé", - backgroundColor: Colors.red, - colorText: Colors.white, - icon: const Icon(Icons.error), - duration: const Duration(seconds: 3), - snackPosition: SnackPosition.TOP, - "Vous n'avez pas les droits pour modifier/supprimer un utilisateur"); + "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, + ); } }, ), @@ -95,19 +100,20 @@ Future clearUserData() async { leading: const Icon(Icons.add), iconColor: Colors.indigoAccent, title: const Text("Ajouter un produit"), - onTap: () { - if (userController.role == "admin") { - // Action lorsque l'utilisateur clique sur "Ajouter un produit" + onTap: () async { + bool hasPermission = await userController.hasAnyPermission(['create']); + if (hasPermission) { Get.to(const AddProductPage()); } else { Get.snackbar( - "Accés refusé", - backgroundColor: Colors.red, - colorText: Colors.white, - icon: const Icon(Icons.error), - duration: const Duration(seconds: 3), - snackPosition: SnackPosition.TOP, - "Vous n'avez pas les droits pour ajouter un produit"); + "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, + ); } }, ), @@ -115,37 +121,60 @@ Future clearUserData() async { leading: const Icon(Icons.edit), iconColor: Colors.redAccent, title: const Text("Modifier/Supprimer un produit"), - onTap: () { - if (userController.role == "admin") { - // Action lorsque l'utilisateur clique sur "Modifier/Supprimer un produit" + onTap: () async { + bool hasPermission = await userController.hasAnyPermission(['update', 'delete']); + if (hasPermission) { Get.to(GestionProduit()); } else { Get.snackbar( - "Accés refusé", - backgroundColor: Colors.red, - colorText: Colors.white, - icon: const Icon(Icons.error), - duration: const Duration(seconds: 3), - snackPosition: SnackPosition.TOP, - "Vous n'avez pas les droits pour modifier/supprimer un produit"); + "Accès refusé", + "Vous n'avez pas les droits pour modifier/supprimer un produit", + backgroundColor: Colors.red, + colorText: Colors.white, + icon: const Icon(Icons.error), + duration: const Duration(seconds: 3), + snackPosition: SnackPosition.TOP, + ); } }, ), ListTile( leading: const Icon(Icons.bar_chart), title: const Text("Bilan"), - onTap: () { - if (userController.role == "admin") { + onTap: () async { + bool hasPermission = await userController.hasAnyPermission(['read']); + if (hasPermission) { Get.to(const BilanMois()); } else { Get.snackbar( - "Accés refusé", - backgroundColor: Colors.red, - colorText: Colors.white, - icon: const Icon(Icons.error_outline_outlined), - duration: const Duration(seconds: 3), - snackPosition: SnackPosition.TOP, - "Vous n'avez pas les droits pour accéder au bilan"); + "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, + ); + } + }, + ), + ListTile( + leading: const Icon(Icons.warning_amber), + title: const Text("Gérer les rôles"), + onTap: () async { + bool hasPermission = await userController.hasAnyPermission(['update', 'delete']); + if (hasPermission) { + Get.to(const HandleUserRole()); + } else { + 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, + ); } }, ), @@ -153,19 +182,20 @@ Future clearUserData() async { leading: const Icon(Icons.inventory), iconColor: Colors.blueAccent, title: const Text("Gestion de stock"), - onTap: () { - if (userController.role == "admin") { - // Action lorsque l'utilisateur clique sur "Gestion de stock" + onTap: () async { + bool hasPermission = await userController.hasAnyPermission(['update']); + if (hasPermission) { Get.to(const GestionStockPage()); } else { Get.snackbar( - "Accés refusé", - backgroundColor: Colors.red, - colorText: Colors.white, - icon: const Icon(Icons.error), - duration: const Duration(seconds: 3), - snackPosition: SnackPosition.TOP, - "Vous n'avez pas les droits pour accéder à la gestion de stock"); + "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, + ); } }, ), @@ -174,7 +204,6 @@ Future clearUserData() async { iconColor: Colors.blue, title: const Text("Historique"), onTap: () { - // Action lorsque l'utilisateur clique sur "Historique" Get.to(HistoryPage()); }, ), @@ -183,8 +212,6 @@ Future clearUserData() async { iconColor: Colors.red, title: const Text("Déconnexion"), onTap: () { - // Action lorsque l'utilisateur clique sur "Déconnexion" - // display confirmation dialog Get.defaultDialog( title: "Déconnexion", content: const Text("Voulez-vous vraiment vous déconnecter ?"), @@ -201,11 +228,11 @@ Future clearUserData() async { onPressed: () { Get.back(); }, - ) + ), ], ); }, - ) + ), ], ), ); diff --git a/lib/Models/Permission.dart b/lib/Models/Permission.dart new file mode 100644 index 0000000..1d420d6 --- /dev/null +++ b/lib/Models/Permission.dart @@ -0,0 +1,16 @@ +class Permission { + final int? id; + final String name; + + Permission({this.id, required this.name}); + + factory Permission.fromMap(Map map) => Permission( + id: map['id'], + name: map['name'], + ); + + Map toMap() => { + 'id': id, + 'name': name, + }; +} \ No newline at end of file diff --git a/lib/Models/Role.dart b/lib/Models/Role.dart new file mode 100644 index 0000000..f674b69 --- /dev/null +++ b/lib/Models/Role.dart @@ -0,0 +1,20 @@ +class Role { + final int? id; + final String designation; + + Role({this.id, required this.designation}); + + Map toMap() { + return { + 'id': id, + 'designation': designation, + }; + } + + factory Role.fromMap(Map map) { + return Role( + id: map['id'], + designation: map['designation'], + ); + } +} diff --git a/lib/Models/users.dart b/lib/Models/users.dart index addedd9..cac46d8 100644 --- a/lib/Models/users.dart +++ b/lib/Models/users.dart @@ -1,34 +1,41 @@ class Users { - int id; + int? id; String name; String lastName; String email; String password; String username; - String role; + int roleId; + String? roleName; // Optionnel, rempli lors des requêtes avec JOIN Users({ - required this.id, + this.id, required this.name, required this.lastName, required this.email, required this.password, required this.username, - required this.role, + required this.roleId, + this.roleName, }); Map toMap() { return { - 'id': id, 'name': name, - 'lastName': lastName, + 'lastname': lastName, 'email': email, 'password': password, 'username': username, - 'role': role, + 'role_id': roleId, }; } + Map toMapWithId() { + final map = toMap(); + if (id != null) map['id'] = id; + return map; + } + factory Users.fromMap(Map map) { return Users( id: map['id'], @@ -37,8 +44,11 @@ class Users { email: map['email'], password: map['password'], username: map['username'], - role: map['role'] + roleId: map['role_id'], + roleName: map['role_name'], // Depuis les requêtes avec JOIN ); } + // Getter pour la compatibilité avec l'ancien code + String get role => roleName ?? ''; } \ No newline at end of file diff --git a/lib/Services/app_database.dart b/lib/Services/app_database.dart new file mode 100644 index 0000000..6391e74 --- /dev/null +++ b/lib/Services/app_database.dart @@ -0,0 +1,477 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:flutter/services.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; +import '../Models/users.dart'; +import '../Models/role.dart'; +import '../Models/Permission.dart'; + +class AppDatabase { + static final AppDatabase instance = AppDatabase._init(); + late Database _database; + + AppDatabase._init() { + sqfliteFfiInit(); + } + + Future get database async { + if (_database.isOpen) return _database; + _database = await _initDB('app_database.db'); + return _database; + } + + Future initDatabase() async { + _database = await _initDB('app_database.db'); + await _createDB(_database, 1); + await insertDefaultPermissions(); + await insertDefaultRoles(); + await insertDefaultSuperAdmin(); + } + + Future _initDB(String filePath) async { + final documentsDirectory = await getApplicationDocumentsDirectory(); + final path = join(documentsDirectory.path, filePath); + + bool dbExists = await File(path).exists(); + if (!dbExists) { + // Optionnel : copier depuis assets si vous avez une DB pré-remplie + try { + ByteData data = await rootBundle.load('assets/database/$filePath'); + List bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); + await File(path).writeAsBytes(bytes); + } catch (e) { + // Si pas de fichier dans assets, on continue avec une DB vide + print('Pas de fichier DB dans assets, création d\'une nouvelle DB'); + } + } + + return await databaseFactoryFfi.openDatabase(path); + } + + Future _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 des rôles + if (!tableNames.contains('roles')) { + await db.execute(''' + CREATE TABLE roles ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + designation TEXT NOT NULL UNIQUE + ) + '''); + print("Table 'roles' créée."); + } + + // Table des permissions + if (!tableNames.contains('permissions')) { + await db.execute(''' + CREATE TABLE permissions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE + ) + '''); + print("Table 'permissions' créée."); + } + + // Table de liaison role_permissions + if (!tableNames.contains('role_permissions')) { + await db.execute(''' + CREATE TABLE role_permissions ( + role_id INTEGER, + permission_id INTEGER, + PRIMARY KEY (role_id, permission_id), + FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE, + FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE + ) + '''); + print("Table 'role_permissions' créée."); + } + + // Table des utilisateurs + if (!tableNames.contains('users')) { + await db.execute(''' + CREATE TABLE users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + lastname TEXT NOT NULL, + email TEXT NOT NULL UNIQUE, + password TEXT NOT NULL, + username TEXT NOT NULL UNIQUE, + role_id INTEGER NOT NULL, + FOREIGN KEY (role_id) REFERENCES roles(id) + ) + '''); + print("Table 'users' créée."); + } + } + + // ========== INSERTION DES DONNÉES PAR DÉFAUT ========== + + Future 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 insertDefaultRoles() async { + final db = await database; + final existingRoles = await db.query('roles'); + + if (existingRoles.isEmpty) { + // Créer le rôle Super Admin + int superAdminRoleId = await db.insert('roles', {'designation': 'Super Admin'}); + + // Créer d'autres rôles de base + int adminRoleId = await db.insert('roles', {'designation': 'Admin'}); + int userRoleId = await db.insert('roles', {'designation': 'User'}); + + // Assigner toutes les permissions au Super Admin + final permissions = await db.query('permissions'); + for (var permission in permissions) { + await db.insert('role_permissions', { + 'role_id': superAdminRoleId, + 'permission_id': permission['id'], + }); + } + + // Assigner quelques permissions à l'Admin + 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_permissions', { + 'role_id': adminRoleId, + 'permission_id': viewPermission.first['id'], + }); + } + if (createPermission.isNotEmpty) { + await db.insert('role_permissions', { + 'role_id': adminRoleId, + 'permission_id': createPermission.first['id'], + }); + } + if (updatePermission.isNotEmpty) { + await db.insert('role_permissions', { + 'role_id': adminRoleId, + 'permission_id': updatePermission.first['id'], + }); + } + + // Assigner seulement la permission view à User + if (viewPermission.isNotEmpty) { + await db.insert('role_permissions', { + 'role_id': userRoleId, + 'permission_id': viewPermission.first['id'], + }); + } + + print("Rôles par défaut créés et permissions assignées"); + } + } + + Future insertDefaultSuperAdmin() async { + final db = await database; + + // Vérifier si un super admin existe déjà + final existingSuperAdmin = await db.rawQuery(''' + SELECT u.* FROM users u + INNER JOIN roles r ON u.role_id = r.id + WHERE r.designation = 'Super Admin' + '''); + + if (existingSuperAdmin.isEmpty) { + // Récupérer l'ID du rôle Super Admin + final superAdminRole = await db.query('roles', + where: 'designation = ?', + whereArgs: ['Super Admin'] + ); + + if (superAdminRole.isNotEmpty) { + final superAdminRoleId = superAdminRole.first['id'] as int; + + // Créer l'utilisateur Super Admin + await db.insert('users', { + 'name': 'Super', + 'lastname': 'Admin', + 'email': 'superadmin@youmazgestion.com', + 'password': 'admin123', // CHANGEZ CE MOT DE PASSE ! + 'username': 'superadmin', + 'role_id': superAdminRoleId, + }); + + print("Super Admin créé avec succès !"); + print("Username: superadmin"); + print("Password: admin123"); + print("ATTENTION: Changez ce mot de passe après la première connexion !"); + } + } else { + print("Super Admin existe déjà"); + } + } + + // ========== GESTION DES UTILISATEURS ========== + + Future createUser(Users user) async { + final db = await database; + return await db.insert('users', user.toMap()); + } + + Future deleteUser(int id) async { + final db = await database; + return await db.delete('users', where: 'id = ?', whereArgs: [id]); + } + + Future updateUser(Users user) async { + final db = await database; + return await db.update('users', user.toMap(), where: 'id = ?', whereArgs: [user.id]); + } + + Future getUserCount() async { + final db = await database; + List> result = await db.rawQuery('SELECT COUNT(*) as count FROM users'); + return result.first['count'] as int; + } + + Future verifyUser(String username, String password) async { + final db = await database; + final result = await db.rawQuery(''' + SELECT users.id + FROM users + WHERE users.username = ? AND users.password = ? + ''', [username, password]); + return result.isNotEmpty; + } + + Future getUser(String username) async { + final db = await database; + final result = await db.rawQuery(''' + SELECT users.*, roles.designation as role_name + FROM users + INNER JOIN roles ON users.role_id = roles.id + WHERE users.username = ? + ''', [username]); + + if (result.isNotEmpty) { + return Users.fromMap(result.first); + } else { + throw Exception('User not found'); + } + } + + Future?> getUserCredentials(String username, String password) async { + final db = await database; + final result = await db.rawQuery(''' + SELECT users.username, users.id, roles.designation as role_name, roles.id as role_id + FROM users + INNER JOIN roles ON users.role_id = roles.id + WHERE username = ? AND password = ? + ''', [username, password]); + + if (result.isNotEmpty) { + return { + 'id': result.first['id'], + 'username': result.first['username'] as String, + 'role': result.first['role_name'] as String, + 'role_id': result.first['role_id'], + }; + } else { + return null; + } + } + + Future> getAllUsers() async { + final db = await database; + final result = await db.rawQuery(''' + SELECT users.*, roles.designation as role_name + FROM users + INNER JOIN roles ON users.role_id = roles.id + ORDER BY users.id ASC + '''); + return result.map((json) => Users.fromMap(json)).toList(); + } + + // ========== GESTION DES RÔLES ========== + + Future createRole(Role role) async { + final db = await database; + return await db.insert('roles', role.toMap()); + } + + Future> getRoles() async { + final db = await database; + final maps = await db.query('roles', orderBy: 'designation ASC'); + return List.generate(maps.length, (i) => Role.fromMap(maps[i])); + } + + Future updateRole(Role role) async { + final db = await database; + return await db.update( + 'roles', + role.toMap(), + where: 'id = ?', + whereArgs: [role.id], + ); + } + + Future deleteRole(int? id) async { + final db = await database; + return await db.delete( + 'roles', + where: 'id = ?', + whereArgs: [id], + ); + } + + // ========== GESTION DES PERMISSIONS ========== + + Future> getAllPermissions() async { + final db = await database; + final result = await db.query('permissions', orderBy: 'name ASC'); + return result.map((e) => Permission.fromMap(e)).toList(); + } + + Future> getPermissionsForRole(int roleId) async { + final db = await database; + final result = await db.rawQuery(''' + SELECT p.id, p.name + FROM permissions p + JOIN role_permissions rp ON p.id = rp.permission_id + WHERE rp.role_id = ? + ORDER BY p.name ASC + ''', [roleId]); + + return result.map((map) => Permission.fromMap(map)).toList(); + } + + Future> getPermissionsForUser(String username) async { + final db = await database; + final result = await db.rawQuery(''' + SELECT DISTINCT p.id, p.name + FROM permissions p + JOIN role_permissions rp ON p.id = rp.permission_id + JOIN roles r ON rp.role_id = r.id + JOIN users u ON u.role_id = r.id + WHERE u.username = ? + ORDER BY p.name ASC + ''', [username]); + + return result.map((map) => Permission.fromMap(map)).toList(); + } + + Future assignPermission(int roleId, int permissionId) async { + final db = await database; + await db.insert('role_permissions', { + 'role_id': roleId, + 'permission_id': permissionId, + }, conflictAlgorithm: ConflictAlgorithm.ignore); + } + + Future removePermission(int roleId, int permissionId) async { + final db = await database; + await db.delete( + 'role_permissions', + where: 'role_id = ? AND permission_id = ?', + whereArgs: [roleId, permissionId], + ); + } + + // ========== MÉTHODES UTILITAIRES POUR LA SÉCURITÉ ========== + + Future isSuperAdmin(String username) async { + final db = await database; + final result = await db.rawQuery(''' + SELECT COUNT(*) as count + FROM users u + INNER JOIN roles r ON u.role_id = r.id + WHERE u.username = ? AND r.designation = 'Super Admin' + ''', [username]); + + return (result.first['count'] as int) > 0; + } + + Future changePassword(String username, String oldPassword, String newPassword) async { + final db = await database; + + // Vérifier l'ancien mot de passe + final isValidOldPassword = await verifyUser(username, oldPassword); + if (!isValidOldPassword) { + throw Exception('Ancien mot de passe incorrect'); + } + + // Changer le mot de passe + await db.update( + 'users', + {'password': newPassword}, + where: 'username = ?', + whereArgs: [username], + ); + } + + // ========== UTILITAIRES ========== + + Future close() async { + if (_database.isOpen) { + await _database.close(); + } + } + + Future hasPermission(String username, String permissionName) async { + final db = await database; + final result = await db.rawQuery(''' + SELECT COUNT(*) as count + FROM permissions p + JOIN role_permissions rp ON p.id = rp.permission_id + JOIN roles r ON rp.role_id = r.id + JOIN users u ON u.role_id = r.id + WHERE u.username = ? AND p.name = ? + ''', [username, permissionName]); + + return (result.first['count'] as int) > 0; + } + + // ========== MÉTHODE DE DEBUG ========== + + Future printDatabaseInfo() async { + final db = await database; + + print("=== INFORMATIONS DE LA BASE DE DONNÉES ==="); + + // Compter les utilisateurs + final userCount = await getUserCount(); + print("Nombre d'utilisateurs: $userCount"); + + // Lister tous les utilisateurs + final users = await getAllUsers(); + print("Utilisateurs:"); + for (var user in users) { + print(" - ${user.username} (${user.name} ) - Email: ${user.email}"); + } + + // Lister tous les rôles + final roles = await getRoles(); + print("Rôles:"); + for (var role in roles) { + print(" - ${role.designation} (ID: ${role.id})"); + } + + // Lister toutes les permissions + final permissions = await getAllPermissions(); + print("Permissions:"); + for (var permission in permissions) { + print(" - ${permission.name} (ID: ${permission.id})"); + } + + print("========================================="); + } +} \ No newline at end of file diff --git a/lib/Services/authDatabase.dart b/lib/Services/authDatabase.dart deleted file mode 100644 index f19061c..0000000 --- a/lib/Services/authDatabase.dart +++ /dev/null @@ -1,147 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:path/path.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:sqflite/sqflite.dart'; -import 'package:sqflite_common_ffi/sqflite_ffi.dart' as sqflite_ffi; -import 'package:sqflite_common_ffi/sqflite_ffi.dart'; -import 'package:youmazgestion/Models/users.dart'; -import 'dart:io'; - -class AuthDatabase { - static final AuthDatabase instance = AuthDatabase._init(); - late Database _database; - - AuthDatabase._init() { - sqflite_ffi.sqfliteFfiInit(); - } - - Future initDatabase() async { - _database = await _initDB('usersDb.db'); - await _createDB(_database, 1); - } - - Future get database async { - if (_database.isOpen) return _database; - - _database = await _initDB('usersDb.db'); - return _database; - } - - Future _initDB(String filePath) async { - // Obtenez le répertoire de stockage local de l'application - final documentsDirectory = await getApplicationDocumentsDirectory(); - final path = join(documentsDirectory.path, filePath); - - // Vérifiez si le fichier de base de données existe déjà dans le répertoire de stockage local - bool dbExists = await File(path).exists(); - if (!dbExists) { - // Si le fichier n'existe pas, copiez-le depuis le dossier assets/database - ByteData data = await rootBundle.load('assets/database/$filePath'); - List bytes = - data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); - await File(path).writeAsBytes(bytes); - } - - // Ouvrez la base de données - return await databaseFactoryFfi.openDatabase(path); - } - - Future _createDB(Database db, int version) async { - final resultUsers = await db.rawQuery( - "SELECT name FROM sqlite_master WHERE type='table' AND name='users'"); - - if (resultUsers.isEmpty) { - await db.execute(''' - CREATE TABLE users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT, - lastname TEXT, - email TEXT, - password TEXT, - username TEXT, - role TEXT - ) - '''); - } - } - - Future createUser(Users user) async { - final db = await database; - return await db.insert('users', user.toMap()); - } - - Future deleteUser(int id) async { - final db = await database; - return await db.delete('users', where: 'id = ?', whereArgs: [id]); - } - - Future updateUser(Users user) async { - final db = await database; - return await db - .update('users', user.toMap(), where: 'id = ?', whereArgs: [user.id]); - } - - Future getUserCount() async { - final db = await database; - List> x = - await db.rawQuery('SELECT COUNT (*) from users'); - int result = Sqflite.firstIntValue(x)!; - return result; - } - - // verify username and password existe - Future verifyUser(String username, String password) async { - final db = await database; - List> x = await db.rawQuery( - 'SELECT COUNT (*) from users WHERE username = ? AND password = ?', - [username, password]); - int result = Sqflite.firstIntValue(x)!; - if (result == 1) { - return true; - } else { - return false; - } - } - - //recuperer un user grace a son username - Future getUser(String username) async { - try { - final db = await database; - List> x = await db - .rawQuery('SELECT * from users WHERE username = ?', [username]); - print(x.first); - Users user = Users.fromMap(x.first); - print(user); - return user; - } catch (e) { - print(e); - rethrow; - } - } - - Future?> getUserCredentials(String username, String password) async { - final db = await database; - - List> result = await db.rawQuery( - 'SELECT username, role FROM users WHERE username = ? AND password = ?', - [username, password], - ); - - if (result.isNotEmpty) { - print('username '+result[0]['username']); - return { - 'username': result[0]['username'], - 'role': result[0]['role'], - }; - } else { - return null; // Aucun utilisateur trouvé - } -} - - Future> getAllUsers() async { - final db = await database; - const orderBy = 'id ASC'; - final result = await db.query('users', orderBy: orderBy); - return result.map((json) => Users.fromMap(json)).toList(); - } -} diff --git a/lib/Views/addProduct.dart b/lib/Views/addProduct.dart index 2039623..85a7ed9 100644 --- a/lib/Views/addProduct.dart +++ b/lib/Views/addProduct.dart @@ -19,17 +19,16 @@ class AddProductPage extends StatefulWidget { } class _AddProductPageState extends State { - // Controllers for text fields final TextEditingController _nameController = TextEditingController(); final TextEditingController _priceController = TextEditingController(); final TextEditingController _imageController = TextEditingController(); final TextEditingController _descriptionController = TextEditingController(); - String? _qrData; // Variable to store QR code data - final List _categories = ['Sucré', 'Salé', 'Jus', 'Gateaux']; // List of product categories - String? _selectedCategory; // Selected category - File? _pickedImage; // Variable to store the selected image file - late ProductDatabase _productDatabase; // Database instance + final List _categories = ['Sucré', 'Salé', 'Jus', 'Gateaux']; + String? _selectedCategory; + File? _pickedImage; + String? _qrData; + late ProductDatabase _productDatabase; @override void initState() { @@ -43,52 +42,12 @@ class _AddProductPageState extends State { void dispose() { _nameController.removeListener(_updateQrData); _nameController.dispose(); + _priceController.dispose(); + _imageController.dispose(); + _descriptionController.dispose(); super.dispose(); } - // Function to select an image from files or drop - void _selectImage() async { - final action = await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Sélectionner une image'), - content: const Text('Choisissez comment sélectionner une image'), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop('pick'); - }, - child: const Text('Choisir depuis les fichiers'), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop('drop'); - }, - child: const Text('Déposer une image'), - ), - ], - ); - }, - ); - - if (action == 'pick') { - final result = await FilePicker.platform.pickFiles( - type: FileType.image, - ); - - if (result != null) { - setState(() { - _pickedImage = File(result.files.single.path!); - _imageController.text = _pickedImage!.path; - }); - } - } else if (action == 'drop') { - // Code to handle image drop - } - } - - // Function to update QR data based on product name void _updateQrData() { if (_nameController.text.isNotEmpty) { final reference = 'PROD_PREVIEW_${_nameController.text}_${DateTime.now().millisecondsSinceEpoch}'; @@ -98,80 +57,82 @@ class _AddProductPageState extends State { } } - // Function to get the database location - Future _getDatabaseLocation() async { - final directory = await getApplicationDocumentsDirectory(); - final dbPath = directory.path; - print('Emplacement de la base de données : $dbPath'); + Future _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; + }); + } } - // Function to generate and save QR code Future _generateAndSaveQRCode(String reference) async { - final qrValidationResult = QrValidator.validate( + final validation = QrValidator.validate( data: 'https://tonsite.com/$reference', version: QrVersions.auto, errorCorrectionLevel: QrErrorCorrectLevel.L, ); - final qrCode = qrValidationResult.qrCode; + + final qrCode = validation.qrCode!; final painter = QrPainter.withQr( - qr: qrCode!, + qr: qrCode, color: Colors.black, emptyColor: Colors.white, gapless: true, ); - final tempDir = await getApplicationDocumentsDirectory(); - final file = File('${tempDir.path}/$reference.png'); + final directory = await getApplicationDocumentsDirectory(); + final path = '${directory.path}/$reference.png'; final picData = await painter.toImageData(2048, format: ImageByteFormat.png); - await file.writeAsBytes(picData!.buffer.asUint8List()); + await File(path).writeAsBytes(picData!.buffer.asUint8List()); - return file.path; + return path; } - // Function to add a product to the database void _addProduct() async { - final name = _nameController.text; - final price = double.tryParse(_priceController.text) ?? 0.0; - final image = _imageController.text; + final name = _nameController.text.trim(); + final price = double.tryParse(_priceController.text.trim()) ?? 0.0; + final image = _imageController.text.trim(); final category = _selectedCategory; - final description = _descriptionController.text; + final description = _descriptionController.text.trim(); - if (name.isNotEmpty && price > 0 && image.isNotEmpty && category != null) { - final reference = 'PROD_${DateTime.now().millisecondsSinceEpoch}'; - final qrPath = await _generateAndSaveQRCode(reference); + if (name.isEmpty || price <= 0 || image.isEmpty || category == null) { + Get.snackbar('Erreur', 'Veuillez remplir tous les champs requis'); + return; + } - final product = Product( - name: name, - price: price, - image: image, - category: category, - description: description, - qrCode: qrPath, - reference: reference, - ); + final reference = 'PROD_${DateTime.now().millisecondsSinceEpoch}'; + final qrPath = await _generateAndSaveQRCode(reference); + + final product = Product( + name: name, + price: price, + image: image, + category: category, + description: description, + qrCode: qrPath, + reference: reference, + ); + + try { + await _productDatabase.createProduct(product); + Get.snackbar('Succès', 'Produit ajouté avec succès'); - _productDatabase.createProduct(product).then((_) { - Get.snackbar('Succès', 'Produit ajouté avec succès'); - setState(() { - _nameController.clear(); - _priceController.clear(); - _imageController.clear(); - _descriptionController.clear(); - _selectedCategory = null; - _pickedImage = null; - }); - }).catchError((error) { - Get.snackbar('Erreur', 'Impossible d\'ajouter le produit : $error'); + setState(() { + _nameController.clear(); + _priceController.clear(); + _imageController.clear(); + _descriptionController.clear(); + _selectedCategory = null; + _pickedImage = null; + _qrData = null; }); - } else { - Get.snackbar( - 'Saisie invalide', - 'Veuillez entrer tous les champs requis.', - ); + } catch (e) { + Get.snackbar('Erreur', 'Ajout du produit échoué : $e'); } } - // Function to display the selected image Widget _displayImage() { if (_pickedImage != null) { return ClipRRect( @@ -184,33 +145,14 @@ class _AddProductPageState extends State { ), ); } else { - return Stack( - alignment: Alignment.center, - children: [ - Container( - width: 100, - height: 100, - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: BorderRadius.circular(8.0), - ), - ), - Icon( - Icons.image, - size: 48, - color: Colors.grey[400], - ), - Positioned( - bottom: 4, - child: Text( - 'Aucune image', - style: TextStyle( - fontSize: 12, - color: Colors.grey[400], - ), - ), - ), - ], + return Container( + width: 100, + height: 100, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(8.0), + ), + child: const Icon(Icons.image, size: 48, color: Colors.grey), ); } } @@ -219,35 +161,23 @@ class _AddProductPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: const CustomAppBar(title: 'Ajouter un produit'), - drawer: CustomDrawer(), - body: Padding( - padding: const EdgeInsets.all(16.0), + drawer: CustomDrawer(), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16), child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'Ajouter un produit', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), + const Text('Ajouter un produit', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)), const SizedBox(height: 16), TextField( controller: _nameController, - decoration: const InputDecoration( - labelText: 'Nom du produit', - border: OutlineInputBorder(), - ), + decoration: const InputDecoration(labelText: 'Nom du produit', border: OutlineInputBorder()), ), const SizedBox(height: 16), TextField( controller: _priceController, - decoration: const InputDecoration( - labelText: 'Prix', - border: OutlineInputBorder(), - ), - keyboardType: const TextInputType.numberWithOptions(decimal: true), + keyboardType: TextInputType.numberWithOptions(decimal: true), + decoration: const InputDecoration(labelText: 'Prix', border: OutlineInputBorder()), ), const SizedBox(height: 16), Row( @@ -256,17 +186,12 @@ class _AddProductPageState extends State { Expanded( child: TextField( controller: _imageController, - decoration: const InputDecoration( - labelText: 'Image', - border: OutlineInputBorder(), - ), + decoration: const InputDecoration(labelText: 'Chemin de l\'image', border: OutlineInputBorder()), + readOnly: true, ), ), const SizedBox(width: 8), - ElevatedButton( - onPressed: _selectImage, - child: const Text('Sélectionner une image'), - ), + ElevatedButton(onPressed: _selectImage, child: const Text('Sélectionner')), ], ), const SizedBox(height: 16), @@ -274,48 +199,37 @@ class _AddProductPageState extends State { const SizedBox(height: 16), DropdownButtonFormField( value: _selectedCategory, - onChanged: (newValue) { - setState(() { - _selectedCategory = newValue; - }); - }, - decoration: const InputDecoration( - labelText: 'Catégorie', - border: OutlineInputBorder(), - ), - items: _categories.map((category) { - return DropdownMenuItem( - value: category, - child: Text(category), - ); - }).toList(), + items: _categories + .map((c) => DropdownMenuItem(value: c, child: Text(c))) + .toList(), + onChanged: (value) => setState(() => _selectedCategory = value), + decoration: const InputDecoration(labelText: 'Catégorie', border: OutlineInputBorder()), ), const SizedBox(height: 16), - if (_qrData != null) - Column( - children: [ - const SizedBox(height: 16), - const Text('Aperçu du QR Code :'), - QrImageView( - data: _qrData!, - version: QrVersions.auto, - size: 120.0, - ), - ], - ), - const SizedBox(height: 16), TextField( controller: _descriptionController, - decoration: const InputDecoration( - labelText: 'Description', - border: OutlineInputBorder(), - ), maxLines: 3, + decoration: const InputDecoration(labelText: 'Description', border: OutlineInputBorder()), ), const SizedBox(height: 16), - ElevatedButton( - onPressed: _addProduct, - child: const Text('Ajouter le produit'), + 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: 24), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _addProduct, + child: const Text('Ajouter le produit'), + ), ), ], ), diff --git a/lib/Views/editUser.dart b/lib/Views/editUser.dart index 16ed576..06616a9 100644 --- a/lib/Views/editUser.dart +++ b/lib/Views/editUser.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:youmazgestion/Models/users.dart'; -import '../Services/authDatabase.dart'; +import 'package:youmazgestion/Models/role.dart'; +import '../Services/app_database.dart'; class EditUserPage extends StatefulWidget { final Users user; @@ -17,7 +18,11 @@ class _EditUserPageState extends State { late TextEditingController _emailController; late TextEditingController _usernameController; late TextEditingController _passwordController; - String _selectedRole = ''; + + List _roles = []; + Role? _selectedRole; + bool _isLoading = false; + bool _isLoadingRoles = true; @override void initState() { @@ -26,9 +31,31 @@ class _EditUserPageState extends State { _lastNameController = TextEditingController(text: widget.user.lastName); _emailController = TextEditingController(text: widget.user.email); _usernameController = TextEditingController(text: widget.user.username); - _passwordController = - TextEditingController(); // Leave password field empty initially - _selectedRole = widget.user.role; + _passwordController = TextEditingController(); + + _loadRoles(); + } + + Future _loadRoles() async { + try { + final roles = await AppDatabase.instance.getRoles(); + final currentRole = roles.firstWhere( + (r) => r.id == widget.user.roleId, + orElse: () => Role(id: widget.user.roleId, designation: widget.user.roleName ?? 'Inconnu'), + ); + + setState(() { + _roles = roles; + _selectedRole = currentRole; + _isLoadingRoles = false; + }); + } catch (e) { + print('Erreur lors du chargement des rôles: $e'); + setState(() { + _isLoadingRoles = false; + }); + _showErrorDialog('Erreur', 'Impossible de charger les rôles.'); + } } @override @@ -41,144 +68,213 @@ class _EditUserPageState extends State { super.dispose(); } - void _updateUser() { - final String name = _nameController.text; - final String lastName = _lastNameController.text; - final String email = _emailController.text; - final String username = _usernameController.text; - final String password = - _passwordController.text; // Get the entered password - final String role = _selectedRole; - - final Users updatedUser = Users( - id: widget.user.id, - name: name, - lastName: lastName, - email: email, - password: password.isNotEmpty - ? password - : widget.user - .password, // Use entered password if not empty, otherwise keep the existing password - username: username, - role: role, - ); + bool _validateFields() { + if (_nameController.text.trim().isEmpty || + _lastNameController.text.trim().isEmpty || + _emailController.text.trim().isEmpty || + _usernameController.text.trim().isEmpty || + _selectedRole == null) { + _showErrorDialog('Champs manquants', 'Veuillez remplir tous les champs requis.'); + return false; + } - AuthDatabase.instance.updateUser(updatedUser).then((value) { - // User update successful - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Update Successful'), - content: const Text('User information has been updated.'), - actions: [ - ElevatedButton( - onPressed: () { - Navigator.of(context).pop(); - Navigator.pop(context, - true); // Return true to indicate successful update - }, - child: const Text('OK'), - ), - ], - ); - }, - ); - }).catchError((error) { - print(error); - // Update failed - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Update Failed'), - content: - const Text('An error occurred during user information update.'), - actions: [ - ElevatedButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text('OK'), - ), - ], - ); - }, - ); + if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$') + .hasMatch(_emailController.text.trim())) { + _showErrorDialog('Email invalide', 'Veuillez saisir un email valide.'); + return false; + } + + if (_passwordController.text.isNotEmpty && + _passwordController.text.length < 6) { + _showErrorDialog('Mot de passe trop court', 'Minimum 6 caractères.'); + return false; + } + + return true; + } + + Future _updateUser() async { + if (!_validateFields() || _isLoading) return; + + setState(() { + _isLoading = true; }); + + try { + final updatedUser = Users( + id: widget.user.id, + name: _nameController.text.trim(), + lastName: _lastNameController.text.trim(), + email: _emailController.text.trim(), + username: _usernameController.text.trim(), + password: _passwordController.text.isNotEmpty + ? _passwordController.text + : widget.user.password, + roleId: _selectedRole!.id!, + roleName: _selectedRole!.designation, + ); + + await AppDatabase.instance.updateUser(updatedUser); + if (mounted) _showSuccessDialog(); + } catch (e) { + print('Erreur de mise à jour: $e'); + if (mounted) { + _showErrorDialog('Échec', 'Une erreur est survenue lors de la mise à jour.'); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } + + void _showSuccessDialog() { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Mise à jour réussie'), + content: const Text('Les informations de l\'utilisateur ont été mises à jour.'), + actions: [ + ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ) + ], + ), + ); + } + + void _showErrorDialog(String title, String message) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(title), + content: Text(message), + actions: [ + ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ) + ], + ), + ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Edit User'), + title: const Text('Modifier Utilisateur', style: TextStyle(color: Colors.white)), + backgroundColor: const Color.fromARGB(255, 4, 54, 95), + iconTheme: const IconThemeData(color: Colors.white), + centerTitle: true, ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - TextField( - controller: _nameController, - decoration: const InputDecoration( - labelText: 'First Name', - ), - ), - const SizedBox(height: 16.0), - TextField( - controller: _lastNameController, - decoration: const InputDecoration( - labelText: 'Last Name', - ), - ), - const SizedBox(height: 16.0), - TextField( - controller: _emailController, - decoration: const InputDecoration( - labelText: 'Email', - ), - keyboardType: TextInputType.emailAddress, - ), - const SizedBox(height: 16.0), - TextField( - controller: _usernameController, - decoration: const InputDecoration( - labelText: 'Username', + body: _isLoadingRoles + ? const Center(child: CircularProgressIndicator()) + : SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Card( + elevation: 4, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + const Icon(Icons.edit, size: 64, color: Colors.blue), + const SizedBox(height: 16), + _buildTextField(_nameController, 'Prénom', Icons.person), + const SizedBox(height: 12), + _buildTextField(_lastNameController, 'Nom', Icons.person_outline), + const SizedBox(height: 12), + _buildTextField(_emailController, 'Email', Icons.email, keyboardType: TextInputType.emailAddress), + const SizedBox(height: 12), + _buildTextField(_usernameController, 'Nom d\'utilisateur', Icons.account_circle), + const SizedBox(height: 12), + _buildTextField( + _passwordController, + 'Mot de passe (laisser vide si inchangé)', + Icons.lock, + obscureText: true, + ), + const SizedBox(height: 12), + _buildDropdown(), + const SizedBox(height: 20), + SizedBox( + width: double.infinity, + height: 48, + child: ElevatedButton( + onPressed: _isLoading ? null : _updateUser, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF0015B7), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: _isLoading + ? const CircularProgressIndicator(color: Colors.white) + : const Text('Mettre à jour', style: TextStyle(color: Colors.white, fontSize: 16)), + ), + ) + ], + ), ), ), - const SizedBox(height: 16.0), - TextField( - controller: _passwordController, - decoration: const InputDecoration( - labelText: 'Password', - ), - obscureText: true, - ), - const SizedBox(height: 16.0), - DropdownButton( - value: _selectedRole, - onChanged: (String? newValue) { + ), + ); + } + + Widget _buildTextField( + TextEditingController controller, + String label, + IconData icon, { + TextInputType keyboardType = TextInputType.text, + bool obscureText = false, + }) { + return TextField( + controller: controller, + decoration: InputDecoration( + labelText: label, + prefixIcon: Icon(icon), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), + ), + keyboardType: keyboardType, + obscureText: obscureText, + ); + } + + Widget _buildDropdown() { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(8), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: _selectedRole, + isExpanded: true, + hint: const Text('Sélectionner un rôle'), + onChanged: _isLoading + ? null + : (Role? newValue) { setState(() { - _selectedRole = newValue!; + _selectedRole = newValue; }); }, - items: ['admin', 'user'] - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - ), - const SizedBox(height: 16.0), - ElevatedButton( - onPressed: _updateUser, - child: const Text('Update'), + items: _roles.map((role) { + return DropdownMenuItem( + value: role, + child: Row( + children: [ + const Icon(Icons.badge, size: 20), + const SizedBox(width: 8), + Text(role.designation), + ], ), - ], - ), + ); + }).toList(), ), ), ); diff --git a/lib/Views/gestionProduct.dart b/lib/Views/gestionProduct.dart index fb89cae..30dc8bc 100644 --- a/lib/Views/gestionProduct.dart +++ b/lib/Views/gestionProduct.dart @@ -11,7 +11,7 @@ import 'dart:io'; class GestionProduit extends StatelessWidget { final ProductDatabase _productDatabase = ProductDatabase.instance; - const GestionProduit({super.key}); + GestionProduit({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/Views/gestionRole.dart b/lib/Views/gestionRole.dart new file mode 100644 index 0000000..c8f84c2 --- /dev/null +++ b/lib/Views/gestionRole.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:youmazgestion/Components/app_bar.dart'; +import 'package:youmazgestion/Services/app_database.dart'; +import 'package:youmazgestion/Models/role.dart'; + +class HandleUserRole extends StatefulWidget { + const HandleUserRole({super.key}); + + @override + State createState() => _HandleUserRoleState(); +} + +class _HandleUserRoleState extends State { + final db = AppDatabase.instance; + + List> roles = []; + Map permissionsMap = {}; + + int? selectedRoleId; + final TextEditingController _roleController = TextEditingController(); + + @override + void initState() { + super.initState(); + _initData(); + } + + Future _initData() async { + final roleList = await db.database.then((db) => db.query('roles')); + final perms = await db.getAllPermissions(); + + setState(() { + roles = roleList; + for (var perm in perms) { + permissionsMap[perm.name] = false; + } + }); + } + + Future _addRole() async { + String designation = _roleController.text.trim(); + if (designation.isEmpty) return; + + await db.createRole(Role(designation: designation)); + _roleController.clear(); + await _initData(); + } + + Future _loadRolePermissions(int roleId) async { + final rolePerms = await db.getPermissionsForRole(roleId); + final allPerms = await db.getAllPermissions(); + + setState(() { + selectedRoleId = roleId; + permissionsMap = { + for (var perm in allPerms) + perm.name: rolePerms.any((rp) => rp.name == perm.name) + }; + }); + } + + Future _onPermissionToggle(String permission, bool enabled) async { + if (selectedRoleId == null) return; + + final allPerms = await db.getAllPermissions(); + final perm = allPerms.firstWhere((p) => p.name == permission); + + if (enabled) { + await db.assignPermission(selectedRoleId!, perm.id!); + } else { + await db.removePermission(selectedRoleId!, perm.id!); + } + + setState(() { + permissionsMap[permission] = enabled; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: const CustomAppBar(title: "Gestion des rôles"), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + // ✅ Champ pour saisir un nouveau rôle + Row( + children: [ + Expanded( + child: TextField( + controller: _roleController, + decoration: const InputDecoration( + labelText: 'Nouveau rôle', + border: OutlineInputBorder(), + ), + ), + ), + const SizedBox(width: 10), + ElevatedButton( + onPressed: _addRole, + child: const Text('Créer'), + ), + ], + ), + + const SizedBox(height: 20), + + // 🔽 Sélection d'un rôle + DropdownButton( + isExpanded: true, + hint: const Text("Choisir un rôle"), + value: selectedRoleId, + items: roles.map((role) { + return DropdownMenuItem( + value: role['id'] as int, + child: Text(role['designation'] ?? ''), + ); + }).toList(), + onChanged: (value) { + if (value != null) _loadRolePermissions(value); + }, + ), + + const SizedBox(height: 16), + + // ✅ Permissions associées + if (selectedRoleId != null) + Expanded( + child: ListView( + children: permissionsMap.entries.map((entry) { + return CheckboxListTile( + title: Text(entry.key), + value: entry.value, + onChanged: (bool? value) { + _onPermissionToggle(entry.key, value ?? false); + }, + ); + }).toList(), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/Views/listUser.dart b/lib/Views/listUser.dart index 9ee1dd6..7f058d6 100644 --- a/lib/Views/listUser.dart +++ b/lib/Views/listUser.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:youmazgestion/Models/users.dart'; import '../Components/app_bar.dart'; -import '../Services/authDatabase.dart'; +import '../Services/app_database.dart'; import 'editUser.dart'; class ListUserPage extends StatefulWidget { @@ -23,7 +23,7 @@ class _ListUserPageState extends State { Future getUsersFromDatabase() async { try { - List users = await AuthDatabase.instance.getAllUsers(); + List users = await AppDatabase.instance.getAllUsers(); setState(() { userList = users; }); @@ -89,8 +89,8 @@ class _ListUserPageState extends State { ), TextButton( onPressed: () async { - await AuthDatabase.instance - .deleteUser(user.id); + await AppDatabase.instance + .deleteUser(user.id!); Navigator.of(context).pop(); setState(() { userList.removeAt(index); diff --git a/lib/Views/loginPage.dart b/lib/Views/loginPage.dart index 70f312f..83096b5 100644 --- a/lib/Views/loginPage.dart +++ b/lib/Views/loginPage.dart @@ -3,7 +3,7 @@ import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:youmazgestion/Views/particles.dart' show ParticleBackground; import 'package:youmazgestion/accueil.dart'; -import 'package:youmazgestion/Services/authDatabase.dart'; +import 'package:youmazgestion/Services/app_database.dart'; import '../Models/users.dart'; import '../controller/userController.dart'; @@ -20,6 +20,8 @@ class _LoginPageState extends State { late TextEditingController _passwordController; final UserController userController = Get.put(UserController()); bool _isErrorVisible = false; + bool _isLoading = false; + String _errorMessage = 'Nom d\'utilisateur ou mot de passe invalide'; @override void initState() { @@ -30,13 +32,27 @@ class _LoginPageState extends State { } void checkUserCount() async { - final userCount = await AuthDatabase.instance.getUserCount(); - if (userCount == 0) { - // No user found, redirect to home page - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => const AccueilPage()), - ); + try { + final userCount = await AppDatabase.instance.getUserCount(); + print('Nombre d\'utilisateurs trouvés: $userCount'); // Debug + + // Commentez cette partie pour permettre le login même sans utilisateurs + /* + if (userCount == 0) { + if (mounted) { + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => const AccueilPage()), + ); + } + } + */ + } catch (error) { + print('Erreur lors de la vérification du nombre d\'utilisateurs: $error'); + setState(() { + _errorMessage = 'Erreur de connexion à la base de données'; + _isErrorVisible = true; + }); } } @@ -46,47 +62,111 @@ class _LoginPageState extends State { _passwordController.dispose(); super.dispose(); } -Future saveUser(String? username,String? role)async{ + + Future saveUser(String username, String role, int userId) async { + try { final prefs = await SharedPreferences.getInstance(); - await prefs.setString('username', username!); - await prefs.setString('role', role!); -} + await prefs.setString('username', username); + await prefs.setString('role', role); + await prefs.setInt('user_id', userId); + print('Utilisateur sauvegardé: $username, rôle: $role, id: $userId'); + } catch (error) { + print('Erreur lors de la sauvegarde: $error'); + throw Exception('Erreur lors de la sauvegarde des données utilisateur'); + } + } + void _login() async { - final String username = _usernameController.text; - final String password = _passwordController.text; - print(username); - print(password); + if (_isLoading) return; + + final String username = _usernameController.text.trim(); + final String password = _passwordController.text.trim(); + + // Validation basique + if (username.isEmpty || password.isEmpty) { + setState(() { + _errorMessage = 'Veuillez saisir le nom d\'utilisateur et le mot de passe'; + _isErrorVisible = true; + }); + return; + } + + setState(() { + _isLoading = true; + _isErrorVisible = false; + }); try { - bool isValidUser = - await AuthDatabase.instance.verifyUser(username, password); - Users user = await AuthDatabase.instance.getUser(username); + print('Tentative de connexion pour: $username'); - Map? getUserCredentials = await AuthDatabase.instance.getUserCredentials(username,password); - print(isValidUser); + // Vérification de la connexion à la base de données + final dbInstance = AppDatabase.instance; + + // Test de connexion à la base + try { + final userCount = await dbInstance.getUserCount(); + print('Base de données accessible, $userCount utilisateurs trouvés'); + } catch (dbError) { + throw Exception('Impossible d\'accéder à la base de données: $dbError'); + } + + // Vérifier les identifiants + bool isValidUser = await dbInstance.verifyUser(username, password); + print('Résultat de la vérification: $isValidUser'); + if (isValidUser) { - print('User is valid'); - print(user); - userController.setUser(user); + // Récupérer les informations complètes de l'utilisateur + Users user = await dbInstance.getUser(username); + print('Utilisateur récupéré: ${user.username}'); - setState(() { - _isErrorVisible = false; - }); - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => const AccueilPage()), - ); - saveUser(getUserCredentials?['username'], getUserCredentials?['role']); + // Récupérer les credentials + Map? userCredentials = + await dbInstance.getUserCredentials(username, password); + + if (userCredentials != null) { + print('Connexion réussie pour: ${user.username}'); + print('Rôle: ${userCredentials['role']}'); + print('ID: ${userCredentials['id']}'); + + // Sauvegarder dans le contrôleur + userController.setUser(user); + + // Sauvegarder dans SharedPreferences + await saveUser( + userCredentials['username'] as String, + userCredentials['role'] as String, + userCredentials['id'] as int, + ); + + // Navigation + if (mounted) { + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => const AccueilPage()), + ); + } + } else { + throw Exception('Erreur lors de la récupération des credentials'); + } } else { + print('Identifiants invalides pour: $username'); setState(() { + _errorMessage = 'Nom d\'utilisateur ou mot de passe invalide'; _isErrorVisible = true; }); } } catch (error) { - print(error); + print('Erreur lors de la connexion: $error'); setState(() { + _errorMessage = 'Erreur de connexion: ${error.toString()}'; _isErrorVisible = true; }); + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } } } @@ -96,8 +176,8 @@ Future saveUser(String? username,String? role)async{ appBar: AppBar( title: const Text( 'Login', - style: TextStyle(color:Colors.white), - ), + style: TextStyle(color: Colors.white), + ), backgroundColor: const Color.fromARGB(255, 4, 54, 95), centerTitle: true, ), @@ -125,10 +205,10 @@ Future saveUser(String? username,String? role)async{ ), TextField( controller: _usernameController, + enabled: !_isLoading, decoration: InputDecoration( labelText: 'Username', - prefixIcon: - const Icon(Icons.person, color: Colors.blueAccent), + prefixIcon: const Icon(Icons.person, color: Colors.blueAccent), border: OutlineInputBorder( borderRadius: BorderRadius.circular(30.0), ), @@ -137,6 +217,7 @@ Future saveUser(String? username,String? role)async{ const SizedBox(height: 16.0), TextField( controller: _passwordController, + enabled: !_isLoading, decoration: InputDecoration( labelText: 'Password', prefixIcon: const Icon(Icons.lock, color: Colors.redAccent), @@ -145,34 +226,67 @@ Future saveUser(String? username,String? role)async{ ), ), obscureText: true, + onSubmitted: (_) => _login(), ), const SizedBox(height: 16.0), Visibility( visible: _isErrorVisible, - child: const Text( - 'Invalid username or password', - style: TextStyle( + child: Text( + _errorMessage, + style: const TextStyle( color: Colors.red, + fontSize: 14, ), + textAlign: TextAlign.center, ), ), const SizedBox(height: 16.0), ElevatedButton( - onPressed: _login, + onPressed: _isLoading ? null : _login, style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF0015B7), // Nouvelle couleur + backgroundColor: const Color(0xFF0015B7), elevation: 5.0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30.0), ), + minimumSize: const Size(double.infinity, 48), ), - child: const Text( - 'Login', - style: TextStyle( - color: Colors.white, - ), - ), + child: _isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ) + : const Text( + 'Se connecter', + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), ), + // Bouton de debug (à supprimer en production) + if (_isErrorVisible) + TextButton( + onPressed: () async { + try { + final count = await AppDatabase.instance.getUserCount(); + print('Debug: $count utilisateurs dans la base'); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('$count utilisateurs trouvés')), + ); + } catch (e) { + print('Debug error: $e'); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Erreur: $e')), + ); + } + }, + child: const Text('Debug: Vérifier BDD'), + ), ], ), ), @@ -180,4 +294,4 @@ Future saveUser(String? username,String? role)async{ ), ); } -} +} \ No newline at end of file diff --git a/lib/Views/produitsCard.dart b/lib/Views/produitsCard.dart new file mode 100644 index 0000000..9c262b2 --- /dev/null +++ b/lib/Views/produitsCard.dart @@ -0,0 +1,449 @@ +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 { + final Product product; + final void Function(Product, int) onAddToCart; + + const ProductCard({ + Key? key, + required this.product, + required this.onAddToCart, + }) : super(key: key); + + @override + State createState() => _ProductCardState(); +} + +class _ProductCardState extends State with TickerProviderStateMixin { + int selectedQuantity = 1; + late AnimationController _scaleController; + late AnimationController _fadeController; + late Animation _scaleAnimation; + late Animation _fadeAnimation; + + @override + void initState() { + super.initState(); + + // Animations pour les interactions + _scaleController = AnimationController( + duration: const Duration(milliseconds: 200), + vsync: this, + ); + _fadeController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + )..forward(); + + _scaleAnimation = Tween( + begin: 1.0, + end: 0.95, + ).animate(CurvedAnimation( + parent: _scaleController, + curve: Curves.easeInOut, + )); + + _fadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _fadeController, + curve: Curves.easeOut, + )); + } + + @override + void dispose() { + _scaleController.dispose(); + _fadeController.dispose(); + super.dispose(); + } + + void _onTapDown(TapDownDetails details) { + _scaleController.forward(); + } + + void _onTapUp(TapUpDetails details) { + _scaleController.reverse(); + } + + void _onTapCancel() { + _scaleController.reverse(); + } + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: _fadeAnimation, + child: AnimatedBuilder( + animation: _scaleAnimation, + builder: (context, child) { + return Transform.scale( + scale: _scaleAnimation.value, + child: Container( + margin: const EdgeInsets.all(8), + height: 280, + child: Material( + elevation: 8, + shadowColor: Colors.black.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Colors.white, + Colors.grey.shade50, + ], + ), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(20), + child: Stack( + children: [ + Positioned.fill( + child: Container( + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(20), + ), + child: widget.product.image != null + ? Image.file( + File(widget.product.image), + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return _buildPlaceholderImage(); + }, + ) + : _buildPlaceholderImage(), + ), + ), + + Positioned.fill( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.transparent, + Colors.transparent, + Colors.black.withOpacity(0.3), + Colors.black.withOpacity(0.7), + ], + stops: const [0.0, 0.4, 0.7, 1.0], + ), + ), + ), + ), + + if (widget.product.isStockDefined()) + Positioned( + top: 12, + right: 12, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.green.withOpacity(0.9), + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.green.withOpacity(0.3), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.check_circle, + color: Colors.white, + size: 12, + ), + const SizedBox(width: 4), + const Text( + 'En stock', + style: TextStyle( + color: Colors.white, + fontSize: 10, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + + Positioned( + left: 0, + right: 0, + bottom: 0, + child: Container( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.product.name, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + color: Colors.white, + shadows: [ + Shadow( + offset: Offset(1, 1), + blurRadius: 3, + color: Colors.black54, + ), + ], + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + Text( + '${widget.product.price.toStringAsFixed(2)} FCFA', + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 13, + color: Colors.white, + shadows: [ + Shadow( + offset: Offset(1, 1), + blurRadius: 3, + color: Colors.black54, + ), + ], + ), + ), + ], + ), + ), + + const SizedBox(height: 12), + + Row( + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.95), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + _buildQuantityButton( + icon: Icons.remove, + onPressed: selectedQuantity > 1 + ? () { + setState(() { + selectedQuantity--; + }); + } + : null, + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + child: Text( + '$selectedQuantity', + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + _buildQuantityButton( + icon: Icons.add, + onPressed: selectedQuantity < 100 + ? () { + setState(() { + selectedQuantity++; + }); + } + : null, + ), + ], + ), + ), + + const SizedBox(width: 8), + + Expanded( + 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, + ), + ), + ], + ), + 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, + ), + 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), + ), + ], + ), + 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, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ), + ); + }, + ), + ); + } + + Widget _buildPlaceholderImage() { + return Container( + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.image_outlined, + size: 40, + color: Colors.grey.shade400, + ), + const SizedBox(height: 8), + Text( + 'Image non disponible', + style: TextStyle( + color: Colors.grey.shade500, + fontSize: 12, + ), + ), + ], + ), + ); + } + + Widget _buildQuantityButton({ + required IconData icon, + required VoidCallback? onPressed, + }) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: onPressed, + borderRadius: BorderRadius.circular(15), + child: Container( + padding: const EdgeInsets.all(6), + child: Icon( + icon, + size: 16, + color: onPressed != null ? const Color.fromARGB(255, 4, 54, 95) : Colors.grey, + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/Views/registrationPage.dart b/lib/Views/registrationPage.dart index b8eef8c..d758678 100644 --- a/lib/Views/registrationPage.dart +++ b/lib/Views/registrationPage.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:youmazgestion/Models/users.dart'; +import 'package:youmazgestion/Models/role.dart'; import 'package:youmazgestion/accueil.dart'; -import '../Services/authDatabase.dart'; +import '../Services/app_database.dart'; // Changé de authDatabase.dart class RegistrationPage extends StatefulWidget { const RegistrationPage({super.key}); @@ -17,7 +18,11 @@ class _RegistrationPageState extends State { late TextEditingController _emailController; late TextEditingController _usernameController; late TextEditingController _passwordController; - String _selectedRole = 'user'; // Default role is 'user' + + List _availableRoles = []; + Role? _selectedRole; + bool _isLoading = false; + bool _isLoadingRoles = true; @override void initState() { @@ -27,7 +32,59 @@ class _RegistrationPageState extends State { _emailController = TextEditingController(); _usernameController = TextEditingController(); _passwordController = TextEditingController(); - AuthDatabase.instance.initDatabase(); + + _initializeDatabase(); + } + + Future _initializeDatabase() async { + try { + await AppDatabase.instance.initDatabase(); + await _loadRoles(); + } catch (error) { + print('Erreur lors de l\'initialisation: $error'); + if (mounted) { + _showErrorDialog('Erreur d\'initialisation', + 'Impossible d\'initialiser l\'application. Veuillez redémarrer.'); + } + } + } + + Future _loadRoles() async { + try { + final roles = await AppDatabase.instance.getRoles(); + + if (mounted) { + setState(() { + _availableRoles = roles; + _selectedRole = roles.isNotEmpty ? roles.first : null; + _isLoadingRoles = false; + }); + } + + // Si aucun rôle n'existe, créer des rôles par défaut + if (roles.isEmpty) { + await _createDefaultRoles(); + await _loadRoles(); // Recharger après création + } + } catch (error) { + print('Erreur lors du chargement des rôles: $error'); + if (mounted) { + setState(() { + _isLoadingRoles = false; + }); + } + } + } + + Future _createDefaultRoles() async { + try { + await AppDatabase.instance.createRole(Role(designation: 'Admin')); + await AppDatabase.instance.createRole(Role(designation: 'Utilisateur')); + await AppDatabase.instance.createRole(Role(designation: 'Gestionnaire')); + print('Rôles par défaut créés'); + } catch (error) { + print('Erreur lors de la création des rôles par défaut: $error'); + } } @override @@ -40,148 +97,319 @@ class _RegistrationPageState extends State { super.dispose(); } - void _register() { - // Get the entered user information - final String name = _nameController.text; - final String lastName = _lastNameController.text; - final String email = _emailController.text; - final String username = _usernameController.text; - final String password = _passwordController.text; - final String role = _selectedRole; - - // Create a new user object - final Users user = Users( - id: 0, // The id will be assigned automatically by the database - name: name, - lastName: lastName, - email: email, - password: password, - username: username, - role: role, - ); + bool _validateFields() { + if (_nameController.text.trim().isEmpty || + _lastNameController.text.trim().isEmpty || + _emailController.text.trim().isEmpty || + _usernameController.text.trim().isEmpty || + _passwordController.text.trim().isEmpty || + _selectedRole == null) { + _showErrorDialog('Champs manquants', 'Veuillez remplir tous les champs.'); + return false; + } - // Save the user in the database - AuthDatabase.instance.createUser(user).then((value) { - // Registration successful - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Registration Successful'), - content: const Text('You have successfully registered.'), - actions: [ - ElevatedButton( - onPressed: () { - Navigator.of(context).pop(); - // Navigate to the login page - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => const AccueilPage()), - ); - }, - child: const Text('OK'), - ), - ], - ); - }, - ); - }).catchError((error) { - print(error); - // Registration failed - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Registration Failed'), - content: const Text('An error occurred during registration.'), - actions: [ - ElevatedButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text('OK'), - ), - ], - ); - }, - ); + // Validation basique de l'email + if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(_emailController.text.trim())) { + _showErrorDialog('Email invalide', 'Veuillez entrer un email valide.'); + return false; + } + + // Validation basique du mot de passe + if (_passwordController.text.length < 6) { + _showErrorDialog('Mot de passe trop court', 'Le mot de passe doit contenir au moins 6 caractères.'); + return false; + } + + return true; + } + + void _register() async { + if (_isLoading) return; + + if (!_validateFields()) return; + + setState(() { + _isLoading = true; }); + + try { + // Créer l'objet utilisateur avec le nouveau modèle + final Users user = Users( + name: _nameController.text.trim(), + lastName: _lastNameController.text.trim(), + email: _emailController.text.trim(), + password: _passwordController.text.trim(), + username: _usernameController.text.trim(), + roleId: _selectedRole!.id!, // Utiliser l'ID du rôle + roleName: _selectedRole!.designation, // Pour l'affichage + ); + + // Sauvegarder l'utilisateur dans la base de données + final int userId = await AppDatabase.instance.createUser(user); + + print('Utilisateur créé avec l\'ID: $userId'); + + // Inscription réussie + if (mounted) { + _showSuccessDialog(); + } + } catch (error) { + print('Erreur lors de l\'inscription: $error'); + + String errorMessage = 'Une erreur est survenue lors de l\'inscription.'; + + // Personnaliser le message d'erreur selon le type d'erreur + if (error.toString().contains('UNIQUE constraint failed: users.email')) { + errorMessage = 'Cet email est déjà utilisé.'; + } else if (error.toString().contains('UNIQUE constraint failed: users.username')) { + errorMessage = 'Ce nom d\'utilisateur est déjà pris.'; + } + + if (mounted) { + _showErrorDialog('Inscription échouée', errorMessage); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } + + void _showSuccessDialog() { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Inscription réussie'), + content: const Text('Vous vous êtes inscrit avec succès.'), + actions: [ + ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => const AccueilPage()), + ); + }, + child: const Text('OK'), + ), + ], + ); + }, + ); + } + + void _showErrorDialog(String title, String message) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(title), + content: Text(message), + actions: [ + ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('OK'), + ), + ], + ); + }, + ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Registration'), + title: const Text( + 'Inscription', + style: TextStyle(color: Colors.white), + ), + backgroundColor: const Color.fromARGB(255, 4, 54, 95), + centerTitle: true, + iconTheme: const IconThemeData(color: Colors.white), ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - TextField( - controller: _nameController, - decoration: const InputDecoration( - labelText: 'First Name', - ), - ), - const SizedBox(height: 16.0), - TextField( - controller: _lastNameController, - decoration: const InputDecoration( - labelText: 'Last Name', - ), - ), - const SizedBox(height: 16.0), - TextField( - controller: _emailController, - decoration: const InputDecoration( - labelText: 'Email', - ), - keyboardType: TextInputType.emailAddress, - ), - const SizedBox(height: 16.0), - TextField( - controller: _usernameController, - decoration: const InputDecoration( - labelText: 'Username', - ), + body: _isLoadingRoles + ? const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('Chargement des rôles...'), + ], ), - const SizedBox(height: 16.0), - TextField( - controller: _passwordController, - decoration: const InputDecoration( - labelText: 'Password', + ) + : Padding( + padding: const EdgeInsets.all(16.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Card( + elevation: 4, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + const Icon( + Icons.person_add, + size: 64, + color: Color.fromARGB(255, 4, 54, 95), + ), + const SizedBox(height: 16), + TextField( + controller: _nameController, + enabled: !_isLoading, + decoration: InputDecoration( + labelText: 'Prénom', + prefixIcon: const Icon(Icons.person), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + const SizedBox(height: 16.0), + TextField( + controller: _lastNameController, + enabled: !_isLoading, + decoration: InputDecoration( + labelText: 'Nom', + prefixIcon: const Icon(Icons.person_outline), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + const SizedBox(height: 16.0), + TextField( + controller: _emailController, + enabled: !_isLoading, + decoration: InputDecoration( + labelText: 'Email', + prefixIcon: const Icon(Icons.email), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + keyboardType: TextInputType.emailAddress, + ), + const SizedBox(height: 16.0), + TextField( + controller: _usernameController, + enabled: !_isLoading, + decoration: InputDecoration( + labelText: 'Nom d\'utilisateur', + prefixIcon: const Icon(Icons.account_circle), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + const SizedBox(height: 16.0), + TextField( + controller: _passwordController, + enabled: !_isLoading, + decoration: InputDecoration( + labelText: 'Mot de passe', + prefixIcon: const Icon(Icons.lock), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + helperText: 'Au moins 6 caractères', + ), + obscureText: true, + ), + const SizedBox(height: 16.0), + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(8), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: _selectedRole, + hint: const Text('Sélectionner un rôle'), + isExpanded: true, + onChanged: _isLoading + ? null + : (Role? newValue) { + setState(() { + _selectedRole = newValue; + }); + }, + items: _availableRoles + .map>((Role role) { + return DropdownMenuItem( + value: role, + child: Row( + children: [ + const Icon(Icons.badge, size: 20), + const SizedBox(width: 8), + Text(role.designation), + ], + ), + ); + }).toList(), + ), + ), + ), + const SizedBox(height: 24.0), + SizedBox( + width: double.infinity, + height: 48, + child: ElevatedButton( + onPressed: _isLoading ? null : _register, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF0015B7), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: _isLoading + ? const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ), + SizedBox(width: 12), + Text( + 'Inscription en cours...', + style: TextStyle(color: Colors.white), + ), + ], + ) + : const Text( + 'S\'inscrire', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ), + ), + ], ), - obscureText: true, ), - const SizedBox(height: 16.0), - DropdownButton( - value: _selectedRole, - onChanged: (String? newValue) { - setState(() { - _selectedRole = newValue!; - }); - }, - items: ['admin', 'user'] - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - ), - const SizedBox(height: 16.0), - ElevatedButton( - onPressed: _register, - child: const Text('Register'), - ), - ], - ), - ), - ), + ), ); } -} +} \ No newline at end of file diff --git a/lib/accueil.dart b/lib/accueil.dart index e9983de..189058d 100644 --- a/lib/accueil.dart +++ b/lib/accueil.dart @@ -5,6 +5,7 @@ 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'; import 'Components/appDrawer.dart'; import 'Components/app_bar.dart'; import 'Components/cartItem.dart'; @@ -35,7 +36,7 @@ class _AccueilPageState extends State { int orderId = 0; List selectedProducts = []; - int selectedQuantity = 1; // Quantité sélectionnée par défaut + int selectedQuantity = 1; double totalCartPrice = 0; double amountPaid = 0; @@ -55,16 +56,15 @@ class _AccueilPageState extends State { role = prefs.getString('role') ?? 'Rôle inconnu'; }); } + Future saveOrderToDatabase() async { final totalPrice = calculateTotalPrice(); final dateTime = DateTime.now().toString(); String user = userController.username; - // Insert the order into the database orderId = await orderDatabase.insertOrder( totalPrice, dateTime, MyApp.startDate!, user); - // Insert order items into the database for (final cartItem in selectedProducts) { final product = cartItem.product; final quantity = cartItem.quantity; @@ -73,7 +73,6 @@ class _AccueilPageState extends State { await orderDatabase.insertOrderItem( orderId, product.name, quantity, price); - // Mettre à jour le stock du produit final updatedStock = product.stock! - quantity; await productDatabase.updateStock(product.id!, updatedStock); } @@ -85,7 +84,6 @@ class _AccueilPageState extends State { final categories = await productDatabase.getCategories(); final productsByCategory = >{}; - // Trier les catégories selon votre préférence categories.sort(); for (final categoryName in categories) { @@ -113,47 +111,34 @@ class _AccueilPageState extends State { return totalPrice; } - void addToCartWithDetails(Product product) { + void addToCartWithDetails(Product product, int quantity) { setState(() { final existingCartItem = selectedProducts.firstWhere( - (cartItem) => cartItem.product == product, + (cartItem) => cartItem.product.id == product.id, orElse: () => CartItem(product, 0), ); if (existingCartItem.quantity == 0) { - selectedProducts.add(CartItem(product, selectedQuantity)); + selectedProducts.add(CartItem(product, quantity)); } else { - existingCartItem.quantity += selectedQuantity; + existingCartItem.quantity += quantity; } }); - // Afficher une notification Get.snackbar( 'Produit ajouté', - 'Le produit ${product.name} a été ajouté au panier', + '${product.name} (x$quantity) ajouté au panier', snackPosition: SnackPosition.TOP, duration: const Duration(seconds: 1), backgroundColor: Colors.green, colorText: Colors.white, ); - resetQuantityAfterDelay(); - } - - Future resetQuantityAfterDelay() async { - await Future.delayed(const Duration(seconds: 1)); - setState(() { - selectedQuantity = 1; - }); } void showTicketPage() { - // Calculer la somme totale du panier final double totalCartPrice = calculateTotalPrice(); - // Vérifier si des produits sont présents dans le panier if (selectedProducts.isNotEmpty) { - // Vérifier si la somme payée est suffisante et supérieure ou égale au total du panier if (amountPaid >= totalCartPrice) { - // Passer les produits et les informations de l'entreprise à la page du ticket Get.offAll(TicketPage( businessName: 'Youmaz', businessAddress: @@ -164,10 +149,9 @@ class _AccueilPageState extends State { amountPaid: amountPaid, )); } else { - // Afficher un message d'erreur si la somme payée est insuffisante Get.snackbar( 'Paiement incomplet', - 'Le montant payé est insuffisant. Veuillez payer le montant total du panier.', + 'Le montant payé est insuffisant.', snackPosition: SnackPosition.BOTTOM, duration: const Duration(seconds: 3), backgroundColor: Colors.red, @@ -175,10 +159,9 @@ class _AccueilPageState extends State { ); } } else { - // Afficher un message d'erreur si le panier est vide Get.snackbar( 'Panier vide', - 'Le panier est vide. Veuillez ajouter des produits avant de passer commande.', + 'Ajoutez des produits avant de passer commande.', snackPosition: SnackPosition.BOTTOM, duration: const Duration(seconds: 3), backgroundColor: Colors.red, @@ -198,7 +181,7 @@ class _AccueilPageState extends State { gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [Colors.white, const Color.fromARGB(255, 4, 54, 95)], + colors: [Colors.white, Color.fromARGB(255, 4, 54, 95)], ), ), child: FutureBuilder>>( @@ -207,35 +190,26 @@ class _AccueilPageState extends State { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { - print('erreur:${snapshot.error}'); - return const Center( - child: Text("Erreur lors du chargement des produits")); + return const Center(child: Text("Erreur de chargement")); } else if (snapshot.hasData) { - final Map> productsByCategory = - snapshot.data!; + final productsByCategory = snapshot.data!; final categories = productsByCategory.keys.toList(); - + if (!MyApp.isRegisterOpen) { - // Afficher le bouton "Démarrer la caisse" si la variable isRegisterOpen est false return Column( children: [ - Text('welcome $username ! votre role est $role'), + Text('Bienvenue $username ! (Rôle: $role)'), Center( child: ElevatedButton( onPressed: () { setState(() { MyApp.isRegisterOpen = true; - // mettre startDate à la date actuelle et au format yyyy-MM-dd String formattedDate = DateFormat('yyyy-MM-dd').format(DateTime.now()); startDate = DateFormat('yyyy-MM-dd').parse(formattedDate); MyApp.startDate = startDate; - - var datee = DateFormat('yyyy-MM-dd') - .format(startDate!) - .toString(); - workDatabase.insertDate(datee); + workDatabase.insertDate(formattedDate); }); }, child: const Text('Démarrer la caisse'), @@ -244,7 +218,6 @@ class _AccueilPageState extends State { ], ); } else { - // Afficher le contenu de la page d'accueil return Row( children: [ Expanded( @@ -254,7 +227,7 @@ class _AccueilPageState extends State { itemBuilder: (context, index) { final category = categories[index]; final products = productsByCategory[category]!; - + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -266,10 +239,7 @@ class _AccueilPageState extends State { style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, - fontStyle: FontStyle.italic, - decorationThickness: 2, ), - textAlign: TextAlign.center, ), ), ), @@ -284,125 +254,11 @@ class _AccueilPageState extends State { itemCount: products.length, itemBuilder: (context, index) { final product = products[index]; - - return Card( - elevation: 7, - shadowColor: Colors.redAccent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - child: InkWell( - onTap: () {}, - child: Container( - padding: const EdgeInsets.all(8), - child: Align( - alignment: Alignment.center, - child: Column( - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - Expanded( - child: AspectRatio( - aspectRatio: 14, - child: product.image != null - ? Image.file( - File(product.image), - fit: BoxFit.cover, - ) - : Image.asset( - 'assets/placeholder_image.png', - fit: BoxFit.cover, - ), - ), - ), - Center( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Text( - product.name, - style: const TextStyle( - fontWeight: - FontWeight.bold, - fontSize: 16, - ), - ), - const SizedBox(height: 4), - Text( - '${product.price.toStringAsFixed(2)} fcfa', - style: const TextStyle( - fontWeight: - FontWeight.bold, - fontSize: 14, - color: Colors.green, - ), - ), - if (product - .isStockDefined()) ...[ - const SizedBox(height: 8), - const Text( - 'En stock', - style: TextStyle( - fontWeight: - FontWeight.bold, - fontSize: 12, - color: Colors.green, - ), - ), - ], - const SizedBox(height: 8), - QuantityInput( - value: selectedQuantity, - minValue: 1, - maxValue: 100, - step: 1, - inputWidth: 60, - buttonColor: - Colors.redAccent, - onChanged: - (String value) { - setState(() { - selectedQuantity = - int.parse(value); - }); - }, - ), - const SizedBox(height: 8), - ElevatedButton( - onPressed: () { - addToCartWithDetails( - product); - }, - style: ElevatedButton - .styleFrom( - backgroundColor: - const Color.fromARGB(255, 4, 54, 95), - onPrimary: Colors.white, - shape: - RoundedRectangleBorder( - borderRadius: - BorderRadius - .circular(18), - ), - ), - child: const Text( - 'Ajouter au panier' - , - style: TextStyle(color: Colors.white), - ), - - ), - ], - ), - ), - ], - ), - ), - ), - ), + return ProductCard( + product: product, + onAddToCart: (product, quantity) { + addToCartWithDetails(product, quantity); + }, ); }, ), @@ -419,93 +275,58 @@ class _AccueilPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - const Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Panier', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: const Color.fromARGB(255, 4, 54, 95), - ), - ), - Icon( - Icons.shopping_cart, - color: Colors.red, - ), - ], + const Text( + 'Panier', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), ), const SizedBox(height: 16), Expanded( - child: ListView.builder( - itemCount: selectedProducts.length, - itemBuilder: (context, index) { - final cartItem = selectedProducts[index]; - final product = cartItem.product; - final quantity = cartItem.quantity; - - return ListTile( - title: Text(product.name), - subtitle: Text(product.category), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - '${product.price.toStringAsFixed(2)} fcfa'), - const SizedBox(width: 8), - Text('x $quantity'), - const SizedBox(width: 8), - IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - setState(() { - selectedProducts.removeAt(index); - }); - }, - color: Colors.redAccent, - ), - ], + child: selectedProducts.isEmpty + ? const Center( + child: Text("Votre panier est vide"), + ) + : ListView.builder( + itemCount: selectedProducts.length, + itemBuilder: (context, index) { + final cartItem = selectedProducts[index]; + return ListTile( + title: Text(cartItem.product.name), + subtitle: Text( + '${cartItem.product.price} FCFA x ${cartItem.quantity}'), + trailing: IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + setState(() { + selectedProducts.removeAt(index); + }); + }, + ), + ); + }, ), - ); - }, - ), ), - const SizedBox(height: 16), Text( - 'Total: ${calculateTotalPrice().toStringAsFixed(2)} fcfa', - textAlign: TextAlign.end, + 'Total: ${calculateTotalPrice().toStringAsFixed(2)} FCFA', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), - const SizedBox(height: 8), - TextFormField( + TextField( keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: 'Montant payé', ), onChanged: (value) { - setState(() { - amountPaid = double.parse(value); - }); + amountPaid = double.tryParse(value) ?? 0; }, ), - const SizedBox(height: 8), ElevatedButton( - onPressed: () { - saveOrderToDatabase(); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color.fromARGB(255, 4, 54, 95), - onPrimary: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(18), - ), - ), - child: const Text('Payer'), + onPressed: saveOrderToDatabase, + child: const Text('Valider la commande'), ), ], ), @@ -523,4 +344,4 @@ class _AccueilPageState extends State { ), ); } -} +} \ No newline at end of file diff --git a/lib/controller/userController.dart b/lib/controller/userController.dart index 82c5a3c..5b5d358 100644 --- a/lib/controller/userController.dart +++ b/lib/controller/userController.dart @@ -1,5 +1,6 @@ import 'package:get/get.dart'; import 'package:youmazgestion/Models/users.dart'; +import 'package:youmazgestion/Services/app_database.dart'; class UserController extends GetxController { final _username = ''.obs; @@ -30,4 +31,18 @@ class UserController extends GetxController { _password.value = user.password; print(_password.value); } + + Future hasPermission(String permissionName) async { + // Utilisez votre instance de AppDatabase pour vérifier la permission + return await AppDatabase.instance.hasPermission(username, permissionName); + } + Future hasAnyPermission(List permissionNames) async { + for (String permissionName in permissionNames) { + if (await hasPermission(permissionName)) { + return true; + } + } + return false; +} + } diff --git a/lib/main.dart b/lib/main.dart index 9a3a170..718968e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,22 +1,38 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:youmazgestion/Services/authDatabase.dart'; +import 'package:youmazgestion/Services/app_database.dart'; import 'Services/productDatabase.dart'; import 'my_app.dart'; import 'package:logging/logging.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - await ProductDatabase.instance.initDatabase(); - await AuthDatabase.instance.initDatabase(); - - setupLogger(); // Appel à la fonction setupLogger() - - //await OrderDatabase.instance.initDatabase(); - runApp(const GetMaterialApp( - debugShowCheckedModeBanner: false, - home: MyApp(), - )); + + try { + // Initialiser les bases de données une seule fois + await ProductDatabase.instance.initDatabase(); + await AppDatabase.instance.initDatabase(); + + // Afficher les informations de la base (pour debug) + await AppDatabase.instance.printDatabaseInfo(); + + setupLogger(); + + runApp(const GetMaterialApp( + debugShowCheckedModeBanner: false, + home: MyApp(), + )); + } catch (e) { + print('Erreur lors de l\'initialisation: $e'); + // Vous pourriez vouloir afficher une page d'erreur ici + runApp(MaterialApp( + home: Scaffold( + body: Center( + child: Text('Erreur d\'initialisation: $e'), + ), + ), + )); + } } void setupLogger() { @@ -24,4 +40,4 @@ void setupLogger() { Logger.root.onRecord.listen((record) { print('${record.level.name}: ${record.time}: ${record.message}'); }); -} +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index f7dd379..3a5ccee 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" barcode: dependency: transitive description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: @@ -500,10 +500,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -1161,10 +1161,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" web: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index fb36bc3..b7ffea9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -99,6 +99,7 @@ flutter: - assets/database/products2.db - assets/database/usersdb.db - assets/database/work.db + - assets/database/roles.db # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware