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