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:get/get.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:youmazgestion/Views/historique.dart';
|
import 'package:youmazgestion/Views/historique.dart';
|
||||||
|
import 'package:youmazgestion/Views/addProduct.dart';
|
||||||
import '../Views/addProduct.dart';
|
import 'package:youmazgestion/Views/bilanMois.dart';
|
||||||
import '../Views/bilanMois.dart';
|
import 'package:youmazgestion/Views/gestionProduct.dart';
|
||||||
import '../Views/gestionProduct.dart';
|
import 'package:youmazgestion/Views/gestionStock.dart';
|
||||||
import '../Views/gestionStock.dart';
|
import 'package:youmazgestion/Views/listUser.dart';
|
||||||
import '../Views/listUser.dart';
|
import 'package:youmazgestion/Views/loginPage.dart';
|
||||||
import '../Views/loginPage.dart';
|
import 'package:youmazgestion/Views/registrationPage.dart';
|
||||||
import '../Views/registrationPage.dart';
|
import 'package:youmazgestion/Views/gestionRole.dart';
|
||||||
import '../accueil.dart';
|
import 'package:youmazgestion/accueil.dart';
|
||||||
import '../controller/userController.dart';
|
import 'package:youmazgestion/controller/userController.dart';
|
||||||
|
|
||||||
class CustomDrawer extends StatelessWidget {
|
class CustomDrawer extends StatelessWidget {
|
||||||
final UserController userController = Get.find<UserController>();
|
final UserController userController = Get.find<UserController>();
|
||||||
Future<void> clearUserData() async {
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
Future<void> clearUserData() async {
|
||||||
await prefs.remove('username');
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.remove('role');
|
await prefs.remove('username');
|
||||||
}
|
await prefs.remove('role');
|
||||||
|
}
|
||||||
|
|
||||||
CustomDrawer({super.key});
|
CustomDrawer({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -30,25 +32,25 @@ Future<void> clearUserData() async {
|
|||||||
children: [
|
children: [
|
||||||
GetBuilder<UserController>(
|
GetBuilder<UserController>(
|
||||||
builder: (controller) => UserAccountsDrawerHeader(
|
builder: (controller) => UserAccountsDrawerHeader(
|
||||||
accountEmail: Text(controller.email),
|
accountEmail: Text(controller.email),
|
||||||
accountName: Text(controller.name),
|
accountName: Text(controller.name),
|
||||||
currentAccountPicture: const CircleAvatar(
|
currentAccountPicture: const CircleAvatar(
|
||||||
backgroundImage: AssetImage("assets/youmaz2.png"),
|
backgroundImage: AssetImage("assets/youmaz2.png"),
|
||||||
|
),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [Colors.white, Color.fromARGB(255, 4, 54, 95)],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
),
|
),
|
||||||
decoration: const BoxDecoration(
|
),
|
||||||
gradient: LinearGradient(
|
),
|
||||||
colors: [Colors.white, const Color.fromARGB(255, 4, 54, 95)],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.home),
|
leading: const Icon(Icons.home),
|
||||||
iconColor: Colors.lightBlueAccent,
|
iconColor: Colors.lightBlueAccent,
|
||||||
title: const Text("Accueil"),
|
title: const Text("Accueil"),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Action lorsque l'utilisateur clique sur "Accueil"
|
|
||||||
Get.to(const AccueilPage());
|
Get.to(const AccueilPage());
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -56,18 +58,20 @@ Future<void> clearUserData() async {
|
|||||||
leading: const Icon(Icons.person_add),
|
leading: const Icon(Icons.person_add),
|
||||||
iconColor: Colors.green,
|
iconColor: Colors.green,
|
||||||
title: const Text("Ajouter un utilisateur"),
|
title: const Text("Ajouter un utilisateur"),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
if (userController.role == "admin") {
|
bool hasPermission = await userController.hasAnyPermission(['create']);
|
||||||
|
if (hasPermission) {
|
||||||
Get.to(const RegistrationPage());
|
Get.to(const RegistrationPage());
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"Accés refusé",
|
"Accès refusé",
|
||||||
backgroundColor: Colors.red,
|
"Vous n'avez pas les droits pour ajouter un utilisateur",
|
||||||
colorText: Colors.white,
|
backgroundColor: Colors.red,
|
||||||
icon: const Icon(Icons.error),
|
colorText: Colors.white,
|
||||||
duration: const Duration(seconds: 3),
|
icon: const Icon(Icons.error),
|
||||||
snackPosition: SnackPosition.TOP,
|
duration: const Duration(seconds: 3),
|
||||||
"Vous n'avez pas les droits pour ajouter un utilisateur");
|
snackPosition: SnackPosition.TOP,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -75,19 +79,20 @@ Future<void> clearUserData() async {
|
|||||||
leading: const Icon(Icons.supervised_user_circle),
|
leading: const Icon(Icons.supervised_user_circle),
|
||||||
iconColor: const Color.fromARGB(255, 4, 54, 95),
|
iconColor: const Color.fromARGB(255, 4, 54, 95),
|
||||||
title: const Text("Modifier/Supprimer un utilisateur"),
|
title: const Text("Modifier/Supprimer un utilisateur"),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
// Action lorsque l'utilisateur clique sur "Modifier/Supprimer un utilisateur"
|
bool hasPermission = await userController.hasAnyPermission(['update', 'delete']);
|
||||||
if (userController.role == "admin") {
|
if (hasPermission) {
|
||||||
Get.to(const ListUserPage());
|
Get.to(const ListUserPage());
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"Accés refusé",
|
"Accès refusé",
|
||||||
backgroundColor: Colors.red,
|
"Vous n'avez pas les droits pour modifier/supprimer un utilisateur",
|
||||||
colorText: Colors.white,
|
backgroundColor: Colors.red,
|
||||||
icon: const Icon(Icons.error),
|
colorText: Colors.white,
|
||||||
duration: const Duration(seconds: 3),
|
icon: const Icon(Icons.error),
|
||||||
snackPosition: SnackPosition.TOP,
|
duration: const Duration(seconds: 3),
|
||||||
"Vous n'avez pas les droits pour modifier/supprimer un utilisateur");
|
snackPosition: SnackPosition.TOP,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -95,19 +100,20 @@ Future<void> clearUserData() async {
|
|||||||
leading: const Icon(Icons.add),
|
leading: const Icon(Icons.add),
|
||||||
iconColor: Colors.indigoAccent,
|
iconColor: Colors.indigoAccent,
|
||||||
title: const Text("Ajouter un produit"),
|
title: const Text("Ajouter un produit"),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
if (userController.role == "admin") {
|
bool hasPermission = await userController.hasAnyPermission(['create']);
|
||||||
// Action lorsque l'utilisateur clique sur "Ajouter un produit"
|
if (hasPermission) {
|
||||||
Get.to(const AddProductPage());
|
Get.to(const AddProductPage());
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"Accés refusé",
|
"Accès refusé",
|
||||||
backgroundColor: Colors.red,
|
"Vous n'avez pas les droits pour ajouter un produit",
|
||||||
colorText: Colors.white,
|
backgroundColor: Colors.red,
|
||||||
icon: const Icon(Icons.error),
|
colorText: Colors.white,
|
||||||
duration: const Duration(seconds: 3),
|
icon: const Icon(Icons.error),
|
||||||
snackPosition: SnackPosition.TOP,
|
duration: const Duration(seconds: 3),
|
||||||
"Vous n'avez pas les droits pour ajouter un produit");
|
snackPosition: SnackPosition.TOP,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -115,37 +121,60 @@ Future<void> clearUserData() async {
|
|||||||
leading: const Icon(Icons.edit),
|
leading: const Icon(Icons.edit),
|
||||||
iconColor: Colors.redAccent,
|
iconColor: Colors.redAccent,
|
||||||
title: const Text("Modifier/Supprimer un produit"),
|
title: const Text("Modifier/Supprimer un produit"),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
if (userController.role == "admin") {
|
bool hasPermission = await userController.hasAnyPermission(['update', 'delete']);
|
||||||
// Action lorsque l'utilisateur clique sur "Modifier/Supprimer un produit"
|
if (hasPermission) {
|
||||||
Get.to(GestionProduit());
|
Get.to(GestionProduit());
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"Accés refusé",
|
"Accès refusé",
|
||||||
backgroundColor: Colors.red,
|
"Vous n'avez pas les droits pour modifier/supprimer un produit",
|
||||||
colorText: Colors.white,
|
backgroundColor: Colors.red,
|
||||||
icon: const Icon(Icons.error),
|
colorText: Colors.white,
|
||||||
duration: const Duration(seconds: 3),
|
icon: const Icon(Icons.error),
|
||||||
snackPosition: SnackPosition.TOP,
|
duration: const Duration(seconds: 3),
|
||||||
"Vous n'avez pas les droits pour modifier/supprimer un produit");
|
snackPosition: SnackPosition.TOP,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.bar_chart),
|
leading: const Icon(Icons.bar_chart),
|
||||||
title: const Text("Bilan"),
|
title: const Text("Bilan"),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
if (userController.role == "admin") {
|
bool hasPermission = await userController.hasAnyPermission(['read']);
|
||||||
|
if (hasPermission) {
|
||||||
Get.to(const BilanMois());
|
Get.to(const BilanMois());
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"Accés refusé",
|
"Accès refusé",
|
||||||
backgroundColor: Colors.red,
|
"Vous n'avez pas les droits pour accéder au bilan",
|
||||||
colorText: Colors.white,
|
backgroundColor: Colors.red,
|
||||||
icon: const Icon(Icons.error_outline_outlined),
|
colorText: Colors.white,
|
||||||
duration: const Duration(seconds: 3),
|
icon: const Icon(Icons.error_outline_outlined),
|
||||||
snackPosition: SnackPosition.TOP,
|
duration: const Duration(seconds: 3),
|
||||||
"Vous n'avez pas les droits pour accéder au bilan");
|
snackPosition: SnackPosition.TOP,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.warning_amber),
|
||||||
|
title: const Text("Gérer les rôles"),
|
||||||
|
onTap: () async {
|
||||||
|
bool hasPermission = await userController.hasAnyPermission(['update', 'delete']);
|
||||||
|
if (hasPermission) {
|
||||||
|
Get.to(const HandleUserRole());
|
||||||
|
} else {
|
||||||
|
Get.snackbar(
|
||||||
|
"Accès refusé",
|
||||||
|
"Vous n'avez pas les droits pour gérer les rôles",
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
icon: const Icon(Icons.error_outline_outlined),
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
|
snackPosition: SnackPosition.TOP,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -153,19 +182,20 @@ Future<void> clearUserData() async {
|
|||||||
leading: const Icon(Icons.inventory),
|
leading: const Icon(Icons.inventory),
|
||||||
iconColor: Colors.blueAccent,
|
iconColor: Colors.blueAccent,
|
||||||
title: const Text("Gestion de stock"),
|
title: const Text("Gestion de stock"),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
if (userController.role == "admin") {
|
bool hasPermission = await userController.hasAnyPermission(['update']);
|
||||||
// Action lorsque l'utilisateur clique sur "Gestion de stock"
|
if (hasPermission) {
|
||||||
Get.to(const GestionStockPage());
|
Get.to(const GestionStockPage());
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"Accés refusé",
|
"Accès refusé",
|
||||||
backgroundColor: Colors.red,
|
"Vous n'avez pas les droits pour accéder à la gestion de stock",
|
||||||
colorText: Colors.white,
|
backgroundColor: Colors.red,
|
||||||
icon: const Icon(Icons.error),
|
colorText: Colors.white,
|
||||||
duration: const Duration(seconds: 3),
|
icon: const Icon(Icons.error),
|
||||||
snackPosition: SnackPosition.TOP,
|
duration: const Duration(seconds: 3),
|
||||||
"Vous n'avez pas les droits pour accéder à la gestion de stock");
|
snackPosition: SnackPosition.TOP,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -174,7 +204,6 @@ Future<void> clearUserData() async {
|
|||||||
iconColor: Colors.blue,
|
iconColor: Colors.blue,
|
||||||
title: const Text("Historique"),
|
title: const Text("Historique"),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Action lorsque l'utilisateur clique sur "Historique"
|
|
||||||
Get.to(HistoryPage());
|
Get.to(HistoryPage());
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -183,8 +212,6 @@ Future<void> clearUserData() async {
|
|||||||
iconColor: Colors.red,
|
iconColor: Colors.red,
|
||||||
title: const Text("Déconnexion"),
|
title: const Text("Déconnexion"),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Action lorsque l'utilisateur clique sur "Déconnexion"
|
|
||||||
// display confirmation dialog
|
|
||||||
Get.defaultDialog(
|
Get.defaultDialog(
|
||||||
title: "Déconnexion",
|
title: "Déconnexion",
|
||||||
content: const Text("Voulez-vous vraiment vous déconnecter ?"),
|
content: const Text("Voulez-vous vraiment vous déconnecter ?"),
|
||||||
@ -201,11 +228,11 @@ Future<void> clearUserData() async {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
Get.back();
|
Get.back();
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
16
lib/Models/Permission.dart
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 {
|
class Users {
|
||||||
int id;
|
int? id;
|
||||||
String name;
|
String name;
|
||||||
String lastName;
|
String lastName;
|
||||||
String email;
|
String email;
|
||||||
String password;
|
String password;
|
||||||
String username;
|
String username;
|
||||||
String role;
|
int roleId;
|
||||||
|
String? roleName; // Optionnel, rempli lors des requêtes avec JOIN
|
||||||
|
|
||||||
Users({
|
Users({
|
||||||
required this.id,
|
this.id,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.lastName,
|
required this.lastName,
|
||||||
required this.email,
|
required this.email,
|
||||||
required this.password,
|
required this.password,
|
||||||
required this.username,
|
required this.username,
|
||||||
required this.role,
|
required this.roleId,
|
||||||
|
this.roleName,
|
||||||
});
|
});
|
||||||
|
|
||||||
Map<String, dynamic> toMap() {
|
Map<String, dynamic> toMap() {
|
||||||
return {
|
return {
|
||||||
'id': id,
|
|
||||||
'name': name,
|
'name': name,
|
||||||
'lastName': lastName,
|
'lastname': lastName,
|
||||||
'email': email,
|
'email': email,
|
||||||
'password': password,
|
'password': password,
|
||||||
'username': username,
|
'username': username,
|
||||||
'role': role,
|
'role_id': roleId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMapWithId() {
|
||||||
|
final map = toMap();
|
||||||
|
if (id != null) map['id'] = id;
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
factory Users.fromMap(Map<String, dynamic> map) {
|
factory Users.fromMap(Map<String, dynamic> map) {
|
||||||
return Users(
|
return Users(
|
||||||
id: map['id'],
|
id: map['id'],
|
||||||
@ -37,8 +44,11 @@ class Users {
|
|||||||
email: map['email'],
|
email: map['email'],
|
||||||
password: map['password'],
|
password: map['password'],
|
||||||
username: map['username'],
|
username: map['username'],
|
||||||
role: map['role']
|
roleId: map['role_id'],
|
||||||
|
roleName: map['role_name'], // Depuis les requêtes avec JOIN
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Getter pour la compatibilité avec l'ancien code
|
||||||
|
String get role => roleName ?? '';
|
||||||
}
|
}
|
||||||
477
lib/Services/app_database.dart
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> {
|
class _AddProductPageState extends State<AddProductPage> {
|
||||||
// Controllers for text fields
|
|
||||||
final TextEditingController _nameController = TextEditingController();
|
final TextEditingController _nameController = TextEditingController();
|
||||||
final TextEditingController _priceController = TextEditingController();
|
final TextEditingController _priceController = TextEditingController();
|
||||||
final TextEditingController _imageController = TextEditingController();
|
final TextEditingController _imageController = TextEditingController();
|
||||||
final TextEditingController _descriptionController = TextEditingController();
|
final TextEditingController _descriptionController = TextEditingController();
|
||||||
|
|
||||||
String? _qrData; // Variable to store QR code data
|
final List<String> _categories = ['Sucré', 'Salé', 'Jus', 'Gateaux'];
|
||||||
final List<String> _categories = ['Sucré', 'Salé', 'Jus', 'Gateaux']; // List of product categories
|
String? _selectedCategory;
|
||||||
String? _selectedCategory; // Selected category
|
File? _pickedImage;
|
||||||
File? _pickedImage; // Variable to store the selected image file
|
String? _qrData;
|
||||||
late ProductDatabase _productDatabase; // Database instance
|
late ProductDatabase _productDatabase;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -43,52 +42,12 @@ class _AddProductPageState extends State<AddProductPage> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_nameController.removeListener(_updateQrData);
|
_nameController.removeListener(_updateQrData);
|
||||||
_nameController.dispose();
|
_nameController.dispose();
|
||||||
|
_priceController.dispose();
|
||||||
|
_imageController.dispose();
|
||||||
|
_descriptionController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to select an image from files or drop
|
|
||||||
void _selectImage() async {
|
|
||||||
final action = await showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: const Text('Sélectionner une image'),
|
|
||||||
content: const Text('Choisissez comment sélectionner une image'),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop('pick');
|
|
||||||
},
|
|
||||||
child: const Text('Choisir depuis les fichiers'),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop('drop');
|
|
||||||
},
|
|
||||||
child: const Text('Déposer une image'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (action == 'pick') {
|
|
||||||
final result = await FilePicker.platform.pickFiles(
|
|
||||||
type: FileType.image,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result != null) {
|
|
||||||
setState(() {
|
|
||||||
_pickedImage = File(result.files.single.path!);
|
|
||||||
_imageController.text = _pickedImage!.path;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (action == 'drop') {
|
|
||||||
// Code to handle image drop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to update QR data based on product name
|
|
||||||
void _updateQrData() {
|
void _updateQrData() {
|
||||||
if (_nameController.text.isNotEmpty) {
|
if (_nameController.text.isNotEmpty) {
|
||||||
final reference = 'PROD_PREVIEW_${_nameController.text}_${DateTime.now().millisecondsSinceEpoch}';
|
final reference = 'PROD_PREVIEW_${_nameController.text}_${DateTime.now().millisecondsSinceEpoch}';
|
||||||
@ -98,80 +57,82 @@ class _AddProductPageState extends State<AddProductPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to get the database location
|
Future<void> _selectImage() async {
|
||||||
Future<void> _getDatabaseLocation() async {
|
final result = await FilePicker.platform.pickFiles(type: FileType.image);
|
||||||
final directory = await getApplicationDocumentsDirectory();
|
if (result != null && result.files.single.path != null) {
|
||||||
final dbPath = directory.path;
|
setState(() {
|
||||||
print('Emplacement de la base de données : $dbPath');
|
_pickedImage = File(result.files.single.path!);
|
||||||
|
_imageController.text = _pickedImage!.path;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to generate and save QR code
|
|
||||||
Future<String> _generateAndSaveQRCode(String reference) async {
|
Future<String> _generateAndSaveQRCode(String reference) async {
|
||||||
final qrValidationResult = QrValidator.validate(
|
final validation = QrValidator.validate(
|
||||||
data: 'https://tonsite.com/$reference',
|
data: 'https://tonsite.com/$reference',
|
||||||
version: QrVersions.auto,
|
version: QrVersions.auto,
|
||||||
errorCorrectionLevel: QrErrorCorrectLevel.L,
|
errorCorrectionLevel: QrErrorCorrectLevel.L,
|
||||||
);
|
);
|
||||||
final qrCode = qrValidationResult.qrCode;
|
|
||||||
|
final qrCode = validation.qrCode!;
|
||||||
final painter = QrPainter.withQr(
|
final painter = QrPainter.withQr(
|
||||||
qr: qrCode!,
|
qr: qrCode,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
emptyColor: Colors.white,
|
emptyColor: Colors.white,
|
||||||
gapless: true,
|
gapless: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
final tempDir = await getApplicationDocumentsDirectory();
|
final directory = await getApplicationDocumentsDirectory();
|
||||||
final file = File('${tempDir.path}/$reference.png');
|
final path = '${directory.path}/$reference.png';
|
||||||
final picData = await painter.toImageData(2048, format: ImageByteFormat.png);
|
final picData = await painter.toImageData(2048, format: ImageByteFormat.png);
|
||||||
await file.writeAsBytes(picData!.buffer.asUint8List());
|
await File(path).writeAsBytes(picData!.buffer.asUint8List());
|
||||||
|
|
||||||
return file.path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to add a product to the database
|
|
||||||
void _addProduct() async {
|
void _addProduct() async {
|
||||||
final name = _nameController.text;
|
final name = _nameController.text.trim();
|
||||||
final price = double.tryParse(_priceController.text) ?? 0.0;
|
final price = double.tryParse(_priceController.text.trim()) ?? 0.0;
|
||||||
final image = _imageController.text;
|
final image = _imageController.text.trim();
|
||||||
final category = _selectedCategory;
|
final category = _selectedCategory;
|
||||||
final description = _descriptionController.text;
|
final description = _descriptionController.text.trim();
|
||||||
|
|
||||||
if (name.isNotEmpty && price > 0 && image.isNotEmpty && category != null) {
|
if (name.isEmpty || price <= 0 || image.isEmpty || category == null) {
|
||||||
final reference = 'PROD_${DateTime.now().millisecondsSinceEpoch}';
|
Get.snackbar('Erreur', 'Veuillez remplir tous les champs requis');
|
||||||
final qrPath = await _generateAndSaveQRCode(reference);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final product = Product(
|
final reference = 'PROD_${DateTime.now().millisecondsSinceEpoch}';
|
||||||
name: name,
|
final qrPath = await _generateAndSaveQRCode(reference);
|
||||||
price: price,
|
|
||||||
image: image,
|
|
||||||
category: category,
|
|
||||||
description: description,
|
|
||||||
qrCode: qrPath,
|
|
||||||
reference: reference,
|
|
||||||
);
|
|
||||||
|
|
||||||
_productDatabase.createProduct(product).then((_) {
|
final product = Product(
|
||||||
Get.snackbar('Succès', 'Produit ajouté avec succès');
|
name: name,
|
||||||
setState(() {
|
price: price,
|
||||||
_nameController.clear();
|
image: image,
|
||||||
_priceController.clear();
|
category: category,
|
||||||
_imageController.clear();
|
description: description,
|
||||||
_descriptionController.clear();
|
qrCode: qrPath,
|
||||||
_selectedCategory = null;
|
reference: reference,
|
||||||
_pickedImage = null;
|
);
|
||||||
});
|
|
||||||
}).catchError((error) {
|
try {
|
||||||
Get.snackbar('Erreur', 'Impossible d\'ajouter le produit : $error');
|
await _productDatabase.createProduct(product);
|
||||||
|
Get.snackbar('Succès', 'Produit ajouté avec succès');
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_nameController.clear();
|
||||||
|
_priceController.clear();
|
||||||
|
_imageController.clear();
|
||||||
|
_descriptionController.clear();
|
||||||
|
_selectedCategory = null;
|
||||||
|
_pickedImage = null;
|
||||||
|
_qrData = null;
|
||||||
});
|
});
|
||||||
} else {
|
} catch (e) {
|
||||||
Get.snackbar(
|
Get.snackbar('Erreur', 'Ajout du produit échoué : $e');
|
||||||
'Saisie invalide',
|
|
||||||
'Veuillez entrer tous les champs requis.',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to display the selected image
|
|
||||||
Widget _displayImage() {
|
Widget _displayImage() {
|
||||||
if (_pickedImage != null) {
|
if (_pickedImage != null) {
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
@ -184,33 +145,14 @@ class _AddProductPageState extends State<AddProductPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Stack(
|
return Container(
|
||||||
alignment: Alignment.center,
|
width: 100,
|
||||||
children: [
|
height: 100,
|
||||||
Container(
|
decoration: BoxDecoration(
|
||||||
width: 100,
|
color: Colors.grey[200],
|
||||||
height: 100,
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
decoration: BoxDecoration(
|
),
|
||||||
color: Colors.grey[200],
|
child: const Icon(Icons.image, size: 48, color: Colors.grey),
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
Icons.image,
|
|
||||||
size: 48,
|
|
||||||
color: Colors.grey[400],
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
bottom: 4,
|
|
||||||
child: Text(
|
|
||||||
'Aucune image',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.grey[400],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -219,35 +161,23 @@ class _AddProductPageState extends State<AddProductPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: const CustomAppBar(title: 'Ajouter un produit'),
|
appBar: const CustomAppBar(title: 'Ajouter un produit'),
|
||||||
drawer: CustomDrawer(),
|
drawer: CustomDrawer(),
|
||||||
body: Padding(
|
body: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text('Ajouter un produit', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
|
||||||
'Ajouter un produit',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _nameController,
|
controller: _nameController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(labelText: 'Nom du produit', border: OutlineInputBorder()),
|
||||||
labelText: 'Nom du produit',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _priceController,
|
controller: _priceController,
|
||||||
decoration: const InputDecoration(
|
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
||||||
labelText: 'Prix',
|
decoration: const InputDecoration(labelText: 'Prix', border: OutlineInputBorder()),
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Row(
|
Row(
|
||||||
@ -256,17 +186,12 @@ class _AddProductPageState extends State<AddProductPage> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _imageController,
|
controller: _imageController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(labelText: 'Chemin de l\'image', border: OutlineInputBorder()),
|
||||||
labelText: 'Image',
|
readOnly: true,
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
ElevatedButton(
|
ElevatedButton(onPressed: _selectImage, child: const Text('Sélectionner')),
|
||||||
onPressed: _selectImage,
|
|
||||||
child: const Text('Sélectionner une image'),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@ -274,48 +199,37 @@ class _AddProductPageState extends State<AddProductPage> {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
DropdownButtonFormField<String>(
|
DropdownButtonFormField<String>(
|
||||||
value: _selectedCategory,
|
value: _selectedCategory,
|
||||||
onChanged: (newValue) {
|
items: _categories
|
||||||
setState(() {
|
.map((c) => DropdownMenuItem(value: c, child: Text(c)))
|
||||||
_selectedCategory = newValue;
|
.toList(),
|
||||||
});
|
onChanged: (value) => setState(() => _selectedCategory = value),
|
||||||
},
|
decoration: const InputDecoration(labelText: 'Catégorie', border: OutlineInputBorder()),
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Catégorie',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
items: _categories.map((category) {
|
|
||||||
return DropdownMenuItem<String>(
|
|
||||||
value: category,
|
|
||||||
child: Text(category),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
if (_qrData != null)
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
const Text('Aperçu du QR Code :'),
|
|
||||||
QrImageView(
|
|
||||||
data: _qrData!,
|
|
||||||
version: QrVersions.auto,
|
|
||||||
size: 120.0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextField(
|
TextField(
|
||||||
controller: _descriptionController,
|
controller: _descriptionController,
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Description',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
|
decoration: const InputDecoration(labelText: 'Description', border: OutlineInputBorder()),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ElevatedButton(
|
if (_qrData != null) ...[
|
||||||
onPressed: _addProduct,
|
const Text('Aperçu du QR Code :'),
|
||||||
child: const Text('Ajouter le produit'),
|
const SizedBox(height: 8),
|
||||||
|
Center(
|
||||||
|
child: QrImageView(
|
||||||
|
data: _qrData!,
|
||||||
|
version: QrVersions.auto,
|
||||||
|
size: 120,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: _addProduct,
|
||||||
|
child: const Text('Ajouter le produit'),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:youmazgestion/Models/users.dart';
|
import 'package:youmazgestion/Models/users.dart';
|
||||||
import '../Services/authDatabase.dart';
|
import 'package:youmazgestion/Models/role.dart';
|
||||||
|
import '../Services/app_database.dart';
|
||||||
|
|
||||||
class EditUserPage extends StatefulWidget {
|
class EditUserPage extends StatefulWidget {
|
||||||
final Users user;
|
final Users user;
|
||||||
@ -17,7 +18,11 @@ class _EditUserPageState extends State<EditUserPage> {
|
|||||||
late TextEditingController _emailController;
|
late TextEditingController _emailController;
|
||||||
late TextEditingController _usernameController;
|
late TextEditingController _usernameController;
|
||||||
late TextEditingController _passwordController;
|
late TextEditingController _passwordController;
|
||||||
String _selectedRole = '';
|
|
||||||
|
List<Role> _roles = [];
|
||||||
|
Role? _selectedRole;
|
||||||
|
bool _isLoading = false;
|
||||||
|
bool _isLoadingRoles = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -26,9 +31,31 @@ class _EditUserPageState extends State<EditUserPage> {
|
|||||||
_lastNameController = TextEditingController(text: widget.user.lastName);
|
_lastNameController = TextEditingController(text: widget.user.lastName);
|
||||||
_emailController = TextEditingController(text: widget.user.email);
|
_emailController = TextEditingController(text: widget.user.email);
|
||||||
_usernameController = TextEditingController(text: widget.user.username);
|
_usernameController = TextEditingController(text: widget.user.username);
|
||||||
_passwordController =
|
_passwordController = TextEditingController();
|
||||||
TextEditingController(); // Leave password field empty initially
|
|
||||||
_selectedRole = widget.user.role;
|
_loadRoles();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadRoles() async {
|
||||||
|
try {
|
||||||
|
final roles = await AppDatabase.instance.getRoles();
|
||||||
|
final currentRole = roles.firstWhere(
|
||||||
|
(r) => r.id == widget.user.roleId,
|
||||||
|
orElse: () => Role(id: widget.user.roleId, designation: widget.user.roleName ?? 'Inconnu'),
|
||||||
|
);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_roles = roles;
|
||||||
|
_selectedRole = currentRole;
|
||||||
|
_isLoadingRoles = false;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print('Erreur lors du chargement des rôles: $e');
|
||||||
|
setState(() {
|
||||||
|
_isLoadingRoles = false;
|
||||||
|
});
|
||||||
|
_showErrorDialog('Erreur', 'Impossible de charger les rôles.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -41,144 +68,213 @@ class _EditUserPageState extends State<EditUserPage> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateUser() {
|
bool _validateFields() {
|
||||||
final String name = _nameController.text;
|
if (_nameController.text.trim().isEmpty ||
|
||||||
final String lastName = _lastNameController.text;
|
_lastNameController.text.trim().isEmpty ||
|
||||||
final String email = _emailController.text;
|
_emailController.text.trim().isEmpty ||
|
||||||
final String username = _usernameController.text;
|
_usernameController.text.trim().isEmpty ||
|
||||||
final String password =
|
_selectedRole == null) {
|
||||||
_passwordController.text; // Get the entered password
|
_showErrorDialog('Champs manquants', 'Veuillez remplir tous les champs requis.');
|
||||||
final String role = _selectedRole;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
final Users updatedUser = Users(
|
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$')
|
||||||
id: widget.user.id,
|
.hasMatch(_emailController.text.trim())) {
|
||||||
name: name,
|
_showErrorDialog('Email invalide', 'Veuillez saisir un email valide.');
|
||||||
lastName: lastName,
|
return false;
|
||||||
email: email,
|
}
|
||||||
password: password.isNotEmpty
|
|
||||||
? password
|
|
||||||
: widget.user
|
|
||||||
.password, // Use entered password if not empty, otherwise keep the existing password
|
|
||||||
username: username,
|
|
||||||
role: role,
|
|
||||||
);
|
|
||||||
|
|
||||||
AuthDatabase.instance.updateUser(updatedUser).then((value) {
|
if (_passwordController.text.isNotEmpty &&
|
||||||
// User update successful
|
_passwordController.text.length < 6) {
|
||||||
showDialog(
|
_showErrorDialog('Mot de passe trop court', 'Minimum 6 caractères.');
|
||||||
context: context,
|
return false;
|
||||||
builder: (BuildContext context) {
|
}
|
||||||
return AlertDialog(
|
|
||||||
title: const Text('Update Successful'),
|
return true;
|
||||||
content: const Text('User information has been updated.'),
|
}
|
||||||
actions: <Widget>[
|
|
||||||
ElevatedButton(
|
Future<void> _updateUser() async {
|
||||||
onPressed: () {
|
if (!_validateFields() || _isLoading) return;
|
||||||
Navigator.of(context).pop();
|
|
||||||
Navigator.pop(context,
|
setState(() {
|
||||||
true); // Return true to indicate successful update
|
_isLoading = true;
|
||||||
},
|
|
||||||
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'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final updatedUser = Users(
|
||||||
|
id: widget.user.id,
|
||||||
|
name: _nameController.text.trim(),
|
||||||
|
lastName: _lastNameController.text.trim(),
|
||||||
|
email: _emailController.text.trim(),
|
||||||
|
username: _usernameController.text.trim(),
|
||||||
|
password: _passwordController.text.isNotEmpty
|
||||||
|
? _passwordController.text
|
||||||
|
: widget.user.password,
|
||||||
|
roleId: _selectedRole!.id!,
|
||||||
|
roleName: _selectedRole!.designation,
|
||||||
|
);
|
||||||
|
|
||||||
|
await AppDatabase.instance.updateUser(updatedUser);
|
||||||
|
if (mounted) _showSuccessDialog();
|
||||||
|
} catch (e) {
|
||||||
|
print('Erreur de mise à jour: $e');
|
||||||
|
if (mounted) {
|
||||||
|
_showErrorDialog('Échec', 'Une erreur est survenue lors de la mise à jour.');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showSuccessDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Mise à jour réussie'),
|
||||||
|
content: const Text('Les informations de l\'utilisateur ont été mises à jour.'),
|
||||||
|
actions: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('OK'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showErrorDialog(String title, String message) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text(title),
|
||||||
|
content: Text(message),
|
||||||
|
actions: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('OK'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Edit User'),
|
title: const Text('Modifier Utilisateur', style: TextStyle(color: Colors.white)),
|
||||||
|
backgroundColor: const Color.fromARGB(255, 4, 54, 95),
|
||||||
|
iconTheme: const IconThemeData(color: Colors.white),
|
||||||
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: _isLoadingRoles
|
||||||
padding: const EdgeInsets.all(16.0),
|
? const Center(child: CircularProgressIndicator())
|
||||||
child: SingleChildScrollView(
|
: SingleChildScrollView(
|
||||||
child: Column(
|
padding: const EdgeInsets.all(16.0),
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
child: Card(
|
||||||
children: [
|
elevation: 4,
|
||||||
TextField(
|
child: Padding(
|
||||||
controller: _nameController,
|
padding: const EdgeInsets.all(16.0),
|
||||||
decoration: const InputDecoration(
|
child: Column(
|
||||||
labelText: 'First Name',
|
children: [
|
||||||
|
const Icon(Icons.edit, size: 64, color: Colors.blue),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_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: 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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: _isLoading
|
||||||
|
? const CircularProgressIndicator(color: Colors.white)
|
||||||
|
: const Text('Mettre à jour', style: TextStyle(color: Colors.white, fontSize: 16)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16.0),
|
),
|
||||||
TextField(
|
);
|
||||||
controller: _lastNameController,
|
}
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Last Name',
|
Widget _buildTextField(
|
||||||
),
|
TextEditingController controller,
|
||||||
),
|
String label,
|
||||||
const SizedBox(height: 16.0),
|
IconData icon, {
|
||||||
TextField(
|
TextInputType keyboardType = TextInputType.text,
|
||||||
controller: _emailController,
|
bool obscureText = false,
|
||||||
decoration: const InputDecoration(
|
}) {
|
||||||
labelText: 'Email',
|
return TextField(
|
||||||
),
|
controller: controller,
|
||||||
keyboardType: TextInputType.emailAddress,
|
decoration: InputDecoration(
|
||||||
),
|
labelText: label,
|
||||||
const SizedBox(height: 16.0),
|
prefixIcon: Icon(icon),
|
||||||
TextField(
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
controller: _usernameController,
|
),
|
||||||
decoration: const InputDecoration(
|
keyboardType: keyboardType,
|
||||||
labelText: 'Username',
|
obscureText: obscureText,
|
||||||
),
|
);
|
||||||
),
|
}
|
||||||
const SizedBox(height: 16.0),
|
|
||||||
TextField(
|
Widget _buildDropdown() {
|
||||||
controller: _passwordController,
|
return Container(
|
||||||
decoration: const InputDecoration(
|
width: double.infinity,
|
||||||
labelText: 'Password',
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
obscureText: true,
|
border: Border.all(color: Colors.grey),
|
||||||
),
|
borderRadius: BorderRadius.circular(8),
|
||||||
const SizedBox(height: 16.0),
|
),
|
||||||
DropdownButton<String>(
|
child: DropdownButtonHideUnderline(
|
||||||
value: _selectedRole,
|
child: DropdownButton<Role>(
|
||||||
onChanged: (String? newValue) {
|
value: _selectedRole,
|
||||||
|
isExpanded: true,
|
||||||
|
hint: const Text('Sélectionner un rôle'),
|
||||||
|
onChanged: _isLoading
|
||||||
|
? null
|
||||||
|
: (Role? newValue) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedRole = newValue!;
|
_selectedRole = newValue;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
items: <String>['admin', 'user']
|
items: _roles.map((role) {
|
||||||
.map<DropdownMenuItem<String>>((String value) {
|
return DropdownMenuItem<Role>(
|
||||||
return DropdownMenuItem<String>(
|
value: role,
|
||||||
value: value,
|
child: Row(
|
||||||
child: Text(value),
|
children: [
|
||||||
);
|
const Icon(Icons.badge, size: 20),
|
||||||
}).toList(),
|
const SizedBox(width: 8),
|
||||||
|
Text(role.designation),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16.0),
|
);
|
||||||
ElevatedButton(
|
}).toList(),
|
||||||
onPressed: _updateUser,
|
|
||||||
child: const Text('Update'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import 'dart:io';
|
|||||||
class GestionProduit extends StatelessWidget {
|
class GestionProduit extends StatelessWidget {
|
||||||
final ProductDatabase _productDatabase = ProductDatabase.instance;
|
final ProductDatabase _productDatabase = ProductDatabase.instance;
|
||||||
|
|
||||||
const GestionProduit({super.key});
|
GestionProduit({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
147
lib/Views/gestionRole.dart
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:get/get.dart';
|
||||||
import 'package:youmazgestion/Models/users.dart';
|
import 'package:youmazgestion/Models/users.dart';
|
||||||
import '../Components/app_bar.dart';
|
import '../Components/app_bar.dart';
|
||||||
import '../Services/authDatabase.dart';
|
import '../Services/app_database.dart';
|
||||||
import 'editUser.dart';
|
import 'editUser.dart';
|
||||||
|
|
||||||
class ListUserPage extends StatefulWidget {
|
class ListUserPage extends StatefulWidget {
|
||||||
@ -23,7 +23,7 @@ class _ListUserPageState extends State<ListUserPage> {
|
|||||||
|
|
||||||
Future<void> getUsersFromDatabase() async {
|
Future<void> getUsersFromDatabase() async {
|
||||||
try {
|
try {
|
||||||
List<Users> users = await AuthDatabase.instance.getAllUsers();
|
List<Users> users = await AppDatabase.instance.getAllUsers();
|
||||||
setState(() {
|
setState(() {
|
||||||
userList = users;
|
userList = users;
|
||||||
});
|
});
|
||||||
@ -89,8 +89,8 @@ class _ListUserPageState extends State<ListUserPage> {
|
|||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await AuthDatabase.instance
|
await AppDatabase.instance
|
||||||
.deleteUser(user.id);
|
.deleteUser(user.id!);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
setState(() {
|
setState(() {
|
||||||
userList.removeAt(index);
|
userList.removeAt(index);
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:youmazgestion/Views/particles.dart' show ParticleBackground;
|
import 'package:youmazgestion/Views/particles.dart' show ParticleBackground;
|
||||||
import 'package:youmazgestion/accueil.dart';
|
import 'package:youmazgestion/accueil.dart';
|
||||||
import 'package:youmazgestion/Services/authDatabase.dart';
|
import 'package:youmazgestion/Services/app_database.dart';
|
||||||
|
|
||||||
import '../Models/users.dart';
|
import '../Models/users.dart';
|
||||||
import '../controller/userController.dart';
|
import '../controller/userController.dart';
|
||||||
@ -20,6 +20,8 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
late TextEditingController _passwordController;
|
late TextEditingController _passwordController;
|
||||||
final UserController userController = Get.put(UserController());
|
final UserController userController = Get.put(UserController());
|
||||||
bool _isErrorVisible = false;
|
bool _isErrorVisible = false;
|
||||||
|
bool _isLoading = false;
|
||||||
|
String _errorMessage = 'Nom d\'utilisateur ou mot de passe invalide';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -30,13 +32,27 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void checkUserCount() async {
|
void checkUserCount() async {
|
||||||
final userCount = await AuthDatabase.instance.getUserCount();
|
try {
|
||||||
if (userCount == 0) {
|
final userCount = await AppDatabase.instance.getUserCount();
|
||||||
// No user found, redirect to home page
|
print('Nombre d\'utilisateurs trouvés: $userCount'); // Debug
|
||||||
Navigator.pushReplacement(
|
|
||||||
context,
|
// Commentez cette partie pour permettre le login même sans utilisateurs
|
||||||
MaterialPageRoute(builder: (context) => const AccueilPage()),
|
/*
|
||||||
);
|
if (userCount == 0) {
|
||||||
|
if (mounted) {
|
||||||
|
Navigator.pushReplacement(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) => const AccueilPage()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
} catch (error) {
|
||||||
|
print('Erreur lors de la vérification du nombre d\'utilisateurs: $error');
|
||||||
|
setState(() {
|
||||||
|
_errorMessage = 'Erreur de connexion à la base de données';
|
||||||
|
_isErrorVisible = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,47 +62,111 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
_passwordController.dispose();
|
_passwordController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
Future<void> saveUser(String? username,String? role)async{
|
|
||||||
|
Future<void> saveUser(String username, String role, int userId) async {
|
||||||
|
try {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setString('username', username!);
|
await prefs.setString('username', username);
|
||||||
await prefs.setString('role', role!);
|
await prefs.setString('role', role);
|
||||||
}
|
await prefs.setInt('user_id', userId);
|
||||||
|
print('Utilisateur sauvegardé: $username, rôle: $role, id: $userId');
|
||||||
|
} catch (error) {
|
||||||
|
print('Erreur lors de la sauvegarde: $error');
|
||||||
|
throw Exception('Erreur lors de la sauvegarde des données utilisateur');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _login() async {
|
void _login() async {
|
||||||
final String username = _usernameController.text;
|
if (_isLoading) return;
|
||||||
final String password = _passwordController.text;
|
|
||||||
print(username);
|
final String username = _usernameController.text.trim();
|
||||||
print(password);
|
final String password = _passwordController.text.trim();
|
||||||
|
|
||||||
|
// Validation basique
|
||||||
|
if (username.isEmpty || password.isEmpty) {
|
||||||
|
setState(() {
|
||||||
|
_errorMessage = 'Veuillez saisir le nom d\'utilisateur et le mot de passe';
|
||||||
|
_isErrorVisible = true;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isLoading = true;
|
||||||
|
_isErrorVisible = false;
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
bool isValidUser =
|
print('Tentative de connexion pour: $username');
|
||||||
await AuthDatabase.instance.verifyUser(username, password);
|
|
||||||
Users user = await AuthDatabase.instance.getUser(username);
|
|
||||||
|
|
||||||
Map<String, String>? getUserCredentials = await AuthDatabase.instance.getUserCredentials(username,password);
|
// Vérification de la connexion à la base de données
|
||||||
print(isValidUser);
|
final dbInstance = AppDatabase.instance;
|
||||||
|
|
||||||
|
// Test de connexion à la base
|
||||||
|
try {
|
||||||
|
final userCount = await dbInstance.getUserCount();
|
||||||
|
print('Base de données accessible, $userCount utilisateurs trouvés');
|
||||||
|
} catch (dbError) {
|
||||||
|
throw Exception('Impossible d\'accéder à la base de données: $dbError');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier les identifiants
|
||||||
|
bool isValidUser = await dbInstance.verifyUser(username, password);
|
||||||
|
print('Résultat de la vérification: $isValidUser');
|
||||||
|
|
||||||
if (isValidUser) {
|
if (isValidUser) {
|
||||||
print('User is valid');
|
// Récupérer les informations complètes de l'utilisateur
|
||||||
print(user);
|
Users user = await dbInstance.getUser(username);
|
||||||
userController.setUser(user);
|
print('Utilisateur récupéré: ${user.username}');
|
||||||
|
|
||||||
setState(() {
|
// Récupérer les credentials
|
||||||
_isErrorVisible = false;
|
Map<String, dynamic>? userCredentials =
|
||||||
});
|
await dbInstance.getUserCredentials(username, password);
|
||||||
Navigator.pushReplacement(
|
|
||||||
context,
|
if (userCredentials != null) {
|
||||||
MaterialPageRoute(builder: (context) => const AccueilPage()),
|
print('Connexion réussie pour: ${user.username}');
|
||||||
);
|
print('Rôle: ${userCredentials['role']}');
|
||||||
saveUser(getUserCredentials?['username'], getUserCredentials?['role']);
|
print('ID: ${userCredentials['id']}');
|
||||||
|
|
||||||
|
// Sauvegarder dans le contrôleur
|
||||||
|
userController.setUser(user);
|
||||||
|
|
||||||
|
// Sauvegarder dans SharedPreferences
|
||||||
|
await saveUser(
|
||||||
|
userCredentials['username'] as String,
|
||||||
|
userCredentials['role'] as String,
|
||||||
|
userCredentials['id'] as int,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
if (mounted) {
|
||||||
|
Navigator.pushReplacement(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) => const AccueilPage()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception('Erreur lors de la récupération des credentials');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
print('Identifiants invalides pour: $username');
|
||||||
setState(() {
|
setState(() {
|
||||||
|
_errorMessage = 'Nom d\'utilisateur ou mot de passe invalide';
|
||||||
_isErrorVisible = true;
|
_isErrorVisible = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
print(error);
|
print('Erreur lors de la connexion: $error');
|
||||||
setState(() {
|
setState(() {
|
||||||
|
_errorMessage = 'Erreur de connexion: ${error.toString()}';
|
||||||
_isErrorVisible = true;
|
_isErrorVisible = true;
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,8 +176,8 @@ Future<void> saveUser(String? username,String? role)async{
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Login',
|
'Login',
|
||||||
style: TextStyle(color:Colors.white),
|
style: TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
backgroundColor: const Color.fromARGB(255, 4, 54, 95),
|
backgroundColor: const Color.fromARGB(255, 4, 54, 95),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
@ -125,10 +205,10 @@ Future<void> saveUser(String? username,String? role)async{
|
|||||||
),
|
),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _usernameController,
|
controller: _usernameController,
|
||||||
|
enabled: !_isLoading,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Username',
|
labelText: 'Username',
|
||||||
prefixIcon:
|
prefixIcon: const Icon(Icons.person, color: Colors.blueAccent),
|
||||||
const Icon(Icons.person, color: Colors.blueAccent),
|
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(30.0),
|
borderRadius: BorderRadius.circular(30.0),
|
||||||
),
|
),
|
||||||
@ -137,6 +217,7 @@ Future<void> saveUser(String? username,String? role)async{
|
|||||||
const SizedBox(height: 16.0),
|
const SizedBox(height: 16.0),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _passwordController,
|
controller: _passwordController,
|
||||||
|
enabled: !_isLoading,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Password',
|
labelText: 'Password',
|
||||||
prefixIcon: const Icon(Icons.lock, color: Colors.redAccent),
|
prefixIcon: const Icon(Icons.lock, color: Colors.redAccent),
|
||||||
@ -145,34 +226,67 @@ Future<void> saveUser(String? username,String? role)async{
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
|
onSubmitted: (_) => _login(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16.0),
|
const SizedBox(height: 16.0),
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: _isErrorVisible,
|
visible: _isErrorVisible,
|
||||||
child: const Text(
|
child: Text(
|
||||||
'Invalid username or password',
|
_errorMessage,
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16.0),
|
const SizedBox(height: 16.0),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: _login,
|
onPressed: _isLoading ? null : _login,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFF0015B7), // Nouvelle couleur
|
backgroundColor: const Color(0xFF0015B7),
|
||||||
elevation: 5.0,
|
elevation: 5.0,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(30.0),
|
borderRadius: BorderRadius.circular(30.0),
|
||||||
),
|
),
|
||||||
|
minimumSize: const Size(double.infinity, 48),
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: _isLoading
|
||||||
'Login',
|
? const SizedBox(
|
||||||
style: TextStyle(
|
height: 20,
|
||||||
color: Colors.white,
|
width: 20,
|
||||||
),
|
child: CircularProgressIndicator(
|
||||||
),
|
color: Colors.white,
|
||||||
|
strokeWidth: 2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Text(
|
||||||
|
'Se connecter',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
// Bouton de debug (à supprimer en production)
|
||||||
|
if (_isErrorVisible)
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
try {
|
||||||
|
final count = await AppDatabase.instance.getUserCount();
|
||||||
|
print('Debug: $count utilisateurs dans la base');
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('$count utilisateurs trouvés')),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Debug error: $e');
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Erreur: $e')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('Debug: Vérifier BDD'),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -180,4 +294,4 @@ Future<void> saveUser(String? username,String? role)async{
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
449
lib/Views/produitsCard.dart
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:flutter/material.dart';
|
||||||
import 'package:youmazgestion/Models/users.dart';
|
import 'package:youmazgestion/Models/users.dart';
|
||||||
|
import 'package:youmazgestion/Models/role.dart';
|
||||||
import 'package:youmazgestion/accueil.dart';
|
import 'package:youmazgestion/accueil.dart';
|
||||||
|
|
||||||
import '../Services/authDatabase.dart';
|
import '../Services/app_database.dart'; // Changé de authDatabase.dart
|
||||||
|
|
||||||
class RegistrationPage extends StatefulWidget {
|
class RegistrationPage extends StatefulWidget {
|
||||||
const RegistrationPage({super.key});
|
const RegistrationPage({super.key});
|
||||||
@ -17,7 +18,11 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
late TextEditingController _emailController;
|
late TextEditingController _emailController;
|
||||||
late TextEditingController _usernameController;
|
late TextEditingController _usernameController;
|
||||||
late TextEditingController _passwordController;
|
late TextEditingController _passwordController;
|
||||||
String _selectedRole = 'user'; // Default role is 'user'
|
|
||||||
|
List<Role> _availableRoles = [];
|
||||||
|
Role? _selectedRole;
|
||||||
|
bool _isLoading = false;
|
||||||
|
bool _isLoadingRoles = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -27,7 +32,59 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
_emailController = TextEditingController();
|
_emailController = TextEditingController();
|
||||||
_usernameController = TextEditingController();
|
_usernameController = TextEditingController();
|
||||||
_passwordController = TextEditingController();
|
_passwordController = TextEditingController();
|
||||||
AuthDatabase.instance.initDatabase();
|
|
||||||
|
_initializeDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initializeDatabase() async {
|
||||||
|
try {
|
||||||
|
await AppDatabase.instance.initDatabase();
|
||||||
|
await _loadRoles();
|
||||||
|
} catch (error) {
|
||||||
|
print('Erreur lors de l\'initialisation: $error');
|
||||||
|
if (mounted) {
|
||||||
|
_showErrorDialog('Erreur d\'initialisation',
|
||||||
|
'Impossible d\'initialiser l\'application. Veuillez redémarrer.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadRoles() async {
|
||||||
|
try {
|
||||||
|
final roles = await AppDatabase.instance.getRoles();
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_availableRoles = roles;
|
||||||
|
_selectedRole = roles.isNotEmpty ? roles.first : null;
|
||||||
|
_isLoadingRoles = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si aucun rôle n'existe, créer des rôles par défaut
|
||||||
|
if (roles.isEmpty) {
|
||||||
|
await _createDefaultRoles();
|
||||||
|
await _loadRoles(); // Recharger après création
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
print('Erreur lors du chargement des rôles: $error');
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isLoadingRoles = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _createDefaultRoles() async {
|
||||||
|
try {
|
||||||
|
await AppDatabase.instance.createRole(Role(designation: 'Admin'));
|
||||||
|
await AppDatabase.instance.createRole(Role(designation: 'Utilisateur'));
|
||||||
|
await AppDatabase.instance.createRole(Role(designation: 'Gestionnaire'));
|
||||||
|
print('Rôles par défaut créés');
|
||||||
|
} catch (error) {
|
||||||
|
print('Erreur lors de la création des rôles par défaut: $error');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -40,148 +97,319 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _register() {
|
bool _validateFields() {
|
||||||
// Get the entered user information
|
if (_nameController.text.trim().isEmpty ||
|
||||||
final String name = _nameController.text;
|
_lastNameController.text.trim().isEmpty ||
|
||||||
final String lastName = _lastNameController.text;
|
_emailController.text.trim().isEmpty ||
|
||||||
final String email = _emailController.text;
|
_usernameController.text.trim().isEmpty ||
|
||||||
final String username = _usernameController.text;
|
_passwordController.text.trim().isEmpty ||
|
||||||
final String password = _passwordController.text;
|
_selectedRole == null) {
|
||||||
final String role = _selectedRole;
|
_showErrorDialog('Champs manquants', 'Veuillez remplir tous les champs.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new user object
|
// Validation basique de l'email
|
||||||
final Users user = Users(
|
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(_emailController.text.trim())) {
|
||||||
id: 0, // The id will be assigned automatically by the database
|
_showErrorDialog('Email invalide', 'Veuillez entrer un email valide.');
|
||||||
name: name,
|
return false;
|
||||||
lastName: lastName,
|
}
|
||||||
email: email,
|
|
||||||
password: password,
|
|
||||||
username: username,
|
|
||||||
role: role,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Save the user in the database
|
// Validation basique du mot de passe
|
||||||
AuthDatabase.instance.createUser(user).then((value) {
|
if (_passwordController.text.length < 6) {
|
||||||
// Registration successful
|
_showErrorDialog('Mot de passe trop court', 'Le mot de passe doit contenir au moins 6 caractères.');
|
||||||
showDialog(
|
return false;
|
||||||
context: context,
|
}
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
return true;
|
||||||
title: const Text('Registration Successful'),
|
}
|
||||||
content: const Text('You have successfully registered.'),
|
|
||||||
actions: <Widget>[
|
void _register() async {
|
||||||
ElevatedButton(
|
if (_isLoading) return;
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
if (!_validateFields()) return;
|
||||||
// Navigate to the login page
|
|
||||||
Navigator.pushReplacement(
|
setState(() {
|
||||||
context,
|
_isLoading = true;
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const AccueilPage()),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const Text('OK'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}).catchError((error) {
|
|
||||||
print(error);
|
|
||||||
// Registration failed
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: const Text('Registration Failed'),
|
|
||||||
content: const Text('An error occurred during registration.'),
|
|
||||||
actions: <Widget>[
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
child: const Text('OK'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Créer l'objet utilisateur avec le nouveau modèle
|
||||||
|
final Users user = Users(
|
||||||
|
name: _nameController.text.trim(),
|
||||||
|
lastName: _lastNameController.text.trim(),
|
||||||
|
email: _emailController.text.trim(),
|
||||||
|
password: _passwordController.text.trim(),
|
||||||
|
username: _usernameController.text.trim(),
|
||||||
|
roleId: _selectedRole!.id!, // Utiliser l'ID du rôle
|
||||||
|
roleName: _selectedRole!.designation, // Pour l'affichage
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sauvegarder l'utilisateur dans la base de données
|
||||||
|
final int userId = await AppDatabase.instance.createUser(user);
|
||||||
|
|
||||||
|
print('Utilisateur créé avec l\'ID: $userId');
|
||||||
|
|
||||||
|
// Inscription réussie
|
||||||
|
if (mounted) {
|
||||||
|
_showSuccessDialog();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
print('Erreur lors de l\'inscription: $error');
|
||||||
|
|
||||||
|
String errorMessage = 'Une erreur est survenue lors de l\'inscription.';
|
||||||
|
|
||||||
|
// Personnaliser le message d'erreur selon le type d'erreur
|
||||||
|
if (error.toString().contains('UNIQUE constraint failed: users.email')) {
|
||||||
|
errorMessage = 'Cet email est déjà utilisé.';
|
||||||
|
} else if (error.toString().contains('UNIQUE constraint failed: users.username')) {
|
||||||
|
errorMessage = 'Ce nom d\'utilisateur est déjà pris.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
_showErrorDialog('Inscription échouée', errorMessage);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showSuccessDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Inscription réussie'),
|
||||||
|
content: const Text('Vous vous êtes inscrit avec succès.'),
|
||||||
|
actions: <Widget>[
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
Navigator.pushReplacement(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) => const AccueilPage()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showErrorDialog(String title, String message) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(title),
|
||||||
|
content: Text(message),
|
||||||
|
actions: <Widget>[
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Registration'),
|
title: const Text(
|
||||||
),
|
'Inscription',
|
||||||
body: Padding(
|
style: TextStyle(color: Colors.white),
|
||||||
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',
|
|
||||||
),
|
|
||||||
obscureText: true,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16.0),
|
|
||||||
DropdownButton<String>(
|
|
||||||
value: _selectedRole,
|
|
||||||
onChanged: (String? newValue) {
|
|
||||||
setState(() {
|
|
||||||
_selectedRole = newValue!;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
items: <String>['admin', 'user']
|
|
||||||
.map<DropdownMenuItem<String>>((String value) {
|
|
||||||
return DropdownMenuItem<String>(
|
|
||||||
value: value,
|
|
||||||
child: Text(value),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16.0),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: _register,
|
|
||||||
child: const Text('Register'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
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,
|
||||||
|
enabled: !_isLoading,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Prénom',
|
||||||
|
prefixIcon: const Icon(Icons.person),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16.0),
|
||||||
|
TextField(
|
||||||
|
controller: _lastNameController,
|
||||||
|
enabled: !_isLoading,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Nom',
|
||||||
|
prefixIcon: const Icon(Icons.person_outline),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16.0),
|
||||||
|
TextField(
|
||||||
|
controller: _emailController,
|
||||||
|
enabled: !_isLoading,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Email',
|
||||||
|
prefixIcon: const Icon(Icons.email),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.emailAddress,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16.0),
|
||||||
|
TextField(
|
||||||
|
controller: _usernameController,
|
||||||
|
enabled: !_isLoading,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Nom d\'utilisateur',
|
||||||
|
prefixIcon: const Icon(Icons.account_circle),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16.0),
|
||||||
|
TextField(
|
||||||
|
controller: _passwordController,
|
||||||
|
enabled: !_isLoading,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Mot de passe',
|
||||||
|
prefixIcon: const Icon(Icons.lock),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
helperText: 'Au moins 6 caractères',
|
||||||
|
),
|
||||||
|
obscureText: true,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16.0),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.grey),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton<Role>(
|
||||||
|
value: _selectedRole,
|
||||||
|
hint: const Text('Sélectionner un rôle'),
|
||||||
|
isExpanded: true,
|
||||||
|
onChanged: _isLoading
|
||||||
|
? null
|
||||||
|
: (Role? newValue) {
|
||||||
|
setState(() {
|
||||||
|
_selectedRole = newValue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
items: _availableRoles
|
||||||
|
.map<DropdownMenuItem<Role>>((Role role) {
|
||||||
|
return DropdownMenuItem<Role>(
|
||||||
|
value: role,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.badge, size: 20),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(role.designation),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24.0),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 48,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: _isLoading ? null : _register,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFF0015B7),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: _isLoading
|
||||||
|
? const Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
color: Colors.white,
|
||||||
|
strokeWidth: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
'Inscription en cours...',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: const Text(
|
||||||
|
'S\'inscrire',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
295
lib/accueil.dart
295
lib/accueil.dart
@ -5,6 +5,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:quantity_input/quantity_input.dart';
|
import 'package:quantity_input/quantity_input.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:youmazgestion/Views/particles.dart' show ParticleBackground;
|
import 'package:youmazgestion/Views/particles.dart' show ParticleBackground;
|
||||||
|
import 'package:youmazgestion/Views/produitsCard.dart';
|
||||||
import 'Components/appDrawer.dart';
|
import 'Components/appDrawer.dart';
|
||||||
import 'Components/app_bar.dart';
|
import 'Components/app_bar.dart';
|
||||||
import 'Components/cartItem.dart';
|
import 'Components/cartItem.dart';
|
||||||
@ -35,7 +36,7 @@ class _AccueilPageState extends State<AccueilPage> {
|
|||||||
|
|
||||||
int orderId = 0;
|
int orderId = 0;
|
||||||
List<CartItem> selectedProducts = [];
|
List<CartItem> selectedProducts = [];
|
||||||
int selectedQuantity = 1; // Quantité sélectionnée par défaut
|
int selectedQuantity = 1;
|
||||||
double totalCartPrice = 0;
|
double totalCartPrice = 0;
|
||||||
double amountPaid = 0;
|
double amountPaid = 0;
|
||||||
|
|
||||||
@ -55,16 +56,15 @@ class _AccueilPageState extends State<AccueilPage> {
|
|||||||
role = prefs.getString('role') ?? 'Rôle inconnu';
|
role = prefs.getString('role') ?? 'Rôle inconnu';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveOrderToDatabase() async {
|
Future<void> saveOrderToDatabase() async {
|
||||||
final totalPrice = calculateTotalPrice();
|
final totalPrice = calculateTotalPrice();
|
||||||
final dateTime = DateTime.now().toString();
|
final dateTime = DateTime.now().toString();
|
||||||
String user = userController.username;
|
String user = userController.username;
|
||||||
|
|
||||||
// Insert the order into the database
|
|
||||||
orderId = await orderDatabase.insertOrder(
|
orderId = await orderDatabase.insertOrder(
|
||||||
totalPrice, dateTime, MyApp.startDate!, user);
|
totalPrice, dateTime, MyApp.startDate!, user);
|
||||||
|
|
||||||
// Insert order items into the database
|
|
||||||
for (final cartItem in selectedProducts) {
|
for (final cartItem in selectedProducts) {
|
||||||
final product = cartItem.product;
|
final product = cartItem.product;
|
||||||
final quantity = cartItem.quantity;
|
final quantity = cartItem.quantity;
|
||||||
@ -73,7 +73,6 @@ class _AccueilPageState extends State<AccueilPage> {
|
|||||||
await orderDatabase.insertOrderItem(
|
await orderDatabase.insertOrderItem(
|
||||||
orderId, product.name, quantity, price);
|
orderId, product.name, quantity, price);
|
||||||
|
|
||||||
// Mettre à jour le stock du produit
|
|
||||||
final updatedStock = product.stock! - quantity;
|
final updatedStock = product.stock! - quantity;
|
||||||
await productDatabase.updateStock(product.id!, updatedStock);
|
await productDatabase.updateStock(product.id!, updatedStock);
|
||||||
}
|
}
|
||||||
@ -85,7 +84,6 @@ class _AccueilPageState extends State<AccueilPage> {
|
|||||||
final categories = await productDatabase.getCategories();
|
final categories = await productDatabase.getCategories();
|
||||||
final productsByCategory = <String, List<Product>>{};
|
final productsByCategory = <String, List<Product>>{};
|
||||||
|
|
||||||
// Trier les catégories selon votre préférence
|
|
||||||
categories.sort();
|
categories.sort();
|
||||||
|
|
||||||
for (final categoryName in categories) {
|
for (final categoryName in categories) {
|
||||||
@ -113,47 +111,34 @@ class _AccueilPageState extends State<AccueilPage> {
|
|||||||
return totalPrice;
|
return totalPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addToCartWithDetails(Product product) {
|
void addToCartWithDetails(Product product, int quantity) {
|
||||||
setState(() {
|
setState(() {
|
||||||
final existingCartItem = selectedProducts.firstWhere(
|
final existingCartItem = selectedProducts.firstWhere(
|
||||||
(cartItem) => cartItem.product == product,
|
(cartItem) => cartItem.product.id == product.id,
|
||||||
orElse: () => CartItem(product, 0),
|
orElse: () => CartItem(product, 0),
|
||||||
);
|
);
|
||||||
if (existingCartItem.quantity == 0) {
|
if (existingCartItem.quantity == 0) {
|
||||||
selectedProducts.add(CartItem(product, selectedQuantity));
|
selectedProducts.add(CartItem(product, quantity));
|
||||||
} else {
|
} else {
|
||||||
existingCartItem.quantity += selectedQuantity;
|
existingCartItem.quantity += quantity;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Afficher une notification
|
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Produit ajouté',
|
'Produit ajouté',
|
||||||
'Le produit ${product.name} a été ajouté au panier',
|
'${product.name} (x$quantity) ajouté au panier',
|
||||||
snackPosition: SnackPosition.TOP,
|
snackPosition: SnackPosition.TOP,
|
||||||
duration: const Duration(seconds: 1),
|
duration: const Duration(seconds: 1),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
resetQuantityAfterDelay();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> resetQuantityAfterDelay() async {
|
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
|
||||||
setState(() {
|
|
||||||
selectedQuantity = 1;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void showTicketPage() {
|
void showTicketPage() {
|
||||||
// Calculer la somme totale du panier
|
|
||||||
final double totalCartPrice = calculateTotalPrice();
|
final double totalCartPrice = calculateTotalPrice();
|
||||||
|
|
||||||
// Vérifier si des produits sont présents dans le panier
|
|
||||||
if (selectedProducts.isNotEmpty) {
|
if (selectedProducts.isNotEmpty) {
|
||||||
// Vérifier si la somme payée est suffisante et supérieure ou égale au total du panier
|
|
||||||
if (amountPaid >= totalCartPrice) {
|
if (amountPaid >= totalCartPrice) {
|
||||||
// Passer les produits et les informations de l'entreprise à la page du ticket
|
|
||||||
Get.offAll(TicketPage(
|
Get.offAll(TicketPage(
|
||||||
businessName: 'Youmaz',
|
businessName: 'Youmaz',
|
||||||
businessAddress:
|
businessAddress:
|
||||||
@ -164,10 +149,9 @@ class _AccueilPageState extends State<AccueilPage> {
|
|||||||
amountPaid: amountPaid,
|
amountPaid: amountPaid,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
// Afficher un message d'erreur si la somme payée est insuffisante
|
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Paiement incomplet',
|
'Paiement incomplet',
|
||||||
'Le montant payé est insuffisant. Veuillez payer le montant total du panier.',
|
'Le montant payé est insuffisant.',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
@ -175,10 +159,9 @@ class _AccueilPageState extends State<AccueilPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Afficher un message d'erreur si le panier est vide
|
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Panier vide',
|
'Panier vide',
|
||||||
'Le panier est vide. Veuillez ajouter des produits avant de passer commande.',
|
'Ajoutez des produits avant de passer commande.',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
@ -198,7 +181,7 @@ class _AccueilPageState extends State<AccueilPage> {
|
|||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
colors: [Colors.white, const Color.fromARGB(255, 4, 54, 95)],
|
colors: [Colors.white, Color.fromARGB(255, 4, 54, 95)],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: FutureBuilder<Map<String, List<Product>>>(
|
child: FutureBuilder<Map<String, List<Product>>>(
|
||||||
@ -207,35 +190,26 @@ class _AccueilPageState extends State<AccueilPage> {
|
|||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
} else if (snapshot.hasError) {
|
} else if (snapshot.hasError) {
|
||||||
print('erreur:${snapshot.error}');
|
return const Center(child: Text("Erreur de chargement"));
|
||||||
return const Center(
|
|
||||||
child: Text("Erreur lors du chargement des produits"));
|
|
||||||
} else if (snapshot.hasData) {
|
} else if (snapshot.hasData) {
|
||||||
final Map<String, List<Product>> productsByCategory =
|
final productsByCategory = snapshot.data!;
|
||||||
snapshot.data!;
|
|
||||||
final categories = productsByCategory.keys.toList();
|
final categories = productsByCategory.keys.toList();
|
||||||
|
|
||||||
if (!MyApp.isRegisterOpen) {
|
if (!MyApp.isRegisterOpen) {
|
||||||
// Afficher le bouton "Démarrer la caisse" si la variable isRegisterOpen est false
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Text('welcome $username ! votre role est $role'),
|
Text('Bienvenue $username ! (Rôle: $role)'),
|
||||||
Center(
|
Center(
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
MyApp.isRegisterOpen = true;
|
MyApp.isRegisterOpen = true;
|
||||||
// mettre startDate à la date actuelle et au format yyyy-MM-dd
|
|
||||||
String formattedDate =
|
String formattedDate =
|
||||||
DateFormat('yyyy-MM-dd').format(DateTime.now());
|
DateFormat('yyyy-MM-dd').format(DateTime.now());
|
||||||
startDate =
|
startDate =
|
||||||
DateFormat('yyyy-MM-dd').parse(formattedDate);
|
DateFormat('yyyy-MM-dd').parse(formattedDate);
|
||||||
MyApp.startDate = startDate;
|
MyApp.startDate = startDate;
|
||||||
|
workDatabase.insertDate(formattedDate);
|
||||||
var datee = DateFormat('yyyy-MM-dd')
|
|
||||||
.format(startDate!)
|
|
||||||
.toString();
|
|
||||||
workDatabase.insertDate(datee);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: const Text('Démarrer la caisse'),
|
child: const Text('Démarrer la caisse'),
|
||||||
@ -244,7 +218,6 @@ class _AccueilPageState extends State<AccueilPage> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Afficher le contenu de la page d'accueil
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -254,7 +227,7 @@ class _AccueilPageState extends State<AccueilPage> {
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final category = categories[index];
|
final category = categories[index];
|
||||||
final products = productsByCategory[category]!;
|
final products = productsByCategory[category]!;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -266,10 +239,7 @@ class _AccueilPageState extends State<AccueilPage> {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontStyle: FontStyle.italic,
|
|
||||||
decorationThickness: 2,
|
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -284,125 +254,11 @@ class _AccueilPageState extends State<AccueilPage> {
|
|||||||
itemCount: products.length,
|
itemCount: products.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final product = products[index];
|
final product = products[index];
|
||||||
|
return ProductCard(
|
||||||
return Card(
|
product: product,
|
||||||
elevation: 7,
|
onAddToCart: (product, quantity) {
|
||||||
shadowColor: Colors.redAccent,
|
addToCartWithDetails(product, quantity);
|
||||||
shape: RoundedRectangleBorder(
|
},
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {},
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 14,
|
|
||||||
child: product.image != null
|
|
||||||
? Image.file(
|
|
||||||
File(product.image),
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
)
|
|
||||||
: Image.asset(
|
|
||||||
'assets/placeholder_image.png',
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Center(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.center,
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
product.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight:
|
|
||||||
FontWeight.bold,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'${product.price.toStringAsFixed(2)} fcfa',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight:
|
|
||||||
FontWeight.bold,
|
|
||||||
fontSize: 14,
|
|
||||||
color: Colors.green,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (product
|
|
||||||
.isStockDefined()) ...[
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
const Text(
|
|
||||||
'En stock',
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight:
|
|
||||||
FontWeight.bold,
|
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.green,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
QuantityInput(
|
|
||||||
value: selectedQuantity,
|
|
||||||
minValue: 1,
|
|
||||||
maxValue: 100,
|
|
||||||
step: 1,
|
|
||||||
inputWidth: 60,
|
|
||||||
buttonColor:
|
|
||||||
Colors.redAccent,
|
|
||||||
onChanged:
|
|
||||||
(String value) {
|
|
||||||
setState(() {
|
|
||||||
selectedQuantity =
|
|
||||||
int.parse(value);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
addToCartWithDetails(
|
|
||||||
product);
|
|
||||||
},
|
|
||||||
style: ElevatedButton
|
|
||||||
.styleFrom(
|
|
||||||
backgroundColor:
|
|
||||||
const Color.fromARGB(255, 4, 54, 95),
|
|
||||||
onPrimary: Colors.white,
|
|
||||||
shape:
|
|
||||||
RoundedRectangleBorder(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius
|
|
||||||
.circular(18),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Text(
|
|
||||||
'Ajouter au panier'
|
|
||||||
,
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -419,93 +275,58 @@ class _AccueilPageState extends State<AccueilPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
const Row(
|
const Text(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
'Panier',
|
||||||
children: [
|
style: TextStyle(
|
||||||
Text(
|
fontSize: 20,
|
||||||
'Panier',
|
fontWeight: FontWeight.bold,
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: const Color.fromARGB(255, 4, 54, 95),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
Icons.shopping_cart,
|
|
||||||
color: Colors.red,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Expanded(
|
|
||||||
child: 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(
|
|
||||||
icon: const Icon(Icons.delete),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
selectedProducts.removeAt(index);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
color: Colors.redAccent,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
Expanded(
|
||||||
|
child: selectedProducts.isEmpty
|
||||||
|
? const Center(
|
||||||
|
child: Text("Votre panier est vide"),
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
itemCount: selectedProducts.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final cartItem = selectedProducts[index];
|
||||||
|
return ListTile(
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
'Total: ${calculateTotalPrice().toStringAsFixed(2)} fcfa',
|
'Total: ${calculateTotalPrice().toStringAsFixed(2)} FCFA',
|
||||||
textAlign: TextAlign.end,
|
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
TextField(
|
||||||
TextFormField(
|
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Montant payé',
|
labelText: 'Montant payé',
|
||||||
),
|
),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
amountPaid = double.tryParse(value) ?? 0;
|
||||||
amountPaid = double.parse(value);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: saveOrderToDatabase,
|
||||||
saveOrderToDatabase();
|
child: const Text('Valider la commande'),
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: const Color.fromARGB(255, 4, 54, 95),
|
|
||||||
onPrimary: Colors.white,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(18),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Text('Payer'),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -523,4 +344,4 @@ class _AccueilPageState extends State<AccueilPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:youmazgestion/Models/users.dart';
|
import 'package:youmazgestion/Models/users.dart';
|
||||||
|
import 'package:youmazgestion/Services/app_database.dart';
|
||||||
|
|
||||||
class UserController extends GetxController {
|
class UserController extends GetxController {
|
||||||
final _username = ''.obs;
|
final _username = ''.obs;
|
||||||
@ -30,4 +31,18 @@ class UserController extends GetxController {
|
|||||||
_password.value = user.password;
|
_password.value = user.password;
|
||||||
print(_password.value);
|
print(_password.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> hasPermission(String permissionName) async {
|
||||||
|
// Utilisez votre instance de AppDatabase pour vérifier la permission
|
||||||
|
return await AppDatabase.instance.hasPermission(username, permissionName);
|
||||||
|
}
|
||||||
|
Future<bool> hasAnyPermission(List<String> permissionNames) async {
|
||||||
|
for (String permissionName in permissionNames) {
|
||||||
|
if (await hasPermission(permissionName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,38 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:youmazgestion/Services/authDatabase.dart';
|
import 'package:youmazgestion/Services/app_database.dart';
|
||||||
import 'Services/productDatabase.dart';
|
import 'Services/productDatabase.dart';
|
||||||
import 'my_app.dart';
|
import 'my_app.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await ProductDatabase.instance.initDatabase();
|
|
||||||
await AuthDatabase.instance.initDatabase();
|
try {
|
||||||
|
// Initialiser les bases de données une seule fois
|
||||||
setupLogger(); // Appel à la fonction setupLogger()
|
await ProductDatabase.instance.initDatabase();
|
||||||
|
await AppDatabase.instance.initDatabase();
|
||||||
//await OrderDatabase.instance.initDatabase();
|
|
||||||
runApp(const GetMaterialApp(
|
// Afficher les informations de la base (pour debug)
|
||||||
debugShowCheckedModeBanner: false,
|
await AppDatabase.instance.printDatabaseInfo();
|
||||||
home: MyApp(),
|
|
||||||
));
|
setupLogger();
|
||||||
|
|
||||||
|
runApp(const GetMaterialApp(
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
home: MyApp(),
|
||||||
|
));
|
||||||
|
} catch (e) {
|
||||||
|
print('Erreur lors de l\'initialisation: $e');
|
||||||
|
// Vous pourriez vouloir afficher une page d'erreur ici
|
||||||
|
runApp(MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Text('Erreur d\'initialisation: $e'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupLogger() {
|
void setupLogger() {
|
||||||
@ -24,4 +40,4 @@ void setupLogger() {
|
|||||||
Logger.root.onRecord.listen((record) {
|
Logger.root.onRecord.listen((record) {
|
||||||
print('${record.level.name}: ${record.time}: ${record.message}');
|
print('${record.level.name}: ${record.time}: ${record.message}');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
16
pubspec.lock
16
pubspec.lock
@ -37,10 +37,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.12.0"
|
version: "2.13.0"
|
||||||
barcode:
|
barcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -173,10 +173,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: fake_async
|
name: fake_async
|
||||||
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
|
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.3.3"
|
||||||
ffi:
|
ffi:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -500,10 +500,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.8"
|
version: "10.0.9"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1161,10 +1161,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
|
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.3.1"
|
version: "15.0.0"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -99,6 +99,7 @@ flutter:
|
|||||||
- assets/database/products2.db
|
- assets/database/products2.db
|
||||||
- assets/database/usersdb.db
|
- assets/database/usersdb.db
|
||||||
- assets/database/work.db
|
- assets/database/work.db
|
||||||
|
- assets/database/roles.db
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user