commit commit
This commit is contained in:
parent
57ea91b3d7
commit
2bef06a2fe
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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,63 +61,112 @@ 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,
|
||||||
|
),
|
||||||
|
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,
|
||||||
),
|
),
|
||||||
CustomPaint(
|
),
|
||||||
painter: QrScannerOverlay(
|
],
|
||||||
borderColor: Colors.blue.shade800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +175,7 @@ class _ScanQRPageState extends State<ScanQRPage> {
|
|||||||
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
|
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 + areaSize - borderLength, areaTop),
|
final double horizontalEnd = isLeft ? x + length : x;
|
||||||
Offset(areaLeft + areaSize, areaTop),
|
final double verticalStart = isTop ? y : y - length;
|
||||||
borderPaint,
|
final double verticalEnd = isTop ? y + length : y;
|
||||||
);
|
|
||||||
canvas.drawLine(
|
|
||||||
Offset(areaLeft + areaSize, areaTop),
|
|
||||||
Offset(areaLeft + areaSize, areaTop + borderLength),
|
|
||||||
borderPaint,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Coin inférieur gauche
|
canvas.drawLine(Offset(horizontalStart, y), Offset(horizontalEnd, y), paint);
|
||||||
canvas.drawLine(
|
canvas.drawLine(Offset(x, verticalStart), Offset(x, verticalEnd), paint);
|
||||||
Offset(areaLeft, areaTop + areaSize - borderLength),
|
|
||||||
Offset(areaLeft, areaTop + areaSize),
|
|
||||||
borderPaint,
|
|
||||||
);
|
|
||||||
canvas.drawLine(
|
|
||||||
Offset(areaLeft, areaTop + areaSize),
|
|
||||||
Offset(areaLeft + borderLength, areaTop + areaSize),
|
|
||||||
borderPaint,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -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
Normal file
36
lib/Models/pointage_model.dart
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
class Pointage {
|
||||||
|
final int? id;
|
||||||
|
final String userName;
|
||||||
|
final String date;
|
||||||
|
final String heureArrivee;
|
||||||
|
final String heureDepart;
|
||||||
|
|
||||||
|
Pointage({
|
||||||
|
this.id,
|
||||||
|
required this.userName,
|
||||||
|
required this.date,
|
||||||
|
required this.heureArrivee,
|
||||||
|
required this.heureDepart,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pour SQLite
|
||||||
|
factory Pointage.fromMap(Map<String, dynamic> map) {
|
||||||
|
return Pointage(
|
||||||
|
id: map['id'],
|
||||||
|
userName: map['userName'] ?? '',
|
||||||
|
date: map['date'],
|
||||||
|
heureArrivee: map['heureArrivee'],
|
||||||
|
heureDepart: map['heureDepart'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'userName': userName,
|
||||||
|
'date': date,
|
||||||
|
'heureArrivee': heureArrivee,
|
||||||
|
'heureDepart': heureDepart,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
60
lib/Services/pointageDatabase.dart
Normal file
60
lib/Services/pointageDatabase.dart
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:path/path.dart';
|
||||||
|
import 'package:sqflite/sqflite.dart';
|
||||||
|
import '../Models/pointage_model.dart';
|
||||||
|
|
||||||
|
class DatabaseHelper {
|
||||||
|
static final DatabaseHelper _instance = DatabaseHelper._internal();
|
||||||
|
|
||||||
|
factory DatabaseHelper() => _instance;
|
||||||
|
|
||||||
|
DatabaseHelper._internal();
|
||||||
|
|
||||||
|
Database? _db;
|
||||||
|
|
||||||
|
Future<Database> get database async {
|
||||||
|
if (_db != null) return _db!;
|
||||||
|
_db = await _initDatabase();
|
||||||
|
return _db!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Database> _initDatabase() async {
|
||||||
|
String databasesPath = await getDatabasesPath();
|
||||||
|
String dbPath = join(databasesPath, 'pointage.db');
|
||||||
|
return await openDatabase(dbPath, version: 1, onCreate: _onCreate);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _onCreate(Database db, int version) async {
|
||||||
|
await db.execute('''
|
||||||
|
CREATE TABLE pointages (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
userName TEXT NOT NULL,
|
||||||
|
date TEXT NOT NULL,
|
||||||
|
heureArrivee TEXT NOT NULL,
|
||||||
|
heureDepart TEXT NOT NULL
|
||||||
|
)
|
||||||
|
''');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> insertPointage(Pointage pointage) async {
|
||||||
|
final db = await database;
|
||||||
|
return await db.insert('pointages', pointage.toMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Pointage>> getPointages() async {
|
||||||
|
final db = await database;
|
||||||
|
final pointages = await db.query('pointages');
|
||||||
|
return pointages.map((pointage) => Pointage.fromMap(pointage)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> updatePointage(Pointage pointage) async {
|
||||||
|
final db = await database;
|
||||||
|
return await db.update('pointages', pointage.toMap(),
|
||||||
|
where: 'id = ?', whereArgs: [pointage.id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> deletePointage(int id) async {
|
||||||
|
final db = await database;
|
||||||
|
return await db.delete('pointages', where: 'id = ?', whereArgs: [id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)}'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -323,9 +326,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
pw.SizedBox(height: 30),
|
pw.SizedBox(height: 30),
|
||||||
|
|
||||||
// Informations client
|
// Informations client
|
||||||
pw.Container(
|
pw.Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
@ -339,11 +342,12 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
|||||||
children: [
|
children: [
|
||||||
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)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -379,24 +383,30 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
|||||||
// Tableau des produits
|
// Tableau des produits
|
||||||
pw.Text('DÉTAILS DE LA COMMANDE', style: titleStyle),
|
pw.Text('DÉTAILS DE LA COMMANDE', style: titleStyle),
|
||||||
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) {
|
||||||
final index = entry.key;
|
final index = entry.key;
|
||||||
final detail = entry.value;
|
final detail = entry.value;
|
||||||
final isEven = index % 2 == 0;
|
final isEven = index % 2 == 0;
|
||||||
|
|
||||||
return pw.TableRow(
|
return pw.TableRow(
|
||||||
decoration: pw.BoxDecoration(
|
decoration: pw.BoxDecoration(
|
||||||
color: isEven ? PdfColors.white : PdfColors.grey50,
|
color: isEven ? PdfColors.white : PdfColors.grey50,
|
||||||
@ -411,9 +421,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
pw.SizedBox(height: 20),
|
pw.SizedBox(height: 20),
|
||||||
|
|
||||||
// Total
|
// Total
|
||||||
pw.Container(
|
pw.Container(
|
||||||
alignment: pw.Alignment.centerRight,
|
alignment: pw.Alignment.centerRight,
|
||||||
@ -433,9 +443,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
pw.Spacer(),
|
pw.Spacer(),
|
||||||
|
|
||||||
// Pied de page
|
// Pied de page
|
||||||
pw.Container(
|
pw.Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
@ -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),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -730,9 +741,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Barre de recherche améliorée
|
// Barre de recherche améliorée
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -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,
|
||||||
@ -763,9 +775,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Filtres améliorés
|
// Filtres améliorés
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@ -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,
|
||||||
@ -825,9 +838,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -875,19 +888,21 @@ 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),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
// Bouton reset
|
// Bouton reset
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -916,9 +931,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
// Toggle pour afficher/masquer les commandes annulées
|
// Toggle pour afficher/masquer les commandes annulées
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -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(
|
||||||
@ -964,7 +980,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Liste des commandes
|
// Liste des commandes
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _filteredCommandes.isEmpty
|
child: _filteredCommandes.isEmpty
|
||||||
@ -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,8 +1113,9 @@ 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 ==
|
||||||
? Colors.red
|
StatutCommande.annulee
|
||||||
|
? 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',
|
||||||
@ -1668,4 +1688,4 @@ class _PaymentMethodDialogState extends State<PaymentMethodDialog> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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,8 +98,7 @@ 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);
|
||||||
print('Utilisateur récupéré: ${user.username}');
|
print('Utilisateur récupéré: ${user.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> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -128,7 +128,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Contenu principal
|
// Contenu principal
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
@ -745,4 +745,4 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
|
|||||||
_adresseController.dispose();
|
_adresseController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
190
lib/Views/pointage.dart
Normal file
190
lib/Views/pointage.dart
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:youmazgestion/Services/pointageDatabase.dart';
|
||||||
|
import 'package:youmazgestion/Models/pointage_model.dart';
|
||||||
|
|
||||||
|
class PointagePage extends StatefulWidget {
|
||||||
|
const PointagePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PointagePage> createState() => _PointagePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PointagePageState extends State<PointagePage> {
|
||||||
|
final DatabaseHelper _databaseHelper = DatabaseHelper();
|
||||||
|
List<Pointage> _pointages = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadPointages();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadPointages() async {
|
||||||
|
final pointages = await _databaseHelper.getPointages();
|
||||||
|
setState(() {
|
||||||
|
_pointages = pointages;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showAddDialog() async {
|
||||||
|
final _arrivalController = TextEditingController();
|
||||||
|
|
||||||
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text('Ajouter Pointage'),
|
||||||
|
content: TextField(
|
||||||
|
controller: _arrivalController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Heure d\'arrivée (HH:mm)',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: Text('Annuler'),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
child: Text('Ajouter'),
|
||||||
|
onPressed: () async {
|
||||||
|
final pointage = Pointage(
|
||||||
|
userName:
|
||||||
|
"Nom de l'utilisateur", // fixed value, customize if needed
|
||||||
|
date: DateTime.now().toString().split(' ')[0],
|
||||||
|
heureArrivee: _arrivalController.text,
|
||||||
|
heureDepart: '',
|
||||||
|
);
|
||||||
|
await _databaseHelper.insertPointage(pointage);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
_loadPointages();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _scanQRCode({required bool isEntree}) {
|
||||||
|
// Ici tu peux intégrer ton scanner QR.
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(isEntree ? "Scan QR pour Entrée" : "Scan QR pour Sortie"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Pointage'),
|
||||||
|
),
|
||||||
|
body: _pointages.isEmpty
|
||||||
|
? Center(child: Text('Aucun pointage enregistré.'))
|
||||||
|
: ListView.builder(
|
||||||
|
itemCount: _pointages.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final pointage = _pointages[index];
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12.0, vertical: 6.0),
|
||||||
|
child: Card(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
side: BorderSide(color: Colors.blueGrey.shade100),
|
||||||
|
),
|
||||||
|
elevation: 4,
|
||||||
|
shadowColor: Colors.blueGrey.shade50,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0, vertical: 4),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
backgroundColor: Colors.blue.shade100,
|
||||||
|
child: Icon(Icons.person, color: Colors.blue),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
pointage
|
||||||
|
.userName, // suppose non-null (corrige si null possible)
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 18),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Divider(),
|
||||||
|
Text(
|
||||||
|
pointage.date,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.black87, fontSize: 15),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.login,
|
||||||
|
size: 18, color: Colors.green.shade700),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text("Arrivée : ${pointage.heureArrivee}",
|
||||||
|
style:
|
||||||
|
TextStyle(color: Colors.green.shade700)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.logout,
|
||||||
|
size: 18, color: Colors.red.shade700),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
"Départ : ${pointage.heureDepart.isNotEmpty ? pointage.heureDepart : "---"}",
|
||||||
|
style: TextStyle(color: Colors.red.shade700)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
floatingActionButton: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
FloatingActionButton.extended(
|
||||||
|
onPressed: () => _scanQRCode(isEntree: true),
|
||||||
|
label: Text('Entrée'),
|
||||||
|
icon: Icon(Icons.qr_code_scanner, color: Colors.green),
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: Colors.green,
|
||||||
|
heroTag: 'btnEntree',
|
||||||
|
),
|
||||||
|
SizedBox(height: 12),
|
||||||
|
FloatingActionButton.extended(
|
||||||
|
onPressed: () => _scanQRCode(isEntree: false),
|
||||||
|
label: Text('Sortie'),
|
||||||
|
icon: Icon(Icons.qr_code_scanner, color: Colors.red),
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: Colors.red,
|
||||||
|
heroTag: 'btnSortie',
|
||||||
|
),
|
||||||
|
SizedBox(height: 12),
|
||||||
|
FloatingActionButton(
|
||||||
|
onPressed: _showAddDialog,
|
||||||
|
tooltip: 'Ajouter Pointage',
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
heroTag: 'btnAdd',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@ import 'package:youmazgestion/Models/produit.dart';
|
|||||||
|
|
||||||
class ProductCard extends StatefulWidget {
|
class ProductCard extends StatefulWidget {
|
||||||
final Product product;
|
final Product product;
|
||||||
final void Function(Product, int) onAddToCart;
|
final void Function(Product, int) onAddToCart;
|
||||||
|
|
||||||
const ProductCard({
|
const ProductCard({
|
||||||
Key? key,
|
Key? key,
|
||||||
@ -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;
|
||||||
@ -26,7 +27,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
// Animations pour les interactions
|
// Animations pour les interactions
|
||||||
_scaleController = AnimationController(
|
_scaleController = AnimationController(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
@ -36,7 +37,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
|||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
vsync: this,
|
vsync: this,
|
||||||
)..forward();
|
)..forward();
|
||||||
|
|
||||||
_scaleAnimation = Tween<double>(
|
_scaleAnimation = Tween<double>(
|
||||||
begin: 1.0,
|
begin: 1.0,
|
||||||
end: 0.95,
|
end: 0.95,
|
||||||
@ -44,7 +45,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
|||||||
parent: _scaleController,
|
parent: _scaleController,
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
));
|
));
|
||||||
|
|
||||||
_fadeAnimation = Tween<double>(
|
_fadeAnimation = Tween<double>(
|
||||||
begin: 0.0,
|
begin: 0.0,
|
||||||
end: 1.0,
|
end: 1.0,
|
||||||
@ -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,10 +452,12 @@ 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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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…
Reference in New Issue
Block a user