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">
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.FLASHLIGHT" />
|
||||
<application
|
||||
android:label="my_app"
|
||||
android:name="${applicationName}"
|
||||
@ -43,5 +45,4 @@
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
</manifest>
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Cette application a besoin d'accéder à la caméra pour scanner les codes QR</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
@ -47,5 +49,6 @@
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
// Ajoutez cette importation en haut du fichier
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
|
||||
// Créez une nouvelle classe pour la page de scan QR
|
||||
class ScanQRPage extends StatefulWidget {
|
||||
const ScanQRPage({super.key});
|
||||
|
||||
@ -13,13 +12,47 @@ class ScanQRPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ScanQRPageState extends State<ScanQRPage> {
|
||||
MobileScannerController cameraController = MobileScannerController();
|
||||
MobileScannerController? cameraController;
|
||||
bool _isScanComplete = false;
|
||||
String? _scannedData;
|
||||
bool _hasError = false;
|
||||
String? _errorMessage;
|
||||
bool get isMobile => !kIsWeb && (Platform.isAndroid || Platform.isIOS);
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeController();
|
||||
}
|
||||
|
||||
void _initializeController() {
|
||||
if (!isMobile) {
|
||||
setState(() {
|
||||
_hasError = true;
|
||||
_errorMessage = "Le scanner QR n'est pas disponible sur cette plateforme.";
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
cameraController = MobileScannerController(
|
||||
detectionSpeed: DetectionSpeed.noDuplicates,
|
||||
facing: CameraFacing.back,
|
||||
torchEnabled: false,
|
||||
);
|
||||
setState(() {
|
||||
_hasError = false;
|
||||
_errorMessage = null;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_hasError = true;
|
||||
_errorMessage = 'Erreur d\'initialisation de la caméra: $e';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
cameraController.dispose();
|
||||
cameraController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -28,63 +61,112 @@ class _ScanQRPageState extends State<ScanQRPage> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Scanner QR Code'),
|
||||
actions: [
|
||||
IconButton(
|
||||
color: Colors.white,
|
||||
icon: ValueListenableBuilder(
|
||||
valueListenable: cameraController.torchState,
|
||||
builder: (context, state, child) {
|
||||
switch (state) {
|
||||
case TorchState.off:
|
||||
return const Icon(Icons.flash_off, color: Colors.grey);
|
||||
case TorchState.on:
|
||||
return const Icon(Icons.flash_on, color: Colors.yellow);
|
||||
}
|
||||
},
|
||||
actions: _hasError ? [] : [
|
||||
if (cameraController != null) ...[
|
||||
IconButton(
|
||||
color: Colors.white,
|
||||
icon: const Icon(Icons.flash_on, color: Colors.white),
|
||||
iconSize: 32.0,
|
||||
onPressed: () => cameraController!.toggleTorch(),
|
||||
),
|
||||
iconSize: 32.0,
|
||||
onPressed: () => cameraController.toggleTorch(),
|
||||
),
|
||||
IconButton(
|
||||
color: Colors.white,
|
||||
icon: ValueListenableBuilder(
|
||||
valueListenable: cameraController.cameraFacingState,
|
||||
builder: (context, state, child) {
|
||||
switch (state) {
|
||||
case CameraFacing.front:
|
||||
return const Icon(Icons.camera_front);
|
||||
case CameraFacing.back:
|
||||
return const Icon(Icons.camera_rear);
|
||||
}
|
||||
},
|
||||
IconButton(
|
||||
color: Colors.white,
|
||||
icon: const Icon(Icons.flip_camera_ios, color: Colors.white),
|
||||
iconSize: 32.0,
|
||||
onPressed: () => cameraController!.switchCamera(),
|
||||
),
|
||||
iconSize: 32.0,
|
||||
onPressed: () => cameraController.switchCamera(),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
MobileScanner(
|
||||
controller: cameraController,
|
||||
onDetect: (capture) {
|
||||
final List<Barcode> barcodes = capture.barcodes;
|
||||
for (final barcode in barcodes) {
|
||||
if (!_isScanComplete && barcode.rawValue != null) {
|
||||
_isScanComplete = true;
|
||||
_scannedData = barcode.rawValue;
|
||||
_showScanResult(context, _scannedData!);
|
||||
}
|
||||
body: _hasError ? _buildErrorWidget() : _buildScannerWidget(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildErrorWidget() {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.error_outline,
|
||||
size: 64,
|
||||
color: Colors.red,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'Erreur de caméra',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_errorMessage ?? 'Une erreur s\'est produite',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
_initializeController();
|
||||
},
|
||||
child: const Text('Réessayer'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'Vérifiez que:\n• Le plugin mobile_scanner est installé\n• Les permissions de caméra sont accordées\n• Votre appareil a une caméra fonctionnelle',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildScannerWidget() {
|
||||
if (cameraController == null) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
MobileScanner(
|
||||
controller: cameraController!,
|
||||
onDetect: (capture) {
|
||||
final List<Barcode> barcodes = capture.barcodes;
|
||||
for (final barcode in barcodes) {
|
||||
if (!_isScanComplete && barcode.rawValue != null) {
|
||||
_isScanComplete = true;
|
||||
_scannedData = barcode.rawValue;
|
||||
_showScanResult(context, _scannedData!);
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
errorBuilder: (context, error, child) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.error, size: 64, color: Colors.red),
|
||||
const SizedBox(height: 16),
|
||||
Text('Erreur: ${error.errorDetails?.message ?? 'Erreur inconnue'}'),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () => _initializeController(),
|
||||
child: const Text('Réessayer'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
CustomPaint(
|
||||
painter: QrScannerOverlay(
|
||||
borderColor: Colors.blue.shade800,
|
||||
),
|
||||
CustomPaint(
|
||||
painter: QrScannerOverlay(
|
||||
borderColor: Colors.blue.shade800,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -93,7 +175,7 @@ class _ScanQRPageState extends State<ScanQRPage> {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Résultat du scan'),
|
||||
content: Text(data),
|
||||
content: SelectableText(data), // Permet de sélectionner le texte
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
@ -102,12 +184,18 @@ class _ScanQRPageState extends State<ScanQRPage> {
|
||||
_isScanComplete = false;
|
||||
});
|
||||
},
|
||||
child: const Text('OK'),
|
||||
child: const Text('Fermer'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context, data); // Retourner la donnée scannée
|
||||
},
|
||||
child: const Text('Utiliser'),
|
||||
),
|
||||
],
|
||||
),
|
||||
).then((_) {
|
||||
// Réinitialiser le scan après la fermeture du dialogue
|
||||
setState(() {
|
||||
_isScanComplete = false;
|
||||
});
|
||||
@ -115,7 +203,6 @@ class _ScanQRPageState extends State<ScanQRPage> {
|
||||
}
|
||||
}
|
||||
|
||||
// Widget personnalisé pour l'overlay du scanner
|
||||
class QrScannerOverlay extends CustomPainter {
|
||||
final Color borderColor;
|
||||
|
||||
@ -129,12 +216,10 @@ class QrScannerOverlay extends CustomPainter {
|
||||
final double borderLength = 30.0;
|
||||
final double areaSize = width * 0.7;
|
||||
|
||||
// Dessiner un rectangle semi-transparent autour de la zone de scan
|
||||
final Paint backgroundPaint = Paint()
|
||||
..color = Colors.black.withOpacity(0.4);
|
||||
canvas.drawRect(Rect.fromLTRB(0, 0, width, height), backgroundPaint);
|
||||
|
||||
// Dessiner la zone de scan transparente
|
||||
final Paint transparentPaint = Paint()
|
||||
..color = Colors.transparent
|
||||
..blendMode = BlendMode.clear;
|
||||
@ -145,59 +230,26 @@ class QrScannerOverlay extends CustomPainter {
|
||||
transparentPaint,
|
||||
);
|
||||
|
||||
// Dessiner les bordures de la zone de scan
|
||||
final Paint borderPaint = Paint()
|
||||
..color = borderColor
|
||||
..strokeWidth = borderWidth
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
// Coin supérieur gauche
|
||||
canvas.drawLine(
|
||||
Offset(areaLeft, areaTop),
|
||||
Offset(areaLeft + borderLength, areaTop),
|
||||
borderPaint,
|
||||
);
|
||||
canvas.drawLine(
|
||||
Offset(areaLeft, areaTop),
|
||||
Offset(areaLeft, areaTop + borderLength),
|
||||
borderPaint,
|
||||
);
|
||||
// Coins du scanner
|
||||
_drawCorner(canvas, borderPaint, areaLeft, areaTop, borderLength, true, true);
|
||||
_drawCorner(canvas, borderPaint, areaLeft + areaSize, areaTop, borderLength, false, true);
|
||||
_drawCorner(canvas, borderPaint, areaLeft, areaTop + areaSize, borderLength, true, false);
|
||||
_drawCorner(canvas, borderPaint, areaLeft + areaSize, areaTop + areaSize, borderLength, false, false);
|
||||
}
|
||||
|
||||
// Coin 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,
|
||||
);
|
||||
void _drawCorner(Canvas canvas, Paint paint, double x, double y, double length, bool isLeft, bool isTop) {
|
||||
final double horizontalStart = isLeft ? x : x - length;
|
||||
final double horizontalEnd = isLeft ? x + length : x;
|
||||
final double verticalStart = isTop ? y : y - length;
|
||||
final double verticalEnd = isTop ? y + length : y;
|
||||
|
||||
// Coin inférieur gauche
|
||||
canvas.drawLine(
|
||||
Offset(areaLeft, areaTop + areaSize - borderLength),
|
||||
Offset(areaLeft, areaTop + areaSize),
|
||||
borderPaint,
|
||||
);
|
||||
canvas.drawLine(
|
||||
Offset(areaLeft, areaTop + areaSize),
|
||||
Offset(areaLeft + borderLength, areaTop + areaSize),
|
||||
borderPaint,
|
||||
);
|
||||
|
||||
// Coin inférieur droit
|
||||
canvas.drawLine(
|
||||
Offset(areaLeft + areaSize - borderLength, areaTop + areaSize),
|
||||
Offset(areaLeft + areaSize, areaTop + areaSize),
|
||||
borderPaint,
|
||||
);
|
||||
canvas.drawLine(
|
||||
Offset(areaLeft + areaSize, areaTop + areaSize - borderLength),
|
||||
Offset(areaLeft + areaSize, areaTop + areaSize),
|
||||
borderPaint,
|
||||
);
|
||||
canvas.drawLine(Offset(horizontalStart, y), Offset(horizontalEnd, y), paint);
|
||||
canvas.drawLine(Offset(x, verticalStart), Offset(x, verticalEnd), paint);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -13,6 +13,7 @@ import 'package:youmazgestion/Views/newCommand.dart';
|
||||
import 'package:youmazgestion/Views/registrationPage.dart';
|
||||
import 'package:youmazgestion/accueil.dart';
|
||||
import 'package:youmazgestion/controller/userController.dart';
|
||||
import 'package:youmazgestion/Views/pointage.dart';
|
||||
|
||||
class CustomDrawer extends StatelessWidget {
|
||||
final UserController userController = Get.find<UserController>();
|
||||
@ -73,7 +74,9 @@ class CustomDrawer extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
controller.name.isNotEmpty ? controller.name : 'Utilisateur',
|
||||
controller.name.isNotEmpty
|
||||
? controller.name
|
||||
: 'Utilisateur',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
@ -123,6 +126,14 @@ class CustomDrawer extends StatelessWidget {
|
||||
permissionRoute: '/modifier-utilisateur',
|
||||
onTap: () => Get.to(const ListUserPage()),
|
||||
),
|
||||
await _buildDrawerItem(
|
||||
icon: Icons.timer,
|
||||
title: "Gestion des pointages",
|
||||
color: const Color.fromARGB(255, 4, 54, 95),
|
||||
permissionAction: 'update',
|
||||
permissionRoute: '/pointage',
|
||||
onTap: () => Get.to(const PointagePage()),
|
||||
)
|
||||
];
|
||||
|
||||
if (gestionUtilisateursItems.any((item) => item is ListTile)) {
|
||||
@ -322,7 +333,8 @@ class CustomDrawer extends StatelessWidget {
|
||||
required VoidCallback onTap,
|
||||
}) async {
|
||||
if (permissionAction != null && permissionRoute != null) {
|
||||
bool hasPermission = await userController.hasPermission(permissionAction, permissionRoute);
|
||||
bool hasPermission =
|
||||
await userController.hasPermission(permissionAction, permissionRoute);
|
||||
if (!hasPermission) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
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();
|
||||
setState(() {
|
||||
_filteredCommandes = _commandes.where((commande) {
|
||||
final matchesSearch = commande.clientNomComplet.toLowerCase().contains(query) ||
|
||||
commande.id.toString().contains(query);
|
||||
final matchesStatut = _selectedStatut == null || commande.statut == _selectedStatut;
|
||||
final matchesSearch =
|
||||
commande.clientNomComplet.toLowerCase().contains(query) ||
|
||||
commande.id.toString().contains(query);
|
||||
final matchesStatut =
|
||||
_selectedStatut == null || commande.statut == _selectedStatut;
|
||||
final matchesDate = _selectedDate == null ||
|
||||
DateFormat('yyyy-MM-dd').format(commande.dateCommande) ==
|
||||
DateFormat('yyyy-MM-dd').format(_selectedDate!);
|
||||
@ -280,12 +282,11 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
width: 100,
|
||||
height: 80,
|
||||
decoration: pw.BoxDecoration(
|
||||
border: pw.Border.all(color: PdfColors.blue900, width: 2),
|
||||
border:
|
||||
pw.Border.all(color: PdfColors.blue900, width: 2),
|
||||
borderRadius: pw.BorderRadius.circular(8),
|
||||
),
|
||||
child: pw.Center(
|
||||
child: pw.Image(image)
|
||||
),
|
||||
child: pw.Center(child: pw.Image(image)),
|
||||
),
|
||||
pw.SizedBox(height: 10),
|
||||
pw.Text('guycom', style: headerStyle),
|
||||
@ -306,7 +307,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Text('FACTURE',
|
||||
pw.Text(
|
||||
'FACTURE',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
@ -315,7 +317,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
),
|
||||
pw.SizedBox(height: 8),
|
||||
pw.Text('N°: ${commande.id}', style: titleStyle),
|
||||
pw.Text('Date: ${DateFormat('dd/MM/yyyy').format(commande.dateCommande)}'),
|
||||
pw.Text(
|
||||
'Date: ${DateFormat('dd/MM/yyyy').format(commande.dateCommande)}'),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -340,10 +343,11 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
pw.Text('FACTURÉ À:', style: titleStyle),
|
||||
pw.SizedBox(height: 5),
|
||||
pw.Text(client?.nomComplet ?? 'Client inconnu',
|
||||
style: pw.TextStyle(fontSize: 12)),
|
||||
style: pw.TextStyle(fontSize: 12)),
|
||||
if (client?.telephone != null)
|
||||
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.Table(
|
||||
border: pw.TableBorder.all(color: PdfColors.grey400, width: 0.5),
|
||||
border:
|
||||
pw.TableBorder.all(color: PdfColors.grey400, width: 0.5),
|
||||
children: [
|
||||
pw.TableRow(
|
||||
decoration: const pw.BoxDecoration(color: PdfColors.blue900),
|
||||
decoration:
|
||||
const pw.BoxDecoration(color: PdfColors.blue900),
|
||||
children: [
|
||||
_buildTableCell('Produit', titleStyle.copyWith(color: PdfColors.white)),
|
||||
_buildTableCell('Qté', titleStyle.copyWith(color: PdfColors.white)),
|
||||
_buildTableCell('Prix unit.', titleStyle.copyWith(color: PdfColors.white)),
|
||||
_buildTableCell('Total', titleStyle.copyWith(color: PdfColors.white)),
|
||||
_buildTableCell('Produit',
|
||||
titleStyle.copyWith(color: PdfColors.white)),
|
||||
_buildTableCell(
|
||||
'Qté', titleStyle.copyWith(color: PdfColors.white)),
|
||||
_buildTableCell('Prix unit.',
|
||||
titleStyle.copyWith(color: PdfColors.white)),
|
||||
_buildTableCell(
|
||||
'Total', titleStyle.copyWith(color: PdfColors.white)),
|
||||
],
|
||||
),
|
||||
...details.asMap().entries.map((entry) {
|
||||
@ -458,7 +468,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
pw.SizedBox(height: 5),
|
||||
pw.Text(
|
||||
'Cette facture est générée automatiquement par le système Youmaz Gestion',
|
||||
style: pw.TextStyle(fontSize: 8, color: PdfColors.grey600),
|
||||
style:
|
||||
pw.TextStyle(fontSize: 8, color: PdfColors.grey600),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -749,7 +760,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Rechercher par client ou numéro de commande',
|
||||
prefixIcon: Icon(Icons.search, color: Colors.blue.shade800),
|
||||
prefixIcon:
|
||||
Icon(Icons.search, color: Colors.blue.shade800),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
@ -786,7 +798,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
value: _selectedStatut,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Filtrer par statut',
|
||||
prefixIcon: Icon(Icons.filter_list, color: Colors.blue.shade600),
|
||||
prefixIcon: Icon(Icons.filter_list,
|
||||
color: Colors.blue.shade600),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
@ -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(
|
||||
_selectedDate == null
|
||||
? 'Date'
|
||||
: DateFormat('dd/MM/yyyy').format(_selectedDate!),
|
||||
: DateFormat('dd/MM/yyyy')
|
||||
.format(_selectedDate!),
|
||||
style: const TextStyle(color: Colors.black87),
|
||||
),
|
||||
),
|
||||
@ -931,7 +946,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
offset: const Offset(0, 2),)
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
@ -1040,9 +1056,10 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
Icon(
|
||||
_getStatutIcon(commande.statut),
|
||||
size: 20,
|
||||
color: commande.statut == StatutCommande.annulee
|
||||
? Colors.red
|
||||
: Colors.blue.shade600,
|
||||
color:
|
||||
commande.statut == StatutCommande.annulee
|
||||
? Colors.red
|
||||
: Colors.blue.shade600,
|
||||
),
|
||||
Text(
|
||||
'#${commande.id}',
|
||||
@ -1074,7 +1091,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
DateFormat('dd/MM/yyyy').format(commande.dateCommande),
|
||||
DateFormat('dd/MM/yyyy')
|
||||
.format(commande.dateCommande),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
@ -1095,7 +1113,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: commande.statut == StatutCommande.annulee
|
||||
color: commande.statut ==
|
||||
StatutCommande.annulee
|
||||
? Colors.red
|
||||
: Colors.blue.shade700,
|
||||
),
|
||||
@ -1419,7 +1438,8 @@ class _CommandeActions extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.check_circle, color: Colors.green.shade600, size: 16),
|
||||
Icon(Icons.check_circle,
|
||||
color: Colors.green.shade600, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Commande confirmée',
|
||||
|
||||
@ -365,7 +365,7 @@ class _HistoriquePageState extends State<HistoriquePage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
commande.dateCommande as String
|
||||
commande.dateCommande.timeZoneName
|
||||
),
|
||||
Text(
|
||||
'${commande.montantTotal.toStringAsFixed(2)} DA',
|
||||
|
||||
@ -74,7 +74,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
|
||||
if (username.isEmpty || password.isEmpty) {
|
||||
setState(() {
|
||||
_errorMessage = 'Veuillez saisir le nom d\'utilisateur et le mot de passe';
|
||||
_errorMessage =
|
||||
'Veuillez saisir le nom d\'utilisateur et le mot de passe';
|
||||
_isErrorVisible = true;
|
||||
});
|
||||
return;
|
||||
@ -97,7 +98,6 @@ class _LoginPageState extends State<LoginPage> {
|
||||
}
|
||||
|
||||
bool isValidUser = await dbInstance.verifyUser(username, password);
|
||||
print('Résultat de la vérification: $isValidUser');
|
||||
|
||||
if (isValidUser) {
|
||||
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');
|
||||
}
|
||||
} else {
|
||||
print('Identifiants invalides pour: $username');
|
||||
setState(() {
|
||||
_errorMessage = 'Nom d\'utilisateur ou mot de passe invalide';
|
||||
_isErrorVisible = true;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
print('Erreur lors de la connexion: $error');
|
||||
setState(() {
|
||||
_errorMessage = 'Erreur de connexion: ${error.toString()}';
|
||||
_isErrorVisible = true;
|
||||
});
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
if (mounted) setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color primaryBlue = const Color(0xFF0033A1);
|
||||
final Color accentRed = const Color(0xFFD70000);
|
||||
final Color secondaryBlue = const Color(0xFF1976D2);
|
||||
final Color primaryColor = primaryBlue;
|
||||
final Color accentColor = secondaryBlue;
|
||||
final Color cardColor = Colors.white;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text(
|
||||
'Login',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
backgroundColor: const Color.fromARGB(255, 4, 54, 95),
|
||||
centerTitle: true,
|
||||
),
|
||||
backgroundColor: primaryColor,
|
||||
body: ParticleBackground(
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.5,
|
||||
height: MediaQuery.of(context).size.height * 0.8,
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Container(
|
||||
child: SingleChildScrollView(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width < 500
|
||||
? double.infinity
|
||||
: 400,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 24.0, vertical: 32.0),
|
||||
decoration: BoxDecoration(
|
||||
color: cardColor.withOpacity(0.98),
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: primaryColor.withOpacity(0.2),
|
||||
blurRadius: 16,
|
||||
spreadRadius: 4,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 38,
|
||||
backgroundColor: accentColor.withOpacity(0.15),
|
||||
child: Icon(
|
||||
Icons.lock_outline,
|
||||
color: accentColor,
|
||||
size: 50,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Text(
|
||||
'GUYCOM',
|
||||
style: TextStyle(
|
||||
color: primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 28,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Connectez-vous à votre compte',
|
||||
style: TextStyle(
|
||||
color: primaryColor.withOpacity(.8),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: const Icon(
|
||||
Icons.lock_outline,
|
||||
@ -256,11 +289,15 @@ class _LoginPageState extends State<LoginPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
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',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,8 @@ class ProductCard extends StatefulWidget {
|
||||
State<ProductCard> createState() => _ProductCardState();
|
||||
}
|
||||
|
||||
class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin {
|
||||
class _ProductCardState extends State<ProductCard>
|
||||
with TickerProviderStateMixin {
|
||||
int selectedQuantity = 1;
|
||||
late AnimationController _scaleController;
|
||||
late AnimationController _fadeController;
|
||||
@ -122,7 +123,6 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
: _buildPlaceholderImage(),
|
||||
),
|
||||
),
|
||||
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
@ -141,7 +141,6 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
if (widget.product.isStockDefined())
|
||||
Positioned(
|
||||
top: 12,
|
||||
@ -183,7 +182,6 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
@ -201,7 +199,8 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
vertical: 8,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.product.name,
|
||||
@ -239,9 +238,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
@ -250,7 +247,8 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
color:
|
||||
Colors.black.withOpacity(0.1),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
@ -295,9 +293,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 8),
|
||||
|
||||
Expanded(
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
@ -306,9 +302,11 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
onTapUp: _onTapUp,
|
||||
onTapCancel: _onTapCancel,
|
||||
onTap: () {
|
||||
widget.onAddToCart(widget.product, selectedQuantity);
|
||||
widget.onAddToCart(widget.product,
|
||||
selectedQuantity);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
@ -320,16 +318,20 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${widget.product.name} (x$selectedQuantity) ajouté au panier',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
overflow: TextOverflow
|
||||
.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.green,
|
||||
duration: const Duration(seconds: 1),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration:
|
||||
const Duration(seconds: 1),
|
||||
behavior:
|
||||
SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius:
|
||||
BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -342,21 +344,27 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [
|
||||
Color.fromARGB(255, 4, 54, 95),
|
||||
Color.fromARGB(255, 6, 80, 140),
|
||||
Color.fromARGB(
|
||||
255, 4, 54, 95),
|
||||
Color.fromARGB(
|
||||
255, 6, 80, 140),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius:
|
||||
BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color.fromARGB(255, 4, 54, 95).withOpacity(0.3),
|
||||
color: const Color.fromARGB(
|
||||
255, 4, 54, 95)
|
||||
.withOpacity(0.3),
|
||||
blurRadius: 6,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.add_shopping_cart,
|
||||
@ -369,10 +377,12 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
'Ajouter',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontWeight:
|
||||
FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
overflow:
|
||||
TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -442,7 +452,9 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 16,
|
||||
color: onPressed != null ? const Color.fromARGB(255, 4, 54, 95) : Colors.grey,
|
||||
color: onPressed != null
|
||||
? const Color.fromARGB(255, 4, 54, 95)
|
||||
: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -449,7 +449,7 @@ class _AccueilPageState extends State<AccueilPage> {
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold)),
|
||||
Text(
|
||||
'${NumberFormat('#,##0.00').format(calculateTotalPrice())} FCFA',
|
||||
'${NumberFormat('#,##0.00').format(calculateTotalPrice())} MGA',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
||||
@ -612,10 +612,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: mobile_scanner
|
||||
sha256: "1b60b8f9d4ce0cb0e7d7bc223c955d083a0737bee66fa1fcfe5de48225e0d5b3"
|
||||
sha256: d234581c090526676fd8fab4ada92f35c6746e3fb4f05a399665d75a399fb760
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.5.7"
|
||||
version: "5.2.3"
|
||||
msix:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@ -62,7 +62,7 @@ dependencies:
|
||||
path_provider: ^2.0.15
|
||||
shared_preferences: ^2.2.2
|
||||
excel: ^2.0.1
|
||||
mobile_scanner: ^3.1.1
|
||||
mobile_scanner: ^5.0.0 # ou la version la plus récente
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user