From 2bef06a2febe2ed17ff51c67232179672534fced Mon Sep 17 00:00:00 2001 From: "b.razafimandimbihery" Date: Sat, 31 May 2025 10:17:02 +0300 Subject: [PATCH] commit commit --- android/app/src/main/AndroidManifest.xml | 3 +- ios/Runner/Info.plist | 3 + lib/Components/QrScan.dart | 268 ++++++++++++++--------- lib/Components/appDrawer.dart | 16 +- lib/Models/pointage_model.dart | 36 +++ lib/Services/pointageDatabase.dart | 60 +++++ lib/Views/commandManagement.dart | 124 ++++++----- lib/Views/historique.dart | 2 +- lib/Views/loginPage.dart | 101 ++++++--- lib/Views/newCommand.dart | 4 +- lib/Views/pointage.dart | 190 ++++++++++++++++ lib/Views/produitsCard.dart | 72 +++--- lib/accueil.dart | 2 +- pubspec.lock | 4 +- pubspec.yaml | 2 +- 15 files changed, 655 insertions(+), 232 deletions(-) create mode 100644 lib/Models/pointage_model.dart create mode 100644 lib/Services/pointageDatabase.dart create mode 100644 lib/Views/pointage.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 421977d..e9bdd1a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,6 @@ + + - diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 97711f9..6cbf325 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + NSCameraUsageDescription + Cette application a besoin d'accéder à la caméra pour scanner les codes QR CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -47,5 +49,6 @@ UIApplicationSupportsIndirectInputEvents + diff --git a/lib/Components/QrScan.dart b/lib/Components/QrScan.dart index 9176241..0ad9243 100644 --- a/lib/Components/QrScan.dart +++ b/lib/Components/QrScan.dart @@ -1,10 +1,9 @@ -// Ajoutez cette importation en haut du fichier +import 'dart:io'; import 'dart:ui'; - +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; -// Créez une nouvelle classe pour la page de scan QR class ScanQRPage extends StatefulWidget { const ScanQRPage({super.key}); @@ -13,13 +12,47 @@ class ScanQRPage extends StatefulWidget { } class _ScanQRPageState extends State { - MobileScannerController cameraController = MobileScannerController(); + 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(); + cameraController?.dispose(); super.dispose(); } @@ -28,72 +61,121 @@ class _ScanQRPageState extends State { return Scaffold( appBar: AppBar( title: const Text('Scanner QR Code'), - actions: [ - IconButton( - color: Colors.white, - icon: ValueListenableBuilder( - valueListenable: cameraController.torchState, - builder: (context, state, child) { - switch (state) { - case TorchState.off: - return const Icon(Icons.flash_off, color: Colors.grey); - case TorchState.on: - return const Icon(Icons.flash_on, color: Colors.yellow); - } - }, + actions: _hasError ? [] : [ + if (cameraController != null) ...[ + IconButton( + color: Colors.white, + icon: const Icon(Icons.flash_on, color: Colors.white), + iconSize: 32.0, + onPressed: () => cameraController!.toggleTorch(), ), - iconSize: 32.0, - onPressed: () => cameraController.toggleTorch(), - ), - IconButton( - color: Colors.white, - icon: ValueListenableBuilder( - valueListenable: cameraController.cameraFacingState, - builder: (context, state, child) { - switch (state) { - case CameraFacing.front: - return const Icon(Icons.camera_front); - case CameraFacing.back: - return const Icon(Icons.camera_rear); - } - }, + IconButton( + color: Colors.white, + icon: const Icon(Icons.flip_camera_ios, color: Colors.white), + iconSize: 32.0, + onPressed: () => cameraController!.switchCamera(), ), - iconSize: 32.0, - onPressed: () => cameraController.switchCamera(), - ), + ], ], ), - body: Stack( - children: [ - MobileScanner( - controller: cameraController, - onDetect: (capture) { - final List barcodes = capture.barcodes; - for (final barcode in barcodes) { - if (!_isScanComplete && barcode.rawValue != null) { - _isScanComplete = true; - _scannedData = barcode.rawValue; - _showScanResult(context, _scannedData!); - } - } - }, - ), - CustomPaint( - painter: QrScannerOverlay( - borderColor: Colors.blue.shade800, + 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 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: Text(data), + content: SelectableText(data), // Permet de sélectionner le texte actions: [ TextButton( onPressed: () { @@ -102,12 +184,18 @@ class _ScanQRPageState extends State { _isScanComplete = false; }); }, - child: const Text('OK'), + child: const Text('Fermer'), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + Navigator.pop(context, data); // Retourner la donnée scannée + }, + child: const Text('Utiliser'), ), ], ), ).then((_) { - // Réinitialiser le scan après la fermeture du dialogue setState(() { _isScanComplete = false; }); @@ -115,7 +203,6 @@ class _ScanQRPageState extends State { } } -// Widget personnalisé pour l'overlay du scanner class QrScannerOverlay extends CustomPainter { final Color borderColor; @@ -129,12 +216,10 @@ class QrScannerOverlay extends CustomPainter { final double borderLength = 30.0; final double areaSize = width * 0.7; - // Dessiner un rectangle semi-transparent autour de la zone de scan final Paint backgroundPaint = Paint() ..color = Colors.black.withOpacity(0.4); canvas.drawRect(Rect.fromLTRB(0, 0, width, height), backgroundPaint); - // Dessiner la zone de scan transparente final Paint transparentPaint = Paint() ..color = Colors.transparent ..blendMode = BlendMode.clear; @@ -145,59 +230,26 @@ class QrScannerOverlay extends CustomPainter { transparentPaint, ); - // Dessiner les bordures de la zone de scan final Paint borderPaint = Paint() ..color = borderColor ..strokeWidth = borderWidth ..style = PaintingStyle.stroke; - // Coin supérieur gauche - canvas.drawLine( - Offset(areaLeft, areaTop), - Offset(areaLeft + borderLength, areaTop), - borderPaint, - ); - canvas.drawLine( - Offset(areaLeft, areaTop), - Offset(areaLeft, areaTop + borderLength), - borderPaint, - ); - - // Coin supérieur droit - canvas.drawLine( - Offset(areaLeft + areaSize - borderLength, areaTop), - Offset(areaLeft + areaSize, areaTop), - borderPaint, - ); - canvas.drawLine( - Offset(areaLeft + areaSize, areaTop), - Offset(areaLeft + areaSize, areaTop + borderLength), - borderPaint, - ); + // 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); + } - // Coin inférieur gauche - canvas.drawLine( - Offset(areaLeft, areaTop + areaSize - borderLength), - Offset(areaLeft, areaTop + areaSize), - borderPaint, - ); - canvas.drawLine( - Offset(areaLeft, areaTop + areaSize), - Offset(areaLeft + borderLength, areaTop + areaSize), - borderPaint, - ); + 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; - // Coin inférieur droit - canvas.drawLine( - Offset(areaLeft + areaSize - borderLength, areaTop + areaSize), - Offset(areaLeft + areaSize, areaTop + areaSize), - borderPaint, - ); - canvas.drawLine( - Offset(areaLeft + areaSize, areaTop + areaSize - borderLength), - Offset(areaLeft + areaSize, areaTop + areaSize), - borderPaint, - ); + canvas.drawLine(Offset(horizontalStart, y), Offset(horizontalEnd, y), paint); + canvas.drawLine(Offset(x, verticalStart), Offset(x, verticalEnd), paint); } @override diff --git a/lib/Components/appDrawer.dart b/lib/Components/appDrawer.dart index 6ac22ac..158b890 100644 --- a/lib/Components/appDrawer.dart +++ b/lib/Components/appDrawer.dart @@ -13,6 +13,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(); @@ -73,7 +74,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, @@ -123,6 +126,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)) { @@ -322,7 +333,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(); } diff --git a/lib/Models/pointage_model.dart b/lib/Models/pointage_model.dart new file mode 100644 index 0000000..f7afeed --- /dev/null +++ b/lib/Models/pointage_model.dart @@ -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 map) { + return Pointage( + id: map['id'], + userName: map['userName'] ?? '', + date: map['date'], + heureArrivee: map['heureArrivee'], + heureDepart: map['heureDepart'], + ); + } + + Map toMap() { + return { + 'id': id, + 'userName': userName, + 'date': date, + 'heureArrivee': heureArrivee, + 'heureDepart': heureDepart, + }; + } +} diff --git a/lib/Services/pointageDatabase.dart b/lib/Services/pointageDatabase.dart new file mode 100644 index 0000000..cc162ff --- /dev/null +++ b/lib/Services/pointageDatabase.dart @@ -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 get database async { + if (_db != null) return _db!; + _db = await _initDatabase(); + return _db!; + } + + Future _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 insertPointage(Pointage pointage) async { + final db = await database; + return await db.insert('pointages', pointage.toMap()); + } + + Future> getPointages() async { + final db = await database; + final pointages = await db.query('pointages'); + return pointages.map((pointage) => Pointage.fromMap(pointage)).toList(); + } + + Future updatePointage(Pointage pointage) async { + final db = await database; + return await db.update('pointages', pointage.toMap(), + where: 'id = ?', whereArgs: [pointage.id]); + } + + Future deletePointage(int id) async { + final db = await database; + return await db.delete('pointages', where: 'id = ?', whereArgs: [id]); + } +} diff --git a/lib/Views/commandManagement.dart b/lib/Views/commandManagement.dart index 28badfe..47b7b48 100644 --- a/lib/Views/commandManagement.dart +++ b/lib/Views/commandManagement.dart @@ -56,9 +56,11 @@ class _GestionCommandesPageState extends State { final query = _searchController.text.toLowerCase(); setState(() { _filteredCommandes = _commandes.where((commande) { - final matchesSearch = commande.clientNomComplet.toLowerCase().contains(query) || - commande.id.toString().contains(query); - final matchesStatut = _selectedStatut == null || commande.statut == _selectedStatut; + final matchesSearch = + commande.clientNomComplet.toLowerCase().contains(query) || + commande.id.toString().contains(query); + final matchesStatut = + _selectedStatut == null || commande.statut == _selectedStatut; final matchesDate = _selectedDate == null || DateFormat('yyyy-MM-dd').format(commande.dateCommande) == DateFormat('yyyy-MM-dd').format(_selectedDate!); @@ -280,12 +282,11 @@ class _GestionCommandesPageState extends State { width: 100, height: 80, decoration: pw.BoxDecoration( - border: pw.Border.all(color: PdfColors.blue900, width: 2), + border: + pw.Border.all(color: PdfColors.blue900, width: 2), borderRadius: pw.BorderRadius.circular(8), ), - child: pw.Center( - child: pw.Image(image) - ), + child: pw.Center(child: pw.Image(image)), ), pw.SizedBox(height: 10), pw.Text('guycom', style: headerStyle), @@ -306,7 +307,8 @@ class _GestionCommandesPageState extends State { child: pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ - pw.Text('FACTURE', + pw.Text( + 'FACTURE', style: pw.TextStyle( fontSize: 20, fontWeight: pw.FontWeight.bold, @@ -315,7 +317,8 @@ class _GestionCommandesPageState extends State { ), pw.SizedBox(height: 8), pw.Text('N°: ${commande.id}', style: titleStyle), - pw.Text('Date: ${DateFormat('dd/MM/yyyy').format(commande.dateCommande)}'), + pw.Text( + 'Date: ${DateFormat('dd/MM/yyyy').format(commande.dateCommande)}'), ], ), ), @@ -323,9 +326,9 @@ class _GestionCommandesPageState extends State { ), ], ), - + pw.SizedBox(height: 30), - + // Informations client pw.Container( width: double.infinity, @@ -339,11 +342,12 @@ class _GestionCommandesPageState extends State { children: [ pw.Text('FACTURÉ À:', style: titleStyle), pw.SizedBox(height: 5), - pw.Text(client?.nomComplet ?? 'Client inconnu', - style: pw.TextStyle(fontSize: 12)), + pw.Text(client?.nomComplet ?? 'Client inconnu', + style: pw.TextStyle(fontSize: 12)), if (client?.telephone != null) - pw.Text('Tél: ${client!.telephone}', - style: pw.TextStyle(fontSize: 10, color: PdfColors.grey600)), + pw.Text('Tél: ${client!.telephone}', + style: pw.TextStyle( + fontSize: 10, color: PdfColors.grey600)), ], ), ), @@ -379,24 +383,30 @@ class _GestionCommandesPageState extends State { // Tableau des produits pw.Text('DÉTAILS DE LA COMMANDE', style: titleStyle), pw.SizedBox(height: 10), - + pw.Table( - border: pw.TableBorder.all(color: PdfColors.grey400, width: 0.5), + border: + pw.TableBorder.all(color: PdfColors.grey400, width: 0.5), children: [ pw.TableRow( - decoration: const pw.BoxDecoration(color: PdfColors.blue900), + decoration: + const pw.BoxDecoration(color: PdfColors.blue900), children: [ - _buildTableCell('Produit', titleStyle.copyWith(color: PdfColors.white)), - _buildTableCell('Qté', titleStyle.copyWith(color: PdfColors.white)), - _buildTableCell('Prix unit.', titleStyle.copyWith(color: PdfColors.white)), - _buildTableCell('Total', titleStyle.copyWith(color: PdfColors.white)), + _buildTableCell('Produit', + titleStyle.copyWith(color: PdfColors.white)), + _buildTableCell( + 'Qté', titleStyle.copyWith(color: PdfColors.white)), + _buildTableCell('Prix unit.', + titleStyle.copyWith(color: PdfColors.white)), + _buildTableCell( + 'Total', titleStyle.copyWith(color: PdfColors.white)), ], ), ...details.asMap().entries.map((entry) { final index = entry.key; final detail = entry.value; final isEven = index % 2 == 0; - + return pw.TableRow( decoration: pw.BoxDecoration( color: isEven ? PdfColors.white : PdfColors.grey50, @@ -411,9 +421,9 @@ class _GestionCommandesPageState extends State { }), ], ), - + pw.SizedBox(height: 20), - + // Total pw.Container( alignment: pw.Alignment.centerRight, @@ -433,9 +443,9 @@ class _GestionCommandesPageState extends State { ), ), ), - + pw.Spacer(), - + // Pied de page pw.Container( width: double.infinity, @@ -458,7 +468,8 @@ class _GestionCommandesPageState extends State { pw.SizedBox(height: 5), pw.Text( 'Cette facture est générée automatiquement par le système Youmaz Gestion', - style: pw.TextStyle(fontSize: 8, color: PdfColors.grey600), + style: + pw.TextStyle(fontSize: 8, color: PdfColors.grey600), ), ], ), @@ -730,9 +741,9 @@ class _GestionCommandesPageState extends State { ), ], ), - + const SizedBox(height: 16), - + // Barre de recherche améliorée Container( decoration: BoxDecoration( @@ -749,7 +760,8 @@ class _GestionCommandesPageState extends State { controller: _searchController, decoration: InputDecoration( labelText: 'Rechercher par client ou numéro de commande', - prefixIcon: Icon(Icons.search, color: Colors.blue.shade800), + prefixIcon: + Icon(Icons.search, color: Colors.blue.shade800), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, @@ -763,9 +775,9 @@ class _GestionCommandesPageState extends State { ), ), ), - + const SizedBox(height: 16), - + // Filtres améliorés Row( children: [ @@ -786,7 +798,8 @@ class _GestionCommandesPageState extends State { value: _selectedStatut, decoration: InputDecoration( labelText: 'Filtrer par statut', - prefixIcon: Icon(Icons.filter_list, color: Colors.blue.shade600), + prefixIcon: Icon(Icons.filter_list, + color: Colors.blue.shade600), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, @@ -825,9 +838,9 @@ class _GestionCommandesPageState extends State { ), ), ), - + const SizedBox(width: 12), - + Expanded( child: Container( decoration: BoxDecoration( @@ -875,19 +888,21 @@ class _GestionCommandesPageState extends State { }); } }, - icon: Icon(Icons.calendar_today, color: Colors.blue.shade600), + icon: Icon(Icons.calendar_today, + color: Colors.blue.shade600), label: Text( _selectedDate == null ? 'Date' - : DateFormat('dd/MM/yyyy').format(_selectedDate!), + : DateFormat('dd/MM/yyyy') + .format(_selectedDate!), style: const TextStyle(color: Colors.black87), ), ), ), ), - + const SizedBox(width: 12), - + // Bouton reset Container( decoration: BoxDecoration( @@ -916,9 +931,9 @@ class _GestionCommandesPageState extends State { ), ], ), - + const SizedBox(height: 12), - + // Toggle pour afficher/masquer les commandes annulées Container( decoration: BoxDecoration( @@ -931,7 +946,8 @@ class _GestionCommandesPageState extends State { offset: const Offset(0, 2),) ], ), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( children: [ Icon( @@ -964,7 +980,7 @@ class _GestionCommandesPageState extends State { ], ), ), - + // Liste des commandes Expanded( child: _filteredCommandes.isEmpty @@ -1040,9 +1056,10 @@ class _GestionCommandesPageState extends State { Icon( _getStatutIcon(commande.statut), size: 20, - color: commande.statut == StatutCommande.annulee - ? Colors.red - : Colors.blue.shade600, + color: + commande.statut == StatutCommande.annulee + ? Colors.red + : Colors.blue.shade600, ), Text( '#${commande.id}', @@ -1074,7 +1091,8 @@ class _GestionCommandesPageState extends State { ), const SizedBox(width: 4), Text( - DateFormat('dd/MM/yyyy').format(commande.dateCommande), + DateFormat('dd/MM/yyyy') + .format(commande.dateCommande), style: TextStyle( fontSize: 12, color: Colors.grey.shade600, @@ -1095,8 +1113,9 @@ class _GestionCommandesPageState extends State { style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, - color: commande.statut == StatutCommande.annulee - ? Colors.red + color: commande.statut == + StatutCommande.annulee + ? Colors.red : Colors.blue.shade700, ), ), @@ -1419,7 +1438,8 @@ class _CommandeActions extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.check_circle, color: Colors.green.shade600, size: 16), + Icon(Icons.check_circle, + color: Colors.green.shade600, size: 16), const SizedBox(width: 8), Text( 'Commande confirmée', @@ -1668,4 +1688,4 @@ class _PaymentMethodDialogState extends State { ], ); } -} \ No newline at end of file +} diff --git a/lib/Views/historique.dart b/lib/Views/historique.dart index 0fc76ad..6f09176 100644 --- a/lib/Views/historique.dart +++ b/lib/Views/historique.dart @@ -365,7 +365,7 @@ class _HistoriquePageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - commande.dateCommande as String + commande.dateCommande.timeZoneName ), Text( '${commande.montantTotal.toStringAsFixed(2)} DA', diff --git a/lib/Views/loginPage.dart b/lib/Views/loginPage.dart index cd2d8b1..c132b74 100644 --- a/lib/Views/loginPage.dart +++ b/lib/Views/loginPage.dart @@ -74,7 +74,8 @@ class _LoginPageState extends State { 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; @@ -97,8 +98,7 @@ class _LoginPageState extends State { } bool isValidUser = await dbInstance.verifyUser(username, password); - print('Résultat de la vérification: $isValidUser'); - + if (isValidUser) { Users user = await dbInstance.getUser(username); print('Utilisateur récupéré: ${user.username}'); @@ -137,53 +137,86 @@ class _LoginPageState extends State { 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( + 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), + ), + ], + ), + 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 SizedBox(height: 14), + Text( + 'GUYCOM', + style: TextStyle( + color: primaryColor, + fontWeight: FontWeight.bold, + fontSize: 28, + ), + ), + const SizedBox(height: 4), + Text( + 'Connectez-vous à votre compte', + style: TextStyle( + color: primaryColor.withOpacity(.8), + fontSize: 16, + ), + ), + Container( padding: const EdgeInsets.symmetric(vertical: 16.0), child: const Icon( Icons.lock_outline, @@ -256,11 +289,15 @@ class _LoginPageState extends State { ), ), ), + ] + ) + ) ], ), ), ), ), + ) ); } -} \ No newline at end of file +} diff --git a/lib/Views/newCommand.dart b/lib/Views/newCommand.dart index 7d89323..aae0b5d 100644 --- a/lib/Views/newCommand.dart +++ b/lib/Views/newCommand.dart @@ -128,7 +128,7 @@ class _NouvelleCommandePageState extends State { ], ), ), - + // Contenu principal Expanded( child: SingleChildScrollView( @@ -745,4 +745,4 @@ class _NouvelleCommandePageState extends State { _adresseController.dispose(); super.dispose(); } -} \ No newline at end of file +} diff --git a/lib/Views/pointage.dart b/lib/Views/pointage.dart new file mode 100644 index 0000000..934ac29 --- /dev/null +++ b/lib/Views/pointage.dart @@ -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 createState() => _PointagePageState(); +} + +class _PointagePageState extends State { + final DatabaseHelper _databaseHelper = DatabaseHelper(); + List _pointages = []; + + @override + void initState() { + super.initState(); + _loadPointages(); + } + + Future _loadPointages() async { + final pointages = await _databaseHelper.getPointages(); + setState(() { + _pointages = pointages; + }); + } + + Future _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', + ), + ], + ), + ); + } +} diff --git a/lib/Views/produitsCard.dart b/lib/Views/produitsCard.dart index d8fc572..c4011f8 100644 --- a/lib/Views/produitsCard.dart +++ b/lib/Views/produitsCard.dart @@ -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 createState() => _ProductCardState(); } -class _ProductCardState extends State with TickerProviderStateMixin { +class _ProductCardState extends State + with TickerProviderStateMixin { int selectedQuantity = 1; late AnimationController _scaleController; late AnimationController _fadeController; @@ -26,7 +27,7 @@ class _ProductCardState extends State 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 with TickerProviderStateMixin duration: const Duration(milliseconds: 300), vsync: this, )..forward(); - + _scaleAnimation = Tween( begin: 1.0, end: 0.95, @@ -44,7 +45,7 @@ class _ProductCardState extends State with TickerProviderStateMixin parent: _scaleController, curve: Curves.easeInOut, )); - + _fadeAnimation = Tween( begin: 0.0, end: 1.0, @@ -122,7 +123,6 @@ class _ProductCardState extends State with TickerProviderStateMixin : _buildPlaceholderImage(), ), ), - Positioned.fill( child: Container( decoration: BoxDecoration( @@ -141,7 +141,6 @@ class _ProductCardState extends State with TickerProviderStateMixin ), ), ), - if (widget.product.isStockDefined()) Positioned( top: 12, @@ -183,7 +182,6 @@ class _ProductCardState extends State with TickerProviderStateMixin ), ), ), - Positioned( left: 0, right: 0, @@ -201,7 +199,8 @@ class _ProductCardState extends State with TickerProviderStateMixin vertical: 8, ), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, children: [ Text( widget.product.name, @@ -239,9 +238,7 @@ class _ProductCardState extends State with TickerProviderStateMixin ], ), ), - const SizedBox(height: 12), - Row( children: [ Container( @@ -250,7 +247,8 @@ class _ProductCardState extends State 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 with TickerProviderStateMixin ], ), ), - const SizedBox(width: 8), - Expanded( child: MouseRegion( cursor: SystemMouseCursors.click, @@ -306,9 +302,11 @@ class _ProductCardState extends State 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 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 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 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 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, ), ), ), ); } -} \ No newline at end of file +} diff --git a/lib/accueil.dart b/lib/accueil.dart index 1a849e2..2bbd1f9 100644 --- a/lib/accueil.dart +++ b/lib/accueil.dart @@ -449,7 +449,7 @@ class _AccueilPageState extends State { 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, diff --git a/pubspec.lock b/pubspec.lock index 4afd464..443bfae 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -612,10 +612,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: "1b60b8f9d4ce0cb0e7d7bc223c955d083a0737bee66fa1fcfe5de48225e0d5b3" + sha256: d234581c090526676fd8fab4ada92f35c6746e3fb4f05a399665d75a399fb760 url: "https://pub.dev" source: hosted - version: "3.5.7" + version: "5.2.3" msix: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index cf81f97..b62d35b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: path_provider: ^2.0.15 shared_preferences: ^2.2.2 excel: ^2.0.1 - mobile_scanner: ^3.1.1 + mobile_scanner: ^5.0.0 # ou la version la plus récente