20 changed files with 2207 additions and 1003 deletions
@ -0,0 +1,16 @@ |
|||||
|
class Permission { |
||||
|
final int? id; |
||||
|
final String name; |
||||
|
|
||||
|
Permission({this.id, required this.name}); |
||||
|
|
||||
|
factory Permission.fromMap(Map<String, dynamic> map) => Permission( |
||||
|
id: map['id'], |
||||
|
name: map['name'], |
||||
|
); |
||||
|
|
||||
|
Map<String, dynamic> toMap() => { |
||||
|
'id': id, |
||||
|
'name': name, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
class Role { |
||||
|
final int? id; |
||||
|
final String designation; |
||||
|
|
||||
|
Role({this.id, required this.designation}); |
||||
|
|
||||
|
Map<String, dynamic> toMap() { |
||||
|
return { |
||||
|
'id': id, |
||||
|
'designation': designation, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
factory Role.fromMap(Map<String, dynamic> map) { |
||||
|
return Role( |
||||
|
id: map['id'], |
||||
|
designation: map['designation'], |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -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<Database> get database async { |
||||
|
if (_database.isOpen) return _database; |
||||
|
_database = await _initDB('app_database.db'); |
||||
|
return _database; |
||||
|
} |
||||
|
|
||||
|
Future<void> initDatabase() async { |
||||
|
_database = await _initDB('app_database.db'); |
||||
|
await _createDB(_database, 1); |
||||
|
await insertDefaultPermissions(); |
||||
|
await insertDefaultRoles(); |
||||
|
await insertDefaultSuperAdmin(); |
||||
|
} |
||||
|
|
||||
|
Future<Database> _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<int> 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<void> _createDB(Database db, int version) async { |
||||
|
final tables = await db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'"); |
||||
|
final tableNames = tables.map((row) => row['name'] as String).toList(); |
||||
|
|
||||
|
// Table 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<void> insertDefaultPermissions() async { |
||||
|
final db = await database; |
||||
|
final existing = await db.query('permissions'); |
||||
|
if (existing.isEmpty) { |
||||
|
await db.insert('permissions', {'name': 'view'}); |
||||
|
await db.insert('permissions', {'name': 'create'}); |
||||
|
await db.insert('permissions', {'name': 'update'}); |
||||
|
await db.insert('permissions', {'name': 'delete'}); |
||||
|
await db.insert('permissions', {'name': 'admin'}); |
||||
|
print("Permissions par défaut insérées"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Future<void> 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<void> 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<int> createUser(Users user) async { |
||||
|
final db = await database; |
||||
|
return await db.insert('users', user.toMap()); |
||||
|
} |
||||
|
|
||||
|
Future<int> deleteUser(int id) async { |
||||
|
final db = await database; |
||||
|
return await db.delete('users', where: 'id = ?', whereArgs: [id]); |
||||
|
} |
||||
|
|
||||
|
Future<int> updateUser(Users user) async { |
||||
|
final db = await database; |
||||
|
return await db.update('users', user.toMap(), where: 'id = ?', whereArgs: [user.id]); |
||||
|
} |
||||
|
|
||||
|
Future<int> getUserCount() async { |
||||
|
final db = await database; |
||||
|
List<Map<String, dynamic>> result = await db.rawQuery('SELECT COUNT(*) as count FROM users'); |
||||
|
return result.first['count'] as int; |
||||
|
} |
||||
|
|
||||
|
Future<bool> 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<Users> 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<Map<String, dynamic>?> 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<List<Users>> 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<int> createRole(Role role) async { |
||||
|
final db = await database; |
||||
|
return await db.insert('roles', role.toMap()); |
||||
|
} |
||||
|
|
||||
|
Future<List<Role>> 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<int> updateRole(Role role) async { |
||||
|
final db = await database; |
||||
|
return await db.update( |
||||
|
'roles', |
||||
|
role.toMap(), |
||||
|
where: 'id = ?', |
||||
|
whereArgs: [role.id], |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
Future<int> deleteRole(int? id) async { |
||||
|
final db = await database; |
||||
|
return await db.delete( |
||||
|
'roles', |
||||
|
where: 'id = ?', |
||||
|
whereArgs: [id], |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// ========== GESTION DES PERMISSIONS ========== |
||||
|
|
||||
|
Future<List<Permission>> getAllPermissions() async { |
||||
|
final db = await database; |
||||
|
final result = await db.query('permissions', orderBy: 'name ASC'); |
||||
|
return result.map((e) => Permission.fromMap(e)).toList(); |
||||
|
} |
||||
|
|
||||
|
Future<List<Permission>> 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<List<Permission>> 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<void> 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<void> 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<bool> 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<void> 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<void> close() async { |
||||
|
if (_database.isOpen) { |
||||
|
await _database.close(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Future<bool> 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<void> 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("========================================="); |
||||
|
} |
||||
|
} |
||||
@ -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<void> initDatabase() async { |
|
||||
_database = await _initDB('usersDb.db'); |
|
||||
await _createDB(_database, 1); |
|
||||
} |
|
||||
|
|
||||
Future<Database> get database async { |
|
||||
if (_database.isOpen) return _database; |
|
||||
|
|
||||
_database = await _initDB('usersDb.db'); |
|
||||
return _database; |
|
||||
} |
|
||||
|
|
||||
Future<Database> _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<int> 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<void> _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<int> createUser(Users user) async { |
|
||||
final db = await database; |
|
||||
return await db.insert('users', user.toMap()); |
|
||||
} |
|
||||
|
|
||||
Future<int> deleteUser(int id) async { |
|
||||
final db = await database; |
|
||||
return await db.delete('users', where: 'id = ?', whereArgs: [id]); |
|
||||
} |
|
||||
|
|
||||
Future<int> updateUser(Users user) async { |
|
||||
final db = await database; |
|
||||
return await db |
|
||||
.update('users', user.toMap(), where: 'id = ?', whereArgs: [user.id]); |
|
||||
} |
|
||||
|
|
||||
Future<int> getUserCount() async { |
|
||||
final db = await database; |
|
||||
List<Map<String, dynamic>> x = |
|
||||
await db.rawQuery('SELECT COUNT (*) from users'); |
|
||||
int result = Sqflite.firstIntValue(x)!; |
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
// verify username and password existe |
|
||||
Future<bool> verifyUser(String username, String password) async { |
|
||||
final db = await database; |
|
||||
List<Map<String, dynamic>> 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<Users> getUser(String username) async { |
|
||||
try { |
|
||||
final db = await database; |
|
||||
List<Map<String, dynamic>> 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<Map<String, String>?> getUserCredentials(String username, String password) async { |
|
||||
final db = await database; |
|
||||
|
|
||||
List<Map<String, dynamic>> 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<List<Users>> 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(); |
|
||||
} |
|
||||
} |
|
||||
@ -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<HandleUserRole> createState() => _HandleUserRoleState(); |
||||
|
} |
||||
|
|
||||
|
class _HandleUserRoleState extends State<HandleUserRole> { |
||||
|
final db = AppDatabase.instance; |
||||
|
|
||||
|
List<Map<String, dynamic>> roles = []; |
||||
|
Map<String, bool> permissionsMap = {}; |
||||
|
|
||||
|
int? selectedRoleId; |
||||
|
final TextEditingController _roleController = TextEditingController(); |
||||
|
|
||||
|
@override |
||||
|
void initState() { |
||||
|
super.initState(); |
||||
|
_initData(); |
||||
|
} |
||||
|
|
||||
|
Future<void> _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<void> _addRole() async { |
||||
|
String designation = _roleController.text.trim(); |
||||
|
if (designation.isEmpty) return; |
||||
|
|
||||
|
await db.createRole(Role(designation: designation)); |
||||
|
_roleController.clear(); |
||||
|
await _initData(); |
||||
|
} |
||||
|
|
||||
|
Future<void> _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<void> _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<int>( |
||||
|
isExpanded: true, |
||||
|
hint: const Text("Choisir un rôle"), |
||||
|
value: selectedRoleId, |
||||
|
items: roles.map((role) { |
||||
|
return DropdownMenuItem<int>( |
||||
|
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(), |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -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<ProductCard> createState() => _ProductCardState(); |
||||
|
} |
||||
|
|
||||
|
class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin { |
||||
|
int selectedQuantity = 1; |
||||
|
late AnimationController _scaleController; |
||||
|
late AnimationController _fadeController; |
||||
|
late Animation<double> _scaleAnimation; |
||||
|
late Animation<double> _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<double>( |
||||
|
begin: 1.0, |
||||
|
end: 0.95, |
||||
|
).animate(CurvedAnimation( |
||||
|
parent: _scaleController, |
||||
|
curve: Curves.easeInOut, |
||||
|
)); |
||||
|
|
||||
|
_fadeAnimation = Tween<double>( |
||||
|
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, |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue