Compare commits
5 Commits
master
...
06062025_0
| Author | SHA1 | Date |
|---|---|---|
|
|
831cce13da | 6 months ago |
|
|
c8fedd08e5 | 6 months ago |
|
|
9eafda610f | 6 months ago |
|
|
2bef06a2fe | 6 months ago |
|
|
57ea91b3d7 | 6 months ago |
47 changed files with 10244 additions and 3118 deletions
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
@ -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,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 |
|||
), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
enum PaymentType { |
|||
cash, |
|||
card, |
|||
mvola, |
|||
orange, |
|||
airtel |
|||
} |
|||
@ -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,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], |
|||
); |
|||
} |
|||
|
|||
} |
|||
@ -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"); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -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', |
|||
), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue