Browse Source

commit commit

31052025_02
b.razafimandimbihery 6 months ago
parent
commit
2bef06a2fe
  1. 3
      android/app/src/main/AndroidManifest.xml
  2. 3
      ios/Runner/Info.plist
  3. 268
      lib/Components/QrScan.dart
  4. 16
      lib/Components/appDrawer.dart
  5. 36
      lib/Models/pointage_model.dart
  6. 60
      lib/Services/pointageDatabase.dart
  7. 78
      lib/Views/commandManagement.dart
  8. 2
      lib/Views/historique.dart
  9. 97
      lib/Views/loginPage.dart
  10. 190
      lib/Views/pointage.dart
  11. 60
      lib/Views/produitsCard.dart
  12. 2
      lib/accueil.dart
  13. 4
      pubspec.lock
  14. 2
      pubspec.yaml

3
android/app/src/main/AndroidManifest.xml

@ -1,4 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
<application <application
android:label="my_app" android:label="my_app"
android:name="${applicationName}" android:name="${applicationName}"
@ -43,5 +45,4 @@
<data android:mimeType="text/plain"/> <data android:mimeType="text/plain"/>
</intent> </intent>
</queries> </queries>
<uses-permission android:name="android.permission.CAMERA" />
</manifest> </manifest>

3
ios/Runner/Info.plist

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>NSCameraUsageDescription</key>
<string>Cette application a besoin d'accéder à la caméra pour scanner les codes QR</string>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
@ -47,5 +49,6 @@
<true/> <true/>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/> <true/>
</dict> </dict>
</plist> </plist>

268
lib/Components/QrScan.dart

