Browse Source

dernier mis a jour

31052025_01
b.razafimandimbihery 6 months ago
parent
commit
e739df3811
  1. 0
      assets/database/roles.db
  2. 207
      lib/Components/appDrawer.dart
  3. 16
      lib/Models/Permission.dart
  4. 20
      lib/Models/Role.dart
  5. 26
      lib/Models/users.dart
  6. 477
      lib/Services/app_database.dart
  7. 147
      lib/Services/authDatabase.dart
  8. 288
      lib/Views/addProduct.dart
  9. 352
      lib/Views/editUser.dart
  10. 2
      lib/Views/gestionProduct.dart
  11. 147
      lib/Views/gestionRole.dart
  12. 8
      lib/Views/listUser.dart
  13. 212
      lib/Views/loginPage.dart
  14. 449
      lib/Views/produitsCard.dart
  15. 496
      lib/Views/registrationPage.dart
  16. 291
      lib/accueil.dart
  17. 15
      lib/controller/userController.dart
  18. 40
      lib/main.dart
  19. 16
      pubspec.lock
  20. 1
      pubspec.yaml

0
assets/database/roles.db

207
lib/Components/appDrawer.dart

@ -2,24 +2,26 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:youmazgestion/Views/historique.dart'; import 'package:youmazgestion/Views/historique.dart';
import 'package:youmazgestion/Views/addProduct.dart';
import '../Views/addProduct.dart'; import 'package:youmazgestion/Views/bilanMois.dart';
import '../Views/bilanMois.dart'; import 'package:youmazgestion/Views/gestionProduct.dart';
import '../Views/gestionProduct.dart'; import 'package:youmazgestion/Views/gestionStock.dart';
import '../Views/gestionStock.dart'; import 'package:youmazgestion/Views/listUser.dart';
import '../Views/listUser.dart'; import 'package:youmazgestion/Views/loginPage.dart';
import '../Views/loginPage.dart'; import 'package:youmazgestion/Views/registrationPage.dart';
import '../Views/registrationPage.dart'; import 'package:youmazgestion/Views/gestionRole.dart';
import '../accueil.dart'; import 'package:youmazgestion/accueil.dart';
import '../controller/userController.dart'; import 'package:youmazgestion/controller/userController.dart';
class CustomDrawer extends StatelessWidget { class CustomDrawer extends StatelessWidget {
final UserController userController = Get.find<UserController>(); final UserController userController = Get.find<UserController>();
Future<void> clearUserData() async {
final prefs = await SharedPreferences.getInstance(); Future<void> clearUserData() async {
await prefs.remove('username'); final prefs = await SharedPreferences.getInstance();
await prefs.remove('role'); await prefs.remove('username');
} await prefs.remove('role');
}
CustomDrawer({super.key}); CustomDrawer({super.key});
@override @override
@ -30,25 +32,25 @@ Future<void> clearUserData() async {
children: [ children: [
GetBuilder<UserController>( GetBuilder<UserController>(
builder: (controller) => UserAccountsDrawerHeader( builder: (controller) => UserAccountsDrawerHeader(
accountEmail: Text(controller.email), accountEmail: Text(controller.email),
accountName: Text(controller.name), accountName: Text(controller.name),
currentAccountPicture: const CircleAvatar( currentAccountPicture: const CircleAvatar(
backgroundImage: AssetImage("assets/youmaz2.png"), 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( ListTile(
leading: const Icon(Icons.home), leading: const Icon(Icons.home),
iconColor: Colors.lightBlueAccent, iconColor: Colors.lightBlueAccent,
title: const Text("Accueil"), title: const Text("Accueil"),
onTap: () { onTap: () {
// Action lorsque l'utilisateur clique sur "Accueil"
Get.to(const AccueilPage()); Get.to(const AccueilPage());
}, },
), ),
@ -56,18 +58,20 @@ Future<void> clearUserData() async {
leading: const Icon(Icons.person_add), leading: const Icon(Icons.person_add),
iconColor: Colors.green, iconColor: Colors.green,
title: const Text("Ajouter un utilisateur"), title: const Text("Ajouter un utilisateur"),
onTap: () { onTap: () async {
if (userController.role == "admin") { bool hasPermission = await userController.hasAnyPermission(['create']);
if (hasPermission) {
Get.to(const RegistrationPage()); Get.to(const RegistrationPage());
} else { } else {
Get.snackbar( Get.snackbar(
"Accés refusé", "Accès refusé",
backgroundColor: Colors.red, "Vous n'avez pas les droits pour ajouter un utilisateur",
colorText: Colors.white, backgroundColor: Colors.red,
icon: const Icon(Icons.error), colorText: Colors.white,
duration: const Duration(seconds: 3), icon: const Icon(Icons.error),
snackPosition: SnackPosition.TOP, duration: const Duration(seconds: 3),
"Vous n'avez pas les droits pour ajouter un utilisateur"); snackPosition: SnackPosition.TOP,
);
} }
}, },
), ),
@ -75,19 +79,20 @@ Future<void> clearUserData() async {
leading: const Icon(Icons.supervised_user_circle), leading: const Icon(Icons.supervised_user_circle),
iconColor: const Color.fromARGB(255, 4, 54, 95), iconColor: const Color.fromARGB(255, 4, 54, 95),
title: const Text("Modifier/Supprimer un utilisateur"), title: const Text("Modifier/Supprimer un utilisateur"),
onTap: () { onTap: () async {
// Action lorsque l'utilisateur clique sur "Modifier/Supprimer un utilisateur" bool hasPermission = await userController.hasAnyPermission(['update', 'delete']);
if (userController.role == "admin") { if (hasPermission) {
Get.to(const ListUserPage()); Get.to(const ListUserPage());
} else { } else {
Get.snackbar( Get.snackbar(
"Accés refusé", "Accès refusé",
backgroundColor: Colors.red, "Vous n'avez pas les droits pour modifier/supprimer un utilisateur",
colorText: Colors.white, backgroundColor: Colors.red,
icon: const Icon(Icons.error), colorText: Colors.white,
duration: const Duration(seconds: 3), icon: const Icon(Icons.error),
snackPosition: SnackPosition.TOP, duration: const Duration(seconds: 3),
"Vous n'avez pas les droits pour modifier/supprimer un utilisateur"); snackPosition: SnackPosition.TOP,
);
} }
}, },
), ),
@ -95,19 +100,20 @@ Future<void> clearUserData() async {
leading: const Icon(Icons.add), leading: const Icon(Icons.add),
iconColor: Colors.indigoAccent, iconColor: Colors.indigoAccent,
title: const Text("Ajouter un produit"), title: const Text("Ajouter un produit"),
onTap: () { onTap: () async {
if (userController.role == "admin") { bool hasPermission = await userController.hasAnyPermission(['create']);
// Action lorsque l'utilisateur clique sur "Ajouter un produit" if (hasPermission) {
Get.to(const AddProductPage()); Get.to(const AddProductPage());
} else { } else {
Get.snackbar( Get.snackbar(
"Accés refusé", "Accès refusé",
backgroundColor: Colors.red, "Vous n'avez pas les droits pour ajouter un produit",
colorText: Colors.white, backgroundColor: Colors.red,
icon: const Icon(Icons.error), colorText: Colors.white,
duration: const Duration(seconds: 3), icon: const Icon(Icons.error),
snackPosition: SnackPosition.TOP, duration: const Duration(seconds: 3),
"Vous n'avez pas les droits pour ajouter un produit"); snackPosition: SnackPosition.TOP,
);
} }
}, },
), ),
@ -115,37 +121,60 @@ Future<void> clearUserData() async {
leading: const Icon(Icons.edit), leading: const Icon(Icons.edit),
iconColor: Colors.redAccent, iconColor: Colors.redAccent,
title: const Text("Modifier/Supprimer un produit"), title: const Text("Modifier/Supprimer un produit"),
onTap: () { onTap: () async {
if (userController.role == "admin") { bool hasPermission = await userController.hasAnyPermission(['update', 'delete']);
// Action lorsque l'utilisateur clique sur "Modifier/Supprimer un produit" if (hasPermission) {
Get.to(GestionProduit()); Get.to(GestionProduit());
} else { } else {
Get.snackbar( Get.snackbar(
"Accés refusé", "Accès refusé",
backgroundColor: Colors.red, "Vous n'avez pas les droits pour modifier/supprimer un produit",
colorText: Colors.white, backgroundColor: Colors.red,
icon: const Icon(Icons.error), colorText: Colors.white,
duration: const Duration(seconds: 3), icon: const Icon(Icons.error),
snackPosition: SnackPosition.TOP, duration: const Duration(seconds: 3),
"Vous n'avez pas les droits pour modifier/supprimer un produit"); snackPosition: SnackPosition.TOP,
);
} }
}, },
), ),
ListTile( ListTile(
leading: const Icon(Icons.bar_chart), leading: const Icon(Icons.bar_chart),
title: const Text("Bilan"), title: const Text("Bilan"),
onTap: () { onTap: () async {
if (userController.role == "admin") { bool hasPermission = await userController.hasAnyPermission(['read']);
if (hasPermission) {
Get.to(const BilanMois()); Get.to(const BilanMois());
} else { } else {
Get.snackbar( Get.snackbar(
"Accés refusé", "Accès refusé",
backgroundColor: Colors.red, "Vous n'avez pas les droits pour accéder au bilan",
colorText: Colors.white, backgroundColor: Colors.red,
icon: const Icon(Icons.error_outline_outlined), colorText: Colors.white,
duration: const Duration(seconds: 3), icon: const Icon(Icons.error_outline_outlined),
snackPosition: SnackPosition.TOP, duration: const Duration(seconds: 3),
"Vous n'avez pas les droits pour accéder au bilan"); 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<void> clearUserData() async {
leading: const Icon(Icons.inventory), leading: const Icon(Icons.inventory),
iconColor: Colors.blueAccent, iconColor: Colors.blueAccent,
title: const Text("Gestion de stock"), title: const Text("Gestion de stock"),
onTap: () { onTap: () async {
if (userController.role == "admin") { bool hasPermission = await userController.hasAnyPermission(['update']);
// Action lorsque l'utilisateur clique sur "Gestion de stock" if (hasPermission) {
Get.to(const GestionStockPage()); Get.to(const GestionStockPage());
} else { } else {
Get.snackbar( Get.snackbar(
"Accés refusé", "Accès refusé",
backgroundColor: Colors.red, "Vous n'avez pas les droits pour accéder à la gestion de stock",
colorText: Colors.white, backgroundColor: Colors.red,
icon: const Icon(Icons.error), colorText: Colors.white,
duration: const Duration(seconds: 3), icon: const Icon(Icons.error),
snackPosition: SnackPosition.TOP, duration: const Duration(seconds: 3),
"Vous n'avez pas les droits pour accéder à la gestion de stock"); snackPosition: SnackPosition.TOP,
);
} }
}, },
), ),
@ -174,7 +204,6 @@ Future<void> clearUserData() async {
iconColor: Colors.blue, iconColor: Colors.blue,
title: const Text("Historique"), title: const Text("Historique"),
onTap: () { onTap: () {
// Action lorsque l'utilisateur clique sur "Historique"
Get.to(HistoryPage()); Get.to(HistoryPage());
}, },
), ),
@ -183,8 +212,6 @@ Future<void> clearUserData() async {
iconColor: Colors.red, iconColor: Colors.red,
title: const Text("Déconnexion"), title: const Text("Déconnexion"),
onTap: () { onTap: () {
// Action lorsque l'utilisateur clique sur "Déconnexion"
// display confirmation dialog
Get.defaultDialog( Get.defaultDialog(
title: "Déconnexion", title: "Déconnexion",
content: const Text("Voulez-vous vraiment vous déconnecter ?"), content: const Text("Voulez-vous vraiment vous déconnecter ?"),
@ -201,11 +228,11 @@ Future<void> clearUserData() async {
onPressed: () { onPressed: () {
Get.back(); Get.back();
}, },
) ),
], ],
); );
}, },
) ),
], ],
), ),
); );

