Compare commits

...

7 Commits

Author SHA1 Message Date
25abc65480 couleur page de login 2025-05-31 00:45:16 +03:00
beb2048a7a boutton scan qr 2025-05-31 00:17:19 +03:00
b1d176aa2a add card on the list 2025-05-30 23:48:07 +03:00
8d86d99e24 pointage de base 2025-05-30 23:42:39 +03:00
5f665acf35 Change DA to MGA 2025-05-30 22:38:17 +03:00
38af810b79 Change DA to MGA 2025-05-30 22:36:29 +03:00
da03076411 Change DA to MGA 2025-05-30 22:31:20 +03:00
10 changed files with 946 additions and 537 deletions

View File

@ -13,6 +13,7 @@ import 'package:youmazgestion/Views/newCommand.dart';
import 'package:youmazgestion/Views/registrationPage.dart'; import 'package:youmazgestion/Views/registrationPage.dart';
import 'package:youmazgestion/accueil.dart'; import 'package:youmazgestion/accueil.dart';
import 'package:youmazgestion/controller/userController.dart'; import 'package:youmazgestion/controller/userController.dart';
import 'package:youmazgestion/Views/pointage.dart';
class CustomDrawer extends StatelessWidget { class CustomDrawer extends StatelessWidget {
final UserController userController = Get.find<UserController>(); final UserController userController = Get.find<UserController>();
@ -73,7 +74,9 @@ class CustomDrawer extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
controller.name.isNotEmpty ? controller.name : 'Utilisateur', controller.name.isNotEmpty
? controller.name
: 'Utilisateur',
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 18, fontSize: 18,
@ -123,6 +126,14 @@ class CustomDrawer extends StatelessWidget {
permissionRoute: '/modifier-utilisateur', permissionRoute: '/modifier-utilisateur',
onTap: () => Get.to(const ListUserPage()), onTap: () => Get.to(const ListUserPage()),
), ),
await _buildDrawerItem(
icon: Icons.timer,
title: "Gestion des pointages",
color: const Color.fromARGB(255, 4, 54, 95),
permissionAction: 'update',
permissionRoute: '/pointage',
onTap: () => Get.to(const PointagePage()),
)
]; ];
if (gestionUtilisateursItems.any((item) => item is ListTile)) { if (gestionUtilisateursItems.any((item) => item is ListTile)) {
@ -321,7 +332,8 @@ class CustomDrawer extends StatelessWidget {
required VoidCallback onTap, required VoidCallback onTap,
}) async { }) async {
if (permissionAction != null && permissionRoute != null) { if (permissionAction != null && permissionRoute != null) {
bool hasPermission = await userController.hasPermission(permissionAction, permissionRoute); bool hasPermission =
await userController.hasPermission(permissionAction, permissionRoute);
if (!hasPermission) { if (!hasPermission) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }

View File

@ -0,0 +1,36 @@
class Pointage {
final int? id;
final String userName;
final String date;
final String heureArrivee;
final String heureDepart;
Pointage({
this.id,
required this.userName,
required this.date,
required this.heureArrivee,
required this.heureDepart,
});
// Pour SQLite
factory Pointage.fromMap(Map<String, dynamic> map) {
return Pointage(
id: map['id'],
userName: map['userName'] ?? '',
date: map['date'],
heureArrivee: map['heureArrivee'],
heureDepart: map['heureDepart'],
);
}
Map<String, dynamic> toMap() {
return {
'id': id,
'userName': userName,
'date': date,
'heureArrivee': heureArrivee,
'heureDepart': heureDepart,
};
}
}

View File

@ -39,7 +39,8 @@ class AppDatabase {
if (!dbExists) { if (!dbExists) {
try { try {
ByteData data = await rootBundle.load('assets/database/$filePath'); ByteData data = await rootBundle.load('assets/database/$filePath');
List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); List<int> bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await File(path).writeAsBytes(bytes); await File(path).writeAsBytes(bytes);
} catch (e) { } catch (e) {
print('Pas de fichier DB dans assets, création d\'une nouvelle DB'); print('Pas de fichier DB dans assets, création d\'une nouvelle DB');
@ -50,7 +51,8 @@ class AppDatabase {
} }
Future<void> _createDB(Database db, int version) async { Future<void> _createDB(Database db, int version) async {
final tables = await db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'"); final tables =
await db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'");
final tableNames = tables.map((row) => row['name'] as String).toList(); final tableNames = tables.map((row) => row['name'] as String).toList();
if (!tableNames.contains('roles')) { if (!tableNames.contains('roles')) {
@ -126,7 +128,7 @@ class AppDatabase {
print("Table 'users' créée."); print("Table 'users' créée.");
} }
if (!tableNames.contains('role_menu_permissions')) { if (!tableNames.contains('role_menu_permissions')) {
await db.execute(''' await db.execute('''
CREATE TABLE role_menu_permissions ( CREATE TABLE role_menu_permissions (
role_id INTEGER, role_id INTEGER,
menu_id INTEGER, menu_id INTEGER,
@ -137,210 +139,242 @@ class AppDatabase {
FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE
) )
'''); ''');
print("Table 'role_menu_permissions' créée."); print("Table 'role_menu_permissions' créée.");
}
}
Future<void> insertDefaultPermissions() async {
final db = await database;
final existing = await db.query('permissions');
if (existing.isEmpty) {
await db.insert('permissions', {'name': 'view'});
await db.insert('permissions', {'name': 'create'});
await db.insert('permissions', {'name': 'update'});
await db.insert('permissions', {'name': 'delete'});
await db.insert('permissions', {'name': 'admin'});
await db.insert('permissions', {'name': 'manage'}); // Nouvelle permission
await db.insert('permissions', {'name': 'read'}); // Nouvelle permission
print("Permissions par défaut insérées");
} else {
// Vérifier et ajouter les nouvelles permissions si elles n'existent pas
final newPermissions = ['manage', 'read'];
for (var permission in newPermissions) {
final existingPermission = await db.query('permissions', where: 'name = ?', whereArgs: [permission]);
if (existingPermission.isEmpty) {
await db.insert('permissions', {'name': permission});
print("Permission ajoutée: $permission");
}
} }
} }
}
Future<void> insertDefaultMenus() async { Future<void> insertDefaultPermissions() async {
final db = await database; final db = await database;
final existingMenus = await db.query('menu'); final existing = await db.query('permissions');
if (existingMenus.isEmpty) {
// Menus existants
await db.insert('menu', {'name': 'Accueil', 'route': '/accueil'});
await db.insert('menu', {'name': 'Ajouter un utilisateur', 'route': '/ajouter-utilisateur'});
await db.insert('menu', {'name': 'Modifier/Supprimer un utilisateur', 'route': '/modifier-utilisateur'});
await db.insert('menu', {'name': 'Ajouter un produit', 'route': '/ajouter-produit'});
await db.insert('menu', {'name': 'Modifier/Supprimer un produit', 'route': '/modifier-produit'});
await db.insert('menu', {'name': 'Bilan', 'route': '/bilan'});
await db.insert('menu', {'name': 'Gérer les rôles', 'route': '/gerer-roles'});
await db.insert('menu', {'name': 'Gestion de stock', 'route': '/gestion-stock'});
await db.insert('menu', {'name': 'Historique', 'route': '/historique'});
await db.insert('menu', {'name': 'Déconnexion', 'route': '/deconnexion'});
// Nouveaux menus ajoutés
await db.insert('menu', {'name': 'Nouvelle commande', 'route': '/nouvelle-commande'});
await db.insert('menu', {'name': 'Gérer les commandes', 'route': '/gerer-commandes'});
print("Menus par défaut insérés");
} else {
// Si des menus existent déjà, vérifier et ajouter les nouveaux menus manquants
await _addMissingMenus(db);
}
}
Future<void> _addMissingMenus(Database db) async {
final menusToAdd = [
{'name': 'Nouvelle commande', 'route': '/nouvelle-commande'},
{'name': 'Gérer les commandes', 'route': '/gerer-commandes'},
];
for (var menu in menusToAdd) {
final existing = await db.query(
'menu',
where: 'route = ?',
whereArgs: [menu['route']],
);
if (existing.isEmpty) { if (existing.isEmpty) {
await db.insert('menu', menu); await db.insert('permissions', {'name': 'view'});
print("Menu ajouté: ${menu['name']}"); await db.insert('permissions', {'name': 'create'});
} await db.insert('permissions', {'name': 'update'});
} await db.insert('permissions', {'name': 'delete'});
} await db.insert('permissions', {'name': 'admin'});
await db.insert('permissions', {'name': 'manage'}); // Nouvelle permission
Future<void> insertDefaultRoles() async { await db.insert('permissions', {'name': 'read'}); // Nouvelle permission
final db = await database; print("Permissions par défaut insérées");
final existingRoles = await db.query('roles'); } else {
// Vérifier et ajouter les nouvelles permissions si elles n'existent pas
if (existingRoles.isEmpty) { final newPermissions = ['manage', 'read'];
int superAdminRoleId = await db.insert('roles', {'designation': 'Super Admin'}); for (var permission in newPermissions) {
int adminRoleId = await db.insert('roles', {'designation': 'Admin'}); final existingPermission = await db
int userRoleId = await db.insert('roles', {'designation': 'User'}); .query('permissions', where: 'name = ?', whereArgs: [permission]);
final permissions = await db.query('permissions');
final menus = await db.query('menu');
// Assigner toutes les permissions à tous les menus pour le Super Admin
for (var menu in menus) {
for (var permission in permissions) {
await db.insert('role_menu_permissions', {
'role_id': superAdminRoleId,
'menu_id': menu['id'],
'permission_id': permission['id'],
},
conflictAlgorithm: ConflictAlgorithm.ignore
);
}
}
// Assigner quelques permissions à l'Admin et à l'User pour les nouveaux menus
await _assignBasicPermissionsToRoles(db, adminRoleId, userRoleId);
print("Rôles par défaut créés et permissions assignées");
} else {
// Si les rôles existent déjà, vérifier et ajouter les permissions manquantes
await _updateExistingRolePermissions(db);
}
}
// Nouvelle méthode pour assigner les permissions de base aux nouveaux menus
Future<void> _assignBasicPermissionsToRoles(Database db, int adminRoleId, int userRoleId) async {
final viewPermission = await db.query('permissions', where: 'name = ?', whereArgs: ['view']);
final createPermission = await db.query('permissions', where: 'name = ?', whereArgs: ['create']);
final updatePermission = await db.query('permissions', where: 'name = ?', whereArgs: ['update']);
final managePermission = await db.query('permissions', where: 'name = ?', whereArgs: ['manage']);
// Récupérer les IDs des nouveaux menus
final nouvelleCommandeMenu = await db.query('menu', where: 'route = ?', whereArgs: ['/nouvelle-commande']);
final gererCommandesMenu = await db.query('menu', where: 'route = ?', whereArgs: ['/gerer-commandes']);
if (nouvelleCommandeMenu.isNotEmpty && createPermission.isNotEmpty) {
// Admin peut créer de nouvelles commandes
await db.insert('role_menu_permissions', {
'role_id': adminRoleId,
'menu_id': nouvelleCommandeMenu.first['id'],
'permission_id': createPermission.first['id'],
},
conflictAlgorithm: ConflictAlgorithm.ignore
);
// User peut aussi créer de nouvelles commandes
await db.insert('role_menu_permissions', {
'role_id': userRoleId,
'menu_id': nouvelleCommandeMenu.first['id'],
'permission_id': createPermission.first['id'],
},
conflictAlgorithm: ConflictAlgorithm.ignore
);
}
if (gererCommandesMenu.isNotEmpty && managePermission.isNotEmpty) {
// Admin peut gérer les commandes
await db.insert('role_menu_permissions', {
'role_id': adminRoleId,
'menu_id': gererCommandesMenu.first['id'],
'permission_id': managePermission.first['id'],
},
conflictAlgorithm: ConflictAlgorithm.ignore
);
}
if (gererCommandesMenu.isNotEmpty && viewPermission.isNotEmpty) {
// User peut voir les commandes
await db.insert('role_menu_permissions', {
'role_id': userRoleId,
'menu_id': gererCommandesMenu.first['id'],
'permission_id': viewPermission.first['id'],
}
, conflictAlgorithm: ConflictAlgorithm.ignore
);
}
}
Future<void> _updateExistingRolePermissions(Database db) async {
final superAdminRole = await db.query('roles', where: 'designation = ?', whereArgs: ['Super Admin']);
if (superAdminRole.isNotEmpty) {
final superAdminRoleId = superAdminRole.first['id'] as int;
final permissions = await db.query('permissions');
final menus = await db.query('menu');
// Vérifier et ajouter les permissions manquantes pour le Super Admin sur tous les menus
for (var menu in menus) {
for (var permission in permissions) {
final existingPermission = await db.query(
'role_menu_permissions',
where: 'role_id = ? AND menu_id = ? AND permission_id = ?',
whereArgs: [superAdminRoleId, menu['id'], permission['id']],
);
if (existingPermission.isEmpty) { if (existingPermission.isEmpty) {
await db.insert('role_menu_permissions', { await db.insert('permissions', {'name': permission});
'role_id': superAdminRoleId, print("Permission ajoutée: $permission");
'menu_id': menu['id'],
'permission_id': permission['id'],
},
conflictAlgorithm: ConflictAlgorithm.ignore
);
} }
} }
} }
}
// Assigner les permissions de base aux autres rôles pour les nouveaux menus Future<void> insertDefaultMenus() async {
final adminRole = await db.query('roles', where: 'designation = ?', whereArgs: ['Admin']); final db = await database;
final userRole = await db.query('roles', where: 'designation = ?', whereArgs: ['User']); final existingMenus = await db.query('menu');
if (adminRole.isNotEmpty && userRole.isNotEmpty) { if (existingMenus.isEmpty) {
await _assignBasicPermissionsToRoles(db, adminRole.first['id'] as int, userRole.first['id'] as int); // Menus existants
await db.insert('menu', {'name': 'Accueil', 'route': '/accueil'});
await db.insert('menu',
{'name': 'Ajouter un utilisateur', 'route': '/ajouter-utilisateur'});
await db.insert('menu', {
'name': 'Modifier/Supprimer un utilisateur',
'route': '/modifier-utilisateur'
});
await db.insert(
'menu', {'name': 'Ajouter un produit', 'route': '/ajouter-produit'});
await db.insert('menu', {
'name': 'Modifier/Supprimer un produit',
'route': '/modifier-produit'
});
await db.insert('menu', {'name': 'Bilan', 'route': '/bilan'});
await db
.insert('menu', {'name': 'Gérer les rôles', 'route': '/gerer-roles'});
await db.insert(
'menu', {'name': 'Gestion de stock', 'route': '/gestion-stock'});
await db.insert('menu', {'name': 'Historique', 'route': '/historique'});
await db.insert('menu', {'name': 'Déconnexion', 'route': '/deconnexion'});
// Nouveaux menus ajoutés
await db.insert(
'menu', {'name': 'Nouvelle commande', 'route': '/nouvelle-commande'});
await db.insert(
'menu', {'name': 'Gérer les commandes', 'route': '/gerer-commandes'});
print("Menus par défaut insérés");
} else {
// Si des menus existent déjà, vérifier et ajouter les nouveaux menus manquants
await _addMissingMenus(db);
}
}
Future<void> _addMissingMenus(Database db) async {
final menusToAdd = [
{'name': 'Nouvelle commande', 'route': '/nouvelle-commande'},
{'name': 'Gérer les commandes', 'route': '/gerer-commandes'},
{'name': 'Gérer les pointages', 'route': '/pointage'},
];
for (var menu in menusToAdd) {
final existing = await db.query(
'menu',
where: 'route = ?',
whereArgs: [menu['route']],
);
if (existing.isEmpty) {
await db.insert('menu', menu);
print("Menu ajouté: ${menu['name']}");
}
}
}
Future<void> insertDefaultRoles() async {
final db = await database;
final existingRoles = await db.query('roles');
if (existingRoles.isEmpty) {
int superAdminRoleId =
await db.insert('roles', {'designation': 'Super Admin'});
int adminRoleId = await db.insert('roles', {'designation': 'Admin'});
int userRoleId = await db.insert('roles', {'designation': 'User'});
final permissions = await db.query('permissions');
final menus = await db.query('menu');
// Assigner toutes les permissions à tous les menus pour le Super Admin
for (var menu in menus) {
for (var permission in permissions) {
await db.insert(
'role_menu_permissions',
{
'role_id': superAdminRoleId,
'menu_id': menu['id'],
'permission_id': permission['id'],
},
conflictAlgorithm: ConflictAlgorithm.ignore);
}
}
// Assigner quelques permissions à l'Admin et à l'User pour les nouveaux menus
await _assignBasicPermissionsToRoles(db, adminRoleId, userRoleId);
print("Rôles par défaut créés et permissions assignées");
} else {
// Si les rôles existent déjà, vérifier et ajouter les permissions manquantes
await _updateExistingRolePermissions(db);
}
}
// Nouvelle méthode pour assigner les permissions de base aux nouveaux menus
Future<void> _assignBasicPermissionsToRoles(
Database db, int adminRoleId, int userRoleId) async {
final viewPermission =
await db.query('permissions', where: 'name = ?', whereArgs: ['view']);
final createPermission =
await db.query('permissions', where: 'name = ?', whereArgs: ['create']);
final updatePermission =
await db.query('permissions', where: 'name = ?', whereArgs: ['update']);
final managePermission =
await db.query('permissions', where: 'name = ?', whereArgs: ['manage']);
// Récupérer les IDs des nouveaux menus
final nouvelleCommandeMenu = await db
.query('menu', where: 'route = ?', whereArgs: ['/nouvelle-commande']);
final gererCommandesMenu = await db
.query('menu', where: 'route = ?', whereArgs: ['/gerer-commandes']);
if (nouvelleCommandeMenu.isNotEmpty && createPermission.isNotEmpty) {
// Admin peut créer de nouvelles commandes
await db.insert(
'role_menu_permissions',
{
'role_id': adminRoleId,
'menu_id': nouvelleCommandeMenu.first['id'],
'permission_id': createPermission.first['id'],
},
conflictAlgorithm: ConflictAlgorithm.ignore);
// User peut aussi créer de nouvelles commandes
await db.insert(
'role_menu_permissions',
{
'role_id': userRoleId,
'menu_id': nouvelleCommandeMenu.first['id'],
'permission_id': createPermission.first['id'],
},
conflictAlgorithm: ConflictAlgorithm.ignore);
} }
print("Permissions mises à jour pour tous les rôles"); if (gererCommandesMenu.isNotEmpty && managePermission.isNotEmpty) {
} // Admin peut gérer les commandes
} await db.insert(
'role_menu_permissions',
{
'role_id': adminRoleId,
'menu_id': gererCommandesMenu.first['id'],
'permission_id': managePermission.first['id'],
},
conflictAlgorithm: ConflictAlgorithm.ignore);
}
if (gererCommandesMenu.isNotEmpty && viewPermission.isNotEmpty) {
// User peut voir les commandes
await db.insert(
'role_menu_permissions',
{
'role_id': userRoleId,
'menu_id': gererCommandesMenu.first['id'],
'permission_id': viewPermission.first['id'],
},
conflictAlgorithm: ConflictAlgorithm.ignore);
}
}
Future<void> _updateExistingRolePermissions(Database db) async {
final superAdminRole = await db
.query('roles', where: 'designation = ?', whereArgs: ['Super Admin']);
if (superAdminRole.isNotEmpty) {
final superAdminRoleId = superAdminRole.first['id'] as int;
final permissions = await db.query('permissions');
final menus = await db.query('menu');
// Vérifier et ajouter les permissions manquantes pour le Super Admin sur tous les menus
for (var menu in menus) {
for (var permission in permissions) {
final existingPermission = await db.query(
'role_menu_permissions',
where: 'role_id = ? AND menu_id = ? AND permission_id = ?',
whereArgs: [superAdminRoleId, menu['id'], permission['id']],
);
if (existingPermission.isEmpty) {
await db.insert(
'role_menu_permissions',
{
'role_id': superAdminRoleId,
'menu_id': menu['id'],
'permission_id': permission['id'],
},
conflictAlgorithm: ConflictAlgorithm.ignore);
}
}
}
// Assigner les permissions de base aux autres rôles pour les nouveaux menus
final adminRole = await db
.query('roles', where: 'designation = ?', whereArgs: ['Admin']);
final userRole = await db
.query('roles', where: 'designation = ?', whereArgs: ['User']);
if (adminRole.isNotEmpty && userRole.isNotEmpty) {
await _assignBasicPermissionsToRoles(
db, adminRole.first['id'] as int, userRole.first['id'] as int);
}
print("Permissions mises à jour pour tous les rôles");
}
}
Future<void> insertDefaultSuperAdmin() async { Future<void> insertDefaultSuperAdmin() async {
final db = await database; final db = await database;
@ -352,10 +386,8 @@ Future<void> _updateExistingRolePermissions(Database db) async {
'''); ''');
if (existingSuperAdmin.isEmpty) { if (existingSuperAdmin.isEmpty) {
final superAdminRole = await db.query('roles', final superAdminRole = await db
where: 'designation = ?', .query('roles', where: 'designation = ?', whereArgs: ['Super Admin']);
whereArgs: ['Super Admin']
);
if (superAdminRole.isNotEmpty) { if (superAdminRole.isNotEmpty) {
final superAdminRoleId = superAdminRole.first['id'] as int; final superAdminRoleId = superAdminRole.first['id'] as int;
@ -372,7 +404,8 @@ Future<void> _updateExistingRolePermissions(Database db) async {
print("Super Admin créé avec succès !"); print("Super Admin créé avec succès !");
print("Username: superadmin"); print("Username: superadmin");
print("Password: admin123"); print("Password: admin123");
print("ATTENTION: Changez ce mot de passe après la première connexion !"); print(
"ATTENTION: Changez ce mot de passe après la première connexion !");
} }
} else { } else {
print("Super Admin existe déjà"); print("Super Admin existe déjà");
@ -391,12 +424,14 @@ Future<void> _updateExistingRolePermissions(Database db) async {
Future<int> updateUser(Users user) async { Future<int> updateUser(Users user) async {
final db = await database; final db = await database;
return await db.update('users', user.toMap(), where: 'id = ?', whereArgs: [user.id]); return await db
.update('users', user.toMap(), where: 'id = ?', whereArgs: [user.id]);
} }
Future<int> getUserCount() async { Future<int> getUserCount() async {
final db = await database; final db = await database;
List<Map<String, dynamic>> result = await db.rawQuery('SELECT COUNT(*) as count FROM users'); List<Map<String, dynamic>> result =
await db.rawQuery('SELECT COUNT(*) as count FROM users');
return result.first['count'] as int; return result.first['count'] as int;
} }
@ -426,7 +461,8 @@ Future<void> _updateExistingRolePermissions(Database db) async {
} }
} }
Future<Map<String, dynamic>?> getUserCredentials(String username, String password) async { Future<Map<String, dynamic>?> getUserCredentials(
String username, String password) async {
final db = await database; final db = await database;
final result = await db.rawQuery(''' final result = await db.rawQuery('''
SELECT users.username, users.id, roles.designation as role_name, roles.id as role_id SELECT users.username, users.id, roles.designation as role_name, roles.id as role_id
@ -524,10 +560,13 @@ Future<void> _updateExistingRolePermissions(Database db) async {
Future<void> assignPermission(int roleId, int permissionId) async { Future<void> assignPermission(int roleId, int permissionId) async {
final db = await database; final db = await database;
await db.insert('role_permissions', { await db.insert(
'role_id': roleId, 'role_permissions',
'permission_id': permissionId, {
}, conflictAlgorithm: ConflictAlgorithm.ignore); 'role_id': roleId,
'permission_id': permissionId,
},
conflictAlgorithm: ConflictAlgorithm.ignore);
} }
Future<void> removePermission(int roleId, int permissionId) async { Future<void> removePermission(int roleId, int permissionId) async {
@ -541,10 +580,13 @@ Future<void> _updateExistingRolePermissions(Database db) async {
Future<void> assignMenuPermission(int menuId, int permissionId) async { Future<void> assignMenuPermission(int menuId, int permissionId) async {
final db = await database; final db = await database;
await db.insert('menu_permissions', { await db.insert(
'menu_id': menuId, 'menu_permissions',
'permission_id': permissionId, {
}, conflictAlgorithm: ConflictAlgorithm.ignore); 'menu_id': menuId,
'permission_id': permissionId,
},
conflictAlgorithm: ConflictAlgorithm.ignore);
} }
Future<void> removeMenuPermission(int menuId, int permissionId) async { Future<void> removeMenuPermission(int menuId, int permissionId) async {
@ -568,7 +610,8 @@ Future<void> _updateExistingRolePermissions(Database db) async {
return (result.first['count'] as int) > 0; return (result.first['count'] as int) > 0;
} }
Future<void> changePassword(String username, String oldPassword, String newPassword) async { Future<void> changePassword(
String username, String oldPassword, String newPassword) async {
final db = await database; final db = await database;
final isValidOldPassword = await verifyUser(username, oldPassword); final isValidOldPassword = await verifyUser(username, oldPassword);
@ -584,9 +627,10 @@ Future<void> _updateExistingRolePermissions(Database db) async {
); );
} }
Future<bool> hasPermission(String username, String permissionName, String menuRoute) async { Future<bool> hasPermission(
final db = await database; String username, String permissionName, String menuRoute) async {
final result = await db.rawQuery(''' final db = await database;
final result = await db.rawQuery('''
SELECT COUNT(*) as count SELECT COUNT(*) as count
FROM permissions p FROM permissions p
JOIN role_menu_permissions rmp ON p.id = rmp.permission_id JOIN role_menu_permissions rmp ON p.id = rmp.permission_id
@ -596,9 +640,8 @@ Future<void> _updateExistingRolePermissions(Database db) async {
WHERE u.username = ? AND p.name = ? AND rmp.menu_id = m.id WHERE u.username = ? AND p.name = ? AND rmp.menu_id = m.id
''', [menuRoute, username, permissionName]); ''', [menuRoute, username, permissionName]);
return (result.first['count'] as int) > 0; return (result.first['count'] as int) > 0;
} }
Future<void> close() async { Future<void> close() async {
if (_database.isOpen) { if (_database.isOpen) {
@ -635,9 +678,10 @@ Future<void> _updateExistingRolePermissions(Database db) async {
print("========================================="); print("=========================================");
} }
Future<List<Permission>> getPermissionsForRoleAndMenu(int roleId, int menuId) async { Future<List<Permission>> getPermissionsForRoleAndMenu(
final db = await database; int roleId, int menuId) async {
final result = await db.rawQuery(''' final db = await database;
final result = await db.rawQuery('''
SELECT p.id, p.name SELECT p.id, p.name
FROM permissions p FROM permissions p
JOIN role_menu_permissions rmp ON p.id = rmp.permission_id JOIN role_menu_permissions rmp ON p.id = rmp.permission_id
@ -645,36 +689,40 @@ Future<void> _updateExistingRolePermissions(Database db) async {
ORDER BY p.name ASC ORDER BY p.name ASC
''', [roleId, menuId]); ''', [roleId, menuId]);
return result.map((map) => Permission.fromMap(map)).toList(); return result.map((map) => Permission.fromMap(map)).toList();
} }
// Ajoutez cette méthode temporaire pour supprimer la DB corrompue // Ajoutez cette méthode temporaire pour supprimer la DB corrompue
Future<void> deleteDatabaseFile() async { Future<void> deleteDatabaseFile() async {
final documentsDirectory = await getApplicationDocumentsDirectory(); final documentsDirectory = await getApplicationDocumentsDirectory();
final path = join(documentsDirectory.path, 'app_database.db'); final path = join(documentsDirectory.path, 'app_database.db');
final file = File(path); final file = File(path);
if (await file.exists()) { if (await file.exists()) {
await file.delete(); await file.delete();
print("Base de données utilisateur supprimée"); print("Base de données utilisateur supprimée");
}
}
Future<void> assignRoleMenuPermission(
int roleId, int menuId, int permissionId) async {
final db = await database;
await db.insert(
'role_menu_permissions',
{
'role_id': roleId,
'menu_id': menuId,
'permission_id': permissionId,
},
conflictAlgorithm: ConflictAlgorithm.ignore);
}
Future<void> removeRoleMenuPermission(
int roleId, int menuId, int permissionId) async {
final db = await database;
await db.delete(
'role_menu_permissions',
where: 'role_id = ? AND menu_id = ? AND permission_id = ?',
whereArgs: [roleId, menuId, permissionId],
);
} }
} }
Future<void> assignRoleMenuPermission(int roleId, int menuId, int permissionId) async {
final db = await database;
await db.insert('role_menu_permissions', {
'role_id': roleId,
'menu_id': menuId,
'permission_id': permissionId,
}, conflictAlgorithm: ConflictAlgorithm.ignore);
}
Future<void> removeRoleMenuPermission(int roleId, int menuId, int permissionId) async {
final db = await database;
await db.delete(
'role_menu_permissions',
where: 'role_id = ? AND menu_id = ? AND permission_id = ?',
whereArgs: [roleId, menuId, permissionId],
);
}
}

View File

@ -0,0 +1,60 @@
import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import '../Models/pointage_model.dart';
class DatabaseHelper {
static final DatabaseHelper _instance = DatabaseHelper._internal();
factory DatabaseHelper() => _instance;
DatabaseHelper._internal();
Database? _db;
Future<Database> get database async {
if (_db != null) return _db!;
_db = await _initDatabase();
return _db!;
}
Future<Database> _initDatabase() async {
String databasesPath = await getDatabasesPath();
String dbPath = join(databasesPath, 'pointage.db');
return await openDatabase(dbPath, version: 1, onCreate: _onCreate);
}
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE pointages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
userName TEXT NOT NULL,
date TEXT NOT NULL,
heureArrivee TEXT NOT NULL,
heureDepart TEXT NOT NULL
)
''');
}
Future<int> insertPointage(Pointage pointage) async {
final db = await database;
return await db.insert('pointages', pointage.toMap());
}
Future<List<Pointage>> getPointages() async {
final db = await database;
final pointages = await db.query('pointages');
return pointages.map((pointage) => Pointage.fromMap(pointage)).toList();
}
Future<int> updatePointage(Pointage pointage) async {
final db = await database;
return await db.update('pointages', pointage.toMap(),
where: 'id = ?', whereArgs: [pointage.id]);
}
Future<int> deletePointage(int id) async {
final db = await database;
return await db.delete('pointages', where: 'id = ?', whereArgs: [id]);
}
}

View File

@ -27,7 +27,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
StatutCommande? _selectedStatut; StatutCommande? _selectedStatut;
DateTime? _selectedDate; DateTime? _selectedDate;
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
bool _showCancelledOrders = false; // Nouveau: contrôle l'affichage des commandes annulées bool _showCancelledOrders =
false; // Nouveau: contrôle l'affichage des commandes annulées
@override @override
void initState() { void initState() {
@ -43,26 +44,33 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
_filterCommandes(); _filterCommandes();
}); });
} }
Future<Uint8List> loadImage() async { Future<Uint8List> loadImage() async {
final data = await rootBundle.load('assets/youmaz2.png'); final data = await rootBundle.load('assets/youmaz2.png');
return data.buffer.asUint8List(); return data.buffer.asUint8List();
} }
void _filterCommandes() { void _filterCommandes() {
final query = _searchController.text.toLowerCase(); final query = _searchController.text.toLowerCase();
setState(() { setState(() {
_filteredCommandes = _commandes.where((commande) { _filteredCommandes = _commandes.where((commande) {
final matchesSearch = commande.clientNomComplet.toLowerCase().contains(query) || final matchesSearch =
commande.id.toString().contains(query); commande.clientNomComplet.toLowerCase().contains(query) ||
final matchesStatut = _selectedStatut == null || commande.statut == _selectedStatut; commande.id.toString().contains(query);
final matchesStatut =
_selectedStatut == null || commande.statut == _selectedStatut;
final matchesDate = _selectedDate == null || final matchesDate = _selectedDate == null ||
DateFormat('yyyy-MM-dd').format(commande.dateCommande) == DateFormat('yyyy-MM-dd').format(commande.dateCommande) ==
DateFormat('yyyy-MM-dd').format(_selectedDate!); DateFormat('yyyy-MM-dd').format(_selectedDate!);
// Nouveau: filtrer les commandes annulées selon le toggle // Nouveau: filtrer les commandes annulées selon le toggle
final shouldShowCancelled = _showCancelledOrders || commande.statut != StatutCommande.annulee; final shouldShowCancelled =
_showCancelledOrders || commande.statut != StatutCommande.annulee;
return matchesSearch && matchesStatut && matchesDate && shouldShowCancelled;
return matchesSearch &&
matchesStatut &&
matchesDate &&
shouldShowCancelled;
}).toList(); }).toList();
}); });
} }
@ -70,11 +78,11 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
Future<void> _updateStatut(int commandeId, StatutCommande newStatut) async { Future<void> _updateStatut(int commandeId, StatutCommande newStatut) async {
await _database.updateStatutCommande(commandeId, newStatut); await _database.updateStatutCommande(commandeId, newStatut);
await _loadCommandes(); await _loadCommandes();
// Amélioration: message plus spécifique selon le statut // Amélioration: message plus spécifique selon le statut
String message = 'Statut de la commande mis à jour'; String message = 'Statut de la commande mis à jour';
Color backgroundColor = Colors.green; Color backgroundColor = Colors.green;
switch (newStatut) { switch (newStatut) {
case StatutCommande.annulee: case StatutCommande.annulee:
message = 'Commande annulée avec succès'; message = 'Commande annulée avec succès';
@ -91,7 +99,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
default: default:
break; break;
} }
Get.snackbar( Get.snackbar(
'Succès', 'Succès',
message, message,
@ -109,7 +117,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
final pdf = pw.Document(); final pdf = pw.Document();
final imageBytes = await loadImage(); // Charge les données de l'image final imageBytes = await loadImage(); // Charge les données de l'image
final image = pw.MemoryImage(imageBytes); final image = pw.MemoryImage(imageBytes);
// Amélioration: styles plus professionnels // Amélioration: styles plus professionnels
final headerStyle = pw.TextStyle( final headerStyle = pw.TextStyle(
fontSize: 18, fontSize: 18,
@ -148,12 +156,11 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
width: 100, width: 100,
height: 80, height: 80,
decoration: pw.BoxDecoration( decoration: pw.BoxDecoration(
border: pw.Border.all(color: PdfColors.blue900, width: 2), border:
pw.Border.all(color: PdfColors.blue900, width: 2),
borderRadius: pw.BorderRadius.circular(8), borderRadius: pw.BorderRadius.circular(8),
), ),
child: pw.Center( child: pw.Center(child: pw.Image(image)),
child: pw.Image(image)
),
), ),
pw.SizedBox(height: 10), pw.SizedBox(height: 10),
pw.Text('guycom', style: headerStyle), pw.Text('guycom', style: headerStyle),
@ -175,7 +182,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
child: pw.Column( child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start, crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [ children: [
pw.Text('FACTURE', pw.Text(
'FACTURE',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: pw.FontWeight.bold, fontWeight: pw.FontWeight.bold,
@ -184,7 +192,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
), ),
pw.SizedBox(height: 8), pw.SizedBox(height: 8),
pw.Text('N°: ${commande.id}', style: titleStyle), pw.Text('N°: ${commande.id}', style: titleStyle),
pw.Text('Date: ${DateFormat('dd/MM/yyyy').format(commande.dateCommande)}'), pw.Text(
'Date: ${DateFormat('dd/MM/yyyy').format(commande.dateCommande)}'),
], ],
), ),
), ),
@ -192,9 +201,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
), ),
], ],
), ),
pw.SizedBox(height: 30), pw.SizedBox(height: 30),
// Informations client // Informations client
pw.Container( pw.Container(
width: double.infinity, width: double.infinity,
@ -208,38 +217,45 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
children: [ children: [
pw.Text('FACTURÉ À:', style: titleStyle), pw.Text('FACTURÉ À:', style: titleStyle),
pw.SizedBox(height: 5), pw.SizedBox(height: 5),
pw.Text(client?.nomComplet ?? 'Client inconnu', pw.Text(client?.nomComplet ?? 'Client inconnu',
style: pw.TextStyle(fontSize: 12)), style: pw.TextStyle(fontSize: 12)),
if (client?.telephone != null) if (client?.telephone != null)
pw.Text('Tél: ${client!.telephone}', pw.Text('Tél: ${client!.telephone}',
style: pw.TextStyle(fontSize: 10, color: PdfColors.grey600)), style: pw.TextStyle(
fontSize: 10, color: PdfColors.grey600)),
], ],
), ),
), ),
pw.SizedBox(height: 30), pw.SizedBox(height: 30),
// Tableau des produits // Tableau des produits
pw.Text('DÉTAILS DE LA COMMANDE', style: titleStyle), pw.Text('DÉTAILS DE LA COMMANDE', style: titleStyle),
pw.SizedBox(height: 10), pw.SizedBox(height: 10),
pw.Table( pw.Table(
border: pw.TableBorder.all(color: PdfColors.grey400, width: 0.5), border:
pw.TableBorder.all(color: PdfColors.grey400, width: 0.5),
children: [ children: [
pw.TableRow( pw.TableRow(
decoration: const pw.BoxDecoration(color: PdfColors.blue900), decoration:
const pw.BoxDecoration(color: PdfColors.blue900),
children: [ children: [
_buildTableCell('Produit', titleStyle.copyWith(color: PdfColors.white)), _buildTableCell('Produit',
_buildTableCell('Qté', titleStyle.copyWith(color: PdfColors.white)), titleStyle.copyWith(color: PdfColors.white)),
_buildTableCell('Prix unit.', titleStyle.copyWith(color: PdfColors.white)), _buildTableCell(
_buildTableCell('Total', titleStyle.copyWith(color: PdfColors.white)), 'Qté', titleStyle.copyWith(color: PdfColors.white)),
_buildTableCell('Prix unit.',
titleStyle.copyWith(color: PdfColors.white)),
_buildTableCell(
'Total', titleStyle.copyWith(color: PdfColors.white)),
], ],
), ),
...details.asMap().entries.map((entry) { ...details.asMap().entries.map((entry) {
final index = entry.key; final index = entry.key;
final detail = entry.value; final detail = entry.value;
final isEven = index % 2 == 0; final isEven = index % 2 == 0;
return pw.TableRow( return pw.TableRow(
decoration: pw.BoxDecoration( decoration: pw.BoxDecoration(
color: isEven ? PdfColors.white : PdfColors.grey50, color: isEven ? PdfColors.white : PdfColors.grey50,
@ -247,16 +263,18 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
children: [ children: [
_buildTableCell(detail.produitNom ?? 'Produit inconnu'), _buildTableCell(detail.produitNom ?? 'Produit inconnu'),
_buildTableCell(detail.quantite.toString()), _buildTableCell(detail.quantite.toString()),
_buildTableCell('${detail.prixUnitaire.toStringAsFixed(2)} DA'), _buildTableCell(
_buildTableCell('${detail.sousTotal.toStringAsFixed(2)} DA'), '${detail.prixUnitaire.toStringAsFixed(2)} DA'),
_buildTableCell(
'${detail.sousTotal.toStringAsFixed(2)} DA'),
], ],
); );
}), }),
], ],
), ),
pw.SizedBox(height: 20), pw.SizedBox(height: 20),
// Total // Total
pw.Container( pw.Container(
alignment: pw.Alignment.centerRight, alignment: pw.Alignment.centerRight,
@ -276,9 +294,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
), ),
), ),
), ),
pw.Spacer(), pw.Spacer(),
// Pied de page // Pied de page
pw.Container( pw.Container(
width: double.infinity, width: double.infinity,
@ -301,7 +319,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
pw.SizedBox(height: 5), pw.SizedBox(height: 5),
pw.Text( pw.Text(
'Cette facture est générée automatiquement par le système Youmaz Gestion', 'Cette facture est générée automatiquement par le système Youmaz Gestion',
style: pw.TextStyle(fontSize: 8, color: PdfColors.grey600), style:
pw.TextStyle(fontSize: 8, color: PdfColors.grey600),
), ),
], ],
), ),
@ -444,9 +463,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Barre de recherche améliorée // Barre de recherche améliorée
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -464,7 +483,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
controller: _searchController, controller: _searchController,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Rechercher par client ou numéro de commande', labelText: 'Rechercher par client ou numéro de commande',
prefixIcon: Icon(Icons.search, color: Colors.blue.shade800), prefixIcon:
Icon(Icons.search, color: Colors.blue.shade800),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none, borderSide: BorderSide.none,
@ -478,9 +498,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
), ),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Filtres améliorés // Filtres améliorés
Row( Row(
children: [ children: [
@ -501,7 +521,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
value: _selectedStatut, value: _selectedStatut,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Filtrer par statut', labelText: 'Filtrer par statut',
prefixIcon: Icon(Icons.filter_list, color: Colors.blue.shade600), prefixIcon: Icon(Icons.filter_list,
color: Colors.blue.shade600),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none, borderSide: BorderSide.none,
@ -540,9 +561,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
), ),
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -590,19 +611,21 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
}); });
} }
}, },
icon: Icon(Icons.calendar_today, color: Colors.blue.shade600), icon: Icon(Icons.calendar_today,
color: Colors.blue.shade600),
label: Text( label: Text(
_selectedDate == null _selectedDate == null
? 'Date' ? 'Date'
: DateFormat('dd/MM/yyyy').format(_selectedDate!), : DateFormat('dd/MM/yyyy')
.format(_selectedDate!),
style: const TextStyle(color: Colors.black87), style: const TextStyle(color: Colors.black87),
), ),
), ),
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
// Bouton reset // Bouton reset
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -631,9 +654,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
), ),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// Toggle pour afficher/masquer les commandes annulées // Toggle pour afficher/masquer les commandes annulées
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -647,7 +670,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
), ),
], ],
), ),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row( child: Row(
children: [ children: [
Icon( Icon(
@ -680,7 +704,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
], ],
), ),
), ),
// Liste des commandes // Liste des commandes
Expanded( Expanded(
child: _filteredCommandes.isEmpty child: _filteredCommandes.isEmpty
@ -756,9 +780,10 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
Icon( Icon(
_getStatutIcon(commande.statut), _getStatutIcon(commande.statut),
size: 20, size: 20,
color: commande.statut == StatutCommande.annulee color:
? Colors.red commande.statut == StatutCommande.annulee
: Colors.blue.shade600, ? Colors.red
: Colors.blue.shade600,
), ),
Text( Text(
'#${commande.id}', '#${commande.id}',
@ -790,7 +815,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
DateFormat('dd/MM/yyyy').format(commande.dateCommande), DateFormat('dd/MM/yyyy')
.format(commande.dateCommande),
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.grey.shade600, color: Colors.grey.shade600,
@ -811,8 +837,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: commande.statut == StatutCommande.annulee color: commande.statut ==
? Colors.red StatutCommande.annulee
? Colors.red
: Colors.blue.shade700, : Colors.blue.shade700,
), ),
), ),
@ -829,7 +856,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
'${commande.montantTotal.toStringAsFixed(2)} DA', '${commande.montantTotal.toStringAsFixed(2)} MGA',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -981,13 +1008,16 @@ class _CommandeDetails extends StatelessWidget {
], ],
), ),
...details.map((detail) => TableRow( ...details.map((detail) => TableRow(
children: [ children: [
_buildTableCell(detail.produitNom ?? 'Produit inconnu'), _buildTableCell(
_buildTableCell('${detail.quantite}'), detail.produitNom ?? 'Produit inconnu'),
_buildTableCell('${detail.prixUnitaire.toStringAsFixed(2)} DA'), _buildTableCell('${detail.quantite}'),
_buildTableCell('${detail.sousTotal.toStringAsFixed(2)} DA'), _buildTableCell(
], '${detail.prixUnitaire.toStringAsFixed(2)} MGA'),
)), _buildTableCell(
'${detail.sousTotal.toStringAsFixed(2)} MGA'),
],
)),
], ],
), ),
), ),
@ -1010,7 +1040,7 @@ class _CommandeDetails extends StatelessWidget {
), ),
), ),
Text( Text(
'${commande.montantTotal.toStringAsFixed(2)} DA', '${commande.montantTotal.toStringAsFixed(2)} MGA',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 18, fontSize: 18,
@ -1204,7 +1234,8 @@ class _CommandeActions extends StatelessWidget {
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(Icons.check_circle, color: Colors.green.shade600, size: 16), Icon(Icons.check_circle,
color: Colors.green.shade600, size: 16),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'Commande livrée', 'Commande livrée',
@ -1326,4 +1357,4 @@ class _CommandeActions extends StatelessWidget {
}, },
); );
} }
} }

View File

@ -35,18 +35,6 @@ class _LoginPageState extends State<LoginPage> {
try { try {
final userCount = await AppDatabase.instance.getUserCount(); final userCount = await AppDatabase.instance.getUserCount();
print('Nombre d\'utilisateurs trouvés: $userCount'); // Debug print('Nombre d\'utilisateurs trouvés: $userCount'); // Debug
// Commentez cette partie pour permettre le login même sans utilisateurs
/*
if (userCount == 0) {
if (mounted) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const AccueilPage()),
);
}
}
*/
} catch (error) { } catch (error) {
print('Erreur lors de la vérification du nombre d\'utilisateurs: $error'); print('Erreur lors de la vérification du nombre d\'utilisateurs: $error');
setState(() { setState(() {
@ -65,11 +53,9 @@ class _LoginPageState extends State<LoginPage> {
Future<void> saveUserData(Users user, String role, int userId) async { Future<void> saveUserData(Users user, String role, int userId) async {
try { try {
// CORRECTION : Utiliser la nouvelle méthode du contrôleur
// Le contrôleur se charge maintenant de tout (observable + SharedPreferences)
userController.setUserWithCredentials(user, role, userId); userController.setUserWithCredentials(user, role, userId);
print(
print('Utilisateur sauvegardé: ${user.username}, rôle: $role, id: $userId'); 'Utilisateur sauvegardé: ${user.username}, rôle: $role, id: $userId');
} catch (error) { } catch (error) {
print('Erreur lors de la sauvegarde: $error'); print('Erreur lors de la sauvegarde: $error');
throw Exception('Erreur lors de la sauvegarde des données utilisateur'); throw Exception('Erreur lors de la sauvegarde des données utilisateur');
@ -82,10 +68,10 @@ class _LoginPageState extends State<LoginPage> {
final String username = _usernameController.text.trim(); final String username = _usernameController.text.trim();
final String password = _passwordController.text.trim(); final String password = _passwordController.text.trim();
// Validation basique
if (username.isEmpty || password.isEmpty) { if (username.isEmpty || password.isEmpty) {
setState(() { setState(() {
_errorMessage = 'Veuillez saisir le nom d\'utilisateur et le mot de passe'; _errorMessage =
'Veuillez saisir le nom d\'utilisateur et le mot de passe';
_isErrorVisible = true; _isErrorVisible = true;
}); });
return; return;
@ -97,45 +83,22 @@ class _LoginPageState extends State<LoginPage> {
}); });
try { try {
print('Tentative de connexion pour: $username');
// Vérification de la connexion à la base de données
final dbInstance = AppDatabase.instance; 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 // Vérifier les identifiants
bool isValidUser = await dbInstance.verifyUser(username, password); bool isValidUser = await dbInstance.verifyUser(username, password);
print('Résultat de la vérification: $isValidUser');
if (isValidUser) { if (isValidUser) {
// Récupérer les informations complètes de l'utilisateur
Users user = await dbInstance.getUser(username); Users user = await dbInstance.getUser(username);
print('Utilisateur récupéré: ${user.username}'); Map<String, dynamic>? userCredentials =
// Récupérer les credentials
Map<String, dynamic>? userCredentials =
await dbInstance.getUserCredentials(username, password); await dbInstance.getUserCredentials(username, password);
if (userCredentials != null) { if (userCredentials != null) {
print('Connexion réussie pour: ${user.username}');
print('Rôle: ${userCredentials['role']}');
print('ID: ${userCredentials['id']}');
// CORRECTION : Sauvegarder ET mettre à jour le contrôleur
await saveUserData( await saveUserData(
user, user,
userCredentials['role'] as String, userCredentials['role'] as String,
userCredentials['id'] as int, userCredentials['id'] as int,
); );
// Navigation
if (mounted) { if (mounted) {
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,
@ -146,149 +109,203 @@ class _LoginPageState extends State<LoginPage> {
throw Exception('Erreur lors de la récupération des credentials'); 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'; _errorMessage = 'Nom d\'utilisateur ou mot de passe invalide';
_isErrorVisible = true; _isErrorVisible = true;
}); });
} }
} catch (error) { } catch (error) {
print('Erreur lors de la connexion: $error');
setState(() { setState(() {
_errorMessage = 'Erreur de connexion: ${error.toString()}'; _errorMessage = 'Erreur de connexion: ${error.toString()}';
_isErrorVisible = true; _isErrorVisible = true;
}); });
} finally { } finally {
if (mounted) { if (mounted) setState(() => _isLoading = false);
setState(() {
_isLoading = false;
});
}
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Color primaryBlue = const Color(0xFF0033A1);
final Color accentRed = const Color(0xFFD70000);
final Color secondaryBlue = const Color(0xFF1976D2);
final Color primaryColor = primaryBlue;
final Color accentColor = secondaryBlue;
final Color cardColor = Colors.white;
return Scaffold( return Scaffold(
appBar: AppBar( backgroundColor: primaryColor,
title: const Text(
'Login',
style: TextStyle(color: Colors.white),
),
backgroundColor: const Color.fromARGB(255, 4, 54, 95),
centerTitle: true,
),
body: ParticleBackground( body: ParticleBackground(
child: Center( child: Center(
child: Container( child: SingleChildScrollView(
width: MediaQuery.of(context).size.width * 0.5, child: Container(
height: MediaQuery.of(context).size.height * 0.8, width: MediaQuery.of(context).size.width < 500
padding: const EdgeInsets.all(16.0), ? double.infinity
decoration: BoxDecoration( : 400,
color: Colors.white, padding:
shape: BoxShape.rectangle, const EdgeInsets.symmetric(horizontal: 24.0, vertical: 32.0),
borderRadius: BorderRadius.circular(30.0), decoration: BoxDecoration(
), color: cardColor.withOpacity(0.98),
child: Column( borderRadius: BorderRadius.circular(30.0),
crossAxisAlignment: CrossAxisAlignment.stretch, boxShadow: [
children: [ BoxShadow(
Container( color: primaryColor.withOpacity(0.2),
padding: const EdgeInsets.symmetric(vertical: 16.0), blurRadius: 16,
child: const Icon( spreadRadius: 4,
Icons.lock_outline, offset: const Offset(0, 8),
size: 100.0,
color: Color.fromARGB(255, 4, 54, 95),
), ),
), ],
TextField( ),
controller: _usernameController, child: Column(
enabled: !_isLoading, crossAxisAlignment: CrossAxisAlignment.stretch,
decoration: InputDecoration( children: [
labelText: 'Username', Center(
prefixIcon: const Icon(Icons.person, color: Colors.blueAccent), child: Column(
border: OutlineInputBorder( children: [
borderRadius: BorderRadius.circular(30.0), CircleAvatar(
), radius: 38,
), backgroundColor: accentColor.withOpacity(0.15),
), child: Icon(
const SizedBox(height: 16.0), Icons.lock_outline,
TextField( color: accentColor,
controller: _passwordController, size: 50,
enabled: !_isLoading,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: const Icon(Icons.lock, color: Colors.redAccent),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
),
),
obscureText: true,
onSubmitted: (_) => _login(),
),
const SizedBox(height: 16.0),
Visibility(
visible: _isErrorVisible,
child: Text(
_errorMessage,
style: const TextStyle(
color: Colors.red,
fontSize: 14,
),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 16.0),
ElevatedButton(
onPressed: _isLoading ? null : _login,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0015B7),
elevation: 5.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
minimumSize: const Size(double.infinity, 48),
),
child: _isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
), ),
) ),
: const Text( const SizedBox(height: 14),
'Se connecter', Text(
'GUYCOM',
style: TextStyle( style: TextStyle(
color: Colors.white, color: primaryColor,
fontWeight: FontWeight.bold,
fontSize: 28,
),
),
const SizedBox(height: 4),
Text(
'Connectez-vous à votre compte',
style: TextStyle(
color: primaryColor.withOpacity(.8),
fontSize: 16, 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'),
), ),
], const SizedBox(height: 24),
TextField(
controller: _usernameController,
enabled: !_isLoading,
decoration: InputDecoration(
labelText: 'Nom d\'utilisateur',
labelStyle: TextStyle(
color: primaryColor.withOpacity(0.7),
),
prefixIcon: Icon(Icons.person, color: accentColor),
filled: true,
fillColor: accentColor.withOpacity(0.045),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
borderSide: BorderSide(color: accentColor, width: 2),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
borderSide: BorderSide(color: accentColor, width: 2),
),
),
),
const SizedBox(height: 18.0),
TextField(
controller: _passwordController,
enabled: !_isLoading,
obscureText: true,
decoration: InputDecoration(
labelText: 'Mot de passe',
labelStyle: TextStyle(
color: primaryColor.withOpacity(0.7),
),
prefixIcon: Icon(Icons.lock, color: accentColor),
filled: true,
fillColor: accentColor.withOpacity(0.045),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
borderSide: BorderSide(color: accentColor, width: 2),
),
),
onSubmitted: (_) => _login(),
),
if (_isErrorVisible) ...[
const SizedBox(height: 12.0),
Text(
_errorMessage,
style: const TextStyle(
color: Colors.redAccent,
fontSize: 15,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
],
const SizedBox(height: 26.0),
ElevatedButton(
onPressed: _isLoading ? null : _login,
style: ElevatedButton.styleFrom(
backgroundColor: accentColor,
disabledBackgroundColor: accentColor.withOpacity(0.3),
foregroundColor: Colors.white,
elevation: 7.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
minimumSize: const Size(double.infinity, 52),
),
child: _isLoading
? const SizedBox(
height: 24,
width: 24,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2.5,
),
)
: const Text(
'Se connecter',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
letterSpacing: .4,
),
),
),
// Option debug, à enlever en prod
if (_isErrorVisible) ...[
TextButton(
onPressed: () async {
try {
final count =
await AppDatabase.instance.getUserCount();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('$count utilisateurs trouvés')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erreur: $e')),
);
}
},
child: const Text('Debug: Vérifier BDD'),
),
],
],
),
), ),
), ),
), ),
), ),
); );
} }
} }

View File

@ -45,7 +45,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: const CustomAppBar(title: 'Nouvelle Commande'), appBar: const CustomAppBar(title: 'Nouvelle Commande'),
drawer: CustomDrawer(), drawer: CustomDrawer(),
body: Column( body: Column(
children: [ children: [
// Header avec logo et titre // Header avec logo et titre
@ -126,7 +126,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
], ],
), ),
), ),
// Contenu principal // Contenu principal
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
@ -221,7 +221,8 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Veuillez entrer un email'; return 'Veuillez entrer un email';
} }
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) { if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$')
.hasMatch(value)) {
return 'Veuillez entrer un email valide'; return 'Veuillez entrer un email valide';
} }
return null; return null;
@ -297,7 +298,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final product = _products[index]; final product = _products[index];
final quantity = _quantites[product.id] ?? 0; final quantity = _quantites[product.id] ?? 0;
return Card( return Card(
margin: const EdgeInsets.symmetric(vertical: 8), margin: const EdgeInsets.symmetric(vertical: 8),
elevation: 2, elevation: 2,
@ -316,7 +317,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
color: Colors.blue.shade50, color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: const Icon(Icons.shopping_bag, child: const Icon(Icons.shopping_bag,
color: Colors.blue), color: Colors.blue),
), ),
title: Text( title: Text(
@ -328,7 +329,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
children: [ children: [
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'${product.price.toStringAsFixed(2)} DA', '${product.price.toStringAsFixed(2)} MGA',
style: TextStyle( style: TextStyle(
color: Colors.green.shade700, color: Colors.green.shade700,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -364,12 +365,14 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
), ),
Text( Text(
quantity.toString(), quantity.toString(),
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(
fontWeight: FontWeight.bold),
), ),
IconButton( IconButton(
icon: const Icon(Icons.add, size: 18), icon: const Icon(Icons.add, size: 18),
onPressed: () { onPressed: () {
if (product.stock == null || quantity < product.stock!) { if (product.stock == null ||
quantity < product.stock!) {
setState(() { setState(() {
_quantites[product.id!] = quantity + 1; _quantites[product.id!] = quantity + 1;
}); });
@ -397,7 +400,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
Widget _buildCartSection() { Widget _buildCartSection() {
final itemsInCart = _quantites.entries.where((e) => e.value > 0).toList(); final itemsInCart = _quantites.entries.where((e) => e.value > 0).toList();
if (itemsInCart.isEmpty) { if (itemsInCart.isEmpty) {
return Card( return Card(
elevation: 4, elevation: 4,
@ -454,9 +457,10 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
child: const Icon(Icons.shopping_bag, size: 20), child: const Icon(Icons.shopping_bag, size: 20),
), ),
title: Text(product.name), title: Text(product.name),
subtitle: Text('${entry.value} x ${product.price.toStringAsFixed(2)} DA'), subtitle: Text(
'${entry.value} x ${product.price.toStringAsFixed(2)} MGA'),
trailing: Text( trailing: Text(
'${(entry.value * product.price).toStringAsFixed(2)} DA', '${(entry.value * product.price).toStringAsFixed(2)} MGA',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.blue.shade800, color: Colors.blue.shade800,
@ -499,7 +503,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
), ),
Text( Text(
'${total.toStringAsFixed(2)} DA', '${total.toStringAsFixed(2)} MGA',
style: const TextStyle( style: const TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -559,11 +563,11 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
// Calculer le total et préparer les détails // Calculer le total et préparer les détails
double total = 0; double total = 0;
final details = <DetailCommande>[]; final details = <DetailCommande>[];
for (final entry in itemsInCart) { for (final entry in itemsInCart) {
final product = _products.firstWhere((p) => p.id == entry.key); final product = _products.firstWhere((p) => p.id == entry.key);
total += entry.value * product.price; total += entry.value * product.price;
details.add(DetailCommande( details.add(DetailCommande(
commandeId: 0, // Valeur temporaire, sera remplacée dans la transaction commandeId: 0, // Valeur temporaire, sera remplacée dans la transaction
produitId: product.id!, produitId: product.id!,
@ -585,7 +589,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
try { try {
// Enregistrer la commande dans la base de données // Enregistrer la commande dans la base de données
await _database.createCommandeComplete(client, commande, details); await _database.createCommandeComplete(client, commande, details);
Get.snackbar( Get.snackbar(
'Succès', 'Succès',
'Votre commande a été enregistrée', 'Votre commande a été enregistrée',
@ -593,13 +597,12 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
backgroundColor: Colors.green, backgroundColor: Colors.green,
colorText: Colors.white, colorText: Colors.white,
); );
// Réinitialiser le formulaire // Réinitialiser le formulaire
_formKey.currentState!.reset(); _formKey.currentState!.reset();
setState(() { setState(() {
_quantites.clear(); _quantites.clear();
}); });
} catch (e) { } catch (e) {
Get.snackbar( Get.snackbar(
'Erreur', 'Erreur',
@ -620,4 +623,4 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
_adresseController.dispose(); _adresseController.dispose();
super.dispose(); super.dispose();
} }
} }

190
lib/Views/pointage.dart Normal file
View File

@ -0,0 +1,190 @@
import 'package:flutter/material.dart';
import 'package:youmazgestion/Services/pointageDatabase.dart';
import 'package:youmazgestion/Models/pointage_model.dart';
class PointagePage extends StatefulWidget {
const PointagePage({Key? key}) : super(key: key);
@override
State<PointagePage> createState() => _PointagePageState();
}
class _PointagePageState extends State<PointagePage> {
final DatabaseHelper _databaseHelper = DatabaseHelper();
List<Pointage> _pointages = [];
@override
void initState() {
super.initState();
_loadPointages();
}
Future<void> _loadPointages() async {
final pointages = await _databaseHelper.getPointages();
setState(() {
_pointages = pointages;
});
}
Future<void> _showAddDialog() async {
final _arrivalController = TextEditingController();
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Ajouter Pointage'),
content: TextField(
controller: _arrivalController,
decoration: InputDecoration(
labelText: 'Heure d\'arrivée (HH:mm)',
),
),
actions: [
TextButton(
child: Text('Annuler'),
onPressed: () => Navigator.of(context).pop(),
),
ElevatedButton(
child: Text('Ajouter'),
onPressed: () async {
final pointage = Pointage(
userName:
"Nom de l'utilisateur", // fixed value, customize if needed
date: DateTime.now().toString().split(' ')[0],
heureArrivee: _arrivalController.text,
heureDepart: '',
);
await _databaseHelper.insertPointage(pointage);
Navigator.of(context).pop();
_loadPointages();
},
),
],
);
},
);
}
void _scanQRCode({required bool isEntree}) {
// Ici tu peux intégrer ton scanner QR.
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(isEntree ? "Scan QR pour Entrée" : "Scan QR pour Sortie"),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Pointage'),
),
body: _pointages.isEmpty
? Center(child: Text('Aucun pointage enregistré.'))
: ListView.builder(
itemCount: _pointages.length,
itemBuilder: (context, index) {
final pointage = _pointages[index];
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12.0, vertical: 6.0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(color: Colors.blueGrey.shade100),
),
elevation: 4,
shadowColor: Colors.blueGrey.shade50,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(
backgroundColor: Colors.blue.shade100,
child: Icon(Icons.person, color: Colors.blue),
),
const SizedBox(width: 10),
Expanded(
child: Text(
pointage
.userName, // suppose non-null (corrige si null possible)
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18),
),
),
],
),
Divider(),
Text(
pointage.date,
style: const TextStyle(
color: Colors.black87, fontSize: 15),
),
Row(
children: [
Icon(Icons.login,
size: 18, color: Colors.green.shade700),
const SizedBox(width: 6),
Text("Arrivée : ${pointage.heureArrivee}",
style:
TextStyle(color: Colors.green.shade700)),
],
),
Row(
children: [
Icon(Icons.logout,
size: 18, color: Colors.red.shade700),
const SizedBox(width: 6),
Text(
"Départ : ${pointage.heureDepart.isNotEmpty ? pointage.heureDepart : "---"}",
style: TextStyle(color: Colors.red.shade700)),
],
),
const SizedBox(height: 6),
],
),
),
),
);
},
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
FloatingActionButton.extended(
onPressed: () => _scanQRCode(isEntree: true),
label: Text('Entrée'),
icon: Icon(Icons.qr_code_scanner, color: Colors.green),
backgroundColor: Colors.white,
foregroundColor: Colors.green,
heroTag: 'btnEntree',
),
SizedBox(height: 12),
FloatingActionButton.extended(
onPressed: () => _scanQRCode(isEntree: false),
label: Text('Sortie'),
icon: Icon(Icons.qr_code_scanner, color: Colors.red),
backgroundColor: Colors.white,
foregroundColor: Colors.red,
heroTag: 'btnSortie',
),
SizedBox(height: 12),
FloatingActionButton(
onPressed: _showAddDialog,
tooltip: 'Ajouter Pointage',
child: const Icon(Icons.add),
heroTag: 'btnAdd',
),
],
),
);
}
}

View File

@ -4,7 +4,7 @@ import 'package:youmazgestion/Models/produit.dart';
class ProductCard extends StatefulWidget { class ProductCard extends StatefulWidget {
final Product product; final Product product;
final void Function(Product, int) onAddToCart; final void Function(Product, int) onAddToCart;
const ProductCard({ const ProductCard({
Key? key, Key? key,
@ -16,7 +16,8 @@ class ProductCard extends StatefulWidget {
State<ProductCard> createState() => _ProductCardState(); State<ProductCard> createState() => _ProductCardState();
} }
class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin { class _ProductCardState extends State<ProductCard>
with TickerProviderStateMixin {
int selectedQuantity = 1; int selectedQuantity = 1;
late AnimationController _scaleController; late AnimationController _scaleController;
late AnimationController _fadeController; late AnimationController _fadeController;
@ -26,7 +27,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Animations pour les interactions // Animations pour les interactions
_scaleController = AnimationController( _scaleController = AnimationController(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
@ -36,7 +37,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
vsync: this, vsync: this,
)..forward(); )..forward();
_scaleAnimation = Tween<double>( _scaleAnimation = Tween<double>(
begin: 1.0, begin: 1.0,
end: 0.95, end: 0.95,
@ -44,7 +45,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
parent: _scaleController, parent: _scaleController,
curve: Curves.easeInOut, curve: Curves.easeInOut,
)); ));
_fadeAnimation = Tween<double>( _fadeAnimation = Tween<double>(
begin: 0.0, begin: 0.0,
end: 1.0, end: 1.0,
@ -122,7 +123,6 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
: _buildPlaceholderImage(), : _buildPlaceholderImage(),
), ),
), ),
Positioned.fill( Positioned.fill(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -141,7 +141,6 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
), ),
), ),
), ),
if (widget.product.isStockDefined()) if (widget.product.isStockDefined())
Positioned( Positioned(
top: 12, top: 12,
@ -183,7 +182,6 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
), ),
), ),
), ),
Positioned( Positioned(
left: 0, left: 0,
right: 0, right: 0,
@ -201,7 +199,8 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
vertical: 8, vertical: 8,
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
Text( Text(
widget.product.name, widget.product.name,
@ -222,7 +221,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'${widget.product.price.toStringAsFixed(2)} FCFA', '${widget.product.price.toStringAsFixed(2)} MGA',
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 13, fontSize: 13,
@ -239,9 +238,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
], ],
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Row( Row(
children: [ children: [
Container( Container(
@ -250,7 +247,8 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color:
Colors.black.withOpacity(0.1),
blurRadius: 4, blurRadius: 4,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@ -295,9 +293,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
], ],
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: MouseRegion( child: MouseRegion(
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
@ -306,9 +302,11 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
onTapUp: _onTapUp, onTapUp: _onTapUp,
onTapCancel: _onTapCancel, onTapCancel: _onTapCancel,
onTap: () { onTap: () {
widget.onAddToCart(widget.product, selectedQuantity); widget.onAddToCart(widget.product,
selectedQuantity);
ScaffoldMessenger.of(context).showSnackBar(
ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar( SnackBar(
content: Row( content: Row(
children: [ children: [
@ -320,16 +318,20 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
Expanded( Expanded(
child: Text( child: Text(
'${widget.product.name} (x$selectedQuantity) ajouté au panier', '${widget.product.name} (x$selectedQuantity) ajouté au panier',
overflow: TextOverflow.ellipsis, overflow: TextOverflow
.ellipsis,
), ),
), ),
], ],
), ),
backgroundColor: Colors.green, backgroundColor: Colors.green,
duration: const Duration(seconds: 1), duration:
behavior: SnackBarBehavior.floating, const Duration(seconds: 1),
behavior:
SnackBarBehavior.floating,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), borderRadius:
BorderRadius.circular(10),
), ),
), ),
); );
@ -342,21 +344,27 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: const LinearGradient( gradient: const LinearGradient(
colors: [ colors: [
Color.fromARGB(255, 4, 54, 95), Color.fromARGB(
Color.fromARGB(255, 6, 80, 140), 255, 4, 54, 95),
Color.fromARGB(
255, 6, 80, 140),
], ],
), ),
borderRadius: BorderRadius.circular(20), borderRadius:
BorderRadius.circular(20),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: const Color.fromARGB(255, 4, 54, 95).withOpacity(0.3), color: const Color.fromARGB(
255, 4, 54, 95)
.withOpacity(0.3),
blurRadius: 6, blurRadius: 6,
offset: const Offset(0, 3), offset: const Offset(0, 3),
), ),
], ],
), ),
child: const Row( child: const Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment:
MainAxisAlignment.center,
children: [ children: [
const Icon( const Icon(
Icons.add_shopping_cart, Icons.add_shopping_cart,
@ -369,10 +377,12 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
'Ajouter', 'Ajouter',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontWeight: FontWeight.bold, fontWeight:
FontWeight.bold,
fontSize: 12, fontSize: 12,
), ),
overflow: TextOverflow.ellipsis, overflow:
TextOverflow.ellipsis,
), ),
), ),
], ],
@ -442,10 +452,12 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
child: Icon( child: Icon(
icon, icon,
size: 16, size: 16,
color: onPressed != null ? const Color.fromARGB(255, 4, 54, 95) : Colors.grey, color: onPressed != null
? const Color.fromARGB(255, 4, 54, 95)
: Colors.grey,
), ),
), ),
), ),
); );
} }
} }

View File

@ -448,7 +448,7 @@ class _AccueilPageState extends State<AccueilPage> {
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold)), fontWeight: FontWeight.bold)),
Text( Text(
'${NumberFormat('#,##0.00').format(calculateTotalPrice())} FCFA', '${NumberFormat('#,##0.00').format(calculateTotalPrice())} MGA',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,