chnages has been made for the request leave and request advance

This commit is contained in:
Daniah Ayad Al-sultani
2025-12-07 12:53:43 +03:00
parent dbf76b2d30
commit db044cb039
6 changed files with 238 additions and 225 deletions

View File

@@ -2,12 +2,12 @@ class AdvanceRequest {
final String id; final String id;
final double amount; final double amount;
final String reason; final String reason;
final String status; // e.g., "pending", "approved", "rejected" final String status; // "waiting", "approved", or "denied"
AdvanceRequest({ AdvanceRequest({
required this.id, required this.id,
required this.amount, required this.amount,
required this.reason, required this.reason,
this.status = "pending", this.status = "waiting", // Default status is "waiting"
}); });
} }

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart' show TimeOfDay; // LeaveRequest model
import 'package:flutter/material.dart';
class LeaveRequest { class LeaveRequest {
final String id; final String id;
@@ -10,7 +11,8 @@ class LeaveRequest {
final TimeOfDay toTime; final TimeOfDay toTime;
final String reason; final String reason;
final DateTime requestDate; final DateTime requestDate;
final String status; // e.g final String status; // "waiting", "approved", or "denied"
LeaveRequest({ LeaveRequest({
required this.id, required this.id,
required this.leaveType, required this.leaveType,
@@ -21,6 +23,8 @@ class LeaveRequest {
required this.toTime, required this.toTime,
required this.reason, required this.reason,
required this.requestDate, required this.requestDate,
this.status = "pending", this.status = "waiting", // Default status is "waiting"
}); });
} }
// AdvanceRequest model

View File

@@ -15,20 +15,33 @@ class HolidayScreen extends StatefulWidget {
class _HolidayScreenState extends State<HolidayScreen> { class _HolidayScreenState extends State<HolidayScreen> {
int activeTab = 0; // 0 = السلف | 1 = الأجازات int activeTab = 0; // 0 = السلف | 1 = الأجازات
// Use the singleton instance
final RequestService _requestService = RequestService(); final RequestService _requestService = RequestService();
late Future<List<LeaveRequest>> _leaveRequestsFuture; late List<LeaveRequest> _leaveRequests;
late Future<List<AdvanceRequest>> _advanceRequestsFuture; late List<AdvanceRequest> _advanceRequests;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_refreshData(); _initializeData();
} }
void _refreshData() { void _initializeData() async {
// Get initial data
_leaveRequests = await _requestService.getLeaveRequests();
_advanceRequests = await _requestService.getAdvanceRequests();
// Listen for updates
_requestService.leaveRequestsStream.listen((requests) {
setState(() { setState(() {
_leaveRequestsFuture = Future.value(_requestService.leaveRequests); _leaveRequests = requests;
_advanceRequestsFuture = Future.value(_requestService.advanceRequests); });
});
_requestService.advanceRequestsStream.listen((requests) {
setState(() {
_advanceRequests = requests;
});
}); });
} }
@@ -36,7 +49,7 @@ class _HolidayScreenState extends State<HolidayScreen> {
switch (status) { switch (status) {
case 'approved': case 'approved':
return Colors.green; return Colors.green;
case 'rejected': case 'denied':
return Colors.red; return Colors.red;
default: default:
return Colors.orange; return Colors.orange;
@@ -47,13 +60,24 @@ class _HolidayScreenState extends State<HolidayScreen> {
switch (status) { switch (status) {
case 'approved': case 'approved':
return 'موافق عليه'; return 'موافق عليه';
case 'rejected': case 'denied':
return 'مرفوض'; return 'مرفوض';
default: default:
return 'قيد الانتظار'; return 'قيد الانتظار';
} }
} }
String _getStatusIconPath(String status) {
switch (status) {
case 'approved':
return "assets/images/yes.svg";
case 'denied':
return "assets/images/no.svg";
default:
return "assets/images/waiting.svg";
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Directionality( return Directionality(
@@ -148,14 +172,15 @@ class _HolidayScreenState extends State<HolidayScreen> {
svgPath: "assets/images/money2.svg", svgPath: "assets/images/money2.svg",
iconWidth: 38, iconWidth: 38,
iconHeight: 30, iconHeight: 30,
onTap: () { onTap: () async {
// Navigate to RequestAdvanceScreen // Navigate to RequestAdvanceScreen
Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const RequestAdvanceScreen(), builder: (context) => const RequestAdvanceScreen(),
), ),
).then((_) => _refreshData()); // Refresh data when returning );
// Data will be updated automatically through the stream
}, },
), ),
const SizedBox(height: 18), const SizedBox(height: 18),
@@ -166,13 +191,14 @@ class _HolidayScreenState extends State<HolidayScreen> {
svgPath: "assets/images/plus.svg", svgPath: "assets/images/plus.svg",
iconWidth: 30, iconWidth: 30,
iconHeight: 30, iconHeight: 30,
onTap: () { onTap: () async {
Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const RequestLeaveScreen(), builder: (context) => const RequestLeaveScreen(),
), ),
).then((_) => _refreshData()); // Refresh data when returning );
// Data will be updated automatically through the stream
}, },
), ),
], ],
@@ -184,14 +210,7 @@ class _HolidayScreenState extends State<HolidayScreen> {
} }
Widget _buildLeaveRequestsTab() { Widget _buildLeaveRequestsTab() {
return FutureBuilder<List<LeaveRequest>>( if (_leaveRequests.isEmpty) {
future: _leaveRequestsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator(color: Colors.white));
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}', style: TextStyle(color: Colors.white)));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const Center( return const Center(
child: Text( child: Text(
'لا توجد طلبات أجازة', 'لا توجد طلبات أجازة',
@@ -203,34 +222,24 @@ class _HolidayScreenState extends State<HolidayScreen> {
); );
} }
final leaveRequests = snapshot.data!;
return RefreshIndicator( return RefreshIndicator(
onRefresh: () async { onRefresh: () async {
_refreshData(); _initializeData();
}, },
color: Colors.white, color: Colors.white,
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 20), padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 20),
itemCount: leaveRequests.length, itemCount: _leaveRequests.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final request = leaveRequests[index]; final request = _leaveRequests[index];
return _buildLeaveRequestCard(request); return _buildLeaveRequestCard(request);
}, },
), ),
); );
},
);
} }
Widget _buildAdvanceRequestsTab() { Widget _buildAdvanceRequestsTab() {
return FutureBuilder<List<AdvanceRequest>>( if (_advanceRequests.isEmpty) {
future: _advanceRequestsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator(color: Colors.white));
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}', style: TextStyle(color: Colors.white)));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const Center( return const Center(
child: Text( child: Text(
'لا توجد طلبات سلف', 'لا توجد طلبات سلف',
@@ -242,23 +251,20 @@ class _HolidayScreenState extends State<HolidayScreen> {
); );
} }
final advanceRequests = snapshot.data!;
return RefreshIndicator( return RefreshIndicator(
onRefresh: () async { onRefresh: () async {
_refreshData(); _initializeData();
}, },
color: Colors.white, color: Colors.white,
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 20), padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 20),
itemCount: advanceRequests.length, itemCount: _advanceRequests.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final request = advanceRequests[index]; final request = _advanceRequests[index];
return _buildAdvanceRequestCard(request); return _buildAdvanceRequestCard(request);
}, },
), ),
); );
},
);
} }
Widget _buildLeaveRequestCard(LeaveRequest request) { Widget _buildLeaveRequestCard(LeaveRequest request) {
@@ -283,19 +289,18 @@ class _HolidayScreenState extends State<HolidayScreen> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
// Status icon instead of text
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _getStatusColor(request.status), color: _getStatusColor(request.status),
borderRadius: BorderRadius.circular(20), shape: BoxShape.circle,
), ),
child: Text( child: SvgPicture.asset(
_getStatusText(request.status), _getStatusIconPath(request.status),
style: const TextStyle( width: 24,
height: 24,
color: Colors.white, color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w600,
),
), ),
), ),
Text( Text(
@@ -399,43 +404,24 @@ class _HolidayScreenState extends State<HolidayScreen> {
), ),
), ),
// Action buttons for pending requests // Status text below the card
if (request.status == 'pending') ...[ const SizedBox(height: 8),
const SizedBox(height: 16), Directionality(
Row( textDirection: TextDirection.rtl,
mainAxisAlignment: MainAxisAlignment.spaceEvenly, child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
ElevatedButton( Text(
onPressed: () { _getStatusText(request.status),
_updateLeaveRequestStatus(request.id, 'approved'); style: TextStyle(
}, color: _getStatusColor(request.status),
style: ElevatedButton.styleFrom( fontSize: 14,
backgroundColor: Colors.green, fontWeight: FontWeight.w600,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
), ),
), ),
child: const Text('موافق'),
),
ElevatedButton(
onPressed: () {
_updateLeaveRequestStatus(request.id, 'rejected');
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: const Text('رفض'),
),
], ],
), ),
], ),
], ],
), ),
); );
@@ -459,23 +445,22 @@ class _HolidayScreenState extends State<HolidayScreen> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
// Header with status // Header with status icon
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
// Status icon instead of text
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _getStatusColor(request.status), color: _getStatusColor(request.status),
borderRadius: BorderRadius.circular(20), shape: BoxShape.circle,
), ),
child: Text( child: SvgPicture.asset(
_getStatusText(request.status), _getStatusIconPath(request.status),
style: const TextStyle( width: 24,
height: 24,
color: Colors.white, color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w600,
),
), ),
), ),
Text( Text(
@@ -526,69 +511,25 @@ class _HolidayScreenState extends State<HolidayScreen> {
), ),
), ),
// Request date - removed since advance requests don't have request date // Status text below the card
// Action buttons for pending requests const SizedBox(height: 8),
if (request.status == 'pending') ...[ Directionality(
const SizedBox(height: 16), textDirection: TextDirection.rtl,
Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
ElevatedButton( Text(
onPressed: () { _getStatusText(request.status),
_updateAdvanceRequestStatus(request.id, 'approved'); style: TextStyle(
}, color: _getStatusColor(request.status),
style: ElevatedButton.styleFrom( fontSize: 14,
backgroundColor: Colors.green, fontWeight: FontWeight.w600,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
), ),
), ),
child: const Text('موافق'),
),
ElevatedButton(
onPressed: () {
_updateAdvanceRequestStatus(request.id, 'rejected');
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: const Text('رفض'),
),
], ],
), ),
],
],
), ),
); ],
}
void _updateLeaveRequestStatus(String id, String status) async {
await _requestService.updateLeaveRequestStatus(id, status);
_refreshData();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('تم ${status == 'approved' ? 'قبول' : 'رفض'} الطلب'),
backgroundColor: status == 'approved' ? Colors.green : Colors.red,
),
);
}
void _updateAdvanceRequestStatus(String id, String status) async {
await _requestService.updateAdvanceRequestStatus(id, status);
_refreshData();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('تم ${status == 'approved' ? 'قبول' : 'رفض'} الطلب'),
backgroundColor: status == 'approved' ? Colors.green : Colors.red,
), ),
); );
} }