16
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<String, dynamic> map) => Permission(
id: map['id'],
name: map['name'],
);
Map<String, dynamic> toMap() => {
'id': id,
'name': name,
};
}

20
lib/Models/Role.dart

@ -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'],
);
}
}

26
lib/Models/users.dart

@ -1,34 +1,41 @@
class Users { class Users {
int id; int? id;
String name; String name;
String lastName; String lastName;
String email; String email;
String password; String password;
String username; String username;
String role; int roleId;
String? roleName; // Optionnel, rempli lors des requêtes avec JOIN
Users({ Users({
required this.id, this.id,
required this.name, required this.name,
required this.lastName, required this.lastName,
required this.email, required this.email,
required this.password, required this.password,
required this.username, required this.username,
required this.role, required this.roleId,
this.roleName,
}); });
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return { return {
'id': id,
'name': name, 'name': name,
'lastName': lastName, 'lastname': lastName,
'email': email, 'email': email,
'password': password, 'password': password,
'username': username, 'username': username,
'role': role, 'role_id': roleId,
}; };
} }
Map<String, dynamic> toMapWithId() {
final map = toMap();
if (id != null) map['id'] = id;
return map;
}
factory Users.fromMap(Map<String, dynamic> map) { factory Users.fromMap(Map<String, dynamic> map) {
return Users( return Users(
id: map['id'], id: map['id'],
@ -37,8 +44,11 @@ class Users {
email: map['email'], email: map['email'],
password: map['password'], password: map['password'],
username: map['username'], 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 ?? '';
} }

477
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<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("=========================================");
}
}

147
lib/Services/authDatabase.dart

@ -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();
}
}

288
lib/Views/addProduct.dart

