Compare commits
5 Commits
master
...
06062025_0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
831cce13da | ||
|
|
c8fedd08e5 | ||
|
|
9eafda610f | ||
|
|
2bef06a2fe | ||
|
|
57ea91b3d7 |
@ -1,4 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.FLASHLIGHT" />
|
||||
<application
|
||||
android:label="my_app"
|
||||
android:name="${applicationName}"
|
||||
@ -12,6 +14,7 @@
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
|
||||
BIN
assets/Orange_money.png
Normal file
BIN
assets/Orange_money.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/airtel_money.png
Normal file
BIN
assets/airtel_money.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
BIN
assets/fa-solid-900.ttf
Normal file
BIN
assets/fa-solid-900.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/Roboto-Italic.ttf
Normal file
BIN
assets/fonts/Roboto-Italic.ttf
Normal file
Binary file not shown.
BIN
assets/mvola.jpg
Normal file
BIN
assets/mvola.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Cette application a besoin d'accéder à la caméra pour scanner les codes QR</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
@ -47,5 +49,6 @@
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
259
lib/Components/QrScan.dart
Normal file
259
lib/Components/QrScan.dart
Normal file
@ -0,0 +1,259 @@
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
|
||||
class ScanQRPage extends StatefulWidget {
|
||||
const ScanQRPage({super.key});
|
||||
|
||||
@override
|
||||
State<ScanQRPage> createState() => _ScanQRPageState();
|
||||
}
|
||||
|
||||
class _ScanQRPageState extends State<ScanQRPage> {
|
||||
MobileScannerController? cameraController;
|
||||
bool _isScanComplete = false;
|
||||
String? _scannedData;
|
||||
bool _hasError = false;
|
||||
String? _errorMessage;
|
||||
bool get isMobile => !kIsWeb && (Platform.isAndroid || Platform.isIOS);
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeController();
|
||||
}
|
||||
|
||||
void _initializeController() {
|
||||
if (!isMobile) {
|
||||
setState(() {
|
||||
_hasError = true;
|
||||
_errorMessage = "Le scanner QR n'est pas disponible sur cette plateforme.";
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
cameraController = MobileScannerController(
|
||||
detectionSpeed: DetectionSpeed.noDuplicates,
|
||||
facing: CameraFacing.back,
|
||||
torchEnabled: false,
|
||||
);
|
||||
setState(() {
|
||||
_hasError = false;
|
||||
_errorMessage = null;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_hasError = true;
|
||||
_errorMessage = 'Erreur d\'initialisation de la caméra: $e';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
cameraController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Scanner QR Code'),
|
||||
actions: _hasError ? [] : [
|
||||
if (cameraController != null) ...[
|
||||
IconButton(
|
||||
color: Colors.white,
|
||||
icon: const Icon(Icons.flash_on, color: Colors.white),
|
||||
iconSize: 32.0,
|
||||
onPressed: () => cameraController!.toggleTorch(),
|
||||
),
|
||||
IconButton(
|
||||
color: Colors.white,
|
||||
icon: const Icon(Icons.flip_camera_ios, color: Colors.white),
|
||||
iconSize: 32.0,
|
||||
onPressed: () => cameraController!.switchCamera(),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
body: _hasError ? _buildErrorWidget() : _buildScannerWidget(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildErrorWidget() {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.error_outline,
|
||||
size: 64,
|
||||
color: Colors.red,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'Erreur de caméra',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_errorMessage ?? 'Une erreur s\'est produite',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
_initializeController();
|
||||
},
|
||||
child: const Text('Réessayer'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'Vérifiez que:\n• Le plugin mobile_scanner est installé\n• Les permissions de caméra sont accordées\n• Votre appareil a une caméra fonctionnelle',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildScannerWidget() {
|
||||
if (cameraController == null) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
MobileScanner(
|
||||
controller: cameraController!,
|
||||
onDetect: (capture) {
|
||||
final List<Barcode> barcodes = capture.barcodes;
|
||||
for (final barcode in barcodes) {
|
||||
if (!_isScanComplete && barcode.rawValue != null) {
|
||||
_isScanComplete = true;
|
||||
_scannedData = barcode.rawValue;
|
||||
_showScanResult(context, _scannedData!);
|
||||
}
|
||||
}
|
||||
},
|
||||
errorBuilder: (context, error, child) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.error, size: 64, color: Colors.red),
|
||||
const SizedBox(height: 16),
|
||||
Text('Erreur: ${error.errorDetails?.message ?? 'Erreur inconnue'}'),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () => _initializeController(),
|
||||
child: const Text('Réessayer'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
CustomPaint(
|
||||
painter: QrScannerOverlay(
|
||||
borderColor: Colors.blue.shade800,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _showScanResult(BuildContext context, String data) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Résultat du scan'),
|
||||
content: SelectableText(data), // Permet de sélectionner le texte
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
setState(() {
|
||||
_isScanComplete = false;
|
||||
});
|
||||
},
|
||||
child: const Text('Fermer'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context, data); // Retourner la donnée scannée
|
||||
},
|
||||
child: const Text('Utiliser'),
|
||||
),
|
||||
],
|
||||
),
|
||||
).then((_) {
|
||||
setState(() {
|
||||
_isScanComplete = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class QrScannerOverlay extends CustomPainter {
|
||||
final Color borderColor;
|
||||
|
||||
QrScannerOverlay({required this.borderColor});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final double width = size.width;
|
||||
final double height = size.height;
|
||||
final double borderWidth = 2.0;
|
||||
final double borderLength = 30.0;
|
||||
final double areaSize = width * 0.7;
|
||||
|
||||
final Paint backgroundPaint = Paint()
|
||||
..color = Colors.black.withOpacity(0.4);
|
||||
canvas.drawRect(Rect.fromLTRB(0, 0, width, height), backgroundPaint);
|
||||
|
||||
final Paint transparentPaint = Paint()
|
||||
..color = Colors.transparent
|
||||
..blendMode = BlendMode.clear;
|
||||
final double areaLeft = (width - areaSize) / 2;
|
||||
final double areaTop = (height - areaSize) / 2;
|
||||
canvas.drawRect(
|
||||
Rect.fromLTRB(areaLeft, areaTop, areaLeft + areaSize, areaTop + areaSize),
|
||||
transparentPaint,
|
||||
);
|
||||
|
||||
final Paint borderPaint = Paint()
|
||||
..color = borderColor
|
||||
..strokeWidth = borderWidth
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
// Coins du scanner
|
||||
_drawCorner(canvas, borderPaint, areaLeft, areaTop, borderLength, true, true);
|
||||
_drawCorner(canvas, borderPaint, areaLeft + areaSize, areaTop, borderLength, false, true);
|
||||
_drawCorner(canvas, borderPaint, areaLeft, areaTop + areaSize, borderLength, true, false);
|
||||
_drawCorner(canvas, borderPaint, areaLeft + areaSize, areaTop + areaSize, borderLength, false, false);
|
||||
}
|
||||
|
||||
void _drawCorner(Canvas canvas, Paint paint, double x, double y, double length, bool isLeft, bool isTop) {
|
||||
final double horizontalStart = isLeft ? x : x - length;
|
||||
final double horizontalEnd = isLeft ? x + length : x;
|
||||
final double verticalStart = isTop ? y : y - length;
|
||||
final double verticalEnd = isTop ? y + length : y;
|
||||
|
||||
canvas.drawLine(Offset(horizontalStart, y), Offset(horizontalEnd, y), paint);
|
||||
canvas.drawLine(Offset(x, verticalStart), Offset(x, verticalEnd), paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:youmazgestion/Views/Dashboard.dart';
|
||||
import 'package:youmazgestion/Views/HandleProduct.dart';
|
||||
import 'package:youmazgestion/Views/RoleListPage.dart';
|
||||
import 'package:youmazgestion/Views/commandManagement.dart';
|
||||
@ -13,6 +14,7 @@ import 'package:youmazgestion/Views/newCommand.dart';
|
||||
import 'package:youmazgestion/Views/registrationPage.dart';
|
||||
import 'package:youmazgestion/accueil.dart';
|
||||
import 'package:youmazgestion/controller/userController.dart';
|
||||
import 'package:youmazgestion/Views/pointage.dart';
|
||||
|
||||
class CustomDrawer extends StatelessWidget {
|
||||
final UserController userController = Get.find<UserController>();
|
||||
@ -73,7 +75,9 @@ class CustomDrawer extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
controller.name.isNotEmpty ? controller.name : 'Utilisateur',
|
||||
controller.name.isNotEmpty
|
||||
? controller.name
|
||||
: 'Utilisateur',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
@ -102,7 +106,7 @@ class CustomDrawer extends StatelessWidget {
|
||||
color: Colors.blue,
|
||||
permissionAction: 'view',
|
||||
permissionRoute: '/accueil',
|
||||
onTap: () => Get.to(const AccueilPage()),
|
||||
onTap: () => Get.to( DashboardPage()),
|
||||
),
|
||||
);
|
||||
|
||||
@ -123,6 +127,14 @@ class CustomDrawer extends StatelessWidget {
|
||||
permissionRoute: '/modifier-utilisateur',
|
||||
onTap: () => Get.to(const ListUserPage()),
|
||||
),
|
||||
await _buildDrawerItem(
|
||||
icon: Icons.timer,
|
||||
title: "Gestion des pointages",
|
||||
color: const Color.fromARGB(255, 4, 54, 95),
|
||||
permissionAction: 'update',
|
||||
permissionRoute: '/pointage',
|
||||
onTap: () => Get.to(const PointagePage()),
|
||||
)
|
||||
];
|
||||
|
||||
if (gestionUtilisateursItems.any((item) => item is ListTile)) {
|
||||
@ -217,11 +229,11 @@ class CustomDrawer extends StatelessWidget {
|
||||
List<Widget> rapportsItems = [
|
||||
await _buildDrawerItem(
|
||||
icon: Icons.bar_chart,
|
||||
title: "Bilan mensuel",
|
||||
title: "Bilan ",
|
||||
color: Colors.teal,
|
||||
permissionAction: 'read',
|
||||
permissionRoute: '/bilan',
|
||||
onTap: () => Get.to(const BilanMois()),
|
||||
onTap: () => Get.to( DashboardPage()),
|
||||
),
|
||||
await _buildDrawerItem(
|
||||
icon: Icons.history,
|
||||
@ -280,31 +292,129 @@ class CustomDrawer extends StatelessWidget {
|
||||
|
||||
drawerItems.add(const Divider());
|
||||
|
||||
|
||||
drawerItems.add(
|
||||
ListTile(
|
||||
leading: const Icon(Icons.logout, color: Colors.red),
|
||||
title: const Text("Déconnexion"),
|
||||
onTap: () {
|
||||
Get.defaultDialog(
|
||||
title: "Déconnexion",
|
||||
content: const Text("Voulez-vous vraiment vous déconnecter ?"),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text("Non"),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.logout_rounded,
|
||||
size: 48,
|
||||
color: Colors.orange.shade600,
|
||||
),
|
||||
child: const Text("Oui"),
|
||||
onPressed: () async {
|
||||
await clearUserData();
|
||||
Get.offAll(const LoginPage());
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
"Déconnexion",
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
"Êtes-vous sûr de vouloir vous déconnecter ?",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.black87,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
"Vous devrez vous reconnecter pour accéder à votre compte.",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade600,
|
||||
height: 1.3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Actions
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () => Get.back(),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
side: BorderSide(
|
||||
color: Colors.grey.shade300,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
"Annuler",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
await clearUserData();
|
||||
Get.offAll(const LoginPage());
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red.shade600,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 2,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
"Se déconnecter",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
barrierDismissible: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
@ -321,7 +431,8 @@ class CustomDrawer extends StatelessWidget {
|
||||
required VoidCallback onTap,
|
||||
}) async {
|
||||
if (permissionAction != null && permissionRoute != null) {
|
||||
bool hasPermission = await userController.hasPermission(permissionAction, permissionRoute);
|
||||
bool hasPermission =
|
||||
await userController.hasPermission(permissionAction, permissionRoute);
|
||||
if (!hasPermission) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
@ -337,3 +448,6 @@ class CustomDrawer extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HistoryPage {
|
||||
}
|
||||
|
||||
@ -1,31 +1,121 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:youmazgestion/controller/userController.dart';
|
||||
|
||||
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
final String title;
|
||||
final Widget? subtitle;
|
||||
|
||||
const CustomAppBar({
|
||||
final List<Widget>? actions;
|
||||
final bool automaticallyImplyLeading;
|
||||
final Color? backgroundColor;
|
||||
final bool isDesktop; // Add this parameter
|
||||
|
||||
final UserController userController = Get.put(UserController());
|
||||
|
||||
CustomAppBar({
|
||||
Key? key,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
this.actions,
|
||||
this.automaticallyImplyLeading = true,
|
||||
this.backgroundColor,
|
||||
this.isDesktop = false, // Add this parameter with default value
|
||||
}) : super(key: key);
|
||||
|
||||
|
||||
@override
|
||||
Size get preferredSize => Size.fromHeight(subtitle == null ? 56.0 : 72.0);
|
||||
|
||||
Size get preferredSize => Size.fromHeight(subtitle == null ? 56.0 : 80.0);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBar(
|
||||
title: subtitle == null
|
||||
? Text(title)
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: TextStyle(fontSize: 20)),
|
||||
subtitle!,
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Colors.blue.shade900,
|
||||
Colors.blue.shade800,
|
||||
],
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.blue.shade900.withOpacity(0.3),
|
||||
offset: const Offset(0, 2),
|
||||
blurRadius: 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: AppBar(
|
||||
backgroundColor: backgroundColor ?? Colors.transparent,
|
||||
elevation: 0,
|
||||
automaticallyImplyLeading: automaticallyImplyLeading,
|
||||
centerTitle: false,
|
||||
iconTheme: const IconThemeData(
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
),
|
||||
actions: actions,
|
||||
title: subtitle == null
|
||||
? Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Obx(() => Text(
|
||||
userController.role != 'Super Admin'
|
||||
? 'Point de vente: ${userController.pointDeVenteDesignation}'
|
||||
: '',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
)),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: 2),
|
||||
DefaultTextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
child: subtitle!,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
flexibleSpace: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Colors.blue.shade900,
|
||||
Colors.blue.shade800,
|
||||
],
|
||||
),
|
||||
// autres propriétés si besoin
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
lib/Components/paymentType.dart
Normal file
7
lib/Components/paymentType.dart
Normal file
@ -0,0 +1,7 @@
|
||||
enum PaymentType {
|
||||
cash,
|
||||
card,
|
||||
mvola,
|
||||
orange,
|
||||
airtel
|
||||
}
|
||||
@ -49,13 +49,9 @@ class Client {
|
||||
String get nomComplet => '$prenom $nom';
|
||||
}
|
||||
|
||||
// Models/commande.dart
|
||||
enum StatutCommande {
|
||||
enAttente,
|
||||
confirmee,
|
||||
enPreparation,
|
||||
expediee,
|
||||
livree,
|
||||
annulee
|
||||
}
|
||||
|
||||
@ -67,7 +63,9 @@ class Commande {
|
||||
final double montantTotal;
|
||||
final String? notes;
|
||||
final DateTime? dateLivraison;
|
||||
|
||||
final int? commandeurId;
|
||||
final int? validateurId;
|
||||
|
||||
// Données du client (pour les jointures)
|
||||
final String? clientNom;
|
||||
final String? clientPrenom;
|
||||
@ -81,6 +79,8 @@ class Commande {
|
||||
required this.montantTotal,
|
||||
this.notes,
|
||||
this.dateLivraison,
|
||||
this.commandeurId,
|
||||
this.validateurId,
|
||||
this.clientNom,
|
||||
this.clientPrenom,
|
||||
this.clientEmail,
|
||||
@ -95,6 +95,8 @@ class Commande {
|
||||
'montantTotal': montantTotal,
|
||||
'notes': notes,
|
||||
'dateLivraison': dateLivraison?.toIso8601String(),
|
||||
'commandeurId': commandeurId,
|
||||
'validateurId': validateurId,
|
||||
};
|
||||
}
|
||||
|
||||
@ -106,9 +108,11 @@ class Commande {
|
||||
statut: StatutCommande.values[map['statut']],
|
||||
montantTotal: map['montantTotal'].toDouble(),
|
||||
notes: map['notes'],
|
||||
dateLivraison: map['dateLivraison'] != null
|
||||
? DateTime.parse(map['dateLivraison'])
|
||||
dateLivraison: map['dateLivraison'] != null
|
||||
? DateTime.parse(map['dateLivraison'])
|
||||
: null,
|
||||
commandeurId: map['commandeurId'],
|
||||
validateurId: map['validateurId'],
|
||||
clientNom: map['clientNom'],
|
||||
clientPrenom: map['clientPrenom'],
|
||||
clientEmail: map['clientEmail'],
|
||||
@ -121,23 +125,26 @@ class Commande {
|
||||
return 'En attente';
|
||||
case StatutCommande.confirmee:
|
||||
return 'Confirmée';
|
||||
case StatutCommande.enPreparation:
|
||||
return 'En préparation';
|
||||
case StatutCommande.expediee:
|
||||
return 'Expédiée';
|
||||
case StatutCommande.livree:
|
||||
return 'Livrée';
|
||||
// case StatutCommande.enPreparation:
|
||||
// return 'En préparation';
|
||||
// case StatutCommande.expediee:
|
||||
// return 'Expédiée';
|
||||
// case StatutCommande.livree:
|
||||
// return 'Livrée';
|
||||
case StatutCommande.annulee:
|
||||
return 'Annulée';
|
||||
default:
|
||||
return 'Inconnu';
|
||||
}
|
||||
}
|
||||
|
||||
String get clientNomComplet =>
|
||||
clientPrenom != null && clientNom != null
|
||||
? '$clientPrenom $clientNom'
|
||||
String get clientNomComplet =>
|
||||
clientPrenom != null && clientNom != null
|
||||
? '$clientPrenom $clientNom'
|
||||
: 'Client inconnu';
|
||||
}
|
||||
|
||||
|
||||
// Models/detail_commande.dart
|
||||
class DetailCommande {
|
||||
final int? id;
|
||||
|
||||
36
lib/Models/pointage_model.dart
Normal file
36
lib/Models/pointage_model.dart
Normal file
@ -0,0 +1,36 @@
|
||||
class Pointage {
|
||||
final int? id;
|
||||
final String userName;
|
||||
final String date;
|
||||
final String heureArrivee;
|
||||
final String heureDepart;
|
||||
|
||||
Pointage({
|
||||
this.id,
|
||||
required this.userName,
|
||||
required this.date,
|
||||
required this.heureArrivee,
|
||||
required this.heureDepart,
|
||||
});
|
||||
|
||||
// Pour SQLite
|
||||
factory Pointage.fromMap(Map<String, dynamic> map) {
|
||||
return Pointage(
|
||||
id: map['id'],
|
||||
userName: map['userName'] ?? '',
|
||||
date: map['date'],
|
||||
heureArrivee: map['heureArrivee'],
|
||||
heureDepart: map['heureDepart'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'userName': userName,
|
||||
'date': date,
|
||||
'heureArrivee': heureArrivee,
|
||||
'heureDepart': heureDepart,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,18 @@
|
||||
class Product {
|
||||
int? id;
|
||||
final int? id;
|
||||
final String name;
|
||||
final double price;
|
||||
final String? image;
|
||||
final String category;
|
||||
final int? stock;
|
||||
final int stock;
|
||||
final String? description;
|
||||
String? qrCode;
|
||||
String? qrCode;
|
||||
final String? reference;
|
||||
final int? pointDeVenteId;
|
||||
final String? marque;
|
||||
final String? ram;
|
||||
final String? memoireInterne;
|
||||
final String? imei;
|
||||
|
||||
Product({
|
||||
this.id,
|
||||
@ -16,11 +21,16 @@ class Product {
|
||||
this.image,
|
||||
required this.category,
|
||||
this.stock = 0,
|
||||
this.description = '',
|
||||
this.description,
|
||||
this.qrCode,
|
||||
this.reference,
|
||||
this.pointDeVenteId,
|
||||
this.marque,
|
||||
this.ram,
|
||||
this.memoireInterne,
|
||||
this.imei,
|
||||
|
||||
});
|
||||
// Vérifie si le stock est défini
|
||||
bool isStockDefined() {
|
||||
if (stock != null) {
|
||||
print("stock is defined : $stock $name");
|
||||
@ -29,31 +39,37 @@ class Product {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'price': price,
|
||||
'image': image ?? '',
|
||||
'category': category,
|
||||
'stock': stock ?? 0,
|
||||
'description': description ?? '',
|
||||
'qrCode': qrCode ?? '',
|
||||
'reference': reference ?? '',
|
||||
};
|
||||
}
|
||||
factory Product.fromMap(Map<String, dynamic> map) => Product(
|
||||
id: map['id'],
|
||||
name: map['name'],
|
||||
price: map['price'],
|
||||
image: map['image'],
|
||||
category: map['category'],
|
||||
stock: map['stock'],
|
||||
description: map['description'],
|
||||
qrCode: map['qrCode'],
|
||||
reference: map['reference'],
|
||||
pointDeVenteId: map['point_de_vente_id'],
|
||||
marque: map['marque'],
|
||||
ram: map['ram'],
|
||||
memoireInterne: map['memoire_interne'],
|
||||
imei: map['imei'],
|
||||
);
|
||||
|
||||
factory Product.fromMap(Map<String, dynamic> map) {
|
||||
return Product(
|
||||
id: map['id'],
|
||||
name: map['name'],
|
||||
price: map['price'],
|
||||
image: map['image'],
|
||||
category: map['category'],
|
||||
stock: map['stock'],
|
||||
description: map['description'],
|
||||
qrCode: map['qrCode'],
|
||||
reference: map['reference'],
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toMap() => {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'price': price,
|
||||
'image': image,
|
||||
'category': category,
|
||||
'stock': stock,
|
||||
'description': description,
|
||||
'qrCode': qrCode,
|
||||
'reference': reference,
|
||||
'point_de_vente_id': pointDeVenteId,
|
||||
'marque': marque,
|
||||
'ram': ram,
|
||||
'memoire_interne': memoireInterne,
|
||||
'imei': imei,
|
||||
};
|
||||
}
|
||||
@ -7,6 +7,7 @@ class Users {
|
||||
String username;
|
||||
int roleId;
|
||||
String? roleName; // Optionnel, rempli lors des requêtes avec JOIN
|
||||
int? pointDeVenteId;
|
||||
|
||||
Users({
|
||||
this.id,
|
||||
@ -17,6 +18,7 @@ class Users {
|
||||
required this.username,
|
||||
required this.roleId,
|
||||
this.roleName,
|
||||
this.pointDeVenteId,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
@ -27,6 +29,7 @@ class Users {
|
||||
'password': password,
|
||||
'username': username,
|
||||
'role_id': roleId,
|
||||
'point_de_vente_id' : pointDeVenteId,
|
||||
};
|
||||
}
|
||||
|
||||
@ -46,9 +49,11 @@ class Users {
|
||||
username: map['username'],
|
||||
roleId: map['role_id'],
|
||||
roleName: map['role_name'], // Depuis les requêtes avec JOIN
|
||||
pointDeVenteId : map['point_de_vente_id']
|
||||
);
|
||||
}
|
||||
|
||||
// Getter pour la compatibilité avec l'ancien code
|
||||
String get role => roleName ?? '';
|
||||
|
||||
}
|
||||
0
lib/Services/GestionStockDatabase.dart
Normal file
0
lib/Services/GestionStockDatabase.dart
Normal file
@ -1,680 +0,0 @@
|
||||
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 insertDefaultMenus();
|
||||
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) {
|
||||
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) {
|
||||
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();
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
if (!tableNames.contains('menu')) {
|
||||
await db.execute('''
|
||||
CREATE TABLE menu (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
route TEXT NOT NULL UNIQUE
|
||||
)
|
||||
''');
|
||||
print("Table 'menu' créée.");
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
if (!tableNames.contains('menu_permissions')) {
|
||||
await db.execute('''
|
||||
CREATE TABLE menu_permissions (
|
||||
menu_id INTEGER,
|
||||
permission_id INTEGER,
|
||||
PRIMARY KEY (menu_id, permission_id),
|
||||
FOREIGN KEY (menu_id) REFERENCES menu(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
print("Table 'menu_permissions' créée.");
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
if (!tableNames.contains('role_menu_permissions')) {
|
||||
await db.execute('''
|
||||
CREATE TABLE role_menu_permissions (
|
||||
role_id INTEGER,
|
||||
menu_id INTEGER,
|
||||
permission_id INTEGER,
|
||||
PRIMARY KEY (role_id, menu_id, permission_id),
|
||||
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (menu_id) REFERENCES menu(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
print("Table 'role_menu_permissions' créée.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Future<void> insertDefaultPermissions() async {
|
||||
final db = await database;
|
||||
final existing = await db.query('permissions');
|
||||
if (existing.isEmpty) {
|
||||
await db.insert('permissions', {'name': 'view'});
|
||||
await db.insert('permissions', {'name': 'create'});
|
||||
await db.insert('permissions', {'name': 'update'});
|
||||
await db.insert('permissions', {'name': 'delete'});
|
||||
await db.insert('permissions', {'name': 'admin'});
|
||||
await db.insert('permissions', {'name': 'manage'}); // Nouvelle permission
|
||||
await db.insert('permissions', {'name': 'read'}); // Nouvelle permission
|
||||
print("Permissions par défaut insérées");
|
||||
} else {
|
||||
// Vérifier et ajouter les nouvelles permissions si elles n'existent pas
|
||||
final newPermissions = ['manage', 'read'];
|
||||
for (var permission in newPermissions) {
|
||||
final existingPermission = await db.query('permissions', where: 'name = ?', whereArgs: [permission]);
|
||||
if (existingPermission.isEmpty) {
|
||||
await db.insert('permissions', {'name': permission});
|
||||
print("Permission ajoutée: $permission");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> insertDefaultMenus() async {
|
||||
final db = await database;
|
||||
final existingMenus = await db.query('menu');
|
||||
|
||||
if (existingMenus.isEmpty) {
|
||||
// Menus existants
|
||||
await db.insert('menu', {'name': 'Accueil', 'route': '/accueil'});
|
||||
await db.insert('menu', {'name': 'Ajouter un utilisateur', 'route': '/ajouter-utilisateur'});
|
||||
await db.insert('menu', {'name': 'Modifier/Supprimer un utilisateur', 'route': '/modifier-utilisateur'});
|
||||
await db.insert('menu', {'name': 'Ajouter un produit', 'route': '/ajouter-produit'});
|
||||
await db.insert('menu', {'name': 'Modifier/Supprimer un produit', 'route': '/modifier-produit'});
|
||||
await db.insert('menu', {'name': 'Bilan', 'route': '/bilan'});
|
||||
await db.insert('menu', {'name': 'Gérer les rôles', 'route': '/gerer-roles'});
|
||||
await db.insert('menu', {'name': 'Gestion de stock', 'route': '/gestion-stock'});
|
||||
await db.insert('menu', {'name': 'Historique', 'route': '/historique'});
|
||||
await db.insert('menu', {'name': 'Déconnexion', 'route': '/deconnexion'});
|
||||
|
||||
// Nouveaux menus ajoutés
|
||||
await db.insert('menu', {'name': 'Nouvelle commande', 'route': '/nouvelle-commande'});
|
||||
await db.insert('menu', {'name': 'Gérer les commandes', 'route': '/gerer-commandes'});
|
||||
|
||||
print("Menus par défaut insérés");
|
||||
} else {
|
||||
// Si des menus existent déjà, vérifier et ajouter les nouveaux menus manquants
|
||||
await _addMissingMenus(db);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addMissingMenus(Database db) async {
|
||||
final menusToAdd = [
|
||||
{'name': 'Nouvelle commande', 'route': '/nouvelle-commande'},
|
||||
{'name': 'Gérer les commandes', 'route': '/gerer-commandes'},
|
||||
];
|
||||
|
||||
for (var menu in menusToAdd) {
|
||||
final existing = await db.query(
|
||||
'menu',
|
||||
where: 'route = ?',
|
||||
whereArgs: [menu['route']],
|
||||
);
|
||||
|
||||
if (existing.isEmpty) {
|
||||
await db.insert('menu', menu);
|
||||
print("Menu ajouté: ${menu['name']}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> insertDefaultRoles() async {
|
||||
final db = await database;
|
||||
final existingRoles = await db.query('roles');
|
||||
|
||||
if (existingRoles.isEmpty) {
|
||||
int superAdminRoleId = await db.insert('roles', {'designation': 'Super Admin'});
|
||||
int adminRoleId = await db.insert('roles', {'designation': 'Admin'});
|
||||
int userRoleId = await db.insert('roles', {'designation': 'User'});
|
||||
|
||||
final permissions = await db.query('permissions');
|
||||
final menus = await db.query('menu');
|
||||
|
||||
// Assigner toutes les permissions à tous les menus pour le Super Admin
|
||||
for (var menu in menus) {
|
||||
for (var permission in permissions) {
|
||||
await db.insert('role_menu_permissions', {
|
||||
'role_id': superAdminRoleId,
|
||||
'menu_id': menu['id'],
|
||||
'permission_id': permission['id'],
|
||||
},
|
||||
conflictAlgorithm: ConflictAlgorithm.ignore
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Assigner quelques permissions à l'Admin et à l'User pour les nouveaux menus
|
||||
await _assignBasicPermissionsToRoles(db, adminRoleId, userRoleId);
|
||||
|
||||
print("Rôles par défaut créés et permissions assignées");
|
||||
} else {
|
||||
// Si les rôles existent déjà, vérifier et ajouter les permissions manquantes
|
||||
await _updateExistingRolePermissions(db);
|
||||
}
|
||||
}
|
||||
// Nouvelle méthode pour assigner les permissions de base aux nouveaux menus
|
||||
Future<void> _assignBasicPermissionsToRoles(Database db, int adminRoleId, int userRoleId) async {
|
||||
final viewPermission = await db.query('permissions', where: 'name = ?', whereArgs: ['view']);
|
||||
final createPermission = await db.query('permissions', where: 'name = ?', whereArgs: ['create']);
|
||||
final updatePermission = await db.query('permissions', where: 'name = ?', whereArgs: ['update']);
|
||||
final managePermission = await db.query('permissions', where: 'name = ?', whereArgs: ['manage']);
|
||||
|
||||
// Récupérer les IDs des nouveaux menus
|
||||
final nouvelleCommandeMenu = await db.query('menu', where: 'route = ?', whereArgs: ['/nouvelle-commande']);
|
||||
final gererCommandesMenu = await db.query('menu', where: 'route = ?', whereArgs: ['/gerer-commandes']);
|
||||
|
||||
if (nouvelleCommandeMenu.isNotEmpty && createPermission.isNotEmpty) {
|
||||
// Admin peut créer de nouvelles commandes
|
||||
await db.insert('role_menu_permissions', {
|
||||
'role_id': adminRoleId,
|
||||
'menu_id': nouvelleCommandeMenu.first['id'],
|
||||
'permission_id': createPermission.first['id'],
|
||||
},
|
||||
conflictAlgorithm: ConflictAlgorithm.ignore
|
||||
);
|
||||
|
||||
// User peut aussi créer de nouvelles commandes
|
||||
await db.insert('role_menu_permissions', {
|
||||
'role_id': userRoleId,
|
||||
'menu_id': nouvelleCommandeMenu.first['id'],
|
||||
'permission_id': createPermission.first['id'],
|
||||
},
|
||||
conflictAlgorithm: ConflictAlgorithm.ignore
|
||||
);
|
||||
}
|
||||
|
||||
if (gererCommandesMenu.isNotEmpty && managePermission.isNotEmpty) {
|
||||
// Admin peut gérer les commandes
|
||||
await db.insert('role_menu_permissions', {
|
||||
'role_id': adminRoleId,
|
||||
'menu_id': gererCommandesMenu.first['id'],
|
||||
'permission_id': managePermission.first['id'],
|
||||
},
|
||||
conflictAlgorithm: ConflictAlgorithm.ignore
|
||||
);
|
||||
}
|
||||
|
||||
if (gererCommandesMenu.isNotEmpty && viewPermission.isNotEmpty) {
|
||||
// User peut voir les commandes
|
||||
await db.insert('role_menu_permissions', {
|
||||
'role_id': userRoleId,
|
||||
'menu_id': gererCommandesMenu.first['id'],
|
||||
'permission_id': viewPermission.first['id'],
|
||||
}
|
||||
, conflictAlgorithm: ConflictAlgorithm.ignore
|
||||
);
|
||||
}
|
||||
}
|
||||
Future<void> _updateExistingRolePermissions(Database db) async {
|
||||
final superAdminRole = await db.query('roles', where: 'designation = ?', whereArgs: ['Super Admin']);
|
||||
if (superAdminRole.isNotEmpty) {
|
||||
final superAdminRoleId = superAdminRole.first['id'] as int;
|
||||
final permissions = await db.query('permissions');
|
||||
final menus = await db.query('menu');
|
||||
|
||||
// Vérifier et ajouter les permissions manquantes pour le Super Admin sur tous les menus
|
||||
for (var menu in menus) {
|
||||
for (var permission in permissions) {
|
||||
final existingPermission = await db.query(
|
||||
'role_menu_permissions',
|
||||
where: 'role_id = ? AND menu_id = ? AND permission_id = ?',
|
||||
whereArgs: [superAdminRoleId, menu['id'], permission['id']],
|
||||
);
|
||||
if (existingPermission.isEmpty) {
|
||||
await db.insert('role_menu_permissions', {
|
||||
'role_id': superAdminRoleId,
|
||||
'menu_id': menu['id'],
|
||||
'permission_id': permission['id'],
|
||||
},
|
||||
conflictAlgorithm: ConflictAlgorithm.ignore
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assigner les permissions de base aux autres rôles pour les nouveaux menus
|
||||
final adminRole = await db.query('roles', where: 'designation = ?', whereArgs: ['Admin']);
|
||||
final userRole = await db.query('roles', where: 'designation = ?', whereArgs: ['User']);
|
||||
|
||||
if (adminRole.isNotEmpty && userRole.isNotEmpty) {
|
||||
await _assignBasicPermissionsToRoles(db, adminRole.first['id'] as int, userRole.first['id'] as int);
|
||||
}
|
||||
|
||||
print("Permissions mises à jour pour tous les rôles");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> insertDefaultSuperAdmin() async {
|
||||
final db = await database;
|
||||
|
||||
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) {
|
||||
final superAdminRole = await db.query('roles',
|
||||
where: 'designation = ?',
|
||||
whereArgs: ['Super Admin']
|
||||
);
|
||||
|
||||
if (superAdminRole.isNotEmpty) {
|
||||
final superAdminRoleId = superAdminRole.first['id'] as int;
|
||||
|
||||
await db.insert('users', {
|
||||
'name': 'Super',
|
||||
'lastname': 'Admin',
|
||||
'email': 'superadmin@youmazgestion.com',
|
||||
'password': 'admin123',
|
||||
'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à");
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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],
|
||||
);
|
||||
}
|
||||
|
||||
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],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> assignMenuPermission(int menuId, int permissionId) async {
|
||||
final db = await database;
|
||||
await db.insert('menu_permissions', {
|
||||
'menu_id': menuId,
|
||||
'permission_id': permissionId,
|
||||
}, conflictAlgorithm: ConflictAlgorithm.ignore);
|
||||
}
|
||||
|
||||
Future<void> removeMenuPermission(int menuId, int permissionId) async {
|
||||
final db = await database;
|
||||
await db.delete(
|
||||
'menu_permissions',
|
||||
where: 'menu_id = ? AND permission_id = ?',
|
||||
whereArgs: [menuId, permissionId],
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
final isValidOldPassword = await verifyUser(username, oldPassword);
|
||||
if (!isValidOldPassword) {
|
||||
throw Exception('Ancien mot de passe incorrect');
|
||||
}
|
||||
|
||||
await db.update(
|
||||
'users',
|
||||
{'password': newPassword},
|
||||
where: 'username = ?',
|
||||
whereArgs: [username],
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> hasPermission(String username, String permissionName, String menuRoute) async {
|
||||
final db = await database;
|
||||
final result = await db.rawQuery('''
|
||||
SELECT COUNT(*) as count
|
||||
FROM permissions p
|
||||
JOIN role_menu_permissions rmp ON p.id = rmp.permission_id
|
||||
JOIN roles r ON rmp.role_id = r.id
|
||||
JOIN users u ON u.role_id = r.id
|
||||
JOIN menu m ON m.route = ?
|
||||
WHERE u.username = ? AND p.name = ? AND rmp.menu_id = m.id
|
||||
''', [menuRoute, username, permissionName]);
|
||||
|
||||
return (result.first['count'] as int) > 0;
|
||||
}
|
||||
|
||||
|
||||
Future<void> close() async {
|
||||
if (_database.isOpen) {
|
||||
await _database.close();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> printDatabaseInfo() async {
|
||||
final db = await database;
|
||||
|
||||
print("=== INFORMATIONS DE LA BASE DE DONNÉES ===");
|
||||
|
||||
final userCount = await getUserCount();
|
||||
print("Nombre d'utilisateurs: $userCount");
|
||||
|
||||
final users = await getAllUsers();
|
||||
print("Utilisateurs:");
|
||||
for (var user in users) {
|
||||
print(" - ${user.username} (${user.name} ) - Email: ${user.email}");
|
||||
}
|
||||
|
||||
final roles = await getRoles();
|
||||
print("Rôles:");
|
||||
for (var role in roles) {
|
||||
print(" - ${role.designation} (ID: ${role.id})");
|
||||
}
|
||||
|
||||
final permissions = await getAllPermissions();
|
||||
print("Permissions:");
|
||||
for (var permission in permissions) {
|
||||
print(" - ${permission.name} (ID: ${permission.id})");
|
||||
}
|
||||
|
||||
print("=========================================");
|
||||
}
|
||||
|
||||
Future<List<Permission>> getPermissionsForRoleAndMenu(int roleId, int menuId) async {
|
||||
final db = await database;
|
||||
final result = await db.rawQuery('''
|
||||
SELECT p.id, p.name
|
||||
FROM permissions p
|
||||
JOIN role_menu_permissions rmp ON p.id = rmp.permission_id
|
||||
WHERE rmp.role_id = ? AND rmp.menu_id = ?
|
||||
ORDER BY p.name ASC
|
||||
''', [roleId, menuId]);
|
||||
|
||||
return result.map((map) => Permission.fromMap(map)).toList();
|
||||
}
|
||||
// Ajoutez cette méthode temporaire pour supprimer la DB corrompue
|
||||
Future<void> deleteDatabaseFile() async {
|
||||
final documentsDirectory = await getApplicationDocumentsDirectory();
|
||||
final path = join(documentsDirectory.path, 'app_database.db');
|
||||
final file = File(path);
|
||||
if (await file.exists()) {
|
||||
await file.delete();
|
||||
print("Base de données utilisateur supprimée");
|
||||
}
|
||||
}
|
||||
Future<void> assignRoleMenuPermission(int roleId, int menuId, int permissionId) async {
|
||||
final db = await database;
|
||||
await db.insert('role_menu_permissions', {
|
||||
'role_id': roleId,
|
||||
'menu_id': menuId,
|
||||
'permission_id': permissionId,
|
||||
}, conflictAlgorithm: ConflictAlgorithm.ignore);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Future<void> removeRoleMenuPermission(int roleId, int menuId, int permissionId) async {
|
||||
final db = await database;
|
||||
await db.delete(
|
||||
'role_menu_permissions',
|
||||
where: 'role_id = ? AND menu_id = ? AND permission_id = ?',
|
||||
whereArgs: [roleId, menuId, permissionId],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
60
lib/Services/pointageDatabase.dart
Normal file
60
lib/Services/pointageDatabase.dart
Normal file
@ -0,0 +1,60 @@
|
||||
import 'dart:async';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import '../Models/pointage_model.dart';
|
||||
|
||||
class DatabaseHelper {
|
||||
static final DatabaseHelper _instance = DatabaseHelper._internal();
|
||||
|
||||
factory DatabaseHelper() => _instance;
|
||||
|
||||
DatabaseHelper._internal();
|
||||
|
||||
Database? _db;
|
||||
|
||||
Future<Database> get database async {
|
||||
if (_db != null) return _db!;
|
||||
_db = await _initDatabase();
|
||||
return _db!;
|
||||
}
|
||||
|
||||
Future<Database> _initDatabase() async {
|
||||
String databasesPath = await getDatabasesPath();
|
||||
String dbPath = join(databasesPath, 'pointage.db');
|
||||
return await openDatabase(dbPath, version: 1, onCreate: _onCreate);
|
||||
}
|
||||
|
||||
Future _onCreate(Database db, int version) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE pointages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
userName TEXT NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
heureArrivee TEXT NOT NULL,
|
||||
heureDepart TEXT NOT NULL
|
||||
)
|
||||
''');
|
||||
}
|
||||
|
||||
Future<int> insertPointage(Pointage pointage) async {
|
||||
final db = await database;
|
||||
return await db.insert('pointages', pointage.toMap());
|
||||
}
|
||||
|
||||
Future<List<Pointage>> getPointages() async {
|
||||
final db = await database;
|
||||
final pointages = await db.query('pointages');
|
||||
return pointages.map((pointage) => Pointage.fromMap(pointage)).toList();
|
||||
}
|
||||
|
||||
Future<int> updatePointage(Pointage pointage) async {
|
||||
final db = await database;
|
||||
return await db.update('pointages', pointage.toMap(),
|
||||
where: 'id = ?', whereArgs: [pointage.id]);
|
||||
}
|
||||
|
||||
Future<int> deletePointage(int id) async {
|
||||
final db = await database;
|
||||
return await db.delete('pointages', where: 'id = ?', whereArgs: [id]);
|
||||
}
|
||||
}
|
||||
@ -1,559 +0,0 @@
|
||||
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/produit.dart';
|
||||
import '../Models/client.dart';
|
||||
|
||||
|
||||
class ProductDatabase {
|
||||
static final ProductDatabase instance = ProductDatabase._init();
|
||||
late Database _database;
|
||||
|
||||
ProductDatabase._init() {
|
||||
sqfliteFfiInit();
|
||||
}
|
||||
|
||||
ProductDatabase();
|
||||
|
||||
Future<Database> get database async {
|
||||
if (_database.isOpen) return _database;
|
||||
_database = await _initDB('products2.db');
|
||||
return _database;
|
||||
}
|
||||
|
||||
Future<void> initDatabase() async {
|
||||
_database = await _initDB('products2.db');
|
||||
await _createDB(_database, 1);
|
||||
await _insertDefaultClients();
|
||||
await _insertDefaultCommandes();
|
||||
}
|
||||
|
||||
Future<Database> _initDB(String filePath) async {
|
||||
final documentsDirectory = await getApplicationDocumentsDirectory();
|
||||
final path = join(documentsDirectory.path, filePath);
|
||||
|
||||
bool dbExists = await File(path).exists();
|
||||
if (!dbExists) {
|
||||
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) {
|
||||
print('Pas de fichier DB dans assets, création 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 products (existante avec améliorations)
|
||||
if (!tableNames.contains('products')) {
|
||||
await db.execute('''
|
||||
CREATE TABLE products(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
price REAL NOT NULL,
|
||||
image TEXT,
|
||||
category TEXT NOT NULL,
|
||||
stock INTEGER NOT NULL DEFAULT 0,
|
||||
description TEXT,
|
||||
qrCode TEXT,
|
||||
reference TEXT UNIQUE
|
||||
)
|
||||
''');
|
||||
print("Table 'products' créée.");
|
||||
} else {
|
||||
// Vérifier et ajouter les colonnes manquantes
|
||||
await _updateProductsTable(db);
|
||||
}
|
||||
|
||||
// Table clients
|
||||
if (!tableNames.contains('clients')) {
|
||||
await db.execute('''
|
||||
CREATE TABLE clients(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
nom TEXT NOT NULL,
|
||||
prenom TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
telephone TEXT NOT NULL,
|
||||
adresse TEXT,
|
||||
dateCreation TEXT NOT NULL,
|
||||
actif INTEGER NOT NULL DEFAULT 1
|
||||
)
|
||||
''');
|
||||
print("Table 'clients' créée.");
|
||||
}
|
||||
|
||||
// Table commandes
|
||||
if (!tableNames.contains('commandes')) {
|
||||
await db.execute('''
|
||||
CREATE TABLE commandes(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
clientId INTEGER NOT NULL,
|
||||
dateCommande TEXT NOT NULL,
|
||||
statut INTEGER NOT NULL DEFAULT 0,
|
||||
montantTotal REAL NOT NULL,
|
||||
notes TEXT,
|
||||
dateLivraison TEXT,
|
||||
FOREIGN KEY (clientId) REFERENCES clients(id) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
print("Table 'commandes' créée.");
|
||||
}
|
||||
|
||||
// Table détails commandes
|
||||
if (!tableNames.contains('details_commandes')) {
|
||||
await db.execute('''
|
||||
CREATE TABLE details_commandes(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
commandeId INTEGER NOT NULL,
|
||||
produitId INTEGER NOT NULL,
|
||||
quantite INTEGER NOT NULL,
|
||||
prixUnitaire REAL NOT NULL,
|
||||
sousTotal REAL NOT NULL,
|
||||
FOREIGN KEY (commandeId) REFERENCES commandes(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (produitId) REFERENCES products(id) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
print("Table 'details_commandes' créée.");
|
||||
}
|
||||
|
||||
// Créer les index pour optimiser les performances
|
||||
await _createIndexes(db);
|
||||
}
|
||||
|
||||
Future<void> _updateProductsTable(Database db) async {
|
||||
final columns = await db.rawQuery('PRAGMA table_info(products)');
|
||||
final columnNames = columns.map((e) => e['name'] as String).toList();
|
||||
|
||||
if (!columnNames.contains('description')) {
|
||||
await db.execute("ALTER TABLE products ADD COLUMN description TEXT");
|
||||
print("Colonne 'description' ajoutée.");
|
||||
}
|
||||
if (!columnNames.contains('qrCode')) {
|
||||
await db.execute("ALTER TABLE products ADD COLUMN qrCode TEXT");
|
||||
print("Colonne 'qrCode' ajoutée.");
|
||||
}
|
||||
if (!columnNames.contains('reference')) {
|
||||
await db.execute("ALTER TABLE products ADD COLUMN reference TEXT");
|
||||
print("Colonne 'reference' ajoutée.");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _createIndexes(Database db) async {
|
||||
await db.execute('CREATE INDEX IF NOT EXISTS idx_products_category ON products(category)');
|
||||
await db.execute('CREATE INDEX IF NOT EXISTS idx_products_reference ON products(reference)');
|
||||
await db.execute('CREATE INDEX IF NOT EXISTS idx_commandes_client ON commandes(clientId)');
|
||||
await db.execute('CREATE INDEX IF NOT EXISTS idx_commandes_date ON commandes(dateCommande)');
|
||||
await db.execute('CREATE INDEX IF NOT EXISTS idx_details_commande ON details_commandes(commandeId)');
|
||||
print("Index créés pour optimiser les performances.");
|
||||
}
|
||||
|
||||
// =========================
|
||||
// MÉTHODES PRODUCTS (existantes)
|
||||
// =========================
|
||||
Future<int> createProduct(Product product) async {
|
||||
final db = await database;
|
||||
return await db.insert('products', product.toMap());
|
||||
}
|
||||
|
||||
Future<List<Product>> getProducts() async {
|
||||
final db = await database;
|
||||
final maps = await db.query('products', orderBy: 'name ASC');
|
||||
return List.generate(maps.length, (i) {
|
||||
return Product.fromMap(maps[i]);
|
||||
});
|
||||
}
|
||||
|
||||
Future<int> updateProduct(Product product) async {
|
||||
final db = await database;
|
||||
return await db.update(
|
||||
'products',
|
||||
product.toMap(),
|
||||
where: 'id = ?',
|
||||
whereArgs: [product.id],
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> deleteProduct(int? id) async {
|
||||
final db = await database;
|
||||
return await db.delete(
|
||||
'products',
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<String>> getCategories() async {
|
||||
final db = await database;
|
||||
final result = await db.rawQuery('SELECT DISTINCT category FROM products ORDER BY category');
|
||||
return List.generate(
|
||||
result.length, (index) => result[index]['category'] as String);
|
||||
}
|
||||
|
||||
Future<List<Product>> getProductsByCategory(String category) async {
|
||||
final db = await database;
|
||||
final maps = await db
|
||||
.query('products', where: 'category = ?', whereArgs: [category], orderBy: 'name ASC');
|
||||
return List.generate(maps.length, (i) {
|
||||
return Product.fromMap(maps[i]);
|
||||
});
|
||||
}
|
||||
|
||||
Future<int> updateStock(int id, int stock) async {
|
||||
final db = await database;
|
||||
return await db
|
||||
.rawUpdate('UPDATE products SET stock = ? WHERE id = ?', [stock, id]);
|
||||
}
|
||||
|
||||
Future<Product?> getProductByReference(String reference) async {
|
||||
final db = await database;
|
||||
final maps = await db.query(
|
||||
'products',
|
||||
where: 'reference = ?',
|
||||
whereArgs: [reference],
|
||||
);
|
||||
|
||||
if (maps.isNotEmpty) {
|
||||
return Product.fromMap(maps.first);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// =========================
|
||||
// MÉTHODES CLIENTS
|
||||
// =========================
|
||||
Future<int> createClient(Client client) async {
|
||||
final db = await database;
|
||||
return await db.insert('clients', client.toMap());
|
||||
}
|
||||
|
||||
Future<List<Client>> getClients() async {
|
||||
final db = await database;
|
||||
final maps = await db.query('clients', where: 'actif = 1', orderBy: 'nom ASC, prenom ASC');
|
||||
return List.generate(maps.length, (i) {
|
||||
return Client.fromMap(maps[i]);
|
||||
});
|
||||
}
|
||||
|
||||
Future<Client?> getClientById(int id) async {
|
||||
final db = await database;
|
||||
final maps = await db.query('clients', where: 'id = ?', whereArgs: [id]);
|
||||
if (maps.isNotEmpty) {
|
||||
return Client.fromMap(maps.first);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<int> updateClient(Client client) async {
|
||||
final db = await database;
|
||||
return await db.update(
|
||||
'clients',
|
||||
client.toMap(),
|
||||
where: 'id = ?',
|
||||
whereArgs: [client.id],
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> deleteClient(int id) async {
|
||||
final db = await database;
|
||||
// Soft delete
|
||||
return await db.update(
|
||||
'clients',
|
||||
{'actif': 0},
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<Client>> searchClients(String query) async {
|
||||
final db = await database;
|
||||
final maps = await db.query(
|
||||
'clients',
|
||||
where: 'actif = 1 AND (nom LIKE ? OR prenom LIKE ? OR email LIKE ?)',
|
||||
whereArgs: ['%$query%', '%$query%', '%$query%'],
|
||||
orderBy: 'nom ASC, prenom ASC',
|
||||
);
|
||||
return List.generate(maps.length, (i) {
|
||||
return Client.fromMap(maps[i]);
|
||||
});
|
||||
}
|
||||
|
||||
// =========================
|
||||
// MÉTHODES COMMANDES
|
||||
// =========================
|
||||
Future<int> createCommande(Commande commande) async {
|
||||
final db = await database;
|
||||
return await db.insert('commandes', commande.toMap());
|
||||
}
|
||||
|
||||
Future<List<Commande>> getCommandes() async {
|
||||
final db = await database;
|
||||
final maps = await db.rawQuery('''
|
||||
SELECT c.*, cl.nom as clientNom, cl.prenom as clientPrenom, cl.email as clientEmail
|
||||
FROM commandes c
|
||||
LEFT JOIN clients cl ON c.clientId = cl.id
|
||||
ORDER BY c.dateCommande DESC
|
||||
''');
|
||||
return List.generate(maps.length, (i) {
|
||||
return Commande.fromMap(maps[i]);
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<Commande>> getCommandesByClient(int clientId) async {
|
||||
final db = await database;
|
||||
final maps = await db.rawQuery('''
|
||||
SELECT c.*, cl.nom as clientNom, cl.prenom as clientPrenom, cl.email as clientEmail
|
||||
FROM commandes c
|
||||
LEFT JOIN clients cl ON c.clientId = cl.id
|
||||
WHERE c.clientId = ?
|
||||
ORDER BY c.dateCommande DESC
|
||||
''', [clientId]);
|
||||
return List.generate(maps.length, (i) {
|
||||
return Commande.fromMap(maps[i]);
|
||||
});
|
||||
}
|
||||
|
||||
Future<Commande?> getCommandeById(int id) async {
|
||||
final db = await database;
|
||||
final maps = await db.rawQuery('''
|
||||
SELECT c.*, cl.nom as clientNom, cl.prenom as clientPrenom, cl.email as clientEmail
|
||||
FROM commandes c
|
||||
LEFT JOIN clients cl ON c.clientId = cl.id
|
||||
WHERE c.id = ?
|
||||
''', [id]);
|
||||
if (maps.isNotEmpty) {
|
||||
return Commande.fromMap(maps.first);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<int> updateCommande(Commande commande) async {
|
||||
final db = await database;
|
||||
return await db.update(
|
||||
'commandes',
|
||||
commande.toMap(),
|
||||
where: 'id = ?',
|
||||
whereArgs: [commande.id],
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> updateStatutCommande(int commandeId, StatutCommande statut) async {
|
||||
final db = await database;
|
||||
return await db.update(
|
||||
'commandes',
|
||||
{'statut': statut.index},
|
||||
where: 'id = ?',
|
||||
whereArgs: [commandeId],
|
||||
);
|
||||
}
|
||||
|
||||
// =========================
|
||||
// MÉTHODES DÉTAILS COMMANDES
|
||||
// =========================
|
||||
Future<int> createDetailCommande(DetailCommande detail) async {
|
||||
final db = await database;
|
||||
return await db.insert('details_commandes', detail.toMap());
|
||||
}
|
||||
|
||||
Future<List<DetailCommande>> getDetailsCommande(int commandeId) async {
|
||||
final db = await database;
|
||||
final maps = await db.rawQuery('''
|
||||
SELECT dc.*, p.name as produitNom, p.image as produitImage, p.reference as produitReference
|
||||
FROM details_commandes dc
|
||||
LEFT JOIN products p ON dc.produitId = p.id
|
||||
WHERE dc.commandeId = ?
|
||||
ORDER BY dc.id
|
||||
''', [commandeId]);
|
||||
return List.generate(maps.length, (i) {
|
||||
return DetailCommande.fromMap(maps[i]);
|
||||
});
|
||||
}
|
||||
|
||||
// =========================
|
||||
// MÉTHODES TRANSACTION COMPLÈTE
|
||||
// =========================
|
||||
Future<int> createCommandeComplete(Client client, Commande commande, List<DetailCommande> details) async {
|
||||
final db = await database;
|
||||
|
||||
return await db.transaction((txn) async {
|
||||
// Créer le client
|
||||
final clientId = await txn.insert('clients', client.toMap());
|
||||
|
||||
// Créer la commande
|
||||
final commandeMap = commande.toMap();
|
||||
commandeMap['clientId'] = clientId;
|
||||
final commandeId = await txn.insert('commandes', commandeMap);
|
||||
|
||||
// Créer les détails et mettre à jour le stock
|
||||
for (var detail in details) {
|
||||
final detailMap = detail.toMap();
|
||||
detailMap['commandeId'] = commandeId; // Ajoute l'ID de la commande
|
||||
await txn.insert('details_commandes', detailMap);
|
||||
|
||||
// Mettre à jour le stock du produit
|
||||
await txn.rawUpdate(
|
||||
'UPDATE products SET stock = stock - ? WHERE id = ?',
|
||||
[detail.quantite, detail.produitId],
|
||||
);
|
||||
}
|
||||
|
||||
return commandeId;
|
||||
});
|
||||
}
|
||||
|
||||
// =========================
|
||||
// STATISTIQUES
|
||||
// =========================
|
||||
Future<Map<String, dynamic>> getStatistiques() async {
|
||||
final db = await database;
|
||||
|
||||
final totalClients = await db.rawQuery('SELECT COUNT(*) as count FROM clients WHERE actif = 1');
|
||||
final totalCommandes = await db.rawQuery('SELECT COUNT(*) as count FROM commandes');
|
||||
final totalProduits = await db.rawQuery('SELECT COUNT(*) as count FROM products');
|
||||
final chiffreAffaires = await db.rawQuery('SELECT SUM(montantTotal) as total FROM commandes WHERE statut != 5'); // 5 = annulée
|
||||
|
||||
return {
|
||||
'totalClients': totalClients.first['count'],
|
||||
'totalCommandes': totalCommandes.first['count'],
|
||||
'totalProduits': totalProduits.first['count'],
|
||||
'chiffreAffaires': chiffreAffaires.first['total'] ?? 0.0,
|
||||
};
|
||||
}
|
||||
|
||||
// =========================
|
||||
// DONNÉES PAR DÉFAUT
|
||||
// =========================
|
||||
Future<void> _insertDefaultClients() async {
|
||||
final db = await database;
|
||||
final existingClients = await db.query('clients');
|
||||
|
||||
if (existingClients.isEmpty) {
|
||||
final defaultClients = [
|
||||
Client(
|
||||
nom: 'Dupont',
|
||||
prenom: 'Jean',
|
||||
email: 'jean.dupont@email.com',
|
||||
telephone: '0123456789',
|
||||
adresse: '123 Rue de la Paix, Paris',
|
||||
dateCreation: DateTime.now(),
|
||||
),
|
||||
Client(
|
||||
nom: 'Martin',
|
||||
prenom: 'Marie',
|
||||
email: 'marie.martin@email.com',
|
||||
telephone: '0987654321',
|
||||
adresse: '456 Avenue des Champs, Lyon',
|
||||
dateCreation: DateTime.now(),
|
||||
),
|
||||
Client(
|
||||
nom: 'Bernard',
|
||||
prenom: 'Pierre',
|
||||
email: 'pierre.bernard@email.com',
|
||||
telephone: '0456789123',
|
||||
adresse: '789 Boulevard Saint-Michel, Marseille',
|
||||
dateCreation: DateTime.now(),
|
||||
),
|
||||
];
|
||||
|
||||
for (var client in defaultClients) {
|
||||
await db.insert('clients', client.toMap());
|
||||
}
|
||||
print("Clients par défaut insérés");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _insertDefaultCommandes() async {
|
||||
final db = await database;
|
||||
final existingCommandes = await db.query('commandes');
|
||||
|
||||
if (existingCommandes.isEmpty) {
|
||||
// Récupérer quelques produits pour créer des commandes
|
||||
final produits = await db.query('products', limit: 3);
|
||||
final clients = await db.query('clients', limit: 3);
|
||||
|
||||
if (produits.isNotEmpty && clients.isNotEmpty) {
|
||||
// Commande 1
|
||||
final commande1Id = await db.insert('commandes', {
|
||||
'clientId': clients[0]['id'],
|
||||
'dateCommande': DateTime.now().subtract(Duration(days: 5)).toIso8601String(),
|
||||
'statut': StatutCommande.livree.index,
|
||||
'montantTotal': 150.0,
|
||||
'notes': 'Commande urgente',
|
||||
});
|
||||
|
||||
await db.insert('details_commandes', {
|
||||
'commandeId': commande1Id,
|
||||
'produitId': produits[0]['id'],
|
||||
'quantite': 2,
|
||||
'prixUnitaire': 75.0,
|
||||
'sousTotal': 150.0,
|
||||
});
|
||||
|
||||
// Commande 2
|
||||
final commande2Id = await db.insert('commandes', {
|
||||
'clientId': clients[1]['id'],
|
||||
'dateCommande': DateTime.now().subtract(Duration(days: 2)).toIso8601String(),
|
||||
'statut': StatutCommande.enPreparation.index,
|
||||
'montantTotal': 225.0,
|
||||
'notes': 'Livraison prévue demain',
|
||||
});
|
||||
|
||||
if (produits.length > 1) {
|
||||
await db.insert('details_commandes', {
|
||||
'commandeId': commande2Id,
|
||||
'produitId': produits[1]['id'],
|
||||
'quantite': 3,
|
||||
'prixUnitaire': 75.0,
|
||||
'sousTotal': 225.0,
|
||||
});
|
||||
}
|
||||
|
||||
// Commande 3
|
||||
final commande3Id = await db.insert('commandes', {
|
||||
'clientId': clients[2]['id'],
|
||||
'dateCommande': DateTime.now().subtract(Duration(hours: 6)).toIso8601String(),
|
||||
'statut': StatutCommande.confirmee.index,
|
||||
'montantTotal': 300.0,
|
||||
'notes': 'Commande standard',
|
||||
});
|
||||
|
||||
if (produits.length > 2) {
|
||||
await db.insert('details_commandes', {
|
||||
'commandeId': commande3Id,
|
||||
'produitId': produits[2]['id'],
|
||||
'quantite': 4,
|
||||
'prixUnitaire': 75.0,
|
||||
'sousTotal': 300.0,
|
||||
});
|
||||
}
|
||||
|
||||
print("Commandes par défaut insérées");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
if (_database.isOpen) {
|
||||
await _database.close();
|
||||
}
|
||||
}
|
||||
// Ajoutez cette méthode temporaire pour supprimer la DB corrompue
|
||||
Future<void> deleteDatabaseFile() async {
|
||||
final documentsDirectory = await getApplicationDocumentsDirectory();
|
||||
final path = join(documentsDirectory.path, 'products2.db');
|
||||
final file = File(path);
|
||||
if (await file.exists()) {
|
||||
await file.delete();
|
||||
print("Base de données product supprimée");
|
||||
}
|
||||
}
|
||||
}
|
||||
1508
lib/Services/stock_managementDatabase.dart
Normal file
1508
lib/Services/stock_managementDatabase.dart
Normal file
File diff suppressed because it is too large
Load Diff
1205
lib/Views/Dashboard.dart
Normal file
1205
lib/Views/Dashboard.dart
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:youmazgestion/Components/app_bar.dart';
|
||||
import 'package:youmazgestion/Models/Permission.dart';
|
||||
import 'package:youmazgestion/Services/app_database.dart';
|
||||
//import 'package:youmazgestion/Models/Permission.dart';
|
||||
//import 'package:youmazgestion/Services/app_database.dart';
|
||||
import 'package:youmazgestion/Models/role.dart';
|
||||
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
|
||||
import 'package:youmazgestion/Views/RolePermissionPage.dart';
|
||||
|
||||
class RoleListPage extends StatefulWidget {
|
||||
@ -47,7 +48,7 @@ class _RoleListPageState extends State<RoleListPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const CustomAppBar(title: "Gestion des rôles"),
|
||||
appBar: CustomAppBar(title: "Gestion des rôles"),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:youmazgestion/Components/app_bar.dart';
|
||||
import 'package:youmazgestion/Models/Permission.dart';
|
||||
import 'package:youmazgestion/Services/app_database.dart';
|
||||
|
||||
import 'package:youmazgestion/Models/role.dart';
|
||||
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
|
||||
|
||||
class RolePermissionsPage extends StatefulWidget {
|
||||
final Role role;
|
||||
|
||||
@ -29,7 +29,7 @@ class _BilanMoisState extends State<BilanMois> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const CustomAppBar(title: 'Bilan du mois'),
|
||||
appBar: CustomAppBar(title: 'Bilan du mois'),
|
||||
body: Column(
|
||||
children: [
|
||||
// Les 3 cartes en haut
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
|
||||
|
||||
import '../Models/produit.dart';
|
||||
import '../Services/productDatabase.dart';
|
||||
//import '../Services/productDatabase.dart';
|
||||
import 'gestionProduct.dart';
|
||||
|
||||
class EditProductPage extends StatelessWidget {
|
||||
@ -31,7 +32,7 @@ class EditProductPage extends StatelessWidget {
|
||||
category: category,
|
||||
);
|
||||
|
||||
await ProductDatabase.instance.updateProduct(updatedProduct);
|
||||
await AppDatabase.instance.updateProduct(updatedProduct);
|
||||
|
||||
Get.to(GestionProduit());
|
||||
} else {
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:youmazgestion/Models/users.dart';
|
||||
import 'package:youmazgestion/Models/role.dart';
|
||||
import '../Services/app_database.dart';
|
||||
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
|
||||
//import '../Services/app_database.dart';
|
||||
|
||||
class EditUserPage extends StatefulWidget {
|
||||
final Users user;
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:youmazgestion/Components/app_bar.dart';
|
||||
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
|
||||
import '../Components/appDrawer.dart';
|
||||
import '../Models/produit.dart';
|
||||
import '../Services/productDatabase.dart';
|
||||
// import '../Services/productDatabase.dart';
|
||||
import 'editProduct.dart';
|
||||
import 'dart:io';
|
||||
|
||||
class GestionProduit extends StatelessWidget {
|
||||
final ProductDatabase _productDatabase = ProductDatabase.instance;
|
||||
final AppDatabase _productDatabase = AppDatabase.instance;
|
||||
|
||||
GestionProduit({super.key});
|
||||
|
||||
@ -17,7 +18,7 @@ class GestionProduit extends StatelessWidget {
|
||||
final screenWidth = MediaQuery.of(context).size.width * 0.8;
|
||||
|
||||
return Scaffold(
|
||||
appBar: const CustomAppBar(title: 'Gestion des produits'),
|
||||
appBar: CustomAppBar(title: 'Gestion des produits'),
|
||||
drawer: CustomDrawer(),
|
||||
body: FutureBuilder<List<Product>>(
|
||||
future: _productDatabase.getProducts(),
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:youmazgestion/Components/app_bar.dart';
|
||||
import 'package:youmazgestion/Models/Permission.dart';
|
||||
import 'package:youmazgestion/Services/app_database.dart';
|
||||
//import 'package:youmazgestion/Services/app_database.dart';
|
||||
import 'package:youmazgestion/Models/role.dart';
|
||||
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
|
||||
|
||||
class HandleUserRole extends StatefulWidget {
|
||||
const HandleUserRole({super.key});
|
||||
@ -83,7 +84,7 @@ class _HandleUserRoleState extends State<HandleUserRole> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const CustomAppBar(title: "Gestion des rôles"),
|
||||
appBar: CustomAppBar(title: "Gestion des rôles"),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
|
||||
@ -2,9 +2,10 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_core/src/get_main.dart';
|
||||
import 'package:youmazgestion/Models/produit.dart';
|
||||
import 'package:youmazgestion/Services/productDatabase.dart';
|
||||
//import 'package:youmazgestion/Services/productDatabase.dart';
|
||||
import 'package:youmazgestion/Components/app_bar.dart';
|
||||
import 'package:youmazgestion/Components/appDrawer.dart';
|
||||
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
|
||||
|
||||
class GestionStockPage extends StatefulWidget {
|
||||
const GestionStockPage({super.key});
|
||||
@ -14,7 +15,7 @@ class GestionStockPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _GestionStockPageState extends State<GestionStockPage> {
|
||||
final ProductDatabase _database = ProductDatabase.instance;
|
||||
final AppDatabase _database = AppDatabase.instance;
|
||||
List<Product> _products = [];
|
||||
List<Product> _filteredProducts = [];
|
||||
String? _selectedCategory;
|
||||
@ -79,7 +80,7 @@ class _GestionStockPageState extends State<GestionStockPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const CustomAppBar(title: 'Gestion des Stocks'),
|
||||
appBar: CustomAppBar(title: 'Gestion des Stocks'),
|
||||
drawer: CustomDrawer(),
|
||||
body: Column(
|
||||
children: [
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -32,7 +32,7 @@ class HistoryDetailPage extends StatelessWidget {
|
||||
init: controller,
|
||||
builder: (controller) {
|
||||
return Scaffold(
|
||||
appBar: const CustomAppBar(title: 'Historique de la journée'),
|
||||
appBar: CustomAppBar(title: 'Historique de la journée'),
|
||||
body: Column(
|
||||
children: [
|
||||
Padding(
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:youmazgestion/Models/users.dart';
|
||||
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
|
||||
import '../Components/app_bar.dart';
|
||||
import '../Services/app_database.dart';
|
||||
//import '../Services/app_database.dart';
|
||||
import 'editUser.dart';
|
||||
|
||||
class ListUserPage extends StatefulWidget {
|
||||
@ -35,7 +36,7 @@ class _ListUserPageState extends State<ListUserPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const CustomAppBar(title: 'Liste des utilisateurs'),
|
||||
appBar: CustomAppBar(title: 'Liste des utilisateurs'),
|
||||
body: ListView.builder(
|
||||
itemCount: userList.length,
|
||||
itemBuilder: (context, index) {
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
|
||||
import 'package:youmazgestion/Views/Dashboard.dart';
|
||||
import 'package:youmazgestion/Views/mobilepage.dart';
|
||||
import 'package:youmazgestion/Views/particles.dart' show ParticleBackground;
|
||||
import 'package:youmazgestion/accueil.dart';
|
||||
import 'package:youmazgestion/Services/app_database.dart';
|
||||
|
||||
import '../Models/users.dart';
|
||||
import '../controller/userController.dart';
|
||||
|
||||
@ -34,19 +34,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
void checkUserCount() async {
|
||||
try {
|
||||
final userCount = await AppDatabase.instance.getUserCount();
|
||||
print('Nombre d\'utilisateurs trouvés: $userCount'); // Debug
|
||||
|
||||
// Commentez cette partie pour permettre le login même sans utilisateurs
|
||||
/*
|
||||
if (userCount == 0) {
|
||||
if (mounted) {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const AccueilPage()),
|
||||
);
|
||||
}
|
||||
}
|
||||
*/
|
||||
print('Nombre d\'utilisateurs trouvés: $userCount');
|
||||
} catch (error) {
|
||||
print('Erreur lors de la vérification du nombre d\'utilisateurs: $error');
|
||||
setState(() {
|
||||
@ -65,11 +53,13 @@ class _LoginPageState extends State<LoginPage> {
|
||||
|
||||
Future<void> saveUserData(Users user, String role, int userId) async {
|
||||
try {
|
||||
// ✅ CORRECTION : Utiliser la nouvelle méthode du contrôleur
|
||||
// Le contrôleur se charge maintenant de tout (observable + SharedPreferences)
|
||||
userController.setUserWithCredentials(user, role, userId);
|
||||
|
||||
print('Utilisateur sauvegardé: ${user.username}, rôle: $role, id: $userId');
|
||||
if (user.pointDeVenteId != null) {
|
||||
await userController.loadPointDeVenteDesignation();
|
||||
}
|
||||
|
||||
print('Utilisateur sauvegardé avec point de vente: ${userController.pointDeVenteDesignation}');
|
||||
} catch (error) {
|
||||
print('Erreur lors de la sauvegarde: $error');
|
||||
throw Exception('Erreur lors de la sauvegarde des données utilisateur');
|
||||
@ -82,10 +72,10 @@ class _LoginPageState extends State<LoginPage> {
|
||||
final String username = _usernameController.text.trim();
|
||||
final String password = _passwordController.text.trim();
|
||||
|
||||
// Validation basique
|
||||
if (username.isEmpty || password.isEmpty) {
|
||||
setState(() {
|
||||
_errorMessage = 'Veuillez saisir le nom d\'utilisateur et le mot de passe';
|
||||
_errorMessage =
|
||||
'Veuillez saisir le nom d\'utilisateur et le mot de passe';
|
||||
_isErrorVisible = true;
|
||||
});
|
||||
return;
|
||||
@ -98,11 +88,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
|
||||
try {
|
||||
print('Tentative de connexion pour: $username');
|
||||
|
||||
// Vérification de la connexion à la base de données
|
||||
final dbInstance = AppDatabase.instance;
|
||||
|
||||
// Test de connexion à la base
|
||||
try {
|
||||
final userCount = await dbInstance.getUserCount();
|
||||
print('Base de données accessible, $userCount utilisateurs trouvés');
|
||||
@ -110,16 +97,12 @@ class _LoginPageState extends State<LoginPage> {
|
||||
throw Exception('Impossible d\'accéder à la base de données: $dbError');
|
||||
}
|
||||
|
||||
// Vérifier les identifiants
|
||||
bool isValidUser = await dbInstance.verifyUser(username, password);
|
||||
print('Résultat de la vérification: $isValidUser');
|
||||
|
||||
|
||||
if (isValidUser) {
|
||||
// Récupérer les informations complètes de l'utilisateur
|
||||
Users user = await dbInstance.getUser(username);
|
||||
print('Utilisateur récupéré: ${user.username}');
|
||||
|
||||
// Récupérer les credentials
|
||||
Map<String, dynamic>? userCredentials =
|
||||
await dbInstance.getUserCredentials(username, password);
|
||||
|
||||
@ -128,163 +111,224 @@ class _LoginPageState extends State<LoginPage> {
|
||||
print('Rôle: ${userCredentials['role']}');
|
||||
print('ID: ${userCredentials['id']}');
|
||||
|
||||
// ✅ CORRECTION : Sauvegarder ET mettre à jour le contrôleur
|
||||
await saveUserData(
|
||||
user,
|
||||
userCredentials['role'] as String,
|
||||
userCredentials['id'] as int,
|
||||
);
|
||||
|
||||
// Navigation
|
||||
// MODIFICATION PRINCIPALE ICI
|
||||
if (mounted) {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const AccueilPage()),
|
||||
);
|
||||
if (userCredentials['role'] == 'commercial') {
|
||||
// Redirection vers MainLayout pour les commerciaux
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const MainLayout()),
|
||||
);
|
||||
} else {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => DashboardPage()),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw Exception('Erreur lors de la récupération des credentials');
|
||||
}
|
||||
} else {
|
||||
print('Identifiants invalides pour: $username');
|
||||
setState(() {
|
||||
_errorMessage = 'Nom d\'utilisateur ou mot de passe invalide';
|
||||
_isErrorVisible = true;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
print('Erreur lors de la connexion: $error');
|
||||
setState(() {
|
||||
_errorMessage = 'Erreur de connexion: ${error.toString()}';
|
||||
_isErrorVisible = true;
|
||||
});
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
if (mounted) setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color primaryBlue = const Color(0xFF0033A1);
|
||||
final Color accentRed = const Color(0xFFD70000);
|
||||
final Color secondaryBlue = const Color(0xFF1976D2);
|
||||
final Color primaryColor = primaryBlue;
|
||||
final Color accentColor = secondaryBlue;
|
||||
final Color cardColor = Colors.white;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text(
|
||||
'Login',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
backgroundColor: const Color.fromARGB(255, 4, 54, 95),
|
||||
centerTitle: true,
|
||||
),
|
||||
backgroundColor: primaryColor,
|
||||
body: ParticleBackground(
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.5,
|
||||
height: MediaQuery.of(context).size.height * 0.8,
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: const Icon(
|
||||
Icons.lock_outline,
|
||||
size: 100.0,
|
||||
color: Color.fromARGB(255, 4, 54, 95),
|
||||
child: SingleChildScrollView(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width < 500
|
||||
? double.infinity
|
||||
: 400,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 24.0, vertical: 32.0),
|
||||
decoration: BoxDecoration(
|
||||
color: cardColor.withOpacity(0.98),
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: primaryColor.withOpacity(0.2),
|
||||
blurRadius: 16,
|
||||
spreadRadius: 4,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
controller: _usernameController,
|
||||
enabled: !_isLoading,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Username',
|
||||
prefixIcon: const Icon(Icons.person, color: Colors.blueAccent),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
TextField(
|
||||
controller: _passwordController,
|
||||
enabled: !_isLoading,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Password',
|
||||
prefixIcon: const Icon(Icons.lock, color: Colors.redAccent),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
),
|
||||
),
|
||||
obscureText: true,
|
||||
onSubmitted: (_) => _login(),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
Visibility(
|
||||
visible: _isErrorVisible,
|
||||
child: Text(
|
||||
_errorMessage,
|
||||
style: const TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 14,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
ElevatedButton(
|
||||
onPressed: _isLoading ? null : _login,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF0015B7),
|
||||
elevation: 5.0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
),
|
||||
minimumSize: const Size(double.infinity, 48),
|
||||
),
|
||||
child: _isLoading
|
||||
? const SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
strokeWidth: 2,
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 38,
|
||||
backgroundColor: accentColor.withOpacity(0.15),
|
||||
child: Icon(
|
||||
Icons.lock_outline,
|
||||
color: accentColor,
|
||||
size: 50,
|
||||
),
|
||||
)
|
||||
: const Text(
|
||||
'Se connecter',
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Text(
|
||||
'GUYCOM',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
color: primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 28,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Connectez-vous à votre compte',
|
||||
style: TextStyle(
|
||||
color: primaryColor.withOpacity(.8),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Bouton de debug (à supprimer en production)
|
||||
if (_isErrorVisible)
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
try {
|
||||
final count = await AppDatabase.instance.getUserCount();
|
||||
print('Debug: $count utilisateurs dans la base');
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('$count utilisateurs trouvés')),
|
||||
);
|
||||
} catch (e) {
|
||||
print('Debug error: $e');
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Erreur: $e')),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text('Debug: Vérifier BDD'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 24),
|
||||
TextField(
|
||||
controller: _usernameController,
|
||||
enabled: !_isLoading,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Nom d\'utilisateur',
|
||||
labelStyle: TextStyle(
|
||||
color: primaryColor.withOpacity(0.7),
|
||||
),
|
||||
prefixIcon: Icon(Icons.person, color: accentColor),
|
||||
filled: true,
|
||||
fillColor: accentColor.withOpacity(0.045),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
borderSide: BorderSide(color: accentColor, width: 2),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
borderSide: BorderSide(color: accentColor, width: 2),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18.0),
|
||||
TextField(
|
||||
controller: _passwordController,
|
||||
enabled: !_isLoading,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Mot de passe',
|
||||
labelStyle: TextStyle(
|
||||
color: primaryColor.withOpacity(0.7),
|
||||
),
|
||||
prefixIcon: Icon(Icons.lock, color: accentColor),
|
||||
filled: true,
|
||||
fillColor: accentColor.withOpacity(0.045),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
borderSide: BorderSide(color: accentColor, width: 2),
|
||||
),
|
||||
),
|
||||
onSubmitted: (_) => _login(),
|
||||
),
|
||||
if (_isErrorVisible) ...[
|
||||
const SizedBox(height: 12.0),
|
||||
Text(
|
||||
_errorMessage,
|
||||
style: const TextStyle(
|
||||
color: Colors.redAccent,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 26.0),
|
||||
ElevatedButton(
|
||||
onPressed: _isLoading ? null : _login,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: accentColor,
|
||||
disabledBackgroundColor: accentColor.withOpacity(0.3),
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 7.0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
),
|
||||
minimumSize: const Size(double.infinity, 52),
|
||||
),
|
||||
child: _isLoading
|
||||
? const SizedBox(
|
||||
height: 24,
|
||||
width: 24,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
strokeWidth: 2.5,
|
||||
),
|
||||
)
|
||||
: const Text(
|
||||
'Se connecter',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: .4,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Option debug, à enlever en prod
|
||||
if (_isErrorVisible) ...[
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
try {
|
||||
final count =
|
||||
await AppDatabase.instance.getUserCount();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('$count utilisateurs trouvés')),
|
||||
);
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Erreur: $e')),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text('Debug: Vérifier BDD'),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
1238
lib/Views/mobilepage.dart
Normal file
1238
lib/Views/mobilepage.dart
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
190
lib/Views/pointage.dart
Normal file
190
lib/Views/pointage.dart
Normal file
@ -0,0 +1,190 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:youmazgestion/Services/pointageDatabase.dart';
|
||||
import 'package:youmazgestion/Models/pointage_model.dart';
|
||||
|
||||
class PointagePage extends StatefulWidget {
|
||||
const PointagePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<PointagePage> createState() => _PointagePageState();
|
||||
}
|
||||
|
||||
class _PointagePageState extends State<PointagePage> {
|
||||
final DatabaseHelper _databaseHelper = DatabaseHelper();
|
||||
List<Pointage> _pointages = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadPointages();
|
||||
}
|
||||
|
||||
Future<void> _loadPointages() async {
|
||||
final pointages = await _databaseHelper.getPointages();
|
||||
setState(() {
|
||||
_pointages = pointages;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _showAddDialog() async {
|
||||
final _arrivalController = TextEditingController();
|
||||
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text('Ajouter Pointage'),
|
||||
content: TextField(
|
||||
controller: _arrivalController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Heure d\'arrivée (HH:mm)',
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('Annuler'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
ElevatedButton(
|
||||
child: Text('Ajouter'),
|
||||
onPressed: () async {
|
||||
final pointage = Pointage(
|
||||
userName:
|
||||
"Nom de l'utilisateur", // fixed value, customize if needed
|
||||
date: DateTime.now().toString().split(' ')[0],
|
||||
heureArrivee: _arrivalController.text,
|
||||
heureDepart: '',
|
||||
);
|
||||
await _databaseHelper.insertPointage(pointage);
|
||||
Navigator.of(context).pop();
|
||||
_loadPointages();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _scanQRCode({required bool isEntree}) {
|
||||
// Ici tu peux intégrer ton scanner QR.
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(isEntree ? "Scan QR pour Entrée" : "Scan QR pour Sortie"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Pointage'),
|
||||
),
|
||||
body: _pointages.isEmpty
|
||||
? Center(child: Text('Aucun pointage enregistré.'))
|
||||
: ListView.builder(
|
||||
itemCount: _pointages.length,
|
||||
itemBuilder: (context, index) {
|
||||
final pointage = _pointages[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0, vertical: 6.0),
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: BorderSide(color: Colors.blueGrey.shade100),
|
||||
),
|
||||
elevation: 4,
|
||||
shadowColor: Colors.blueGrey.shade50,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0, vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: Colors.blue.shade100,
|
||||
child: Icon(Icons.person, color: Colors.blue),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
pointage
|
||||
.userName, // suppose non-null (corrige si null possible)
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Divider(),
|
||||
Text(
|
||||
pointage.date,
|
||||
style: const TextStyle(
|
||||
color: Colors.black87, fontSize: 15),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.login,
|
||||
size: 18, color: Colors.green.shade700),
|
||||
const SizedBox(width: 6),
|
||||
Text("Arrivée : ${pointage.heureArrivee}",
|
||||
style:
|
||||
TextStyle(color: Colors.green.shade700)),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.logout,
|
||||
size: 18, color: Colors.red.shade700),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
"Départ : ${pointage.heureDepart.isNotEmpty ? pointage.heureDepart : "---"}",
|
||||
style: TextStyle(color: Colors.red.shade700)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
floatingActionButton: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
FloatingActionButton.extended(
|
||||
onPressed: () => _scanQRCode(isEntree: true),
|
||||
label: Text('Entrée'),
|
||||
icon: Icon(Icons.qr_code_scanner, color: Colors.green),
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.green,
|
||||
heroTag: 'btnEntree',
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
FloatingActionButton.extended(
|
||||
onPressed: () => _scanQRCode(isEntree: false),
|
||||
label: Text('Sortie'),
|
||||
icon: Icon(Icons.qr_code_scanner, color: Colors.red),
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.red,
|
||||
heroTag: 'btnSortie',
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
FloatingActionButton(
|
||||
onPressed: _showAddDialog,
|
||||
tooltip: 'Ajouter Pointage',
|
||||
child: const Icon(Icons.add),
|
||||
heroTag: 'btnAdd',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@ import 'package:youmazgestion/Models/produit.dart';
|
||||
|
||||
class ProductCard extends StatefulWidget {
|
||||
final Product product;
|
||||
final void Function(Product, int) onAddToCart;
|
||||
final void Function(Product, int) onAddToCart;
|
||||
|
||||
const ProductCard({
|
||||
Key? key,
|
||||
@ -16,7 +16,8 @@ class ProductCard extends StatefulWidget {
|
||||
State<ProductCard> createState() => _ProductCardState();
|
||||
}
|
||||
|
||||
class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin {
|
||||
class _ProductCardState extends State<ProductCard>
|
||||
with TickerProviderStateMixin {
|
||||
int selectedQuantity = 1;
|
||||
late AnimationController _scaleController;
|
||||
late AnimationController _fadeController;
|
||||
@ -26,7 +27,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
|
||||
// Animations pour les interactions
|
||||
_scaleController = AnimationController(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
@ -36,7 +37,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
duration: const Duration(milliseconds: 300),
|
||||
vsync: this,
|
||||
)..forward();
|
||||
|
||||
|
||||
_scaleAnimation = Tween<double>(
|
||||
begin: 1.0,
|
||||
end: 0.95,
|
||||
@ -44,7 +45,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
parent: _scaleController,
|
||||
curve: Curves.easeInOut,
|
||||
));
|
||||
|
||||
|
||||
_fadeAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
@ -122,7 +123,6 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
: _buildPlaceholderImage(),
|
||||
),
|
||||
),
|
||||
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
@ -141,7 +141,6 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
if (widget.product.isStockDefined())
|
||||
Positioned(
|
||||
top: 12,
|
||||
@ -183,7 +182,6 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
@ -201,7 +199,8 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
vertical: 8,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.product.name,
|
||||
@ -222,7 +221,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${widget.product.price.toStringAsFixed(2)} FCFA',
|
||||
'${widget.product.price.toStringAsFixed(2)} MGA',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 13,
|
||||
@ -239,9 +238,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
@ -250,7 +247,8 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
color:
|
||||
Colors.black.withOpacity(0.1),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
@ -295,9 +293,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 8),
|
||||
|
||||
Expanded(
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
@ -306,9 +302,11 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
onTapUp: _onTapUp,
|
||||
onTapCancel: _onTapCancel,
|
||||
onTap: () {
|
||||
widget.onAddToCart(widget.product, selectedQuantity);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
widget.onAddToCart(widget.product,
|
||||
selectedQuantity);
|
||||
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
@ -320,16 +318,20 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${widget.product.name} (x$selectedQuantity) ajouté au panier',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
overflow: TextOverflow
|
||||
.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.green,
|
||||
duration: const Duration(seconds: 1),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration:
|
||||
const Duration(seconds: 1),
|
||||
behavior:
|
||||
SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius:
|
||||
BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -342,21 +344,27 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [
|
||||
Color.fromARGB(255, 4, 54, 95),
|
||||
Color.fromARGB(255, 6, 80, 140),
|
||||
Color.fromARGB(
|
||||
255, 4, 54, 95),
|
||||
Color.fromARGB(
|
||||
255, 6, 80, 140),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius:
|
||||
BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color.fromARGB(255, 4, 54, 95).withOpacity(0.3),
|
||||
color: const Color.fromARGB(
|
||||
255, 4, 54, 95)
|
||||
.withOpacity(0.3),
|
||||
blurRadius: 6,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.add_shopping_cart,
|
||||
@ -369,10 +377,12 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
'Ajouter',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontWeight:
|
||||
FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
overflow:
|
||||
TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -442,10 +452,12 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 16,
|
||||
color: onPressed != null ? const Color.fromARGB(255, 4, 54, 95) : Colors.grey,
|
||||
color: onPressed != null
|
||||
? const Color.fromARGB(255, 4, 54, 95)
|
||||
: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:youmazgestion/Models/users.dart';
|
||||
import 'package:youmazgestion/Models/role.dart';
|
||||
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
|
||||
import 'package:youmazgestion/Views/Dashboard.dart';
|
||||
import 'package:youmazgestion/accueil.dart';
|
||||
|
||||
import '../Services/app_database.dart'; // Changé de authDatabase.dart
|
||||
//import '../Services/app_database.dart'; // Changé de authDatabase.dart
|
||||
|
||||
class RegistrationPage extends StatefulWidget {
|
||||
const RegistrationPage({super.key});
|
||||
@ -23,7 +25,9 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
Role? _selectedRole;
|
||||
bool _isLoading = false;
|
||||
bool _isLoadingRoles = true;
|
||||
|
||||
List<Map<String, dynamic>> _availablePointsDeVente = [];
|
||||
int? _selectedPointDeVenteId;
|
||||
bool _isLoadingPointsDeVente = true;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -36,19 +40,38 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
_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> _initializeDatabase() async {
|
||||
try {
|
||||
await AppDatabase.instance.initDatabase();
|
||||
await _loadRoles();
|
||||
await _loadPointsDeVente(); // Ajouté ici
|
||||
} 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> _loadPointsDeVente() async {
|
||||
try {
|
||||
final points = await AppDatabase.instance.getPointsDeVente();
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_availablePointsDeVente = points;
|
||||
_isLoadingPointsDeVente = false;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
print('Erreur lors du chargement des points de vente: $error');
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoadingPointsDeVente = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Future<void> _loadRoles() async {
|
||||
try {
|
||||
final roles = await AppDatabase.instance.getRoles();
|
||||
@ -98,15 +121,16 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
}
|
||||
|
||||
bool _validateFields() {
|
||||
if (_nameController.text.trim().isEmpty ||
|
||||
_lastNameController.text.trim().isEmpty ||
|
||||
_emailController.text.trim().isEmpty ||
|
||||
_usernameController.text.trim().isEmpty ||
|
||||
_passwordController.text.trim().isEmpty ||
|
||||
_selectedRole == null) {
|
||||
_showErrorDialog('Champs manquants', 'Veuillez remplir tous les champs.');
|
||||
return false;
|
||||
}
|
||||
if (_nameController.text.trim().isEmpty ||
|
||||
_lastNameController.text.trim().isEmpty ||
|
||||
_emailController.text.trim().isEmpty ||
|
||||
_usernameController.text.trim().isEmpty ||
|
||||
_passwordController.text.trim().isEmpty ||
|
||||
_selectedRole == null ||
|
||||
_selectedPointDeVenteId == null) { // Ajouté ici
|
||||
_showErrorDialog('Champs manquants', 'Veuillez remplir tous les champs.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validation basique de l'email
|
||||
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(_emailController.text.trim())) {
|
||||
@ -126,23 +150,24 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
void _register() async {
|
||||
if (_isLoading) return;
|
||||
|
||||
if (!_validateFields()) return;
|
||||
if (!_validateFields()) return;
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
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
|
||||
);
|
||||
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
|
||||
pointDeVenteId: _selectedPointDeVenteId, // Ajouté ici
|
||||
);
|
||||
|
||||
// Sauvegarder l'utilisateur dans la base de données
|
||||
final int userId = await AppDatabase.instance.createUser(user);
|
||||
@ -191,7 +216,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const AccueilPage()),
|
||||
MaterialPageRoute(builder: (context) => DashboardPage()),
|
||||
);
|
||||
},
|
||||
child: const Text('OK'),
|
||||
@ -361,6 +386,46 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
// Dans la méthode build, après le DropdownButton des rôles
|
||||
const SizedBox(height: 16.0),
|
||||
_isLoadingPointsDeVente
|
||||
? const CircularProgressIndicator()
|
||||
: 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<int>(
|
||||
value: _selectedPointDeVenteId,
|
||||
hint: const Text('Sélectionner un point de vente'),
|
||||
isExpanded: true,
|
||||
onChanged: _isLoading
|
||||
? null
|
||||
: (int? newValue) {
|
||||
setState(() {
|
||||
_selectedPointDeVenteId = newValue;
|
||||
});
|
||||
},
|
||||
items: _availablePointsDeVente
|
||||
.map<DropdownMenuItem<int>>((Map<String, dynamic> point) {
|
||||
return DropdownMenuItem<int>(
|
||||
value: point['id'] as int,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.store, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Text(point['nom']),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
const SizedBox(height: 24.0),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
|
||||
@ -3,6 +3,7 @@ import 'package:intl/intl.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
|
||||
import 'package:youmazgestion/Views/particles.dart' show ParticleBackground;
|
||||
import 'package:youmazgestion/Views/produitsCard.dart';
|
||||
import 'Components/appDrawer.dart';
|
||||
@ -10,7 +11,7 @@ import 'Components/app_bar.dart';
|
||||
import 'Components/cartItem.dart';
|
||||
import 'Models/produit.dart';
|
||||
import 'Services/OrderDatabase.dart';
|
||||
import 'Services/productDatabase.dart';
|
||||
//import 'Services/productDatabase.dart';
|
||||
import 'Views/ticketPage.dart';
|
||||
import 'controller/userController.dart';
|
||||
import 'my_app.dart';
|
||||
@ -25,7 +26,7 @@ class AccueilPage extends StatefulWidget {
|
||||
|
||||
class _AccueilPageState extends State<AccueilPage> {
|
||||
final UserController userController = Get.put(UserController());
|
||||
final ProductDatabase productDatabase = ProductDatabase();
|
||||
final AppDatabase productDatabase = AppDatabase.instance;
|
||||
late Future<Map<String, List<Product>>> productsFuture;
|
||||
final OrderDatabase orderDatabase = OrderDatabase.instance;
|
||||
final WorkDatabase workDatabase = WorkDatabase.instance;
|
||||
@ -448,7 +449,7 @@ class _AccueilPageState extends State<AccueilPage> {
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold)),
|
||||
Text(
|
||||
'${NumberFormat('#,##0.00').format(calculateTotalPrice())} FCFA',
|
||||
'${NumberFormat('#,##0.00').format(calculateTotalPrice())} MGA',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
|
||||
import 'package:youmazgestion/controller/userController.dart';
|
||||
import '../Components/cartItem.dart';
|
||||
import '../Models/produit.dart';
|
||||
import '../Services/OrderDatabase.dart';
|
||||
import '../Services/WorkDatabase.dart';
|
||||
import '../Services/productDatabase.dart';
|
||||
//import '../Services/productDatabase.dart';
|
||||
import '../Views/ticketPage.dart';
|
||||
import '../my_app.dart';
|
||||
|
||||
class AccueilController extends GetxController {
|
||||
final UserController userController = Get.find();
|
||||
final ProductDatabase productDatabase = ProductDatabase();
|
||||
final AppDatabase productDatabase = AppDatabase.instance;
|
||||
final Rx<Map<String, List<Product>>> productsFuture = Rx({}); // Observable
|
||||
final OrderDatabase orderDatabase = OrderDatabase.instance;
|
||||
final WorkDatabase workDatabase = WorkDatabase.instance;
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:youmazgestion/Models/users.dart';
|
||||
import 'package:youmazgestion/Services/app_database.dart';
|
||||
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
|
||||
//import 'package:youmazgestion/Services/app_database.dart';
|
||||
|
||||
class UserController extends GetxController {
|
||||
final _username = ''.obs;
|
||||
@ -11,6 +12,8 @@ class UserController extends GetxController {
|
||||
final _lastname = ''.obs;
|
||||
final _password = ''.obs;
|
||||
final _userId = 0.obs; // ✅ Ajout de l'ID utilisateur
|
||||
final _pointDeVenteId = 0.obs;
|
||||
final _pointDeVenteDesignation = ''.obs;
|
||||
|
||||
String get username => _username.value;
|
||||
String get email => _email.value;
|
||||
@ -19,6 +22,8 @@ class UserController extends GetxController {
|
||||
String get lastname => _lastname.value;
|
||||
String get password => _password.value;
|
||||
int get userId => _userId.value;
|
||||
int get pointDeVenteId => _pointDeVenteId.value;
|
||||
String get pointDeVenteDesignation => _pointDeVenteDesignation.value;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -27,48 +32,64 @@ class UserController extends GetxController {
|
||||
}
|
||||
|
||||
// ✅ CORRECTION : Charger les données complètes depuis SharedPreferences ET la base de données
|
||||
Future<void> loadUserData() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
final storedUsername = prefs.getString('username') ?? '';
|
||||
final storedRole = prefs.getString('role') ?? '';
|
||||
final storedUserId = prefs.getInt('user_id') ?? 0;
|
||||
|
||||
if (storedUsername.isNotEmpty) {
|
||||
try {
|
||||
// Récupérer les données complètes depuis la base de données
|
||||
Users user = await AppDatabase.instance.getUser(storedUsername);
|
||||
|
||||
// Mettre à jour TOUTES les données
|
||||
_username.value = user.username;
|
||||
_email.value = user.email;
|
||||
_name.value = user.name;
|
||||
_lastname.value = user.lastName;
|
||||
_password.value = user.password;
|
||||
_role.value = storedRole; // Récupéré depuis SharedPreferences
|
||||
_userId.value = storedUserId; // Récupéré depuis SharedPreferences
|
||||
|
||||
print("✅ Données chargées depuis la DB - Username: ${_username.value}");
|
||||
print("✅ Name: ${_name.value}, Email: ${_email.value}");
|
||||
print("✅ Role: ${_role.value}, UserID: ${_userId.value}");
|
||||
} catch (dbError) {
|
||||
print('❌ Erreur DB, chargement depuis SharedPreferences uniquement: $dbError');
|
||||
// Fallback : charger depuis SharedPreferences uniquement
|
||||
_username.value = storedUsername;
|
||||
_email.value = prefs.getString('email') ?? '';
|
||||
_role.value = storedRole;
|
||||
_name.value = prefs.getString('name') ?? '';
|
||||
_lastname.value = prefs.getString('lastname') ?? '';
|
||||
_userId.value = storedUserId;
|
||||
Future<void> loadUserData() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
final storedUsername = prefs.getString('username') ?? '';
|
||||
final storedRole = prefs.getString('role') ?? '';
|
||||
final storedUserId = prefs.getInt('user_id') ?? 0;
|
||||
final storedPointDeVenteId = prefs.getInt('point_de_vente_id') ?? 0;
|
||||
final storedPointDeVenteDesignation = prefs.getString('point_de_vente_designation') ?? '';
|
||||
|
||||
if (storedUsername.isNotEmpty) {
|
||||
try {
|
||||
Users user = await AppDatabase.instance.getUser(storedUsername);
|
||||
|
||||
_username.value = user.username;
|
||||
_email.value = user.email;
|
||||
_name.value = user.name;
|
||||
_lastname.value = user.lastName;
|
||||
_password.value = user.password;
|
||||
_role.value = storedRole;
|
||||
_userId.value = storedUserId;
|
||||
_pointDeVenteId.value = storedPointDeVenteId;
|
||||
_pointDeVenteDesignation.value = storedPointDeVenteDesignation;
|
||||
|
||||
// Si la désignation n'est pas sauvegardée, on peut la récupérer
|
||||
if (_pointDeVenteDesignation.value.isEmpty && _pointDeVenteId.value > 0) {
|
||||
await loadPointDeVenteDesignation();
|
||||
}
|
||||
} else {
|
||||
print("❌ Aucun utilisateur stocké trouvé");
|
||||
|
||||
} catch (dbError) {
|
||||
// Fallback
|
||||
_username.value = storedUsername;
|
||||
_email.value = prefs.getString('email') ?? '';
|
||||
_role.value = storedRole;
|
||||
_name.value = prefs.getString('name') ?? '';
|
||||
_lastname.value = prefs.getString('lastname') ?? '';
|
||||
_userId.value = storedUserId;
|
||||
_pointDeVenteId.value = storedPointDeVenteId;
|
||||
_pointDeVenteDesignation.value = storedPointDeVenteDesignation;
|
||||
}
|
||||
} catch (e) {
|
||||
print('❌ Erreur lors du chargement des données utilisateur: $e');
|
||||
}
|
||||
} catch (e) {
|
||||
print('❌ Erreur lors du chargement des données utilisateur: $e');
|
||||
}
|
||||
}
|
||||
Future<void> loadPointDeVenteDesignation() async {
|
||||
if (_pointDeVenteId.value <= 0) return;
|
||||
|
||||
try {
|
||||
final pointDeVente = await AppDatabase.instance.getPointDeVenteById(_pointDeVenteId.value);
|
||||
if (pointDeVente != null) {
|
||||
_pointDeVenteDesignation.value = pointDeVente['designation'] as String;
|
||||
await saveUserData(); // Sauvegarder la désignation
|
||||
}
|
||||
} catch (e) {
|
||||
print('❌ Erreur lors du chargement de la désignation du point de vente: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NOUVELLE MÉTHODE : Mise à jour complète avec Users + credentials
|
||||
void setUserWithCredentials(Users user, String role, int userId) {
|
||||
@ -79,6 +100,7 @@ class UserController extends GetxController {
|
||||
_lastname.value = user.lastName;
|
||||
_password.value = user.password;
|
||||
_userId.value = userId; // ID depuis les credentials
|
||||
_pointDeVenteId.value = user.pointDeVenteId ?? 0;
|
||||
|
||||
print("✅ Utilisateur mis à jour avec credentials:");
|
||||
print(" Username: ${_username.value}");
|
||||
@ -111,49 +133,52 @@ class UserController extends GetxController {
|
||||
|
||||
// ✅ CORRECTION : Sauvegarder TOUTES les données importantes
|
||||
Future<void> saveUserData() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
await prefs.setString('username', _username.value);
|
||||
await prefs.setString('email', _email.value);
|
||||
await prefs.setString('role', _role.value);
|
||||
await prefs.setString('name', _name.value);
|
||||
await prefs.setString('lastname', _lastname.value);
|
||||
await prefs.setInt('user_id', _userId.value); // ✅ Sauvegarder l'ID
|
||||
|
||||
print("✅ Données sauvegardées avec succès dans SharedPreferences");
|
||||
} catch (e) {
|
||||
print('❌ Erreur lors de la sauvegarde des données utilisateur: $e');
|
||||
}
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
await prefs.setString('username', _username.value);
|
||||
await prefs.setString('email', _email.value);
|
||||
await prefs.setString('role', _role.value);
|
||||
await prefs.setString('name', _name.value);
|
||||
await prefs.setString('lastname', _lastname.value);
|
||||
await prefs.setInt('user_id', _userId.value);
|
||||
await prefs.setInt('point_de_vente_id', _pointDeVenteId.value);
|
||||
await prefs.setString('point_de_vente_designation', _pointDeVenteDesignation.value);
|
||||
|
||||
print("✅ Données sauvegardées avec succès dans SharedPreferences");
|
||||
} catch (e) {
|
||||
print('❌ Erreur lors de la sauvegarde des données utilisateur: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ CORRECTION : Vider TOUTES les données (SharedPreferences + Observables)
|
||||
Future<void> clearUserData() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
// Vider SharedPreferences
|
||||
await prefs.remove('username');
|
||||
await prefs.remove('email');
|
||||
await prefs.remove('role');
|
||||
await prefs.remove('name');
|
||||
await prefs.remove('lastname');
|
||||
await prefs.remove('user_id'); // ✅ Supprimer l'ID aussi
|
||||
|
||||
// Vider les variables observables
|
||||
_username.value = '';
|
||||
_email.value = '';
|
||||
_role.value = '';
|
||||
_name.value = '';
|
||||
_lastname.value = '';
|
||||
_password.value = '';
|
||||
_userId.value = 0; // ✅ Réinitialiser l'ID
|
||||
|
||||
print("✅ Toutes les données utilisateur ont été effacées");
|
||||
} catch (e) {
|
||||
print('❌ Erreur lors de l\'effacement des données utilisateur: $e');
|
||||
}
|
||||
Future<void> clearUserData() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
await prefs.remove('username');
|
||||
await prefs.remove('email');
|
||||
await prefs.remove('role');
|
||||
await prefs.remove('name');
|
||||
await prefs.remove('lastname');
|
||||
await prefs.remove('user_id');
|
||||
await prefs.remove('point_de_vente_id');
|
||||
await prefs.remove('point_de_vente_designation');
|
||||
|
||||
_username.value = '';
|
||||
_email.value = '';
|
||||
_role.value = '';
|
||||
_name.value = '';
|
||||
_lastname.value = '';
|
||||
_password.value = '';
|
||||
_userId.value = 0;
|
||||
_pointDeVenteId.value = 0;
|
||||
_pointDeVenteDesignation.value = '';
|
||||
|
||||
} catch (e) {
|
||||
print('❌ Erreur lors de l\'effacement des données utilisateur: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ MÉTHODE UTILITAIRE : Vérifier si un utilisateur est connecté
|
||||
bool get isLoggedIn => _username.value.isNotEmpty && _userId.value > 0;
|
||||
@ -191,14 +216,16 @@ class UserController extends GetxController {
|
||||
|
||||
// ✅ MÉTHODE DEBUG : Afficher l'état actuel
|
||||
void debugPrintUserState() {
|
||||
print("=== ÉTAT UTILISATEUR ===");
|
||||
print("Username: ${_username.value}");
|
||||
print("Name: ${_name.value}");
|
||||
print("Lastname: ${_lastname.value}");
|
||||
print("Email: ${_email.value}");
|
||||
print("Role: ${_role.value}");
|
||||
print("UserID: ${_userId.value}");
|
||||
print("IsLoggedIn: $isLoggedIn");
|
||||
print("========================");
|
||||
}
|
||||
print("=== ÉTAT UTILISATEUR ===");
|
||||
print("Username: ${_username.value}");
|
||||
print("Name: ${_name.value}");
|
||||
print("Lastname: ${_lastname.value}");
|
||||
print("Email: ${_email.value}");
|
||||
print("Role: ${_role.value}");
|
||||
print("UserID: ${_userId.value}");
|
||||
print("PointDeVenteID: ${_pointDeVenteId.value}");
|
||||
print("PointDeVente: ${_pointDeVenteDesignation.value}");
|
||||
print("IsLoggedIn: $isLoggedIn");
|
||||
print("========================");
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:youmazgestion/Services/app_database.dart';
|
||||
//import 'package:youmazgestion/Services/app_database.dart';
|
||||
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
|
||||
import 'package:youmazgestion/controller/userController.dart';
|
||||
import 'Services/productDatabase.dart';
|
||||
//import 'Services/productDatabase.dart';
|
||||
import 'my_app.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
@ -11,14 +12,15 @@ void main() async {
|
||||
|
||||
try {
|
||||
// Initialiser les bases de données une seule fois
|
||||
// await AppDatabase.instance.deleteDatabaseFile();
|
||||
// await AppDatabase.instance.deleteDatabaseFile();
|
||||
// await ProductDatabase.instance.deleteDatabaseFile();
|
||||
|
||||
await ProductDatabase.instance.initDatabase();
|
||||
// await ProductDatabase.instance.initDatabase();
|
||||
await AppDatabase.instance.initDatabase();
|
||||
|
||||
|
||||
// Afficher les informations de la base (pour debug)
|
||||
await AppDatabase.instance.printDatabaseInfo();
|
||||
// await AppDatabase.instance.printDatabaseInfo();
|
||||
Get.put(
|
||||
UserController()); // Ajoute ce code AVANT tout accès au UserController
|
||||
setupLogger();
|
||||
|
||||
@ -7,6 +7,7 @@ import Foundation
|
||||
|
||||
import file_picker
|
||||
import file_selector_macos
|
||||
import mobile_scanner
|
||||
import open_file_mac
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
@ -16,6 +17,7 @@ import url_launcher_macos
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
|
||||
OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
|
||||
32
pubspec.lock
32
pubspec.lock
@ -241,6 +241,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+4"
|
||||
fl_chart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fl_chart
|
||||
sha256: "5a74434cc83bf64346efb562f1a06eefaf1bcb530dc3d96a104f631a1eff8d79"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.65.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -608,6 +616,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
mobile_scanner:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: mobile_scanner
|
||||
sha256: d234581c090526676fd8fab4ada92f35c6746e3fb4f05a399665d75a399fb760
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.2.3"
|
||||
msix:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -624,6 +640,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
numbers_to_letters:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: numbers_to_letters
|
||||
sha256: "70c7ed2f04c1982a299e753101fbc2d52ed5b39a2b3dd2a9c07ba131e9c0948e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
open_file:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1041,18 +1065,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: syncfusion_flutter_charts
|
||||
sha256: bdb7cc5814ceb187793cea587f4a5946afcffd96726b219cee79df8460f44b7b
|
||||
sha256: "0222ac9d8cb6c671f014effe9bd5c0aef35eadb16471355345ba87cc0ac007b3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "21.2.4"
|
||||
version: "20.4.54"
|
||||
syncfusion_flutter_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: syncfusion_flutter_core
|
||||
sha256: "8db8f55c77f56968681447d3837c10f27a9e861e238a898fda116c7531def979"
|
||||
sha256: "3979f0b1c5a97422cadae52d476c21fa3e0fb671ef51de6cae1d646d8b99fe1f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "21.2.10"
|
||||
version: "20.4.54"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
11
pubspec.yaml
11
pubspec.yaml
@ -50,7 +50,7 @@ dependencies:
|
||||
logging: ^1.2.0
|
||||
msix: ^3.7.0
|
||||
flutter_charts: ^0.5.1
|
||||
syncfusion_flutter_charts: ^21.2.4
|
||||
syncfusion_flutter_charts: ^20.4.48
|
||||
shelf: ^1.4.1
|
||||
shelf_router: ^1.1.4
|
||||
pdf: ^3.8.4
|
||||
@ -62,7 +62,9 @@ dependencies:
|
||||
path_provider: ^2.0.15
|
||||
shared_preferences: ^2.2.2
|
||||
excel: ^2.0.1
|
||||
|
||||
mobile_scanner: ^5.0.0 # ou la version la plus récente
|
||||
fl_chart: ^0.65.0 # Version la plus récente au moment de cette répons
|
||||
numbers_to_letters: ^1.0.0
|
||||
|
||||
|
||||
|
||||
@ -101,6 +103,11 @@ flutter:
|
||||
- assets/database/usersdb.db
|
||||
- assets/database/work.db
|
||||
- assets/database/roles.db
|
||||
- assets/airtel_money.png
|
||||
- assets/mvola.jpg
|
||||
- assets/Orange_money.png
|
||||
- assets/fa-solid-900.ttf
|
||||
- assets/fonts/Roboto-Italic.ttf
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||
|
||||
Loading…
Reference in New Issue
Block a user