Compare commits
7 Commits
28062025_0
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 25abc65480 | |||
| beb2048a7a | |||
| b1d176aa2a | |||
| 8d86d99e24 | |||
| 5f665acf35 | |||
| 38af810b79 | |||
| da03076411 |
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
36
lib/Models/pointage_model.dart
Normal file
36
lib/Models/pointage_model.dart
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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')) {
|
||||||
@ -139,7 +141,6 @@ class AppDatabase {
|
|||||||
''');
|
''');
|
||||||
print("Table 'role_menu_permissions' créée.");
|
print("Table 'role_menu_permissions' créée.");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> insertDefaultPermissions() async {
|
Future<void> insertDefaultPermissions() async {
|
||||||
@ -158,7 +159,8 @@ class AppDatabase {
|
|||||||
// Vérifier et ajouter les nouvelles permissions si elles n'existent pas
|
// Vérifier et ajouter les nouvelles permissions si elles n'existent pas
|
||||||
final newPermissions = ['manage', 'read'];
|
final newPermissions = ['manage', 'read'];
|
||||||
for (var permission in newPermissions) {
|
for (var permission in newPermissions) {
|
||||||
final existingPermission = await db.query('permissions', where: 'name = ?', whereArgs: [permission]);
|
final existingPermission = await db
|
||||||
|
.query('permissions', where: 'name = ?', whereArgs: [permission]);
|
||||||
if (existingPermission.isEmpty) {
|
if (existingPermission.isEmpty) {
|
||||||
await db.insert('permissions', {'name': permission});
|
await db.insert('permissions', {'name': permission});
|
||||||
print("Permission ajoutée: $permission");
|
print("Permission ajoutée: $permission");
|
||||||
@ -174,19 +176,31 @@ class AppDatabase {
|
|||||||
if (existingMenus.isEmpty) {
|
if (existingMenus.isEmpty) {
|
||||||
// Menus existants
|
// Menus existants
|
||||||
await db.insert('menu', {'name': 'Accueil', 'route': '/accueil'});
|
await db.insert('menu', {'name': 'Accueil', 'route': '/accueil'});
|
||||||
await db.insert('menu', {'name': 'Ajouter un utilisateur', 'route': '/ajouter-utilisateur'});
|
await db.insert('menu',
|
||||||
await db.insert('menu', {'name': 'Modifier/Supprimer un utilisateur', 'route': '/modifier-utilisateur'});
|
{'name': 'Ajouter un utilisateur', 'route': '/ajouter-utilisateur'});
|
||||||
await db.insert('menu', {'name': 'Ajouter un produit', 'route': '/ajouter-produit'});
|
await db.insert('menu', {
|
||||||
await db.insert('menu', {'name': 'Modifier/Supprimer un produit', 'route': '/modifier-produit'});
|
'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': 'Bilan', 'route': '/bilan'});
|
||||||
await db.insert('menu', {'name': 'Gérer les rôles', 'route': '/gerer-roles'});
|
await db
|
||||||
await db.insert('menu', {'name': 'Gestion de stock', 'route': '/gestion-stock'});
|
.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': 'Historique', 'route': '/historique'});
|
||||||
await db.insert('menu', {'name': 'Déconnexion', 'route': '/deconnexion'});
|
await db.insert('menu', {'name': 'Déconnexion', 'route': '/deconnexion'});
|
||||||
|
|
||||||
// Nouveaux menus ajoutés
|
// Nouveaux menus ajoutés
|
||||||
await db.insert('menu', {'name': 'Nouvelle commande', 'route': '/nouvelle-commande'});
|
await db.insert(
|
||||||
await db.insert('menu', {'name': 'Gérer les commandes', 'route': '/gerer-commandes'});
|
'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");
|
print("Menus par défaut insérés");
|
||||||
} else {
|
} else {
|
||||||
@ -199,6 +213,7 @@ Future<void> _addMissingMenus(Database db) async {
|
|||||||
final menusToAdd = [
|
final menusToAdd = [
|
||||||
{'name': 'Nouvelle commande', 'route': '/nouvelle-commande'},
|
{'name': 'Nouvelle commande', 'route': '/nouvelle-commande'},
|
||||||
{'name': 'Gérer les commandes', 'route': '/gerer-commandes'},
|
{'name': 'Gérer les commandes', 'route': '/gerer-commandes'},
|
||||||
|
{'name': 'Gérer les pointages', 'route': '/pointage'},
|
||||||
];
|
];
|
||||||
|
|
||||||
for (var menu in menusToAdd) {
|
for (var menu in menusToAdd) {
|
||||||
@ -220,7 +235,8 @@ Future<void> _addMissingMenus(Database db) async {
|
|||||||
final existingRoles = await db.query('roles');
|
final existingRoles = await db.query('roles');
|
||||||
|
|
||||||
if (existingRoles.isEmpty) {
|
if (existingRoles.isEmpty) {
|
||||||
int superAdminRoleId = await db.insert('roles', {'designation': 'Super Admin'});
|
int superAdminRoleId =
|
||||||
|
await db.insert('roles', {'designation': 'Super Admin'});
|
||||||
int adminRoleId = await db.insert('roles', {'designation': 'Admin'});
|
int adminRoleId = await db.insert('roles', {'designation': 'Admin'});
|
||||||
int userRoleId = await db.insert('roles', {'designation': 'User'});
|
int userRoleId = await db.insert('roles', {'designation': 'User'});
|
||||||
|
|
||||||
@ -230,13 +246,14 @@ Future<void> _addMissingMenus(Database db) async {
|
|||||||
// Assigner toutes les permissions à tous les menus pour le Super Admin
|
// Assigner toutes les permissions à tous les menus pour le Super Admin
|
||||||
for (var menu in menus) {
|
for (var menu in menus) {
|
||||||
for (var permission in permissions) {
|
for (var permission in permissions) {
|
||||||
await db.insert('role_menu_permissions', {
|
await db.insert(
|
||||||
|
'role_menu_permissions',
|
||||||
|
{
|
||||||
'role_id': superAdminRoleId,
|
'role_id': superAdminRoleId,
|
||||||
'menu_id': menu['id'],
|
'menu_id': menu['id'],
|
||||||
'permission_id': permission['id'],
|
'permission_id': permission['id'],
|
||||||
},
|
},
|
||||||
conflictAlgorithm: ConflictAlgorithm.ignore
|
conflictAlgorithm: ConflictAlgorithm.ignore);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,61 +266,75 @@ Future<void> _addMissingMenus(Database db) async {
|
|||||||
await _updateExistingRolePermissions(db);
|
await _updateExistingRolePermissions(db);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nouvelle méthode pour assigner les permissions de base aux nouveaux menus
|
// Nouvelle méthode pour assigner les permissions de base aux nouveaux menus
|
||||||
Future<void> _assignBasicPermissionsToRoles(Database db, int adminRoleId, int userRoleId) async {
|
Future<void> _assignBasicPermissionsToRoles(
|
||||||
final viewPermission = await db.query('permissions', where: 'name = ?', whereArgs: ['view']);
|
Database db, int adminRoleId, int userRoleId) async {
|
||||||
final createPermission = await db.query('permissions', where: 'name = ?', whereArgs: ['create']);
|
final viewPermission =
|
||||||
final updatePermission = await db.query('permissions', where: 'name = ?', whereArgs: ['update']);
|
await db.query('permissions', where: 'name = ?', whereArgs: ['view']);
|
||||||
final managePermission = await db.query('permissions', where: 'name = ?', whereArgs: ['manage']);
|
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
|
// Récupérer les IDs des nouveaux menus
|
||||||
final nouvelleCommandeMenu = await db.query('menu', where: 'route = ?', whereArgs: ['/nouvelle-commande']);
|
final nouvelleCommandeMenu = await db
|
||||||
final gererCommandesMenu = await db.query('menu', where: 'route = ?', whereArgs: ['/gerer-commandes']);
|
.query('menu', where: 'route = ?', whereArgs: ['/nouvelle-commande']);
|
||||||
|
final gererCommandesMenu = await db
|
||||||
|
.query('menu', where: 'route = ?', whereArgs: ['/gerer-commandes']);
|
||||||
|
|
||||||
if (nouvelleCommandeMenu.isNotEmpty && createPermission.isNotEmpty) {
|
if (nouvelleCommandeMenu.isNotEmpty && createPermission.isNotEmpty) {
|
||||||
// Admin peut créer de nouvelles commandes
|
// Admin peut créer de nouvelles commandes
|
||||||
await db.insert('role_menu_permissions', {
|
await db.insert(
|
||||||
|
'role_menu_permissions',
|
||||||
|
{
|
||||||
'role_id': adminRoleId,
|
'role_id': adminRoleId,
|
||||||
'menu_id': nouvelleCommandeMenu.first['id'],
|
'menu_id': nouvelleCommandeMenu.first['id'],
|
||||||
'permission_id': createPermission.first['id'],
|
'permission_id': createPermission.first['id'],
|
||||||
},
|
},
|
||||||
conflictAlgorithm: ConflictAlgorithm.ignore
|
conflictAlgorithm: ConflictAlgorithm.ignore);
|
||||||
);
|
|
||||||
|
|
||||||
// User peut aussi créer de nouvelles commandes
|
// User peut aussi créer de nouvelles commandes
|
||||||
await db.insert('role_menu_permissions', {
|
await db.insert(
|
||||||
|
'role_menu_permissions',
|
||||||
|
{
|
||||||
'role_id': userRoleId,
|
'role_id': userRoleId,
|
||||||
'menu_id': nouvelleCommandeMenu.first['id'],
|
'menu_id': nouvelleCommandeMenu.first['id'],
|
||||||
'permission_id': createPermission.first['id'],
|
'permission_id': createPermission.first['id'],
|
||||||
},
|
},
|
||||||
conflictAlgorithm: ConflictAlgorithm.ignore
|
conflictAlgorithm: ConflictAlgorithm.ignore);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gererCommandesMenu.isNotEmpty && managePermission.isNotEmpty) {
|
if (gererCommandesMenu.isNotEmpty && managePermission.isNotEmpty) {
|
||||||
// Admin peut gérer les commandes
|
// Admin peut gérer les commandes
|
||||||
await db.insert('role_menu_permissions', {
|
await db.insert(
|
||||||
|
'role_menu_permissions',
|
||||||
|
{
|
||||||
'role_id': adminRoleId,
|
'role_id': adminRoleId,
|
||||||
'menu_id': gererCommandesMenu.first['id'],
|
'menu_id': gererCommandesMenu.first['id'],
|
||||||
'permission_id': managePermission.first['id'],
|
'permission_id': managePermission.first['id'],
|
||||||
},
|
},
|
||||||
conflictAlgorithm: ConflictAlgorithm.ignore
|
conflictAlgorithm: ConflictAlgorithm.ignore);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gererCommandesMenu.isNotEmpty && viewPermission.isNotEmpty) {
|
if (gererCommandesMenu.isNotEmpty && viewPermission.isNotEmpty) {
|
||||||
// User peut voir les commandes
|
// User peut voir les commandes
|
||||||
await db.insert('role_menu_permissions', {
|
await db.insert(
|
||||||
|
'role_menu_permissions',
|
||||||
|
{
|
||||||
'role_id': userRoleId,
|
'role_id': userRoleId,
|
||||||
'menu_id': gererCommandesMenu.first['id'],
|
'menu_id': gererCommandesMenu.first['id'],
|
||||||
'permission_id': viewPermission.first['id'],
|
'permission_id': viewPermission.first['id'],
|
||||||
}
|
},
|
||||||
, conflictAlgorithm: ConflictAlgorithm.ignore
|
conflictAlgorithm: ConflictAlgorithm.ignore);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateExistingRolePermissions(Database db) async {
|
Future<void> _updateExistingRolePermissions(Database db) async {
|
||||||
final superAdminRole = await db.query('roles', where: 'designation = ?', whereArgs: ['Super Admin']);
|
final superAdminRole = await db
|
||||||
|
.query('roles', where: 'designation = ?', whereArgs: ['Super Admin']);
|
||||||
if (superAdminRole.isNotEmpty) {
|
if (superAdminRole.isNotEmpty) {
|
||||||
final superAdminRoleId = superAdminRole.first['id'] as int;
|
final superAdminRoleId = superAdminRole.first['id'] as int;
|
||||||
final permissions = await db.query('permissions');
|
final permissions = await db.query('permissions');
|
||||||
@ -318,30 +349,33 @@ Future<void> _updateExistingRolePermissions(Database db) async {
|
|||||||
whereArgs: [superAdminRoleId, menu['id'], permission['id']],
|
whereArgs: [superAdminRoleId, menu['id'], permission['id']],
|
||||||
);
|
);
|
||||||
if (existingPermission.isEmpty) {
|
if (existingPermission.isEmpty) {
|
||||||
await db.insert('role_menu_permissions', {
|
await db.insert(
|
||||||
|
'role_menu_permissions',
|
||||||
|
{
|
||||||
'role_id': superAdminRoleId,
|
'role_id': superAdminRoleId,
|
||||||
'menu_id': menu['id'],
|
'menu_id': menu['id'],
|
||||||
'permission_id': permission['id'],
|
'permission_id': permission['id'],
|
||||||
},
|
},
|
||||||
conflictAlgorithm: ConflictAlgorithm.ignore
|
conflictAlgorithm: ConflictAlgorithm.ignore);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assigner les permissions de base aux autres rôles pour les nouveaux menus
|
// Assigner les permissions de base aux autres rôles pour les nouveaux menus
|
||||||
final adminRole = await db.query('roles', where: 'designation = ?', whereArgs: ['Admin']);
|
final adminRole = await db
|
||||||
final userRole = await db.query('roles', where: 'designation = ?', whereArgs: ['User']);
|
.query('roles', where: 'designation = ?', whereArgs: ['Admin']);
|
||||||
|
final userRole = await db
|
||||||
|
.query('roles', where: 'designation = ?', whereArgs: ['User']);
|
||||||
|
|
||||||
if (adminRole.isNotEmpty && userRole.isNotEmpty) {
|
if (adminRole.isNotEmpty && userRole.isNotEmpty) {
|
||||||
await _assignBasicPermissionsToRoles(db, adminRole.first['id'] as int, userRole.first['id'] as int);
|
await _assignBasicPermissionsToRoles(
|
||||||
|
db, adminRole.first['id'] as int, userRole.first['id'] as int);
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Permissions mises à jour pour tous les rôles");
|
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_permissions',
|
||||||
|
{
|
||||||
'role_id': roleId,
|
'role_id': roleId,
|
||||||
'permission_id': permissionId,
|
'permission_id': permissionId,
|
||||||
}, conflictAlgorithm: ConflictAlgorithm.ignore);
|
},
|
||||||
|
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_permissions',
|
||||||
|
{
|
||||||
'menu_id': menuId,
|
'menu_id': menuId,
|
||||||
'permission_id': permissionId,
|
'permission_id': permissionId,
|
||||||
}, conflictAlgorithm: ConflictAlgorithm.ignore);
|
},
|
||||||
|
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,7 +627,8 @@ Future<void> _updateExistingRolePermissions(Database db) async {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> hasPermission(String username, String permissionName, String menuRoute) async {
|
Future<bool> hasPermission(
|
||||||
|
String username, String permissionName, String menuRoute) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
final result = await db.rawQuery('''
|
final result = await db.rawQuery('''
|
||||||
SELECT COUNT(*) as count
|
SELECT COUNT(*) as count
|
||||||
@ -599,7 +643,6 @@ Future<void> _updateExistingRolePermissions(Database db) async {
|
|||||||
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) {
|
||||||
await _database.close();
|
await _database.close();
|
||||||
@ -635,7 +678,8 @@ Future<void> _updateExistingRolePermissions(Database db) async {
|
|||||||
print("=========================================");
|
print("=========================================");
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Permission>> getPermissionsForRoleAndMenu(int roleId, int menuId) async {
|
Future<List<Permission>> getPermissionsForRoleAndMenu(
|
||||||
|
int roleId, int menuId) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
final result = await db.rawQuery('''
|
final result = await db.rawQuery('''
|
||||||
SELECT p.id, p.name
|
SELECT p.id, p.name
|
||||||
@ -647,6 +691,7 @@ Future<void> _updateExistingRolePermissions(Database db) async {
|
|||||||
|
|
||||||
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();
|
||||||
@ -657,18 +702,22 @@ Future<void> deleteDatabaseFile() async {
|
|||||||
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 {
|
|
||||||
|
Future<void> assignRoleMenuPermission(
|
||||||
|
int roleId, int menuId, int permissionId) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
await db.insert('role_menu_permissions', {
|
await db.insert(
|
||||||
|
'role_menu_permissions',
|
||||||
|
{
|
||||||
'role_id': roleId,
|
'role_id': roleId,
|
||||||
'menu_id': menuId,
|
'menu_id': menuId,
|
||||||
'permission_id': permissionId,
|
'permission_id': permissionId,
|
||||||
}, conflictAlgorithm: ConflictAlgorithm.ignore);
|
},
|
||||||
|
conflictAlgorithm: ConflictAlgorithm.ignore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> removeRoleMenuPermission(
|
||||||
|
int roleId, int menuId, int permissionId) async {
|
||||||
Future<void> removeRoleMenuPermission(int roleId, int menuId, int permissionId) async {
|
|
||||||
final db = await database;
|
final db = await database;
|
||||||
await db.delete(
|
await db.delete(
|
||||||
'role_menu_permissions',
|
'role_menu_permissions',
|
||||||
@ -676,5 +725,4 @@ Future<void> removeRoleMenuPermission(int roleId, int menuId, int permissionId)
|
|||||||
whereArgs: [roleId, menuId, permissionId],
|
whereArgs: [roleId, menuId, permissionId],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
60
lib/Services/pointageDatabase.dart
Normal file
60
lib/Services/pointageDatabase.dart
Normal 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,6 +44,7 @@ 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();
|
||||||
@ -52,17 +54,23 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
|||||||
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.clientNomComplet.toLowerCase().contains(query) ||
|
||||||
commande.id.toString().contains(query);
|
commande.id.toString().contains(query);
|
||||||
final matchesStatut = _selectedStatut == null || commande.statut == _selectedStatut;
|
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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -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)}'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -212,7 +221,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
|||||||
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)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -224,15 +234,21 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
|||||||
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) {
|
||||||
@ -247,8 +263,10 @@ 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'),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -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),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -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,
|
||||||
@ -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,
|
||||||
@ -590,11 +611,13 @@ 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),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -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(
|
||||||
@ -756,7 +780,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
|||||||
Icon(
|
Icon(
|
||||||
_getStatutIcon(commande.statut),
|
_getStatutIcon(commande.statut),
|
||||||
size: 20,
|
size: 20,
|
||||||
color: commande.statut == StatutCommande.annulee
|
color:
|
||||||
|
commande.statut == StatutCommande.annulee
|
||||||
? Colors.red
|
? Colors.red
|
||||||
: Colors.blue.shade600,
|
: Colors.blue.shade600,
|
||||||
),
|
),
|
||||||
@ -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,7 +837,8 @@ 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 ==
|
||||||
|
StatutCommande.annulee
|
||||||
? Colors.red
|
? 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,
|
||||||
@ -982,10 +1009,13 @@ class _CommandeDetails extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
...details.map((detail) => TableRow(
|
...details.map((detail) => TableRow(
|
||||||
children: [
|
children: [
|
||||||
_buildTableCell(detail.produitNom ?? 'Produit inconnu'),
|
_buildTableCell(
|
||||||
|
detail.produitNom ?? 'Produit inconnu'),
|
||||||
_buildTableCell('${detail.quantite}'),
|
_buildTableCell('${detail.quantite}'),
|
||||||
_buildTableCell('${detail.prixUnitaire.toStringAsFixed(2)} DA'),
|
_buildTableCell(
|
||||||
_buildTableCell('${detail.sousTotal.toStringAsFixed(2)} DA'),
|
'${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',
|
||||||
|
|||||||
@ -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}');
|
|
||||||
|
|
||||||
// Récupérer les credentials
|
|
||||||
Map<String, dynamic>? userCredentials =
|
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,137 +109,189 @@ 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: SingleChildScrollView(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: MediaQuery.of(context).size.width * 0.5,
|
width: MediaQuery.of(context).size.width < 500
|
||||||
height: MediaQuery.of(context).size.height * 0.8,
|
? double.infinity
|
||||||
padding: const EdgeInsets.all(16.0),
|
: 400,
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 24.0, vertical: 32.0),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: cardColor.withOpacity(0.98),
|
||||||
shape: BoxShape.rectangle,
|
|
||||||
borderRadius: BorderRadius.circular(30.0),
|
borderRadius: BorderRadius.circular(30.0),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: primaryColor.withOpacity(0.2),
|
||||||
|
blurRadius: 16,
|
||||||
|
spreadRadius: 4,
|
||||||
|
offset: const Offset(0, 8),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Center(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
child: Column(
|
||||||
child: const Icon(
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 38,
|
||||||
|
backgroundColor: accentColor.withOpacity(0.15),
|
||||||
|
child: Icon(
|
||||||
Icons.lock_outline,
|
Icons.lock_outline,
|
||||||
size: 100.0,
|
color: accentColor,
|
||||||
color: Color.fromARGB(255, 4, 54, 95),
|
size: 50,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
Text(
|
||||||
|
'GUYCOM',
|
||||||
|
style: TextStyle(
|
||||||
|
color: primaryColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 28,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'Connectez-vous à votre compte',
|
||||||
|
style: TextStyle(
|
||||||
|
color: primaryColor.withOpacity(.8),
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _usernameController,
|
controller: _usernameController,
|
||||||
enabled: !_isLoading,
|
enabled: !_isLoading,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Username',
|
labelText: 'Nom d\'utilisateur',
|
||||||
prefixIcon: const Icon(Icons.person, color: Colors.blueAccent),
|
labelStyle: TextStyle(
|
||||||
|
color: primaryColor.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
prefixIcon: Icon(Icons.person, color: accentColor),
|
||||||
|
filled: true,
|
||||||
|
fillColor: accentColor.withOpacity(0.045),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(30.0),
|
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: 16.0),
|
const SizedBox(height: 18.0),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _passwordController,
|
controller: _passwordController,
|
||||||
enabled: !_isLoading,
|
enabled: !_isLoading,
|
||||||
|
obscureText: true,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Password',
|
labelText: 'Mot de passe',
|
||||||
prefixIcon: const Icon(Icons.lock, color: Colors.redAccent),
|
labelStyle: TextStyle(
|
||||||
|
color: primaryColor.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
prefixIcon: Icon(Icons.lock, color: accentColor),
|
||||||
|
filled: true,
|
||||||
|
fillColor: accentColor.withOpacity(0.045),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(30.0),
|
borderRadius: BorderRadius.circular(30.0),
|
||||||
),
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(30.0),
|
||||||
|
borderSide: BorderSide(color: accentColor, width: 2),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
obscureText: true,
|
|
||||||
onSubmitted: (_) => _login(),
|
onSubmitted: (_) => _login(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16.0),
|
if (_isErrorVisible) ...[
|
||||||
Visibility(
|
const SizedBox(height: 12.0),
|
||||||
visible: _isErrorVisible,
|
Text(
|
||||||
child: Text(
|
|
||||||
_errorMessage,
|
_errorMessage,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.red,
|
color: Colors.redAccent,
|
||||||
fontSize: 14,
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
const SizedBox(height: 16.0),
|
const SizedBox(height: 26.0),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: _isLoading ? null : _login,
|
onPressed: _isLoading ? null : _login,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFF0015B7),
|
backgroundColor: accentColor,
|
||||||
elevation: 5.0,
|
disabledBackgroundColor: accentColor.withOpacity(0.3),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
elevation: 7.0,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(30.0),
|
borderRadius: BorderRadius.circular(30.0),
|
||||||
),
|
),
|
||||||
minimumSize: const Size(double.infinity, 48),
|
minimumSize: const Size(double.infinity, 52),
|
||||||
),
|
),
|
||||||
child: _isLoading
|
child: _isLoading
|
||||||
? const SizedBox(
|
? const SizedBox(
|
||||||
height: 20,
|
height: 24,
|
||||||
width: 20,
|
width: 24,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
strokeWidth: 2,
|
strokeWidth: 2.5,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const Text(
|
: const Text(
|
||||||
'Se connecter',
|
'Se connecter',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 16,
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: .4,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Bouton de debug (à supprimer en production)
|
// Option debug, à enlever en prod
|
||||||
if (_isErrorVisible)
|
if (_isErrorVisible) ...[
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
try {
|
try {
|
||||||
final count = await AppDatabase.instance.getUserCount();
|
final count =
|
||||||
print('Debug: $count utilisateurs dans la base');
|
await AppDatabase.instance.getUserCount();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text('$count utilisateurs trouvés')),
|
SnackBar(
|
||||||
|
content: Text('$count utilisateurs trouvés')),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Debug error: $e');
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text('Erreur: $e')),
|
SnackBar(content: Text('Erreur: $e')),
|
||||||
);
|
);
|
||||||
@ -285,6 +300,8 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
child: const Text('Debug: Vérifier BDD'),
|
child: const Text('Debug: Vérifier BDD'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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;
|
||||||
@ -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;
|
||||||
});
|
});
|
||||||
@ -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,
|
||||||
@ -599,7 +603,6 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_quantites.clear();
|
_quantites.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Erreur',
|
'Erreur',
|
||||||
|
|||||||
190
lib/Views/pointage.dart
Normal file
190
lib/Views/pointage.dart
Normal 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',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
@ -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,7 +452,9 @@ 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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user