This commit is contained in:
Mohammed Al-Samarraie
2026-01-18 19:40:54 +03:00
parent 8adab4c4af
commit 79b53b6303
13 changed files with 771 additions and 94 deletions

View File

@@ -5,6 +5,13 @@ import '../widgets/settings_bar.dart';
import '../widgets/onboarding_button.dart';
import '../../models/leave_request.dart';
import '../../core/services/request_service.dart';
import '../../core/di/injection_container.dart';
import '../../data/datasources/user_local_data_source.dart';
import '../../domain/usecases/create_vacation_usecase.dart';
import '../../domain/usecases/get_vacation_types_usecase.dart';
import '../../domain/models/vacation_request.dart';
import '../../domain/models/vacation_type_model.dart';
import '../../core/error/failures.dart';
class RequestLeaveScreen extends StatefulWidget {
const RequestLeaveScreen({super.key});
@@ -33,6 +40,14 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
// Use the singleton instance
final RequestService _requestService = RequestService();
// Use cases
final CreateVacationUseCase _createVacationUseCase = sl<CreateVacationUseCase>();
final GetVacationTypesUseCase _getVacationTypesUseCase = sl<GetVacationTypesUseCase>();
// Vacation types from API
List<VacationTypeModel> _vacationTypes = [];
int? _selectedVacationTypeValue; // Store selected type value instead of string
/// PICK DATE
Future<void> pickDate(bool isFrom) async {
DateTime initial = isFrom ? fromDate! : toDate!;
@@ -79,52 +94,211 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
}
}
@override
void initState() {
super.initState();
_loadVacationTypes();
}
Future<void> _loadVacationTypes() async {
final result = await _getVacationTypesUseCase();
result.fold(
(failure) {
// Silently fail - user can still submit with default types
print('Failed to load vacation types: ${_getFailureMessage(failure)}');
},
(response) {
if (mounted) {
setState(() {
_vacationTypes = response.data;
// Set default to SickLeave (value: 2) if available
if (_vacationTypes.isNotEmpty) {
final sickLeave = _vacationTypes.firstWhere(
(type) => type.value == 2,
orElse: () => _vacationTypes.first,
);
_selectedVacationTypeValue = sickLeave.value;
leaveType = _getArabicName(sickLeave.name);
}
});
}
},
);
}
String _getArabicName(String apiName) {
// Map API names to Arabic display names
switch (apiName) {
case 'TimeOff':
return 'أجازة زمنية';
case 'SickLeave':
return 'إجازة مرضية';
case 'PaidLeave':
return 'إجازة مدفوعة';
case 'UnpaidLeave':
return 'إجازة غير مدفوعة';
default:
return apiName;
}
}
int _getVacationTypeValue() {
// Use selected value if available, otherwise fallback to mapping
if (_selectedVacationTypeValue != null) {
return _selectedVacationTypeValue!;
}
// Fallback: Map display names to API type values
if (leaveType.contains("مرضية") || leaveType == "SickLeave") {
return 2; // SickLeave
} else if (leaveType.contains("مدفوعة") && !leaveType.contains("غير")) {
return 3; // PaidLeave
} else if (leaveType.contains("غير مدفوعة") || leaveType == "UnpaidLeave") {
return 4; // UnpaidLeave
} else if (leaveType.contains("زمنية") || leaveType == "TimeOff") {
return 1; // TimeOff
}
return 1; // Default to TimeOff
}
String _getFailureMessage(Failure failure) {
if (failure is ServerFailure) {
return failure.message;
} else if (failure is NetworkFailure) {
return failure.message;
}
return 'حدث خطأ غير متوقع';
}
// Method to save the leave request
Future<void> _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,
),
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('الرجاء إدخال السبب'),
backgroundColor: Colors.red,
),
);
}
return;
}
// Create a new leave request with default status "waiting"
final leaveRequest = LeaveRequest(
id: DateTime.now().millisecondsSinceEpoch.toString(),
leaveType: leaveType, // Use the current leaveType value
isTimedLeave: isTimedLeave,
fromDate: fromDate!,
toDate: toDate!,
fromTime: fromTime!,
toTime: toTime!,
reason: reasonController.text,
requestDate: DateTime.now(),
status: "waiting", // Default status
);
// Get employee ID
final employeeId = await sl<UserLocalDataSource>().getCachedEmployeeId();
if (employeeId == null) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('خطأ: لم يتم العثور على رقم الموظف'),
backgroundColor: Colors.red,
),
);
}
return;
}
// Prepare dates - if timed leave, use same date with time differences
DateTime finalStartDate = fromDate!;
DateTime finalEndDate = toDate!;
if (isTimedLeave) {
// For timed leave, use the same date but with different times
finalStartDate = DateTime(
fromDate!.year,
fromDate!.month,
fromDate!.day,
fromTime!.hour,
fromTime!.minute,
);
finalEndDate = DateTime(
fromDate!.year,
fromDate!.month,
fromDate!.day,
toTime!.hour,
toTime!.minute,
);
} else {
// For regular leave, use dates at midnight
finalStartDate = DateTime(fromDate!.year, fromDate!.month, fromDate!.day);
finalEndDate = DateTime(toDate!.year, toDate!.month, toDate!.day);
}
// Get vacation type value
final typeValue = _getVacationTypeValue();
// Show loading indicator
if (mounted) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const Center(child: CircularProgressIndicator()),
);
}
try {
// Save the leave request
await _requestService.addLeaveRequest(leaveRequest);
// Show a success message
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('تم إرسال طلب الأجازة بنجاح'),
backgroundColor: Colors.green,
),
// Create vacation request
final vacationRequest = VacationRequest(
employeeId: employeeId,
startDate: finalStartDate,
endDate: finalEndDate,
reason: reasonController.text,
type: typeValue,
);
// Navigate back to the previous screen
Navigator.pop(context);
final result = await _createVacationUseCase(vacationRequest);
if (mounted) {
Navigator.pop(context); // Close loading dialog
result.fold(
(failure) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(_getFailureMessage(failure)),
backgroundColor: Colors.red,
),
);
},
(response) {
// Also save locally for UI display
final leaveRequest = LeaveRequest(
id: response.data?.id ?? DateTime.now().millisecondsSinceEpoch.toString(),
leaveType: leaveType,
isTimedLeave: isTimedLeave,
fromDate: fromDate!,
toDate: toDate!,
fromTime: fromTime!,
toTime: toTime!,
reason: reasonController.text,
requestDate: DateTime.now(),
status: "waiting", // Default status
);
_requestService.addLeaveRequest(leaveRequest);
// Show success message
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('تم إرسال طلب الأجازة بنجاح'),
backgroundColor: Colors.green,
),
);
// Navigate back to the previous screen
Navigator.pop(context);
},
);
}
} catch (e) {
// Show an error message if something went wrong
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('حدث خطأ: $e'), backgroundColor: Colors.red),
);
if (mounted) {
Navigator.pop(context); // Close loading dialog
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('حدث خطأ غير متوقع: $e'),
backgroundColor: Colors.red,
),
);
}
}
}
@@ -216,68 +390,51 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
],
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: leaveType,
icon: const Icon(
Icons.keyboard_arrow_down_rounded,
color: Colors.black,
size: 28,
),
style: const TextStyle(
color: Colors.black,
fontSize: 17,
),
isExpanded: true,
onChanged: (value) {
setState(() {
leaveType = value!;
// Set toggle based on selected value
isTimedLeave = value == "أجازة زمنية";
});
},
items: [
DropdownMenuItem(
value: "إجازة مرضية ",
child: Directionality(
textDirection: TextDirection.rtl,
child: Align(
alignment: Alignment.centerRight,
child: Text("إجازة مرضية "),
child: _vacationTypes.isEmpty
? const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
),
),
),
DropdownMenuItem(
value: "إجازة مدفوعة",
child: Directionality(
textDirection: TextDirection.rtl,
child: Align(
alignment: Alignment.centerRight,
child: Text("إجازة مدفوعة"),
)
: DropdownButton<int>(
value: _selectedVacationTypeValue,
icon: const Icon(
Icons.keyboard_arrow_down_rounded,
color: Colors.black,
size: 28,
),
),
),
DropdownMenuItem(
value: "إجازة غير مدفوعة",
child: Directionality(
textDirection: TextDirection.rtl,
child: Align(
alignment: Alignment.centerRight,
child: Text("إجازة غير مدفوعة"),
style: const TextStyle(
color: Colors.black,
fontSize: 17,
),
isExpanded: true,
onChanged: (value) {
if (value != null) {
setState(() {
_selectedVacationTypeValue = value;
final selectedType = _vacationTypes
.firstWhere((t) => t.value == value);
leaveType = _getArabicName(selectedType.name);
// Set toggle based on selected value (TimeOff = 1)
isTimedLeave = value == 1;
});
}
},
items: _vacationTypes.map((type) {
final arabicName = _getArabicName(type.name);
return DropdownMenuItem<int>(
value: type.value,
child: Directionality(
textDirection: TextDirection.rtl,
child: Align(
alignment: Alignment.centerRight,
child: Text(arabicName),
),
),
);
}).toList(),
),
),
DropdownMenuItem(
value: "أجازة زمنية",
child: Directionality(
textDirection: TextDirection.rtl,
child: Align(
alignment: Alignment.centerRight,
child: Text("أجازة زمنية"),
),
),
),
],
),
),
),
),
@@ -293,9 +450,12 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
onTap: () {
setState(() {
isTimedLeave = !isTimedLeave;
// Set leave type to "أجازة زمنية" when toggle is ON
// Set leave type to TimeOff (value: 1) when toggle is ON
if (isTimedLeave) {
leaveType = "أجازة زمنية";
final timeOffType = _vacationTypes
.firstWhere((t) => t.value == 1, orElse: () => _vacationTypes.first);
_selectedVacationTypeValue = timeOffType.value;
leaveType = _getArabicName(timeOffType.name);
}
});
},