View File

@@ -20,6 +20,7 @@ class _RequestAdvanceScreenState extends State<RequestAdvanceScreen> {
// Text controller for reason // Text controller for reason
final TextEditingController reasonController = TextEditingController(); final TextEditingController reasonController = TextEditingController();
// Use the singleton instance
final RequestService _requestService = RequestService(); final RequestService _requestService = RequestService();
// Method to save the advance request // Method to save the advance request
@@ -48,13 +49,15 @@ class _RequestAdvanceScreenState extends State<RequestAdvanceScreen> {
return; return;
} }
// Create a new advance request // Create a new advance request with default status "waiting"
final advanceRequest = AdvanceRequest( final advanceRequest = AdvanceRequest(
id: DateTime.now().millisecondsSinceEpoch.toString(), id: DateTime.now().millisecondsSinceEpoch.toString(),
amount: amount, amount: amount,
reason: reasonController.text, reason: reasonController.text,
status: "waiting", // Default status
); );
try {
// Save the advance request // Save the advance request
await _requestService.addAdvanceRequest(advanceRequest); await _requestService.addAdvanceRequest(advanceRequest);
@@ -68,6 +71,15 @@ class _RequestAdvanceScreenState extends State<RequestAdvanceScreen> {
// Navigate back to the previous screen // Navigate back to the previous screen
Navigator.pop(context); Navigator.pop(context);
} catch (e) {
// Show an error message if something went wrong
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('حدث خطأ: $e'),
backgroundColor: Colors.red,
),
);
}
} }
@override @override

