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