@ -1,10 +1,9 @@
// Ajoutez cette importation en haut du fichier import 'dart:io';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:mobile_scanner/mobile_scanner.dart';
// Créez une nouvelle classe pour la page de scan QR
class ScanQRPage extends StatefulWidget { class ScanQRPage extends StatefulWidget {
const ScanQRPage({super.key}); const ScanQRPage({super.key});
@ -13,13 +12,47 @@ class ScanQRPage extends StatefulWidget {
} }
class _ScanQRPageState extends State<ScanQRPage> { class _ScanQRPageState extends State<ScanQRPage> {
MobileScannerController cameraController = MobileScannerController(); MobileScannerController? cameraController;
bool _isScanComplete = false; bool _isScanComplete = false;
String? _scannedData; 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 @override
void dispose() { void dispose() {
cameraController.dispose(); cameraController?.dispose();
super.dispose(); super.dispose();
} }
@ -28,72 +61,121 @@ class _ScanQRPageState extends State<ScanQRPage> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Scanner QR Code'), title: const Text('Scanner QR Code'),
actions: [ actions: _hasError ? [] : [
IconButton( if (cameraController != null) ...[
color: Colors.white, IconButton(
icon: ValueListenableBuilder( color: Colors.white,
valueListenable: cameraController.torchState, icon: const Icon(Icons.flash_on, color: Colors.white),
builder: (context, state, child) { iconSize: 32.0,
switch (state) { onPressed: () => cameraController!.toggleTorch(),
case TorchState.off:
return const Icon(Icons.flash_off, color: Colors.grey);
case TorchState.on:
return const Icon(Icons.flash_on, color: Colors.yellow);
}
},
), ),
iconSize: 32.0, IconButton(
onPressed: () => cameraController.toggleTorch(), color: Colors.white,
), icon: const Icon(Icons.flip_camera_ios, color: Colors.white),
IconButton( iconSize: 32.0,
color: Colors.white, onPressed: () => cameraController!.switchCamera(),
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);
}
},
), ),
iconSize: 32.0, ],
onPressed: () => cameraController.switchCamera(),
),
], ],
), ),
body: Stack( body: _hasError ? _buildErrorWidget() : _buildScannerWidget(),
children: [ );
MobileScanner( }
controller: cameraController,
onDetect: (capture) { Widget _buildErrorWidget() {
final List<Barcode> barcodes = capture.barcodes; return Center(
for (final barcode in barcodes) { child: Padding(
if (!_isScanComplete && barcode.rawValue != null) { padding: const EdgeInsets.all(16.0),
_isScanComplete = true; child: Column(
_scannedData = barcode.rawValue; mainAxisAlignment: MainAxisAlignment.center,
_showScanResult(context, _scannedData!); children: [
} const Icon(
} Icons.error_outline,
}, size: 64,
), color: Colors.red,
CustomPaint(
painter: QrScannerOverlay(
borderColor: Colors.blue.shade800,
), ),
), 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) { void _showScanResult(BuildContext context, String data) {
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Résultat du scan'), title: const Text('Résultat du scan'),
content: Text(data), content: SelectableText(data), // Permet de sélectionner le texte
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
@ -102,12 +184,18 @@ class _ScanQRPageState extends State<ScanQRPage> {
_isScanComplete = false; _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((_) { ).then((_) {
// Réinitialiser le scan après la fermeture du dialogue
setState(() { setState(() {
_isScanComplete = false; _isScanComplete = false;
}); });
@ -115,7 +203,6 @@ class _ScanQRPageState extends State<ScanQRPage> {
} }
} }
// Widget personnalisé pour l'overlay du scanner
class QrScannerOverlay extends CustomPainter { class QrScannerOverlay extends CustomPainter {
final Color borderColor; final Color borderColor;
@ -129,12 +216,10 @@ class QrScannerOverlay extends CustomPainter {
final double borderLength = 30.0; final double borderLength = 30.0;
final double areaSize = width * 0.7; final double areaSize = width * 0.7;
// Dessiner un rectangle semi-transparent autour de la zone de scan
final Paint backgroundPaint = Paint() final Paint backgroundPaint = Paint()
..color = Colors.black.withOpacity(0.4); ..color = Colors.black.withOpacity(0.4);
canvas.drawRect(Rect.fromLTRB(0, 0, width, height), backgroundPaint); canvas.drawRect(Rect.fromLTRB(0, 0, width, height), backgroundPaint);
// Dessiner la zone de scan transparente
final Paint transparentPaint = Paint() final Paint transparentPaint = Paint()
..color = Colors.transparent ..color = Colors.transparent
..blendMode = BlendMode.clear; ..blendMode = BlendMode.clear;
@ -145,59 +230,26 @@ class QrScannerOverlay extends CustomPainter {
transparentPaint, transparentPaint,
); );
// Dessiner les bordures de la zone de scan
final Paint borderPaint = Paint() final Paint borderPaint = Paint()
..color = borderColor ..color = borderColor
..strokeWidth = borderWidth ..strokeWidth = borderWidth
..style = PaintingStyle.stroke; ..style = PaintingStyle.stroke;
// Coin supérieur gauche // Coins du scanner
canvas.drawLine( _drawCorner(canvas, borderPaint, areaLeft, areaTop, borderLength, true, true);
Offset(areaLeft, areaTop), _drawCorner(canvas, borderPaint, areaLeft + areaSize, areaTop, borderLength, false, true);
Offset(areaLeft + borderLength, areaTop), _drawCorner(canvas, borderPaint, areaLeft, areaTop + areaSize, borderLength, true, false);
borderPaint, _drawCorner(canvas, borderPaint, areaLeft + areaSize, areaTop + areaSize, borderLength, false, false);
); }
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,
);
// Coin inférieur gauche void _drawCorner(Canvas canvas, Paint paint, double x, double y, double length, bool isLeft, bool isTop) {
canvas.drawLine( final double horizontalStart = isLeft ? x : x - length;
Offset(areaLeft, areaTop + areaSize - borderLength), final double horizontalEnd = isLeft ? x + length : x;
Offset(areaLeft, areaTop + areaSize), final double verticalStart = isTop ? y : y - length;
borderPaint, final double verticalEnd = isTop ? y + length : y;
);
canvas.drawLine(
Offset(areaLeft, areaTop + areaSize),
Offset(areaLeft + borderLength, areaTop + areaSize),
borderPaint,
);
// Coin inférieur droit canvas.drawLine(Offset(horizontalStart, y), Offset(horizontalEnd, y), paint);
canvas.drawLine( canvas.drawLine(Offset(x, verticalStart), Offset(x, verticalEnd), paint);
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,
);
} }
@override @override

16
lib/Components/appDrawer.dart

@ -13,6 +13,7 @@ import 'package:youmazgestion/Views/newCommand.dart';
import 'package:youmazgestion/Views/registrationPage.dart'; import 'package:youmazgestion/Views/registrationPage.dart';
import 'package:youmazgestion/accueil.dart'; import 'package:youmazgestion/accueil.dart';
import 'package:youmazgestion/controller/userController.dart'; import 'package:youmazgestion/controller/userController.dart';
import 'package:youmazgestion/Views/pointage.dart';
class CustomDrawer extends StatelessWidget { class CustomDrawer extends StatelessWidget {
final UserController userController = Get.find<UserController>(); final UserController userController = Get.find<UserController>();
@ -73,7 +74,9 @@ class CustomDrawer extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
controller.name.isNotEmpty ? controller.name : 'Utilisateur', controller.name.isNotEmpty
? controller.name
: 'Utilisateur',
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 18, fontSize: 18,
@ -123,6 +126,14 @@ class CustomDrawer extends StatelessWidget {
permissionRoute: '/modifier-utilisateur', permissionRoute: '/modifier-utilisateur',
onTap: () => Get.to(const ListUserPage()), 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)) { if (gestionUtilisateursItems.any((item) => item is ListTile)) {
@ -322,7 +333,8 @@ class CustomDrawer extends StatelessWidget {
required VoidCallback onTap, required VoidCallback onTap,
}) async { }) async {
if (permissionAction != null && permissionRoute != null) { if (permissionAction != null && permissionRoute != null) {
bool hasPermission = await userController.hasPermission(permissionAction, permissionRoute); bool hasPermission =
await userController.hasPermission(permissionAction, permissionRoute);
if (!hasPermission) { if (!hasPermission) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }

36
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<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,
};
}
}

60
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<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]);
}
}

78
lib/Views/commandManagement.dart

@ -56,9 +56,11 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
final query = _searchController.text.toLowerCase(); final query = _searchController.text.toLowerCase();
setState(() { setState(() {
_filteredCommandes = _commandes.where((commande) { _filteredCommandes = _commandes.where((commande) {
final matchesSearch = commande.clientNomComplet.toLowerCase().contains(query) || final matchesSearch =
commande.id.toString().contains(query); commande.clientNomComplet.toLowerCase().contains(query) ||
final matchesStatut = _selectedStatut == null || commande.statut == _selectedStatut; commande.id.toString().contains(query);
final matchesStatut =
_selectedStatut == null || commande.statut == _selectedStatut;
final matchesDate = _selectedDate == null || final matchesDate = _selectedDate == null ||
DateFormat('yyyy-MM-dd').format(commande.dateCommande) == DateFormat('yyyy-MM-dd').format(commande.dateCommande) ==
DateFormat('yyyy-MM-dd').format(_selectedDate!); DateFormat('yyyy-MM-dd').format(_selectedDate!);
@ -280,12 +282,11 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
width: 100, width: 100,
height: 80, height: 80,
decoration: pw.BoxDecoration( 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), borderRadius: pw.BorderRadius.circular(8),
), ),
child: pw.Center( child: pw.Center(child: pw.Image(image)),
child: pw.Image(image)
),
), ),
pw.SizedBox(height: 10), pw.SizedBox(height: 10),
pw.Text('guycom', style: headerStyle), pw.Text('guycom', style: headerStyle),
@ -306,7 +307,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
child: pw.Column( child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start, crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [ children: [
pw.Text('FACTURE', pw.Text(
'FACTURE',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: pw.FontWeight.bold, fontWeight: pw.FontWeight.bold,
@ -315,7 +317,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
), ),
pw.SizedBox(height: 8), pw.SizedBox(height: 8),
pw.Text('N°: ${commande.id}', style: titleStyle), 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)}'),
], ],
), ),
), ),
@ -340,10 +343,11 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
pw.Text('FACTURÉ À:', style: titleStyle), pw.Text('FACTURÉ À:', style: titleStyle),
pw.SizedBox(height: 5), pw.SizedBox(height: 5),
pw.Text(client?.nomComplet ?? 'Client inconnu', pw.Text(client?.nomComplet ?? 'Client inconnu',
style: pw.TextStyle(fontSize: 12)), style: pw.TextStyle(fontSize: 12)),
if (client?.telephone != null) if (client?.telephone != null)
pw.Text('Tél: ${client!.telephone}', pw.Text('Tél: ${client!.telephone}',
style: pw.TextStyle(fontSize: 10, color: PdfColors.grey600)), style: pw.TextStyle(
fontSize: 10, color: PdfColors.grey600)),
], ],
), ),
), ),
@ -381,15 +385,21 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
pw.SizedBox(height: 10), pw.SizedBox(height: 10),
pw.Table( pw.Table(
border: pw.TableBorder.all(color: PdfColors.grey400, width: 0.5), border:
pw.TableBorder.all(color: PdfColors.grey400, width: 0.5),
children: [ children: [
pw.TableRow( pw.TableRow(
decoration: const pw.BoxDecoration(color: PdfColors.blue900), decoration:
const pw.BoxDecoration(color: PdfColors.blue900),
children: [ children: [
_buildTableCell('Produit', titleStyle.copyWith(color: PdfColors.white)), _buildTableCell('Produit',
_buildTableCell('Qté', titleStyle.copyWith(color: PdfColors.white)), titleStyle.copyWith(color: PdfColors.white)),
_buildTableCell('Prix unit.', titleStyle.copyWith(color: PdfColors.white)), _buildTableCell(
_buildTableCell('Total', titleStyle.copyWith(color: PdfColors.white)), '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) { ...details.asMap().entries.map((entry) {
@ -458,7 +468,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
pw.SizedBox(height: 5), pw.SizedBox(height: 5),
pw.Text( pw.Text(
'Cette facture est générée automatiquement par le système Youmaz Gestion', '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),
), ),
], ],
), ),
@ -749,7 +760,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
controller: _searchController, controller: _searchController,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Rechercher par client ou numéro de commande', 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( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none, borderSide: BorderSide.none,
@ -786,7 +798,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
value: _selectedStatut, value: _selectedStatut,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Filtrer par statut', labelText: 'Filtrer par statut',
prefixIcon: Icon(Icons.filter_list, color: Colors.blue.shade600), prefixIcon: Icon(Icons.filter_list,
color: Colors.blue.shade600),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none, borderSide: BorderSide.none,
@ -875,11 +888,13 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
}); });
} }
}, },
icon: Icon(Icons.calendar_today, color: Colors.blue.shade600), icon: Icon(Icons.calendar_today,
color: Colors.blue.shade600),
label: Text( label: Text(
_selectedDate == null _selectedDate == null
? 'Date' ? 'Date'
: DateFormat('dd/MM/yyyy').format(_selectedDate!), : DateFormat('dd/MM/yyyy')
.format(_selectedDate!),
style: const TextStyle(color: Colors.black87), style: const TextStyle(color: Colors.black87),
), ),
), ),
@ -931,7 +946,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
offset: const Offset(0, 2),) offset: const Offset(0, 2),)
], ],
), ),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row( child: Row(
children: [ children: [
Icon( Icon(
@ -1040,9 +1056,10 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
Icon( Icon(
_getStatutIcon(commande.statut), _getStatutIcon(commande.statut),
size: 20, size: 20,
color: commande.statut == StatutCommande.annulee color:
? Colors.red commande.statut == StatutCommande.annulee
: Colors.blue.shade600, ? Colors.red
: Colors.blue.shade600,
), ),
Text( Text(
'#${commande.id}', '#${commande.id}',
@ -1074,7 +1091,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
DateFormat('dd/MM/yyyy').format(commande.dateCommande), DateFormat('dd/MM/yyyy')
.format(commande.dateCommande),
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.grey.shade600, color: Colors.grey.shade600,
@ -1095,7 +1113,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: commande.statut == StatutCommande.annulee color: commande.statut ==
StatutCommande.annulee
? Colors.red ? Colors.red
: Colors.blue.shade700, : Colors.blue.shade700,
), ),
@ -1419,7 +1438,8 @@ class _CommandeActions extends StatelessWidget {
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ 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), const SizedBox(width: 8),
Text( Text(
'Commande confirmée', 'Commande confirmée',

2
lib/Views/historique.dart

@ -365,7 +365,7 @@ class _HistoriquePageState extends State<HistoriquePage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
commande.dateCommande as String commande.dateCommande.timeZoneName
), ),
Text( Text(
'${commande.montantTotal.toStringAsFixed(2)} DA', '${commande.montantTotal.toStringAsFixed(2)} DA',

97
lib/Views/loginPage.dart

@ -74,7 +74,8 @@ class _LoginPageState extends State<LoginPage> {
if (username.isEmpty || password.isEmpty) { if (username.isEmpty || password.isEmpty) {
setState(() { 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; _isErrorVisible = true;
}); });
return; return;
@ -97,7 +98,6 @@ class _LoginPageState extends State<LoginPage> {
} }
bool isValidUser = await dbInstance.verifyUser(username, password); bool isValidUser = await dbInstance.verifyUser(username, password);
print('Résultat de la vérification: $isValidUser');
if (isValidUser) { if (isValidUser) {
Users user = await dbInstance.getUser(username); Users user = await dbInstance.getUser(username);
@ -137,53 +137,86 @@ class _LoginPageState extends State<LoginPage> {
throw Exception('Erreur lors de la récupération des credentials'); throw Exception('Erreur lors de la récupération des credentials');
} }
} else { } else {
print('Identifiants invalides pour: $username');
setState(() { setState(() {
_errorMessage = 'Nom d\'utilisateur ou mot de passe invalide'; _errorMessage = 'Nom d\'utilisateur ou mot de passe invalide';
_isErrorVisible = true; _isErrorVisible = true;
}); });
} }
} catch (error) { } catch (error) {
print('Erreur lors de la connexion: $error');
setState(() { setState(() {
_errorMessage = 'Erreur de connexion: ${error.toString()}'; _errorMessage = 'Erreur de connexion: ${error.toString()}';
_isErrorVisible = true; _isErrorVisible = true;
}); });
} finally { } finally {
if (mounted) { if (mounted) setState(() => _isLoading = false);
setState(() {
_isLoading = false;
});
}
} }
} }
@override @override
Widget build(BuildContext context) { 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( return Scaffold(
appBar: AppBar( backgroundColor: primaryColor,
title: const Text(
'Login',
style: TextStyle(color: Colors.white),
),
backgroundColor: const Color.fromARGB(255, 4, 54, 95),
centerTitle: true,
),
body: ParticleBackground( body: ParticleBackground(
child: Center( child: Center(
child: Container( child: SingleChildScrollView(
width: MediaQuery.of(context).size.width * 0.5, child: Container(
height: MediaQuery.of(context).size.height * 0.8, width: MediaQuery.of(context).size.width < 500
padding: const EdgeInsets.all(16.0), ? double.infinity
decoration: BoxDecoration( : 400,
color: Colors.white, padding:
shape: BoxShape.rectangle, const EdgeInsets.symmetric(horizontal: 24.0, vertical: 32.0),
borderRadius: BorderRadius.circular(30.0), decoration: BoxDecoration(
), color: cardColor.withOpacity(0.98),
child: Column( borderRadius: BorderRadius.circular(30.0),
crossAxisAlignment: CrossAxisAlignment.stretch, boxShadow: [
children: [ BoxShadow(
Container( 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), padding: const EdgeInsets.symmetric(vertical: 16.0),
child: const Icon( child: const Icon(
Icons.lock_outline, Icons.lock_outline,
@ -256,11 +289,15 @@ class _LoginPageState extends State<LoginPage> {
), ),
), ),
), ),
]
)
)
], ],
), ),
), ),
), ),
), ),
)
); );
} }
} }