View File

@@ -30,6 +30,7 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
// Text controller for reason // Text controller for reason
final TextEditingController reasonController = TextEditingController(); final TextEditingController reasonController = TextEditingController();
// Use the singleton instance
final RequestService _requestService = RequestService(); final RequestService _requestService = RequestService();
/// PICK DATE /// PICK DATE
@@ -91,7 +92,7 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
return; return;
} }
// Create a new leave request // Create a new leave request with default status "waiting"
final leaveRequest = LeaveRequest( final leaveRequest = LeaveRequest(
id: DateTime.now().millisecondsSinceEpoch.toString(), id: DateTime.now().millisecondsSinceEpoch.toString(),
leaveType: leaveType, leaveType: leaveType,
@@ -102,8 +103,10 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
toTime: toTime!, toTime: toTime!,
reason: reasonController.text, reason: reasonController.text,
requestDate: DateTime.now(), requestDate: DateTime.now(),
status: "waiting", // Default status
); );
try {
// Save the leave request // Save the leave request
await _requestService.addLeaveRequest(leaveRequest); await _requestService.addLeaveRequest(leaveRequest);
@@ -117,6 +120,15 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
// Navigate back to the previous screen // Navigate back to the previous screen
Navigator.pop(context); Navigator.pop(context);
} catch (e) {
// Show an error message if something went wrong
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('حدث خطأ: $e'),
backgroundColor: Colors.red,
),
);
}
} }
@override @override

View File

@@ -1,8 +1,9 @@
import 'package:flutter/material.dart'; import 'dart:async';
import '../models/leave_request.dart'; import '../models/leave_request.dart';
import '../models/advance_request.dart'; import '../models/advance_request.dart';
class RequestService { class RequestService {
// Singleton implementation
static final RequestService _instance = RequestService._internal(); static final RequestService _instance = RequestService._internal();
factory RequestService() => _instance; factory RequestService() => _instance;
RequestService._internal(); RequestService._internal();
@@ -10,22 +11,47 @@ class RequestService {
final List<LeaveRequest> _leaveRequests = []; final List<LeaveRequest> _leaveRequests = [];
final List<AdvanceRequest> _advanceRequests = []; final List<AdvanceRequest> _advanceRequests = [];
List<LeaveRequest> get leaveRequests => List.unmodifiable(_leaveRequests); // Stream controllers to notify listeners of changes
List<AdvanceRequest> get advanceRequests => List.unmodifiable(_advanceRequests); final _leaveRequestsController = StreamController<List<LeaveRequest>>.broadcast();
final _advanceRequestsController = StreamController<List<AdvanceRequest>>.broadcast();
// Stream getters
Stream<List<LeaveRequest>> get leaveRequestsStream => _leaveRequestsController.stream;
Stream<List<AdvanceRequest>> get advanceRequestsStream => _advanceRequestsController.stream;
// Get all leave requests as a Future
Future<List<LeaveRequest>> getLeaveRequests() async {
print("Getting ${_leaveRequests.length} leave requests");
return List.from(_leaveRequests);
}
// Get all advance requests as a Future
Future<List<AdvanceRequest>> getAdvanceRequests() async {
print("Getting ${_advanceRequests.length} advance requests");
return List.from(_advanceRequests);
}
// Add a new leave request
Future<void> addLeaveRequest(LeaveRequest request) async { Future<void> addLeaveRequest(LeaveRequest request) async {
_leaveRequests.add(request); _leaveRequests.add(request);
// In a real app, you would save this to a database or API print("Added leave request: ${request.id}, total: ${_leaveRequests.length}");
// Notify listeners
_leaveRequestsController.add(List.from(_leaveRequests));
} }
// Add a new advance request
Future<void> addAdvanceRequest(AdvanceRequest request) async { Future<void> addAdvanceRequest(AdvanceRequest request) async {
_advanceRequests.add(request); _advanceRequests.add(request);
// In a real app, you would save this to a database or API print("Added advance request: ${request.id}, total: ${_advanceRequests.length}");
// Notify listeners
_advanceRequestsController.add(List.from(_advanceRequests));
} }
// Update leave request status
Future<void> updateLeaveRequestStatus(String id, String status) async { Future<void> updateLeaveRequestStatus(String id, String status) async {
final index = _leaveRequests.indexWhere((request) => request.id == id); final index = _leaveRequests.indexWhere((request) => request.id == id);
if (index != -1) { if (index != -1) {
// Create a new request with the updated status
final updatedRequest = LeaveRequest( final updatedRequest = LeaveRequest(
id: _leaveRequests[index].id, id: _leaveRequests[index].id,
leaveType: _leaveRequests[index].leaveType, leaveType: _leaveRequests[index].leaveType,
@@ -36,22 +62,40 @@ class RequestService {
toTime: _leaveRequests[index].toTime, toTime: _leaveRequests[index].toTime,
reason: _leaveRequests[index].reason, reason: _leaveRequests[index].reason,
requestDate: _leaveRequests[index].requestDate, requestDate: _leaveRequests[index].requestDate,
status: status, status: status, // Updated status
); );
// Replace the old request with the updated one
_leaveRequests[index] = updatedRequest; _leaveRequests[index] = updatedRequest;
print("Updated leave request status: $id to $status");
// Notify listeners
_leaveRequestsController.add(List.from(_leaveRequests));
} }
} }
// Update advance request status
Future<void> updateAdvanceRequestStatus(String id, String status) async { Future<void> updateAdvanceRequestStatus(String id, String status) async {
final index = _advanceRequests.indexWhere((request) => request.id == id); final index = _advanceRequests.indexWhere((request) => request.id == id);
if (index != -1) { if (index != -1) {
// Create a new request with the updated status
final updatedRequest = AdvanceRequest( final updatedRequest = AdvanceRequest(
id: _advanceRequests[index].id, id: _advanceRequests[index].id,
amount: _advanceRequests[index].amount, amount: _advanceRequests[index].amount,
reason: _advanceRequests[index].reason, reason: _advanceRequests[index].reason,
status: status, status: status, // Updated status
); );
// Replace the old request with the updated one
_advanceRequests[index] = updatedRequest; _advanceRequests[index] = updatedRequest;
print("Updated advance request status: $id to $status");
// Notify listeners
_advanceRequestsController.add(List.from(_advanceRequests));
} }
} }
// Dispose method to close streams
void dispose() {
_leaveRequestsController.close();
_advanceRequestsController.close();
}
} }