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