@ -19,17 +19,16 @@ class AddProductPage extends StatefulWidget {
} }
class _AddProductPageState extends State<AddProductPage> { class _AddProductPageState extends State<AddProductPage> {
// Controllers for text fields
final TextEditingController _nameController = TextEditingController(); final TextEditingController _nameController = TextEditingController();
final TextEditingController _priceController = TextEditingController(); final TextEditingController _priceController = TextEditingController();
final TextEditingController _imageController = TextEditingController(); final TextEditingController _imageController = TextEditingController();
final TextEditingController _descriptionController = TextEditingController(); final TextEditingController _descriptionController = TextEditingController();
String? _qrData; // Variable to store QR code data final List<String> _categories = ['Sucré', 'Salé', 'Jus', 'Gateaux'];
final List<String> _categories = ['Sucré', 'Salé', 'Jus', 'Gateaux']; // List of product categories String? _selectedCategory;
String? _selectedCategory; // Selected category File? _pickedImage;
File? _pickedImage; // Variable to store the selected image file String? _qrData;
late ProductDatabase _productDatabase; // Database instance late ProductDatabase _productDatabase;
@override @override
void initState() { void initState() {
@ -43,52 +42,12 @@ class _AddProductPageState extends State<AddProductPage> {
void dispose() { void dispose() {
_nameController.removeListener(_updateQrData); _nameController.removeListener(_updateQrData);
_nameController.dispose(); _nameController.dispose();
_priceController.dispose();
_imageController.dispose();
_descriptionController.dispose();
super.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() { void _updateQrData() {
if (_nameController.text.isNotEmpty) { if (_nameController.text.isNotEmpty) {
final reference = 'PROD_PREVIEW_${_nameController.text}_${DateTime.now().millisecondsSinceEpoch}'; final reference = 'PROD_PREVIEW_${_nameController.text}_${DateTime.now().millisecondsSinceEpoch}';
@ -98,80 +57,82 @@ class _AddProductPageState extends State<AddProductPage> {
} }
} }
// Function to get the database location Future<void> _selectImage() async {
Future<void> _getDatabaseLocation() async { final result = await FilePicker.platform.pickFiles(type: FileType.image);
final directory = await getApplicationDocumentsDirectory(); if (result != null && result.files.single.path != null) {
final dbPath = directory.path; setState(() {
print('Emplacement de la base de données : $dbPath'); _pickedImage = File(result.files.single.path!);
_imageController.text = _pickedImage!.path;
});
}
} }
// Function to generate and save QR code
Future<String> _generateAndSaveQRCode(String reference) async { Future<String> _generateAndSaveQRCode(String reference) async {
final qrValidationResult = QrValidator.validate( final validation = QrValidator.validate(
data: 'https://tonsite.com/$reference', data: 'https://tonsite.com/$reference',
version: QrVersions.auto, version: QrVersions.auto,
errorCorrectionLevel: QrErrorCorrectLevel.L, errorCorrectionLevel: QrErrorCorrectLevel.L,
); );
final qrCode = qrValidationResult.qrCode;
final qrCode = validation.qrCode!;
final painter = QrPainter.withQr( final painter = QrPainter.withQr(
qr: qrCode!, qr: qrCode,
color: Colors.black, color: Colors.black,
emptyColor: Colors.white, emptyColor: Colors.white,
gapless: true, gapless: true,
); );
final tempDir = await getApplicationDocumentsDirectory(); final directory = await getApplicationDocumentsDirectory();
final file = File('${tempDir.path}/$reference.png'); final path = '${directory.path}/$reference.png';
final picData = await painter.toImageData(2048, format: ImageByteFormat.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 { void _addProduct() async {
final name = _nameController.text; final name = _nameController.text.trim();
final price = double.tryParse(_priceController.text) ?? 0.0; final price = double.tryParse(_priceController.text.trim()) ?? 0.0;
final image = _imageController.text; final image = _imageController.text.trim();
final category = _selectedCategory; final category = _selectedCategory;
final description = _descriptionController.text; final description = _descriptionController.text.trim();
if (name.isNotEmpty && price > 0 && image.isNotEmpty && category != null) { if (name.isEmpty || price <= 0 || image.isEmpty || category == null) {
final reference = 'PROD_${DateTime.now().millisecondsSinceEpoch}'; Get.snackbar('Erreur', 'Veuillez remplir tous les champs requis');
final qrPath = await _generateAndSaveQRCode(reference); return;
}
final product = Product( final reference = 'PROD_${DateTime.now().millisecondsSinceEpoch}';
name: name, final qrPath = await _generateAndSaveQRCode(reference);
price: price,
image: image, final product = Product(
category: category, name: name,
description: description, price: price,
qrCode: qrPath, image: image,
reference: reference, 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((_) { setState(() {
Get.snackbar('Succès', 'Produit ajouté avec succès'); _nameController.clear();
setState(() { _priceController.clear();
_nameController.clear(); _imageController.clear();
_priceController.clear(); _descriptionController.clear();
_imageController.clear(); _selectedCategory = null;
_descriptionController.clear(); _pickedImage = null;
_selectedCategory = null; _qrData = null;
_pickedImage = null;
});
}).catchError((error) {
Get.snackbar('Erreur', 'Impossible d\'ajouter le produit : $error');
}); });
} else { } catch (e) {
Get.snackbar( Get.snackbar('Erreur', 'Ajout du produit échoué : $e');
'Saisie invalide',
'Veuillez entrer tous les champs requis.',
);
} }
} }
// Function to display the selected image
Widget _displayImage() { Widget _displayImage() {
if (_pickedImage != null) { if (_pickedImage != null) {
return ClipRRect( return ClipRRect(
@ -184,33 +145,14 @@ class _AddProductPageState extends State<AddProductPage> {
), ),
); );
} else { } else {
return Stack( return Container(
alignment: Alignment.center, width: 100,
children: [ height: 100,
Container( decoration: BoxDecoration(
width: 100, color: Colors.grey[200],
height: 100, borderRadius: BorderRadius.circular(8.0),
decoration: BoxDecoration( ),
color: Colors.grey[200], child: const Icon(Icons.image, size: 48, color: Colors.grey),
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],
),
),
),
],
); );
} }
} }
@ -219,35 +161,23 @@ class _AddProductPageState extends State<AddProductPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: const CustomAppBar(title: 'Ajouter un produit'), appBar: const CustomAppBar(title: 'Ajouter un produit'),
drawer: CustomDrawer(), drawer: CustomDrawer(),
body: Padding( body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( const Text('Ajouter un produit', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
'Ajouter un produit',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
controller: _nameController, controller: _nameController,
decoration: const InputDecoration( decoration: const InputDecoration(labelText: 'Nom du produit', border: OutlineInputBorder()),
labelText: 'Nom du produit',
border: OutlineInputBorder(),
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
controller: _priceController, controller: _priceController,
decoration: const InputDecoration( keyboardType: TextInputType.numberWithOptions(decimal: true),
labelText: 'Prix', decoration: const InputDecoration(labelText: 'Prix', border: OutlineInputBorder()),
border: OutlineInputBorder(),
),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
@ -256,17 +186,12 @@ class _AddProductPageState extends State<AddProductPage> {
Expanded( Expanded(
child: TextField( child: TextField(
controller: _imageController, controller: _imageController,
decoration: const InputDecoration( decoration: const InputDecoration(labelText: 'Chemin de l\'image', border: OutlineInputBorder()),
labelText: 'Image', readOnly: true,
border: OutlineInputBorder(),
),
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
ElevatedButton( ElevatedButton(onPressed: _selectImage, child: const Text('Sélectionner')),
onPressed: _selectImage,
child: const Text('Sélectionner une image'),
),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@ -274,48 +199,37 @@ class _AddProductPageState extends State<AddProductPage> {
const SizedBox(height: 16), const SizedBox(height: 16),
DropdownButtonFormField<String>( DropdownButtonFormField<String>(
value: _selectedCategory, value: _selectedCategory,
onChanged: (newValue) { items: _categories
setState(() { .map((c) => DropdownMenuItem(value: c, child: Text(c)))
_selectedCategory = newValue; .toList(),
}); onChanged: (value) => setState(() => _selectedCategory = value),
}, decoration: const InputDecoration(labelText: 'Catégorie', border: OutlineInputBorder()),
decoration: const InputDecoration(
labelText: 'Catégorie',
border: OutlineInputBorder(),
),
items: _categories.map((category) {
return DropdownMenuItem<String>(
value: category,
child: Text(category),
);
}).toList(),
), ),
const SizedBox(height: 16), 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( TextField(
controller: _descriptionController, controller: _descriptionController,
decoration: const InputDecoration(
labelText: 'Description',
border: OutlineInputBorder(),
),
maxLines: 3, maxLines: 3,
decoration: const InputDecoration(labelText: 'Description', border: OutlineInputBorder()),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
ElevatedButton( if (_qrData != null) ...[
onPressed: _addProduct, const Text('Aperçu du QR Code :'),
child: const Text('Ajouter le produit'), 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'),
),
), ),
], ],
), ),

352
lib/Views/editUser.dart

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:youmazgestion/Models/users.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 { class EditUserPage extends StatefulWidget {
final Users user; final Users user;
@ -17,7 +18,11 @@ class _EditUserPageState extends State<EditUserPage> {
late TextEditingController _emailController; late TextEditingController _emailController;
late TextEditingController _usernameController; late TextEditingController _usernameController;
late TextEditingController _passwordController; late TextEditingController _passwordController;
String _selectedRole = '';
List<Role> _roles = [];
Role? _selectedRole;
bool _isLoading = false;
bool _isLoadingRoles = true;
@override @override
void initState() { void initState() {
@ -26,9 +31,31 @@ class _EditUserPageState extends State<EditUserPage> {
_lastNameController = TextEditingController(text: widget.user.lastName); _lastNameController = TextEditingController(text: widget.user.lastName);
_emailController = TextEditingController(text: widget.user.email); _emailController = TextEditingController(text: widget.user.email);
_usernameController = TextEditingController(text: widget.user.username); _usernameController = TextEditingController(text: widget.user.username);
_passwordController = _passwordController = TextEditingController();
TextEditingController(); // Leave password field empty initially
_selectedRole = widget.user.role; _loadRoles();
}
Future<void> _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 @override
@ -41,144 +68,213 @@ class _EditUserPageState extends State<EditUserPage> {
super.dispose(); super.dispose();
} }
void _updateUser() { bool _validateFields() {
final String name = _nameController.text; if (_nameController.text.trim().isEmpty ||
final String lastName = _lastNameController.text; _lastNameController.text.trim().isEmpty ||
final String email = _emailController.text; _emailController.text.trim().isEmpty ||
final String username = _usernameController.text; _usernameController.text.trim().isEmpty ||
final String password = _selectedRole == null) {
_passwordController.text; // Get the entered password _showErrorDialog('Champs manquants', 'Veuillez remplir tous les champs requis.');
final String role = _selectedRole; return false;
}
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,
);
AuthDatabase.instance.updateUser(updatedUser).then((value) { if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$')
// User update successful .hasMatch(_emailController.text.trim())) {
showDialog( _showErrorDialog('Email invalide', 'Veuillez saisir un email valide.');
context: context, return false;
builder: (BuildContext context) { }
return AlertDialog(
title: const Text('Update Successful'), if (_passwordController.text.isNotEmpty &&
content: const Text('User information has been updated.'), _passwordController.text.length < 6) {
actions: <Widget>[ _showErrorDialog('Mot de passe trop court', 'Minimum 6 caractères.');
ElevatedButton( return false;
onPressed: () { }
Navigator.of(context).pop();
Navigator.pop(context, return true;
true); // Return true to indicate successful update }
},
child: const Text('OK'), Future<void> _updateUser() async {
), if (!_validateFields() || _isLoading) return;
],
); setState(() {
}, _isLoading = true;
);
}).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: <Widget>[
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('OK'),
),
],
);
},
);
}); });
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( 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( body: _isLoadingRoles
padding: const EdgeInsets.all(16.0), ? const Center(child: CircularProgressIndicator())
child: SingleChildScrollView( : SingleChildScrollView(
child: Column( padding: const EdgeInsets.all(16.0),
crossAxisAlignment: CrossAxisAlignment.stretch, child: Card(
children: [ elevation: 4,
TextField( child: Padding(
controller: _nameController, padding: const EdgeInsets.all(16.0),
decoration: const InputDecoration( child: Column(
labelText: 'First Name', children: [
), const Icon(Icons.edit, size: 64, color: Colors.blue),
), const SizedBox(height: 16),
const SizedBox(height: 16.0), _buildTextField(_nameController, 'Prénom', Icons.person),
TextField( const SizedBox(height: 12),
controller: _lastNameController, _buildTextField(_lastNameController, 'Nom', Icons.person_outline),
decoration: const InputDecoration( const SizedBox(height: 12),
labelText: 'Last Name', _buildTextField(_emailController, 'Email', Icons.email, keyboardType: TextInputType.emailAddress),
), const SizedBox(height: 12),
), _buildTextField(_usernameController, 'Nom d\'utilisateur', Icons.account_circle),
const SizedBox(height: 16.0), const SizedBox(height: 12),
TextField( _buildTextField(
controller: _emailController, _passwordController,
decoration: const InputDecoration( 'Mot de passe (laisser vide si inchangé)',
labelText: 'Email', Icons.lock,
), obscureText: true,
keyboardType: TextInputType.emailAddress, ),
), const SizedBox(height: 12),
const SizedBox(height: 16.0), _buildDropdown(),
TextField( const SizedBox(height: 20),
controller: _usernameController, SizedBox(
decoration: const InputDecoration( width: double.infinity,
labelText: 'Username', 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', Widget _buildTextField(
), TextEditingController controller,
obscureText: true, String label,
), IconData icon, {
const SizedBox(height: 16.0), TextInputType keyboardType = TextInputType.text,
DropdownButton<String>( bool obscureText = false,
value: _selectedRole, }) {
onChanged: (String? newValue) { 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<Role>(
value: _selectedRole,
isExpanded: true,
hint: const Text('Sélectionner un rôle'),
onChanged: _isLoading
? null
: (Role? newValue) {
setState(() { setState(() {
_selectedRole = newValue!; _selectedRole = newValue;
}); });
}, },
items: <String>['admin', 'user'] items: _roles.map((role) {
.map<DropdownMenuItem<String>>((String value) { return DropdownMenuItem<Role>(
return DropdownMenuItem<String>( value: role,
value: value, child: Row(
child: Text(value), children: [
); const Icon(Icons.badge, size: 20),
}).toList(), const SizedBox(width: 8),
), Text(role.designation),
const SizedBox(height: 16.0), ],
ElevatedButton(
onPressed: _updateUser,
child: const Text('Update'),
), ),
], );
), }).toList(),
), ),
), ),
); );

2
lib/Views/gestionProduct.dart

@ -11,7 +11,7 @@ import 'dart:io';
class GestionProduit extends StatelessWidget { class GestionProduit extends StatelessWidget {
final ProductDatabase _productDatabase = ProductDatabase.instance; final ProductDatabase _productDatabase = ProductDatabase.instance;
const GestionProduit({super.key}); GestionProduit({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

147
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<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(),
),
),
],
),
),
);
}
}

8
lib/Views/listUser.dart

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:youmazgestion/Models/users.dart'; import 'package:youmazgestion/Models/users.dart';
import '../Components/app_bar.dart'; import '../Components/app_bar.dart';
import '../Services/authDatabase.dart'; import '../Services/app_database.dart';
import 'editUser.dart'; import 'editUser.dart';
class ListUserPage extends StatefulWidget { class ListUserPage extends StatefulWidget {
@ -23,7 +23,7 @@ class _ListUserPageState extends State<ListUserPage> {
Future<void> getUsersFromDatabase() async { Future<void> getUsersFromDatabase() async {
try { try {
List<Users> users = await AuthDatabase.instance.getAllUsers(); List<Users> users = await AppDatabase.instance.getAllUsers();
setState(() { setState(() {
userList = users; userList = users;
}); });
@ -89,8 +89,8 @@ class _ListUserPageState extends State<ListUserPage> {
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
await AuthDatabase.instance await AppDatabase.instance
.deleteUser(user.id); .deleteUser(user.id!);
Navigator.of(context).pop(); Navigator.of(context).pop();
setState(() { setState(() {
userList.removeAt(index); userList.removeAt(index);

212
lib/Views/loginPage.dart

@ -3,7 +3,7 @@ import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:youmazgestion/Views/particles.dart' show ParticleBackground; import 'package:youmazgestion/Views/particles.dart' show ParticleBackground;
import 'package:youmazgestion/accueil.dart'; import 'package:youmazgestion/accueil.dart';
import 'package:youmazgestion/Services/authDatabase.dart'; import 'package:youmazgestion/Services/app_database.dart';
import '../Models/users.dart'; import '../Models/users.dart';
import '../controller/userController.dart'; import '../controller/userController.dart';
@ -20,6 +20,8 @@ class _LoginPageState extends State<LoginPage> {
late TextEditingController _passwordController; late TextEditingController _passwordController;
final UserController userController = Get.put(UserController()); final UserController userController = Get.put(UserController());
bool _isErrorVisible = false; bool _isErrorVisible = false;
bool _isLoading = false;
String _errorMessage = 'Nom d\'utilisateur ou mot de passe invalide';
@override @override
void initState() { void initState() {
@ -30,13 +32,27 @@ class _LoginPageState extends State<LoginPage> {
} }
void checkUserCount() async { void checkUserCount() async {
final userCount = await AuthDatabase.instance.getUserCount(); try {
if (userCount == 0) { final userCount = await AppDatabase.instance.getUserCount();
// No user found, redirect to home page print('Nombre d\'utilisateurs trouvés: $userCount'); // Debug
Navigator.pushReplacement(
context, // Commentez cette partie pour permettre le login même sans utilisateurs
MaterialPageRoute(builder: (context) => const AccueilPage()), /*
); 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<LoginPage> {
_passwordController.dispose(); _passwordController.dispose();
super.dispose(); super.dispose();
} }
Future<void> saveUser(String? username,String? role)async{
Future<void> saveUser(String username, String role, int userId) async {
try {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', username!); await prefs.setString('username', username);
await prefs.setString('role', role!); 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 { void _login() async {
final String username = _usernameController.text; if (_isLoading) return;
final String password = _passwordController.text;
print(username); final String username = _usernameController.text.trim();
print(password); 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 { try {
bool isValidUser = print('Tentative de connexion pour: $username');
await AuthDatabase.instance.verifyUser(username, password);
Users user = await AuthDatabase.instance.getUser(username);
Map<String, String>? getUserCredentials = await AuthDatabase.instance.getUserCredentials(username,password); // Vérification de la connexion à la base de données
print(isValidUser); 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) { if (isValidUser) {
print('User is valid'); // Récupérer les informations complètes de l'utilisateur
print(user); Users user = await dbInstance.getUser(username);
userController.setUser(user); print('Utilisateur récupéré: ${user.username}');
setState(() { // Récupérer les credentials
_isErrorVisible = false; Map<String, dynamic>? userCredentials =
}); await dbInstance.getUserCredentials(username, password);
Navigator.pushReplacement(
context, if (userCredentials != null) {
MaterialPageRoute(builder: (context) => const AccueilPage()), print('Connexion réussie pour: ${user.username}');
); print('Rôle: ${userCredentials['role']}');
saveUser(getUserCredentials?['username'], getUserCredentials?['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 { } else {
print('Identifiants invalides pour: $username');
setState(() { setState(() {
_errorMessage = 'Nom d\'utilisateur ou mot de passe invalide';
_isErrorVisible = true; _isErrorVisible = true;
}); });
} }
} catch (error) { } catch (error) {
print(error); print('Erreur lors de la connexion: $error');
setState(() { setState(() {
_errorMessage = 'Erreur de connexion: ${error.toString()}';
_isErrorVisible = true; _isErrorVisible = true;
}); });
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
} }
} }
@ -96,8 +176,8 @@ Future<void> saveUser(String? username,String? role)async{
appBar: AppBar( appBar: AppBar(
title: const Text( title: const Text(
'Login', 'Login',
style: TextStyle(color:Colors.white), style: TextStyle(color: Colors.white),
), ),
backgroundColor: const Color.fromARGB(255, 4, 54, 95), backgroundColor: const Color.fromARGB(255, 4, 54, 95),
centerTitle: true, centerTitle: true,
), ),
@ -125,10 +205,10 @@ Future<void> saveUser(String? username,String? role)async{
), ),
TextField( TextField(
controller: _usernameController, controller: _usernameController,
enabled: !_isLoading,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Username', labelText: 'Username',
prefixIcon: prefixIcon: const Icon(Icons.person, color: Colors.blueAccent),
const Icon(Icons.person, color: Colors.blueAccent),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0), borderRadius: BorderRadius.circular(30.0),
), ),
@ -137,6 +217,7 @@ Future<void> saveUser(String? username,String? role)async{
const SizedBox(height: 16.0), const SizedBox(height: 16.0),
TextField( TextField(
controller: _passwordController, controller: _passwordController,
enabled: !_isLoading,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Password', labelText: 'Password',
prefixIcon: const Icon(Icons.lock, color: Colors.redAccent), prefixIcon: const Icon(Icons.lock, color: Colors.redAccent),
@ -145,34 +226,67 @@ Future<void> saveUser(String? username,String? role)async{
), ),
), ),
obscureText: true, obscureText: true,
onSubmitted: (_) => _login(),
), ),
const SizedBox(height: 16.0), const SizedBox(height: 16.0),
Visibility( Visibility(
visible: _isErrorVisible, visible: _isErrorVisible,
child: const Text( child: Text(
'Invalid username or password', _errorMessage,
style: TextStyle( style: const TextStyle(
color: Colors.red, color: Colors.red,
fontSize: 14,
), ),
textAlign: TextAlign.center,
), ),
), ),
const SizedBox(height: 16.0), const SizedBox(height: 16.0),
ElevatedButton( ElevatedButton(
onPressed: _login, onPressed: _isLoading ? null : _login,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0015B7), // Nouvelle couleur backgroundColor: const Color(0xFF0015B7),
elevation: 5.0, elevation: 5.0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0), borderRadius: BorderRadius.circular(30.0),
), ),
minimumSize: const Size(double.infinity, 48),
), ),
child: const Text( child: _isLoading
'Login', ? const SizedBox(
style: TextStyle( height: 20,
color: Colors.white, 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<void> saveUser(String? username,String? role)async{
), ),
); );
} }
} }

449
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<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,
),
),
),
);
}
}

496
lib/Views/registrationPage.dart

@ -1,8 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:youmazgestion/Models/users.dart'; import 'package:youmazgestion/Models/users.dart';
import 'package:youmazgestion/Models/role.dart';
import 'package:youmazgestion/accueil.dart'; import 'package:youmazgestion/accueil.dart';
import '../Services/authDatabase.dart'; import '../Services/app_database.dart'; // Changé de authDatabase.dart
class RegistrationPage extends StatefulWidget { class RegistrationPage extends StatefulWidget {
const RegistrationPage({super.key}); const RegistrationPage({super.key});
@ -17,7 +18,11 @@ class _RegistrationPageState extends State<RegistrationPage> {
late TextEditingController _emailController; late TextEditingController _emailController;
late TextEditingController _usernameController; late TextEditingController _usernameController;
late TextEditingController _passwordController; late TextEditingController _passwordController;
String _selectedRole = 'user'; // Default role is 'user'
List<Role> _availableRoles = [];
Role? _selectedRole;
bool _isLoading = false;
bool _isLoadingRoles = true;
@override @override
void initState() { void initState() {
@ -27,7 +32,59 @@ class _RegistrationPageState extends State<RegistrationPage> {
_emailController = TextEditingController(); _emailController = TextEditingController();
_usernameController = TextEditingController(); _usernameController = TextEditingController();
_passwordController = TextEditingController(); _passwordController = TextEditingController();
AuthDatabase.instance.initDatabase();
_initializeDatabase();
}
Future<void> _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<void> _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<void> _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 @override
@ -40,148 +97,319 @@ class _RegistrationPageState extends State<RegistrationPage> {
super.dispose(); super.dispose();
} }
void _register() { bool _validateFields() {
// Get the entered user information if (_nameController.text.trim().isEmpty ||
final String name = _nameController.text; _lastNameController.text.trim().isEmpty ||
final String lastName = _lastNameController.text; _emailController.text.trim().isEmpty ||
final String email = _emailController.text; _usernameController.text.trim().isEmpty ||
final String username = _usernameController.text; _passwordController.text.trim().isEmpty ||
final String password = _passwordController.text; _selectedRole == null) {
final String role = _selectedRole; _showErrorDialog('Champs manquants', 'Veuillez remplir tous les champs.');
return false;
// 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,
);
// Save the user in the database // Validation basique de l'email
AuthDatabase.instance.createUser(user).then((value) { if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(_emailController.text.trim())) {
// Registration successful _showErrorDialog('Email invalide', 'Veuillez entrer un email valide.');
showDialog( return false;
context: context, }
builder: (BuildContext context) {
return AlertDialog( // Validation basique du mot de passe
title: const Text('Registration Successful'), if (_passwordController.text.length < 6) {
content: const Text('You have successfully registered.'), _showErrorDialog('Mot de passe trop court', 'Le mot de passe doit contenir au moins 6 caractères.');
actions: <Widget>[ return false;
ElevatedButton( }
onPressed: () {
Navigator.of(context).pop(); return true;
// Navigate to the login page }
Navigator.pushReplacement(
context, void _register() async {
MaterialPageRoute( if (_isLoading) return;
builder: (context) => const AccueilPage()),
); if (!_validateFields()) return;
},
child: const Text('OK'), setState(() {
), _isLoading = true;
],
);
},
);
}).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: <Widget>[
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('OK'),
),
],
);
},
);
}); });
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: <Widget>[
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: <Widget>[
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('OK'),
),
],
);
},
);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( 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( body: _isLoadingRoles
padding: const EdgeInsets.all(16.0), ? const Center(
child: SingleChildScrollView( child: Column(
child: Column( mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch, children: [
children: [ CircularProgressIndicator(),
TextField( SizedBox(height: 16),
controller: _nameController, Text('Chargement des rôles...'),
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',
),
), ),
const SizedBox(height: 16.0), )
TextField( : Padding(
controller: _passwordController, padding: const EdgeInsets.all(16.0),
decoration: const InputDecoration( child: SingleChildScrollView(
labelText: 'Password', 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<Role>(
value: _selectedRole,
hint: const Text('Sélectionner un rôle'),
isExpanded: true,
onChanged: _isLoading
? null
: (Role? newValue) {
setState(() {
_selectedRole = newValue;
});
},
items: _availableRoles
.map<DropdownMenuItem<Role>>((Role role) {
return DropdownMenuItem<Role>(
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<String>(
value: _selectedRole,
onChanged: (String? newValue) {
setState(() {
_selectedRole = newValue!;
});
},
items: <String>['admin', 'user']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
const SizedBox(height: 16.0),
ElevatedButton(
onPressed: _register,
child: const Text('Register'),
),
],
),
),
),
); );
} }
} }

291
lib/accueil.dart

@ -5,6 +5,7 @@ import 'package:get/get.dart';
import 'package:quantity_input/quantity_input.dart'; import 'package:quantity_input/quantity_input.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:youmazgestion/Views/particles.dart' show ParticleBackground; import 'package:youmazgestion/Views/particles.dart' show ParticleBackground;
import 'package:youmazgestion/Views/produitsCard.dart';
import 'Components/appDrawer.dart'; import 'Components/appDrawer.dart';
import 'Components/app_bar.dart'; import 'Components/app_bar.dart';
import 'Components/cartItem.dart'; import 'Components/cartItem.dart';
@ -35,7 +36,7 @@ class _AccueilPageState extends State<AccueilPage> {
int orderId = 0; int orderId = 0;
List<CartItem> selectedProducts = []; List<CartItem> selectedProducts = [];
int selectedQuantity = 1; // Quantité sélectionnée par défaut int selectedQuantity = 1;
double totalCartPrice = 0; double totalCartPrice = 0;
double amountPaid = 0; double amountPaid = 0;
@ -55,16 +56,15 @@ class _AccueilPageState extends State<AccueilPage> {
role = prefs.getString('role') ?? 'Rôle inconnu'; role = prefs.getString('role') ?? 'Rôle inconnu';
}); });
} }
Future<void> saveOrderToDatabase() async { Future<void> saveOrderToDatabase() async {
final totalPrice = calculateTotalPrice(); final totalPrice = calculateTotalPrice();
final dateTime = DateTime.now().toString(); final dateTime = DateTime.now().toString();
String user = userController.username; String user = userController.username;
// Insert the order into the database
orderId = await orderDatabase.insertOrder( orderId = await orderDatabase.insertOrder(
totalPrice, dateTime, MyApp.startDate!, user); totalPrice, dateTime, MyApp.startDate!, user);
// Insert order items into the database
for (final cartItem in selectedProducts) { for (final cartItem in selectedProducts) {
final product = cartItem.product; final product = cartItem.product;
final quantity = cartItem.quantity; final quantity = cartItem.quantity;
@ -73,7 +73,6 @@ class _AccueilPageState extends State<AccueilPage> {
await orderDatabase.insertOrderItem( await orderDatabase.insertOrderItem(
orderId, product.name, quantity, price); orderId, product.name, quantity, price);
// Mettre à jour le stock du produit
final updatedStock = product.stock! - quantity; final updatedStock = product.stock! - quantity;
await productDatabase.updateStock(product.id!, updatedStock); await productDatabase.updateStock(product.id!, updatedStock);
} }
@ -85,7 +84,6 @@ class _AccueilPageState extends State<AccueilPage> {
final categories = await productDatabase.getCategories(); final categories = await productDatabase.getCategories();
final productsByCategory = <String, List<Product>>{}; final productsByCategory = <String, List<Product>>{};
// Trier les catégories selon votre préférence
categories.sort(); categories.sort();
for (final categoryName in categories) { for (final categoryName in categories) {
@ -113,47 +111,34 @@ class _AccueilPageState extends State<AccueilPage> {
return totalPrice; return totalPrice;
} }
void addToCartWithDetails(Product product) { void addToCartWithDetails(Product product, int quantity) {
setState(() { setState(() {
final existingCartItem = selectedProducts.firstWhere( final existingCartItem = selectedProducts.firstWhere(
(cartItem) => cartItem.product == product, (cartItem) => cartItem.product.id == product.id,
orElse: () => CartItem(product, 0), orElse: () => CartItem(product, 0),
); );
if (existingCartItem.quantity == 0) { if (existingCartItem.quantity == 0) {
selectedProducts.add(CartItem(product, selectedQuantity)); selectedProducts.add(CartItem(product, quantity));
} else { } else {
existingCartItem.quantity += selectedQuantity; existingCartItem.quantity += quantity;
} }
}); });
// Afficher une notification
Get.snackbar( Get.snackbar(
'Produit ajouté', 'Produit ajouté',
'Le produit ${product.name} a été ajouté au panier', '${product.name} (x$quantity) ajouté au panier',
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 1), duration: const Duration(seconds: 1),
backgroundColor: Colors.green, backgroundColor: Colors.green,
colorText: Colors.white, colorText: Colors.white,
); );
resetQuantityAfterDelay();
}
Future<void> resetQuantityAfterDelay() async {
await Future.delayed(const Duration(seconds: 1));
setState(() {
selectedQuantity = 1;
});
} }
void showTicketPage() { void showTicketPage() {
// Calculer la somme totale du panier
final double totalCartPrice = calculateTotalPrice(); final double totalCartPrice = calculateTotalPrice();
// Vérifier si des produits sont présents dans le panier
if (selectedProducts.isNotEmpty) { if (selectedProducts.isNotEmpty) {
// Vérifier si la somme payée est suffisante et supérieure ou égale au total du panier
if (amountPaid >= totalCartPrice) { if (amountPaid >= totalCartPrice) {
// Passer les produits et les informations de l'entreprise à la page du ticket
Get.offAll(TicketPage( Get.offAll(TicketPage(
businessName: 'Youmaz', businessName: 'Youmaz',
businessAddress: businessAddress:
@ -164,10 +149,9 @@ class _AccueilPageState extends State<AccueilPage> {
amountPaid: amountPaid, amountPaid: amountPaid,
)); ));
} else { } else {
// Afficher un message d'erreur si la somme payée est insuffisante
Get.snackbar( Get.snackbar(
'Paiement incomplet', 'Paiement incomplet',
'Le montant payé est insuffisant. Veuillez payer le montant total du panier.', 'Le montant payé est insuffisant.',
snackPosition: SnackPosition.BOTTOM, snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 3), duration: const Duration(seconds: 3),
backgroundColor: Colors.red, backgroundColor: Colors.red,
@ -175,10 +159,9 @@ class _AccueilPageState extends State<AccueilPage> {
); );
} }
} else { } else {
// Afficher un message d'erreur si le panier est vide
Get.snackbar( Get.snackbar(
'Panier vide', 'Panier vide',
'Le panier est vide. Veuillez ajouter des produits avant de passer commande.', 'Ajoutez des produits avant de passer commande.',
snackPosition: SnackPosition.BOTTOM, snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 3), duration: const Duration(seconds: 3),
backgroundColor: Colors.red, backgroundColor: Colors.red,
@ -198,7 +181,7 @@ class _AccueilPageState extends State<AccueilPage> {
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [Colors.white, const Color.fromARGB(255, 4, 54, 95)], colors: [Colors.white, Color.fromARGB(255, 4, 54, 95)],
), ),
), ),
child: FutureBuilder<Map<String, List<Product>>>( child: FutureBuilder<Map<String, List<Product>>>(
@ -207,35 +190,26 @@ class _AccueilPageState extends State<AccueilPage> {
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) { } else if (snapshot.hasError) {
print('erreur:${snapshot.error}'); return const Center(child: Text("Erreur de chargement"));
return const Center(
child: Text("Erreur lors du chargement des produits"));
} else if (snapshot.hasData) { } else if (snapshot.hasData) {
final Map<String, List<Product>> productsByCategory = final productsByCategory = snapshot.data!;
snapshot.data!;
final categories = productsByCategory.keys.toList(); final categories = productsByCategory.keys.toList();
if (!MyApp.isRegisterOpen) { if (!MyApp.isRegisterOpen) {
// Afficher le bouton "Démarrer la caisse" si la variable isRegisterOpen est false
return Column( return Column(
children: [ children: [
Text('welcome $username ! votre role est $role'), Text('Bienvenue $username ! (Rôle: $role)'),
Center( Center(
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
setState(() { setState(() {
MyApp.isRegisterOpen = true; MyApp.isRegisterOpen = true;
// mettre startDate à la date actuelle et au format yyyy-MM-dd
String formattedDate = String formattedDate =
DateFormat('yyyy-MM-dd').format(DateTime.now()); DateFormat('yyyy-MM-dd').format(DateTime.now());
startDate = startDate =
DateFormat('yyyy-MM-dd').parse(formattedDate); DateFormat('yyyy-MM-dd').parse(formattedDate);
MyApp.startDate = startDate; MyApp.startDate = startDate;
workDatabase.insertDate(formattedDate);
var datee = DateFormat('yyyy-MM-dd')
.format(startDate!)
.toString();
workDatabase.insertDate(datee);
}); });
}, },
child: const Text('Démarrer la caisse'), child: const Text('Démarrer la caisse'),
@ -244,7 +218,6 @@ class _AccueilPageState extends State<AccueilPage> {
], ],
); );
} else { } else {
// Afficher le contenu de la page d'accueil
return Row( return Row(
children: [ children: [
Expanded( Expanded(
@ -254,7 +227,7 @@ class _AccueilPageState extends State<AccueilPage> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final category = categories[index]; final category = categories[index];
final products = productsByCategory[category]!; final products = productsByCategory[category]!;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -266,10 +239,7 @@ class _AccueilPageState extends State<AccueilPage> {
style: const TextStyle( style: const TextStyle(
fontSize: 22, fontSize: 22,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
decorationThickness: 2,
), ),
textAlign: TextAlign.center,
), ),
), ),
), ),
@ -284,125 +254,11 @@ class _AccueilPageState extends State<AccueilPage> {
itemCount: products.length, itemCount: products.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final product = products[index]; final product = products[index];
return ProductCard(
return Card( product: product,
elevation: 7, onAddToCart: (product, quantity) {
shadowColor: Colors.redAccent, addToCartWithDetails(product, quantity);
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),
),
),
],
),
),
],
),
),
),
),
); );
}, },
), ),
@ -419,93 +275,58 @@ class _AccueilPageState extends State<AccueilPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
const Row( const Text(
mainAxisAlignment: MainAxisAlignment.center, 'Panier',
children: [ style: TextStyle(
Text( fontSize: 20,
'Panier', fontWeight: FontWeight.bold,
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 SizedBox(height: 16), const SizedBox(height: 16),
Expanded( Expanded(
child: ListView.builder( child: selectedProducts.isEmpty
itemCount: selectedProducts.length, ? const Center(
itemBuilder: (context, index) { child: Text("Votre panier est vide"),
final cartItem = selectedProducts[index]; )
final product = cartItem.product; : ListView.builder(
final quantity = cartItem.quantity; itemCount: selectedProducts.length,
itemBuilder: (context, index) {
return ListTile( final cartItem = selectedProducts[index];
title: Text(product.name), return ListTile(
subtitle: Text(product.category), title: Text(cartItem.product.name),
trailing: Row( subtitle: Text(
mainAxisSize: MainAxisSize.min, '${cartItem.product.price} FCFA x ${cartItem.quantity}'),
children: [ trailing: IconButton(
Text( icon: const Icon(Icons.delete),
'${product.price.toStringAsFixed(2)} fcfa'), onPressed: () {
const SizedBox(width: 8), setState(() {
Text('x $quantity'), selectedProducts.removeAt(index);
const SizedBox(width: 8), });
IconButton( },
icon: const Icon(Icons.delete), ),
onPressed: () { );
setState(() { },
selectedProducts.removeAt(index);
});
},
color: Colors.redAccent,
),
],
), ),
);
},
),
), ),
const SizedBox(height: 16),
Text( Text(
'Total: ${calculateTotalPrice().toStringAsFixed(2)} fcfa', 'Total: ${calculateTotalPrice().toStringAsFixed(2)} FCFA',
textAlign: TextAlign.end,
style: const TextStyle( style: const TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
const SizedBox(height: 8), TextField(
TextFormField(
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Montant payé', labelText: 'Montant payé',
), ),
onChanged: (value) { onChanged: (value) {
setState(() { amountPaid = double.tryParse(value) ?? 0;
amountPaid = double.parse(value);
});
}, },
), ),
const SizedBox(height: 8),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: saveOrderToDatabase,
saveOrderToDatabase(); child: const Text('Valider la commande'),
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color.fromARGB(255, 4, 54, 95),
onPrimary: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
),
child: const Text('Payer'),
), ),
], ],
), ),
@ -523,4 +344,4 @@ class _AccueilPageState extends State<AccueilPage> {
), ),
); );
} }
} }

15
lib/controller/userController.dart

@ -1,5 +1,6 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:youmazgestion/Models/users.dart'; import 'package:youmazgestion/Models/users.dart';
import 'package:youmazgestion/Services/app_database.dart';
class UserController extends GetxController { class UserController extends GetxController {
final _username = ''.obs; final _username = ''.obs;
@ -30,4 +31,18 @@ class UserController extends GetxController {
_password.value = user.password; _password.value = user.password;
print(_password.value); print(_password.value);
} }
Future<bool> hasPermission(String permissionName) async {
// Utilisez votre instance de AppDatabase pour vérifier la permission
return await AppDatabase.instance.hasPermission(username, permissionName);
}
Future<bool> hasAnyPermission(List<String> permissionNames) async {
for (String permissionName in permissionNames) {
if (await hasPermission(permissionName)) {
return true;
}
}
return false;
}
} }

40
lib/main.dart

@ -1,22 +1,38 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:youmazgestion/Services/authDatabase.dart'; import 'package:youmazgestion/Services/app_database.dart';
import 'Services/productDatabase.dart'; import 'Services/productDatabase.dart';
import 'my_app.dart'; import 'my_app.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await ProductDatabase.instance.initDatabase();
await AuthDatabase.instance.initDatabase(); try {
// Initialiser les bases de données une seule fois
setupLogger(); // Appel à la fonction setupLogger() await ProductDatabase.instance.initDatabase();
await AppDatabase.instance.initDatabase();
//await OrderDatabase.instance.initDatabase();
runApp(const GetMaterialApp( // Afficher les informations de la base (pour debug)
debugShowCheckedModeBanner: false, await AppDatabase.instance.printDatabaseInfo();
home: MyApp(),
)); 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() { void setupLogger() {
@ -24,4 +40,4 @@ void setupLogger() {
Logger.root.onRecord.listen((record) { Logger.root.onRecord.listen((record) {
print('${record.level.name}: ${record.time}: ${record.message}'); print('${record.level.name}: ${record.time}: ${record.message}');
}); });
} }

16
pubspec.lock

@ -37,10 +37,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: async name: async
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.12.0" version: "2.13.0"
barcode: barcode:
dependency: transitive dependency: transitive
description: description:
@ -173,10 +173,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.2" version: "1.3.3"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
@ -500,10 +500,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.8" version: "10.0.9"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
@ -1161,10 +1161,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.3.1" version: "15.0.0"
web: web:
dependency: transitive dependency: transitive
description: description:

1
pubspec.yaml

@ -99,6 +99,7 @@ flutter:
- assets/database/products2.db - assets/database/products2.db
- assets/database/usersdb.db - assets/database/usersdb.db
- assets/database/work.db - assets/database/work.db
- assets/database/roles.db
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware # https://flutter.dev/assets-and-images/#resolution-aware

Loading…
Cancel
Save