190
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<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',
),
],
),
);
}
}

60
lib/Views/produitsCard.dart

@ -16,7 +16,8 @@ class ProductCard extends StatefulWidget {
State<ProductCard> createState() => _ProductCardState(); State<ProductCard> createState() => _ProductCardState();
} }
class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin { class _ProductCardState extends State<ProductCard>
with TickerProviderStateMixin {
int selectedQuantity = 1; int selectedQuantity = 1;
late AnimationController _scaleController; late AnimationController _scaleController;
late AnimationController _fadeController; late AnimationController _fadeController;
@ -122,7 +123,6 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
: _buildPlaceholderImage(), : _buildPlaceholderImage(),
), ),
), ),
Positioned.fill( Positioned.fill(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -141,7 +141,6 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
), ),
), ),
), ),
if (widget.product.isStockDefined()) if (widget.product.isStockDefined())
Positioned( Positioned(
top: 12, top: 12,
@ -183,7 +182,6 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
), ),
), ),
), ),
Positioned( Positioned(
left: 0, left: 0,
right: 0, right: 0,
@ -201,7 +199,8 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
vertical: 8, vertical: 8,
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
Text( Text(
widget.product.name, widget.product.name,
@ -239,9 +238,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
], ],
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Row( Row(
children: [ children: [
Container( Container(
@ -250,7 +247,8 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color:
Colors.black.withOpacity(0.1),
blurRadius: 4, blurRadius: 4,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@ -295,9 +293,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
], ],
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: MouseRegion( child: MouseRegion(
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
@ -306,9 +302,11 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
onTapUp: _onTapUp, onTapUp: _onTapUp,
onTapCancel: _onTapCancel, onTapCancel: _onTapCancel,
onTap: () { onTap: () {
widget.onAddToCart(widget.product, selectedQuantity); widget.onAddToCart(widget.product,
selectedQuantity);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar( SnackBar(
content: Row( content: Row(
children: [ children: [
@ -320,16 +318,20 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
Expanded( Expanded(
child: Text( child: Text(
'${widget.product.name} (x$selectedQuantity) ajouté au panier', '${widget.product.name} (x$selectedQuantity) ajouté au panier',
overflow: TextOverflow.ellipsis, overflow: TextOverflow
.ellipsis,
), ),
), ),
], ],
), ),
backgroundColor: Colors.green, backgroundColor: Colors.green,
duration: const Duration(seconds: 1), duration:
behavior: SnackBarBehavior.floating, const Duration(seconds: 1),
behavior:
SnackBarBehavior.floating,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), borderRadius:
BorderRadius.circular(10),
), ),
), ),
); );
@ -342,21 +344,27 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: const LinearGradient( gradient: const LinearGradient(
colors: [ colors: [
Color.fromARGB(255, 4, 54, 95), Color.fromARGB(
Color.fromARGB(255, 6, 80, 140), 255, 4, 54, 95),
Color.fromARGB(
255, 6, 80, 140),
], ],
), ),
borderRadius: BorderRadius.circular(20), borderRadius:
BorderRadius.circular(20),
boxShadow: [ boxShadow: [
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, blurRadius: 6,
offset: const Offset(0, 3), offset: const Offset(0, 3),
), ),
], ],
), ),
child: const Row( child: const Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment:
MainAxisAlignment.center,
children: [ children: [
const Icon( const Icon(
Icons.add_shopping_cart, Icons.add_shopping_cart,
@ -369,10 +377,12 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
'Ajouter', 'Ajouter',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontWeight: FontWeight.bold, fontWeight:
FontWeight.bold,
fontSize: 12, fontSize: 12,
), ),
overflow: TextOverflow.ellipsis, overflow:
TextOverflow.ellipsis,
), ),
), ),
], ],
@ -442,7 +452,9 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
child: Icon( child: Icon(
icon, icon,
size: 16, 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,
), ),
), ),
), ),

2
lib/accueil.dart

@ -449,7 +449,7 @@ class _AccueilPageState extends State<AccueilPage> {
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold)), fontWeight: FontWeight.bold)),
Text( Text(
'${NumberFormat('#,##0.00').format(calculateTotalPrice())} FCFA', '${NumberFormat('#,##0.00').format(calculateTotalPrice())} MGA',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

4
pubspec.lock

@ -612,10 +612,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: mobile_scanner name: mobile_scanner
sha256: "1b60b8f9d4ce0cb0e7d7bc223c955d083a0737bee66fa1fcfe5de48225e0d5b3" sha256: d234581c090526676fd8fab4ada92f35c6746e3fb4f05a399665d75a399fb760
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.5.7" version: "5.2.3"
msix: msix:
dependency: "direct main" dependency: "direct main"
description: description:

2
pubspec.yaml

@ -62,7 +62,7 @@ dependencies:
path_provider: ^2.0.15 path_provider: ^2.0.15
shared_preferences: ^2.2.2 shared_preferences: ^2.2.2
excel: ^2.0.1 excel: ^2.0.1
mobile_scanner: ^3.1.1 mobile_scanner: ^5.0.0 # ou la version la plus récente

Loading…
Cancel
Save