15 changed files with 655 additions and 232 deletions
@ -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, |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -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]); |
||||
|
} |
||||
|
} |
||||
@ -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', |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue