From dbf76b2d30b8c9151e75fa85a2490fc6433435f2 Mon Sep 17 00:00:00 2001 From: Daniah Ayad Al-sultani <148902945+Cactuskiller@users.noreply.github.com> Date: Sun, 7 Dec 2025 12:01:23 +0300 Subject: [PATCH] chnages was made --- assets/images/waiting.svg | 3 + lib/models/advance_request.dart | 13 + lib/models/leave_request.dart | 26 ++ lib/screens/holiday_screen.dart | 470 +++++++++++++++++++++++- lib/screens/request_advance_scrren.dart | 59 ++- lib/screens/request_leave_screen.dart | 55 ++- lib/services/request_service.dart | 57 +++ 7 files changed, 672 insertions(+), 11 deletions(-) create mode 100644 assets/images/waiting.svg create mode 100644 lib/models/advance_request.dart create mode 100644 lib/models/leave_request.dart create mode 100644 lib/services/request_service.dart diff --git a/assets/images/waiting.svg b/assets/images/waiting.svg new file mode 100644 index 0000000..46c7dd4 --- /dev/null +++ b/assets/images/waiting.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/models/advance_request.dart b/lib/models/advance_request.dart new file mode 100644 index 0000000..4791151 --- /dev/null +++ b/lib/models/advance_request.dart @@ -0,0 +1,13 @@ +class AdvanceRequest { + final String id; + final double amount; + final String reason; + final String status; // e.g., "pending", "approved", "rejected" + + AdvanceRequest({ + required this.id, + required this.amount, + required this.reason, + this.status = "pending", + }); +} \ No newline at end of file diff --git a/lib/models/leave_request.dart b/lib/models/leave_request.dart new file mode 100644 index 0000000..6599a11 --- /dev/null +++ b/lib/models/leave_request.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart' show TimeOfDay; + +class LeaveRequest { + final String id; + final String leaveType; + final bool isTimedLeave; + final DateTime fromDate; + final DateTime toDate; + final TimeOfDay fromTime; + final TimeOfDay toTime; + final String reason; + final DateTime requestDate; + final String status; // e.g + LeaveRequest({ + required this.id, + required this.leaveType, + required this.isTimedLeave, + required this.fromDate, + required this.toDate, + required this.fromTime, + required this.toTime, + required this.reason, + required this.requestDate, + this.status = "pending", + }); +} diff --git a/lib/screens/holiday_screen.dart b/lib/screens/holiday_screen.dart index 2ffb0f5..c1eb540 100644 --- a/lib/screens/holiday_screen.dart +++ b/lib/screens/holiday_screen.dart @@ -2,6 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import '../screens/request_leave_screen.dart'; import '../screens/request_advance_scrren.dart'; +import '../models/leave_request.dart'; +import '../models/advance_request.dart'; +import '../services/request_service.dart'; class HolidayScreen extends StatefulWidget { const HolidayScreen({super.key}); @@ -12,6 +15,44 @@ class HolidayScreen extends StatefulWidget { class _HolidayScreenState extends State { int activeTab = 0; // 0 = السلف | 1 = الأجازات + final RequestService _requestService = RequestService(); + late Future> _leaveRequestsFuture; + late Future> _advanceRequestsFuture; + + @override + void initState() { + super.initState(); + _refreshData(); + } + + void _refreshData() { + setState(() { + _leaveRequestsFuture = Future.value(_requestService.leaveRequests); + _advanceRequestsFuture = Future.value(_requestService.advanceRequests); + }); + } + + Color _getStatusColor(String status) { + switch (status) { + case 'approved': + return Colors.green; + case 'rejected': + return Colors.red; + default: + return Colors.orange; + } + } + + String _getStatusText(String status) { + switch (status) { + case 'approved': + return 'موافق عليه'; + case 'rejected': + return 'مرفوض'; + default: + return 'قيد الانتظار'; + } + } @override Widget build(BuildContext context) { @@ -19,6 +60,7 @@ class _HolidayScreenState extends State { textDirection: TextDirection.rtl, child: Stack( children: [ + // Tabs Positioned( top: 40, left: 0, @@ -84,6 +126,17 @@ class _HolidayScreenState extends State { ], ), ), + + // Content area for requests + Positioned( + top: 100, // Position below the tabs + left: 0, + right: 0, + bottom: 120, // Leave space for action buttons + child: activeTab == 1 ? _buildLeaveRequestsTab() : _buildAdvanceRequestsTab(), + ), + + // Action buttons Positioned( bottom: 30, right: 20, @@ -96,14 +149,13 @@ class _HolidayScreenState extends State { iconWidth: 38, iconHeight: 30, onTap: () { - // Navigate to RequestAdvanceScreen Navigator.push( context, MaterialPageRoute( builder: (context) => const RequestAdvanceScreen(), ), - ); + ).then((_) => _refreshData()); // Refresh data when returning }, ), const SizedBox(height: 18), @@ -120,7 +172,7 @@ class _HolidayScreenState extends State { MaterialPageRoute( builder: (context) => const RequestLeaveScreen(), ), - ); + ).then((_) => _refreshData()); // Refresh data when returning }, ), ], @@ -130,6 +182,416 @@ class _HolidayScreenState extends State { ), ); } + + Widget _buildLeaveRequestsTab() { + return FutureBuilder>( + 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( + child: Text( + 'لا توجد طلبات أجازة', + style: TextStyle( + color: Colors.white, + fontSize: 18, + ), + ), + ); + } + + final leaveRequests = snapshot.data!; + return RefreshIndicator( + onRefresh: () async { + _refreshData(); + }, + color: Colors.white, + child: ListView.builder( + padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 20), + itemCount: leaveRequests.length, + itemBuilder: (context, index) { + final request = leaveRequests[index]; + return _buildLeaveRequestCard(request); + }, + ), + ); + }, + ); + } + + Widget _buildAdvanceRequestsTab() { + return FutureBuilder>( + 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( + child: Text( + 'لا توجد طلبات سلف', + style: TextStyle( + color: Colors.white, + fontSize: 18, + ), + ), + ); + } + + final advanceRequests = snapshot.data!; + return RefreshIndicator( + onRefresh: () async { + _refreshData(); + }, + color: Colors.white, + child: ListView.builder( + padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 20), + itemCount: advanceRequests.length, + itemBuilder: (context, index) { + final request = advanceRequests[index]; + return _buildAdvanceRequestCard(request); + }, + ), + ); + }, + ); + } + + Widget _buildLeaveRequestCard(LeaveRequest request) { + return Container( + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: const [ + BoxShadow( + color: Color(0x33000000), + blurRadius: 10, + offset: Offset(0, 4), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + // Header with type and status + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: _getStatusColor(request.status), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + _getStatusText(request.status), + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + Text( + request.isTimedLeave ? "أجازة زمنية" : request.leaveType, + style: const TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + + const SizedBox(height: 12), + + // Date range + Directionality( + textDirection: TextDirection.rtl, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + 'من: ${request.fromDate.day}/${request.fromDate.month}/${request.fromDate.year}', + style: const TextStyle( + color: Colors.black87, + fontSize: 16, + ), + ), + const SizedBox(width: 20), + Text( + 'إلى: ${request.toDate.day}/${request.toDate.month}/${request.toDate.year}', + style: const TextStyle( + color: Colors.black87, + fontSize: 16, + ), + ), + ], + ), + ), + + if (request.isTimedLeave) ...[ + const SizedBox(height: 8), + Directionality( + textDirection: TextDirection.rtl, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + 'من: ${request.fromTime.hour}:${request.fromTime.minute.toString().padLeft(2, '0')}', + style: const TextStyle( + color: Colors.black87, + fontSize: 16, + ), + ), + const SizedBox(width: 20), + Text( + 'إلى: ${request.toTime.hour}:${request.toTime.minute.toString().padLeft(2, '0')}', + style: const TextStyle( + color: Colors.black87, + fontSize: 16, + ), + ), + ], + ), + ), + ], + + const SizedBox(height: 12), + + // Reason + Directionality( + textDirection: TextDirection.rtl, + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFFF5F5F5), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + request.reason, + style: const TextStyle( + color: Colors.black87, + fontSize: 16, + ), + ), + ), + ), + + const SizedBox(height: 12), + + // Request date + Directionality( + textDirection: TextDirection.rtl, + child: Text( + 'تاريخ الطلب: ${request.requestDate.day}/${request.requestDate.month}/${request.requestDate.year}', + style: const TextStyle( + color: Colors.grey, + fontSize: 14, + ), + ), + ), + + // Action buttons for pending requests + if (request.status == 'pending') ...[ + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: () { + _updateLeaveRequestStatus(request.id, 'approved'); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + 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('رفض'), + ), + ], + ), + ], + ], + ), + ); + } + + Widget _buildAdvanceRequestCard(AdvanceRequest request) { + return Container( + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: const [ + BoxShadow( + color: Color(0x33000000), + blurRadius: 10, + offset: Offset(0, 4), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + // Header with status + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: _getStatusColor(request.status), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + _getStatusText(request.status), + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + Text( + 'طلب سلفة', + style: const TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + + const SizedBox(height: 12), + + // Amount + Directionality( + textDirection: TextDirection.rtl, + child: Text( + 'المبلغ: ${request.amount.toStringAsFixed(2)} دع', + style: const TextStyle( + color: Colors.black87, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + + const SizedBox(height: 12), + + // Reason + Directionality( + textDirection: TextDirection.rtl, + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFFF5F5F5), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + request.reason, + style: const TextStyle( + color: Colors.black87, + fontSize: 16, + ), + ), + ), + ), + + // Request date - removed since advance requests don't have request date + // Action buttons for pending requests + if (request.status == 'pending') ...[ + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: () { + _updateAdvanceRequestStatus(request.id, 'approved'); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + 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, + ), + ); + } } class _HolidayActionButton extends StatelessWidget { @@ -189,4 +651,4 @@ class _HolidayActionButton extends StatelessWidget { ), ); } -} +} \ No newline at end of file diff --git a/lib/screens/request_advance_scrren.dart b/lib/screens/request_advance_scrren.dart index 8bad87b..34d643f 100644 --- a/lib/screens/request_advance_scrren.dart +++ b/lib/screens/request_advance_scrren.dart @@ -3,6 +3,8 @@ import 'package:flutter_svg/flutter_svg.dart'; import '../widgets/app_background.dart'; import '../widgets/settings_bar.dart'; import '../widgets/onboarding_button.dart'; +import '../models/advance_request.dart'; +import '../services/request_service.dart'; class RequestAdvanceScreen extends StatefulWidget { const RequestAdvanceScreen({super.key}); @@ -18,6 +20,56 @@ class _RequestAdvanceScreenState extends State { // Text controller for reason final TextEditingController reasonController = TextEditingController(); + final RequestService _requestService = RequestService(); + + // Method to save the advance request + Future _saveAdvanceRequest() async { + if (amountController.text.isEmpty || reasonController.text.isEmpty) { + // Show an error message if fields are empty + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('الرجاء إدخال جميع الحقول'), + backgroundColor: Colors.red, + ), + ); + return; + } + + // Parse the amount to double + final amount = double.tryParse(amountController.text); + if (amount == null || amount <= 0) { + // Show an error message if amount is invalid + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('الرجاء إدخال مبلغ صحيح'), + backgroundColor: Colors.red, + ), + ); + return; + } + + // Create a new advance request + final advanceRequest = AdvanceRequest( + id: DateTime.now().millisecondsSinceEpoch.toString(), + amount: amount, + reason: reasonController.text, + ); + + // Save the advance request + await _requestService.addAdvanceRequest(advanceRequest); + + // Show a success message + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('تم إرسال طلب السلفة بنجاح'), + backgroundColor: Colors.green, + ), + ); + + // Navigate back to the previous screen + Navigator.pop(context); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -155,7 +207,8 @@ class _RequestAdvanceScreenState extends State { controller: reasonController, maxLines: 5, textAlign: - TextAlign.right, // Added this to align text to the right + TextAlign + .right, // Added this to align text to the right decoration: const InputDecoration( border: InputBorder.none, hintText: @@ -175,7 +228,7 @@ class _RequestAdvanceScreenState extends State { text: "تأكيد الطلب", backgroundColor: const Color(0xFFD1FEF0), textColor: Colors.black, - onPressed: () {}, + onPressed: _saveAdvanceRequest, // Call the save method ), ), @@ -190,4 +243,4 @@ class _RequestAdvanceScreenState extends State { ), ); } -} +} \ No newline at end of file diff --git a/lib/screens/request_leave_screen.dart b/lib/screens/request_leave_screen.dart index 61cff36..db5a856 100644 --- a/lib/screens/request_leave_screen.dart +++ b/lib/screens/request_leave_screen.dart @@ -3,6 +3,8 @@ import 'package:flutter_svg/flutter_svg.dart'; import '../widgets/app_background.dart'; import '../widgets/settings_bar.dart'; import '../widgets/onboarding_button.dart'; +import '../models/leave_request.dart'; +import '../services/request_service.dart'; class RequestLeaveScreen extends StatefulWidget { const RequestLeaveScreen({super.key}); @@ -28,6 +30,8 @@ class _RequestLeaveScreenState extends State { // Text controller for reason final TextEditingController reasonController = TextEditingController(); + final RequestService _requestService = RequestService(); + /// PICK DATE Future pickDate(bool isFrom) async { DateTime initial = isFrom ? fromDate! : toDate!; @@ -74,6 +78,47 @@ class _RequestLeaveScreenState extends State { } } + // Method to save the leave request + Future _saveLeaveRequest() async { + if (reasonController.text.isEmpty) { + // Show an error message if reason is empty + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('الرجاء إدخال السبب'), + backgroundColor: Colors.red, + ), + ); + return; + } + + // Create a new leave request + final leaveRequest = LeaveRequest( + id: DateTime.now().millisecondsSinceEpoch.toString(), + leaveType: leaveType, + isTimedLeave: isTimedLeave, + fromDate: fromDate!, + toDate: toDate!, + fromTime: fromTime!, + toTime: toTime!, + reason: reasonController.text, + requestDate: DateTime.now(), + ); + + // Save the leave request + await _requestService.addLeaveRequest(leaveRequest); + + // Show a success message + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('تم إرسال طلب الأجازة بنجاح'), + backgroundColor: Colors.green, + ), + ); + + // Navigate back to the previous screen + Navigator.pop(context); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -104,7 +149,7 @@ class _RequestLeaveScreenState extends State { children: [ // Fixed alignment for "طلب أجازة" const Align( - alignment: Alignment.topRight, // Changed to centerRight + alignment: Alignment.topRight, child: Text( "طلب أجازة ", style: TextStyle( @@ -459,7 +504,7 @@ class _RequestLeaveScreenState extends State { text: "تأكيد الطلب", backgroundColor: const Color(0xFFD1FEF0), textColor: Colors.black, // Changed to black - onPressed: () {}, + onPressed: _saveLeaveRequest, // Call the save method ), ), @@ -559,11 +604,13 @@ class _RequestLeaveScreenState extends State { case 4: return "الخميس"; case 5: - return "السبت"; + return "الجمعة"; case 6: + return "السبت"; + case 7: return "الأحد"; default: return ""; } } -} +} \ No newline at end of file diff --git a/lib/services/request_service.dart b/lib/services/request_service.dart new file mode 100644 index 0000000..a04f31b --- /dev/null +++ b/lib/services/request_service.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import '../models/leave_request.dart'; +import '../models/advance_request.dart'; + +class RequestService { + static final RequestService _instance = RequestService._internal(); + factory RequestService() => _instance; + RequestService._internal(); + + final List _leaveRequests = []; + final List _advanceRequests = []; + + List get leaveRequests => List.unmodifiable(_leaveRequests); + List get advanceRequests => List.unmodifiable(_advanceRequests); + + Future addLeaveRequest(LeaveRequest request) async { + _leaveRequests.add(request); + // In a real app, you would save this to a database or API + } + + Future addAdvanceRequest(AdvanceRequest request) async { + _advanceRequests.add(request); + // In a real app, you would save this to a database or API + } + + Future updateLeaveRequestStatus(String id, String status) async { + final index = _leaveRequests.indexWhere((request) => request.id == id); + if (index != -1) { + final updatedRequest = LeaveRequest( + id: _leaveRequests[index].id, + leaveType: _leaveRequests[index].leaveType, + isTimedLeave: _leaveRequests[index].isTimedLeave, + fromDate: _leaveRequests[index].fromDate, + toDate: _leaveRequests[index].toDate, + fromTime: _leaveRequests[index].fromTime, + toTime: _leaveRequests[index].toTime, + reason: _leaveRequests[index].reason, + requestDate: _leaveRequests[index].requestDate, + status: status, + ); + _leaveRequests[index] = updatedRequest; + } + } + + Future updateAdvanceRequestStatus(String id, String status) async { + final index = _advanceRequests.indexWhere((request) => request.id == id); + if (index != -1) { + final updatedRequest = AdvanceRequest( + id: _advanceRequests[index].id, + amount: _advanceRequests[index].amount, + reason: _advanceRequests[index].reason, + status: status, + ); + _advanceRequests[index] = updatedRequest; + } + } +} \ No newline at end of file