diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 62e0485..29da374 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,6 @@ + + diff --git a/lib/data/datasources/advance_remote_data_source.dart b/lib/data/datasources/advance_remote_data_source.dart index e5bbd20..2dbcaf2 100644 --- a/lib/data/datasources/advance_remote_data_source.dart +++ b/lib/data/datasources/advance_remote_data_source.dart @@ -7,7 +7,7 @@ import '../dto/advances_list_response_dto.dart'; abstract class AdvanceRemoteDataSource { Future createAdvance(AdvanceRequestDto request); - Future getAdvances(); + Future getAdvances(String employeeId); } class AdvanceRemoteDataSourceImpl implements AdvanceRemoteDataSource { @@ -71,9 +71,12 @@ class AdvanceRemoteDataSourceImpl implements AdvanceRemoteDataSource { } @override - Future getAdvances() async { + Future getAdvances(String employeeId) async { try { - final response = await apiClient.get('/SalaryInAdvance'); + final response = await apiClient.get( + '/SalaryInAdvance', + queryParameters: {'EmployeeId': employeeId}, + ); if (response.statusCode == 200) { final responseData = response.data; diff --git a/lib/data/datasources/vacation_remote_data_source.dart b/lib/data/datasources/vacation_remote_data_source.dart index 3a9d129..d8da349 100644 --- a/lib/data/datasources/vacation_remote_data_source.dart +++ b/lib/data/datasources/vacation_remote_data_source.dart @@ -9,7 +9,7 @@ import '../dto/vacations_list_response_dto.dart'; abstract class VacationRemoteDataSource { Future createVacation(VacationRequestDto request); Future getVacationTypes(); - Future getVacations(); + Future getVacations(String employeeId); } class VacationRemoteDataSourceImpl implements VacationRemoteDataSource { @@ -125,9 +125,12 @@ class VacationRemoteDataSourceImpl implements VacationRemoteDataSource { } @override - Future getVacations() async { + Future getVacations(String employeeId) async { try { - final response = await apiClient.get('/Vacation'); + final response = await apiClient.get( + '/Vacation', + queryParameters: {'EmployeeId': employeeId}, + ); if (response.statusCode == 200) { final responseData = response.data; diff --git a/lib/data/repositories/advance_repository_impl.dart b/lib/data/repositories/advance_repository_impl.dart index 092e620..ebdbe83 100644 --- a/lib/data/repositories/advance_repository_impl.dart +++ b/lib/data/repositories/advance_repository_impl.dart @@ -31,23 +31,24 @@ class AdvanceRepositoryImpl implements AdvanceRepository { statusCode: responseDto.statusCode, isSuccess: responseDto.isSuccess, message: responseDto.message, - data: responseDto.data != null - ? AdvanceDataModel( - employeeId: responseDto.data!.employeeId, - employeeFullName: responseDto.data!.employeeFullName, - date: responseDto.data!.date, - amount: responseDto.data!.amount, - submittedBy: responseDto.data!.submittedBy, - submittedByUser: responseDto.data!.submittedByUser, - reason: responseDto.data!.reason, - state: responseDto.data!.state, - id: responseDto.data!.id, - createdAt: responseDto.data!.createdAt, - updatedAt: responseDto.data!.updatedAt, - deletedAt: responseDto.data!.deletedAt, - isDeleted: responseDto.data!.isDeleted, - ) - : null, + data: + responseDto.data != null + ? AdvanceDataModel( + employeeId: responseDto.data!.employeeId, + employeeFullName: responseDto.data!.employeeFullName, + date: responseDto.data!.date, + amount: responseDto.data!.amount, + submittedBy: responseDto.data!.submittedBy, + submittedByUser: responseDto.data!.submittedByUser, + reason: responseDto.data!.reason, + state: responseDto.data!.state, + id: responseDto.data!.id, + createdAt: responseDto.data!.createdAt, + updatedAt: responseDto.data!.updatedAt, + deletedAt: responseDto.data!.deletedAt, + isDeleted: responseDto.data!.isDeleted, + ) + : null, ); return Right(responseModel); @@ -61,40 +62,46 @@ class AdvanceRepositoryImpl implements AdvanceRepository { } @override - Future> getAdvances() async { + Future> getAdvances( + String employeeId, + ) async { try { - final responseDto = await remoteDataSource.getAdvances(); + final responseDto = await remoteDataSource.getAdvances(employeeId); // Convert DTO to Model final responseModel = AdvancesListResponseModel( statusCode: responseDto.statusCode, isSuccess: responseDto.isSuccess, message: responseDto.message, - data: responseDto.data != null - ? AdvancesListDataModel( - items: responseDto.data!.items - .map((dto) => AdvanceDataModel( - employeeId: dto.employeeId, - employeeFullName: dto.employeeFullName, - date: dto.date, - amount: dto.amount, - submittedBy: dto.submittedBy, - submittedByUser: dto.submittedByUser, - reason: dto.reason, - state: dto.state, - id: dto.id, - createdAt: dto.createdAt, - updatedAt: dto.updatedAt, - deletedAt: dto.deletedAt, - isDeleted: dto.isDeleted, - )) - .toList(), - pageNumber: responseDto.data!.pageNumber, - pageSize: responseDto.data!.pageSize, - totalCount: responseDto.data!.totalCount, - totalPages: responseDto.data!.totalPages, - ) - : null, + data: + responseDto.data != null + ? AdvancesListDataModel( + items: + responseDto.data!.items + .map( + (dto) => AdvanceDataModel( + employeeId: dto.employeeId, + employeeFullName: dto.employeeFullName, + date: dto.date, + amount: dto.amount, + submittedBy: dto.submittedBy, + submittedByUser: dto.submittedByUser, + reason: dto.reason, + state: dto.state, + id: dto.id, + createdAt: dto.createdAt, + updatedAt: dto.updatedAt, + deletedAt: dto.deletedAt, + isDeleted: dto.isDeleted, + ), + ) + .toList(), + pageNumber: responseDto.data!.pageNumber, + pageSize: responseDto.data!.pageSize, + totalCount: responseDto.data!.totalCount, + totalPages: responseDto.data!.totalPages, + ) + : null, ); return Right(responseModel); diff --git a/lib/data/repositories/vacation_repository_impl.dart b/lib/data/repositories/vacation_repository_impl.dart index 00d5c7c..4a3c313 100644 --- a/lib/data/repositories/vacation_repository_impl.dart +++ b/lib/data/repositories/vacation_repository_impl.dart @@ -34,24 +34,25 @@ class VacationRepositoryImpl implements VacationRepository { statusCode: responseDto.statusCode, isSuccess: responseDto.isSuccess, message: responseDto.message, - data: responseDto.data != null - ? VacationDataModel( - employeeId: responseDto.data!.employeeId, - employeeFullName: responseDto.data!.employeeFullName, - startDate: responseDto.data!.startDate, - endDate: responseDto.data!.endDate, - reason: responseDto.data!.reason, - submittedBy: responseDto.data!.submittedBy, - submittedByUser: responseDto.data!.submittedByUser, - state: responseDto.data!.state, - type: responseDto.data!.type, - id: responseDto.data!.id, - createdAt: responseDto.data!.createdAt, - updatedAt: responseDto.data!.updatedAt, - deletedAt: responseDto.data!.deletedAt, - isDeleted: responseDto.data!.isDeleted, - ) - : null, + data: + responseDto.data != null + ? VacationDataModel( + employeeId: responseDto.data!.employeeId, + employeeFullName: responseDto.data!.employeeFullName, + startDate: responseDto.data!.startDate, + endDate: responseDto.data!.endDate, + reason: responseDto.data!.reason, + submittedBy: responseDto.data!.submittedBy, + submittedByUser: responseDto.data!.submittedByUser, + state: responseDto.data!.state, + type: responseDto.data!.type, + id: responseDto.data!.id, + createdAt: responseDto.data!.createdAt, + updatedAt: responseDto.data!.updatedAt, + deletedAt: responseDto.data!.deletedAt, + isDeleted: responseDto.data!.isDeleted, + ) + : null, ); return Right(responseModel); @@ -74,12 +75,12 @@ class VacationRepositoryImpl implements VacationRepository { statusCode: responseDto.statusCode, isSuccess: responseDto.isSuccess, message: responseDto.message, - data: responseDto.data - .map((dto) => VacationTypeModel( - value: dto.value, - name: dto.name, - )) - .toList(), + data: + responseDto.data + .map( + (dto) => VacationTypeModel(value: dto.value, name: dto.name), + ) + .toList(), ); return Right(responseModel); @@ -93,41 +94,47 @@ class VacationRepositoryImpl implements VacationRepository { } @override - Future> getVacations() async { + Future> getVacations( + String employeeId, + ) async { try { - final responseDto = await remoteDataSource.getVacations(); + final responseDto = await remoteDataSource.getVacations(employeeId); // Convert DTO to Model final responseModel = VacationsListResponseModel( statusCode: responseDto.statusCode, isSuccess: responseDto.isSuccess, message: responseDto.message, - data: responseDto.data != null - ? VacationsListDataModel( - items: responseDto.data!.items - .map((dto) => VacationDataModel( - employeeId: dto.employeeId, - employeeFullName: dto.employeeFullName, - startDate: dto.startDate, - endDate: dto.endDate, - reason: dto.reason, - submittedBy: dto.submittedBy, - submittedByUser: dto.submittedByUser, - state: dto.state, - type: dto.type, - id: dto.id, - createdAt: dto.createdAt, - updatedAt: dto.updatedAt, - deletedAt: dto.deletedAt, - isDeleted: dto.isDeleted, - )) - .toList(), - pageNumber: responseDto.data!.pageNumber, - pageSize: responseDto.data!.pageSize, - totalCount: responseDto.data!.totalCount, - totalPages: responseDto.data!.totalPages, - ) - : null, + data: + responseDto.data != null + ? VacationsListDataModel( + items: + responseDto.data!.items + .map( + (dto) => VacationDataModel( + employeeId: dto.employeeId, + employeeFullName: dto.employeeFullName, + startDate: dto.startDate, + endDate: dto.endDate, + reason: dto.reason, + submittedBy: dto.submittedBy, + submittedByUser: dto.submittedByUser, + state: dto.state, + type: dto.type, + id: dto.id, + createdAt: dto.createdAt, + updatedAt: dto.updatedAt, + deletedAt: dto.deletedAt, + isDeleted: dto.isDeleted, + ), + ) + .toList(), + pageNumber: responseDto.data!.pageNumber, + pageSize: responseDto.data!.pageSize, + totalCount: responseDto.data!.totalCount, + totalPages: responseDto.data!.totalPages, + ) + : null, ); return Right(responseModel); diff --git a/lib/domain/repositories/advance_repository.dart b/lib/domain/repositories/advance_repository.dart index 2abb171..692032b 100644 --- a/lib/domain/repositories/advance_repository.dart +++ b/lib/domain/repositories/advance_repository.dart @@ -7,5 +7,7 @@ abstract class AdvanceRepository { Future> createAdvance( AdvanceRequestModel request, ); - Future> getAdvances(); + Future> getAdvances( + String employeeId, + ); } diff --git a/lib/domain/repositories/vacation_repository.dart b/lib/domain/repositories/vacation_repository.dart index 28abad7..5a17886 100644 --- a/lib/domain/repositories/vacation_repository.dart +++ b/lib/domain/repositories/vacation_repository.dart @@ -10,5 +10,7 @@ abstract class VacationRepository { VacationRequest request, ); Future> getVacationTypes(); - Future> getVacations(); + Future> getVacations( + String employeeId, + ); } diff --git a/lib/domain/usecases/get_advances_usecase.dart b/lib/domain/usecases/get_advances_usecase.dart index b126b22..d5310a2 100644 --- a/lib/domain/usecases/get_advances_usecase.dart +++ b/lib/domain/usecases/get_advances_usecase.dart @@ -8,7 +8,7 @@ class GetAdvancesUseCase { GetAdvancesUseCase({required this.repository}); - Future> call() { - return repository.getAdvances(); + Future> call(String employeeId) { + return repository.getAdvances(employeeId); } } diff --git a/lib/domain/usecases/get_vacations_usecase.dart b/lib/domain/usecases/get_vacations_usecase.dart index 8dd0ef2..8ecec2e 100644 --- a/lib/domain/usecases/get_vacations_usecase.dart +++ b/lib/domain/usecases/get_vacations_usecase.dart @@ -8,7 +8,7 @@ class GetVacationsUseCase { GetVacationsUseCase({required this.repository}); - Future> call() { - return repository.getVacations(); + Future> call(String employeeId) { + return repository.getVacations(employeeId); } } diff --git a/lib/main.dart b/lib/main.dart index 4689e2e..0b14721 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,13 +5,36 @@ import 'core/di/injection_container.dart'; import 'presentation/screens/splash_screen.dart'; void main() async { - WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); - FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); + try { + WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); + FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); - // Initialize dependency injection - await initializeDependencies(); + // Initialize dependency injection + await initializeDependencies(); - runApp(const CodaApp()); + runApp(const CodaApp()); + } catch (e) { + debugPrint('CRITICAL INITIALIZATION ERROR: $e'); + // If initialization fails, show a simple error screen instead of a broken app + runApp( + MaterialApp( + home: Scaffold( + body: Center( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Text( + 'Failed to start the app. Please try: \n1. flutter clean\n2. flutter pub get\n\nError: $e', + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.red), + ), + ), + ), + ), + ), + ); + // Ensure splash is removed so user can see the error + FlutterNativeSplash.remove(); + } } class CodaApp extends StatelessWidget { diff --git a/lib/presentation/screens/holiday_screen.dart b/lib/presentation/screens/holiday_screen.dart index 5707526..94e35bc 100644 --- a/lib/presentation/screens/holiday_screen.dart +++ b/lib/presentation/screens/holiday_screen.dart @@ -17,6 +17,7 @@ import '../../domain/usecases/get_advances_usecase.dart'; import '../../domain/models/vacation_response_model.dart'; // import '../../domain/models/advances_list_response_model.dart'; import '../../domain/models/advance_request_model.dart'; +import '../../data/datasources/user_local_data_source.dart'; // import '../../core/error/failures.dart'; class HolidayScreen extends StatefulWidget { @@ -38,6 +39,7 @@ class _HolidayScreenState extends State { List _advanceRequests = []; bool _isLoadingVacations = false; bool _isLoadingAdvances = false; + String? _employeeId; final ScrollController _scrollController = ScrollController(); bool _showActions = true; @@ -74,9 +76,13 @@ class _HolidayScreenState extends State { } void _initializeData() async { - // Load from API - _loadVacationsFromAPI(); - _loadAdvancesFromAPI(); + _employeeId = await sl().getCachedEmployeeId(); + if (mounted) { + setState(() {}); + // Load from API + _loadVacationsFromAPI(); + _loadAdvancesFromAPI(); + } // Also listen to local changes (for newly created requests) _requestService.leaveRequestsStream.listen((requests) { @@ -94,11 +100,13 @@ class _HolidayScreenState extends State { } Future _loadVacationsFromAPI() async { + if (_employeeId == null || _employeeId!.isEmpty) return; + setState(() { _isLoadingVacations = true; }); - final result = await _getVacationsUseCase(); + final result = await _getVacationsUseCase(_employeeId!); result.fold( (failure) { if (mounted) { @@ -178,11 +186,13 @@ class _HolidayScreenState extends State { } Future _loadAdvancesFromAPI() async { + if (_employeeId == null || _employeeId!.isEmpty) return; + setState(() { _isLoadingAdvances = true; }); - final result = await _getAdvancesUseCase(); + final result = await _getAdvancesUseCase(_employeeId!); result.fold( (failure) { if (mounted) { diff --git a/lib/presentation/screens/request_leave_screen.dart b/lib/presentation/screens/request_leave_screen.dart index 8e7d7bd..588a7dc 100644 --- a/lib/presentation/screens/request_leave_screen.dart +++ b/lib/presentation/screens/request_leave_screen.dart @@ -22,7 +22,7 @@ class RequestLeaveScreen extends StatefulWidget { class _RequestLeaveScreenState extends State { // Dropdown value - initialize with default - String leaveType = "إجازة مرضية "; + String leaveType = "إجازة مرضية"; // Toggle switch bool isTimedLeave = false; @@ -41,12 +41,133 @@ class _RequestLeaveScreenState extends State { final RequestService _requestService = RequestService(); // Use cases - final CreateVacationUseCase _createVacationUseCase = sl(); - final GetVacationTypesUseCase _getVacationTypesUseCase = sl(); + final CreateVacationUseCase _createVacationUseCase = + sl(); + final GetVacationTypesUseCase _getVacationTypesUseCase = + sl(); // Vacation types from API List _vacationTypes = []; - int? _selectedVacationTypeValue; // Store selected type value instead of string + + // Store selected type value instead of string + int? _selectedVacationTypeValue; + + @override + void initState() { + super.initState(); + _loadVacationTypes(); + } + + // ------------------------------- + // STEP A: single helper for selecting type safely + // ------------------------------- + void _setSelectedVacationType(int value) { + _selectedVacationTypeValue = value; + + if (_vacationTypes.isNotEmpty) { + final selectedType = _vacationTypes.firstWhere( + (t) => t.value == value, + orElse: () => _vacationTypes.first, + ); + leaveType = _getArabicName(selectedType.name); + } else { + // Fallback text (when API not loaded yet) + if (value == 0) leaveType = "إجازة زمنية"; + if (value == 1) leaveType = "إجازة مرضية"; + if (value == 2) leaveType = "إجازة مدفوعة"; + if (value == 3) leaveType = "إجازة غير مدفوعة"; + } + } + + // ------------------------------- + // STEP B: single helper for timed leave rule + // ------------------------------- + void _setTimedLeave(bool on) { + setState(() { + isTimedLeave = on; + + if (isTimedLeave) { + // FORCE TimeOff (value = 1) + _setSelectedVacationType(0); + + // Optional: keep dates consistent for timed leave (same day) + toDate = fromDate; + } + }); + } + + Future _loadVacationTypes() async { + final result = await _getVacationTypesUseCase(); + result.fold( + (failure) { + print('Failed to load vacation types: ${_getFailureMessage(failure)}'); + }, + (response) { + if (!mounted) return; + + setState(() { + _vacationTypes = response.data; + + if (_vacationTypes.isEmpty) return; + + // IMPORTANT: + // If toggle already ON -> force TimeOff + if (isTimedLeave) { + final timeOff = _vacationTypes.firstWhere( + (t) => t.value == 1, + orElse: () => _vacationTypes.first, + ); + _setSelectedVacationType(timeOff.value); + } else { + // Otherwise default to SickLeave (2) if available + final sickLeave = _vacationTypes.firstWhere( + (type) => type.value == 2, + orElse: () => _vacationTypes.first, + ); + _setSelectedVacationType(sickLeave.value); + } + }); + }, + ); + } + + String _getArabicName(String apiName) { + switch (apiName) { + case 'TimeOff': + return 'إجازة زمنية'; + case 'SickLeave': + return 'إجازة مرضية'; + case 'PaidLeave': + return 'إجازة مدفوعة'; + case 'UnpaidLeave': + return 'إجازة غير مدفوعة'; + default: + return apiName; + } + } + + int _getVacationTypeValue() { + if (_selectedVacationTypeValue != null) { + return _selectedVacationTypeValue!; + } + + if (leaveType.contains("مرضية") || leaveType == "SickLeave") { + return 1; + } else if (leaveType.contains("مدفوعة") && !leaveType.contains("غير")) { + return 2; + } else if (leaveType.contains("غير مدفوعة") || leaveType == "UnpaidLeave") { + return 3; + } else if (leaveType.contains("زمنية") || leaveType == "TimeOff") { + return 0; + } + return 1; + } + + String _getFailureMessage(Failure failure) { + if (failure is ServerFailure) return failure.message; + if (failure is NetworkFailure) return failure.message; + return 'حدث خطأ غير متوقع'; + } /// PICK DATE Future pickDate(bool isFrom) async { @@ -56,15 +177,15 @@ class _RequestLeaveScreenState extends State { initialDate: initial, firstDate: DateTime(2020), lastDate: DateTime(2035), - builder: (context, child) { - return Theme(data: ThemeData.dark(), child: child!); - }, + builder: (context, child) => Theme(data: ThemeData.dark(), child: child!), ); if (newDate != null) { setState(() { if (isFrom) { fromDate = newDate; + // If timed leave, keep toDate same as fromDate + if (isTimedLeave) toDate = newDate; } else { toDate = newDate; } @@ -78,9 +199,7 @@ class _RequestLeaveScreenState extends State { TimeOfDay? newTime = await showTimePicker( context: context, initialTime: initial, - builder: (context, child) { - return Theme(data: ThemeData.dark(), child: child!); - }, + builder: (context, child) => Theme(data: ThemeData.dark(), child: child!), ); if (newTime != null) { @@ -94,85 +213,8 @@ class _RequestLeaveScreenState extends State { } } - @override - void initState() { - super.initState(); - _loadVacationTypes(); - } - - Future _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 _saveLeaveRequest() async { if (reasonController.text.isEmpty) { - // Show an error message if reason is empty if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( @@ -184,7 +226,6 @@ class _RequestLeaveScreenState extends State { return; } - // Get employee ID final employeeId = await sl().getCachedEmployeeId(); if (employeeId == null) { if (mounted) { @@ -198,12 +239,10 @@ class _RequestLeaveScreenState extends State { 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, @@ -219,15 +258,12 @@ class _RequestLeaveScreenState extends State { 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, @@ -237,7 +273,6 @@ class _RequestLeaveScreenState extends State { } try { - // Create vacation request final vacationRequest = VacationRequest( employeeId: employeeId, startDate: finalStartDate, @@ -248,50 +283,48 @@ class _RequestLeaveScreenState extends State { final result = await _createVacationUseCase(vacationRequest); - if (mounted) { - Navigator.pop(context); // Close loading dialog + if (!mounted) return; + Navigator.pop(context); - 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); + result.fold( + (failure) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(_getFailureMessage(failure)), + backgroundColor: Colors.red, + ), + ); + }, + (response) { + 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", + ); + _requestService.addLeaveRequest(leaveRequest); - // Show success message - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('تم إرسال طلب الأجازة بنجاح'), - backgroundColor: Colors.green, - ), - ); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('تم إرسال طلب الأجازة بنجاح'), + backgroundColor: Colors.green, + ), + ); - // Navigate back to the previous screen - Navigator.pop(context); - }, - ); - } + Navigator.pop(context); + }, + ); } catch (e) { if (mounted) { - Navigator.pop(context); // Close loading dialog + Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('حدث خطأ غير متوقع: $e'), @@ -305,12 +338,11 @@ class _RequestLeaveScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - resizeToAvoidBottomInset: true, // ✅ IMPORTANT + resizeToAvoidBottomInset: true, body: AppBackground( child: SafeArea( child: Column( children: [ - // In your RequestLeaveScreen's build method, replace the SettingsBar with this: SettingsBar( selectedIndex: -1, onTap: (_) {}, @@ -322,8 +354,6 @@ class _RequestLeaveScreenState extends State { ], ), - // Title - // const SizedBox(height: 30), Flexible( child: SingleChildScrollView( child: Padding( @@ -331,7 +361,6 @@ class _RequestLeaveScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Fixed alignment for "طلب أجازة" const Align( alignment: Alignment.topRight, child: Text( @@ -343,11 +372,9 @@ class _RequestLeaveScreenState extends State { ), ), ), - + const SizedBox(height: 10), - //============================= - // DROPDOWN: نوع الإجازة - //============================= + Align( alignment: Alignment.centerRight, child: const Text( @@ -359,10 +386,9 @@ class _RequestLeaveScreenState extends State { ), ), ), - + const SizedBox(height: 5), - - // Modified dropdown with disabled state + Directionality( textDirection: TextDirection.rtl, child: Opacity( @@ -390,78 +416,77 @@ class _RequestLeaveScreenState extends State { ], ), child: DropdownButtonHideUnderline( - child: _vacationTypes.isEmpty - ? const Center( - child: Padding( - padding: EdgeInsets.all(8.0), - child: CircularProgressIndicator(), - ), - ) - : DropdownButton( - value: _selectedVacationTypeValue, - 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) { - if (value != null) { + child: + _vacationTypes.isEmpty + ? const Center( + child: Padding( + padding: EdgeInsets.all(8.0), + child: + CircularProgressIndicator(), + ), + ) + : DropdownButton( + value: _selectedVacationTypeValue, + 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) { + if (value == null) return; + 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; + _setSelectedVacationType(value); }); - } - }, - items: _vacationTypes.map((type) { - final arabicName = _getArabicName(type.name); - return DropdownMenuItem( - value: type.value, - child: Directionality( - textDirection: TextDirection.rtl, - child: Align( - alignment: Alignment.centerRight, - child: Text(arabicName), - ), - ), - ); - }).toList(), - ), + + // Sync toggle with selection + if (value == 0 && !isTimedLeave) { + _setTimedLeave(true); + } else if (value != 0 && + isTimedLeave) { + setState( + () => isTimedLeave = false, + ); + } + }, + items: + _vacationTypes.map((type) { + final arabicName = + _getArabicName(type.name); + return DropdownMenuItem( + value: type.value, + child: Directionality( + textDirection: + TextDirection.rtl, + child: Align( + alignment: + Alignment + .centerRight, + child: Text(arabicName), + ), + ), + ); + }).toList(), + ), ), ), ), ), ), - + const SizedBox(height: 25), - - //============================= - // PERFECT CUSTOM TOGGLE (NEW) - //============================= + GestureDetector( onTap: () { - setState(() { - isTimedLeave = !isTimedLeave; - // Set leave type to TimeOff (value: 1) when toggle is ON - if (isTimedLeave) { - final timeOffType = _vacationTypes - .firstWhere((t) => t.value == 1, orElse: () => _vacationTypes.first); - _selectedVacationTypeValue = timeOffType.value; - leaveType = _getArabicName(timeOffType.name); - } - }); + _setTimedLeave(!isTimedLeave); }, child: Row( children: [ - // ---------- TOGGLE ---------- AnimatedContainer( duration: const Duration(milliseconds: 250), width: 75, @@ -472,12 +497,8 @@ class _RequestLeaveScreenState extends State { decoration: BoxDecoration( color: isTimedLeave - ? const Color( - 0xFF0A6B4A, - ) // ON green track - : const Color( - 0xFF9E9E9E, - ), // OFF grey track + ? const Color(0xFF0A6B4A) + : const Color(0xFF9E9E9E), borderRadius: BorderRadius.circular(40), ), child: Align( @@ -492,8 +513,8 @@ class _RequestLeaveScreenState extends State { decoration: BoxDecoration( color: isTimedLeave - ? const Color(0xFF12BE85) // ON knob - : Colors.white, // OFF knob + ? const Color(0xFF12BE85) + : Colors.white, shape: BoxShape.circle, boxShadow: [ BoxShadow( @@ -506,10 +527,9 @@ class _RequestLeaveScreenState extends State { ), ), ), - + const SizedBox(width: 14), - - // ---------- Dot (ALWAYS visible) ---------- + Container( width: 10, height: 10, @@ -518,20 +538,18 @@ class _RequestLeaveScreenState extends State { shape: BoxShape.circle, ), ), - + const SizedBox(width: 6), - - // ---------- Line (ALWAYS visible) ---------- + Expanded( child: Container( height: 2, color: const Color(0xFF32C59A), ), ), - + const SizedBox(width: 12), - - // ---------- Label ---------- + const Text( "إجازة زمنية", style: TextStyle( @@ -543,12 +561,9 @@ class _RequestLeaveScreenState extends State { ], ), ), - + const SizedBox(height: 20), - - // ============================= - // DATE PICKER BOX - // ============================= + Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( @@ -565,10 +580,8 @@ class _RequestLeaveScreenState extends State { child: Column( children: [ Row( - mainAxisAlignment: - MainAxisAlignment.end, // ALIGN RIGHT + mainAxisAlignment: MainAxisAlignment.end, children: [ - // TEXT aligned right const Text( "فترة الإجازة", style: TextStyle( @@ -577,24 +590,17 @@ class _RequestLeaveScreenState extends State { fontWeight: FontWeight.bold, ), ), - const SizedBox(width: 8), - - // 🟢 YOUR CUSTOM SVG ICON SvgPicture.asset( - "assets/images/calendar.svg", // <-- replace with your icon filename + "assets/images/calendar.svg", width: 24, height: 24, - color: Color( - 0xFF007C46, - ), // Optional: match your green + color: const Color(0xFF007C46), ), ], ), - const SizedBox(height: 15), - - // From date row + _dateRow( label: "من", date: fromDate!, @@ -603,10 +609,9 @@ class _RequestLeaveScreenState extends State { isTimedLeave ? null : () => pickDate(true), onTimeTap: () => pickTime(true), ), - + const SizedBox(height: 15), - - // To date row + _dateRow( label: "الى", date: toDate!, @@ -618,12 +623,9 @@ class _RequestLeaveScreenState extends State { ], ), ), - + const SizedBox(height: 10), - - // ============================= - // REASON TEXTFIELD (Two Containers) - // ============================= + Align( alignment: Alignment.centerRight, child: Directionality( @@ -638,21 +640,16 @@ class _RequestLeaveScreenState extends State { ), ), ), - - // OUTER BORDER CONTAINER + Container( - padding: const EdgeInsets.all( - 12, - ), // border thickness space + padding: const EdgeInsets.all(12), decoration: BoxDecoration( border: Border.all( - color: Color(0xFF00FFAA), // green border + color: const Color(0xFF00FFAA), width: 0.5, ), borderRadius: BorderRadius.circular(14), ), - - // INNER TEXTFIELD CONTAINER child: Container( height: 70, padding: const EdgeInsets.symmetric( @@ -663,35 +660,30 @@ class _RequestLeaveScreenState extends State { color: const Color(0xFFEAEAEA), borderRadius: BorderRadius.circular(12), ), - // Added Directionality to fix text direction child: Directionality( textDirection: TextDirection.rtl, child: TextField( controller: reasonController, maxLines: 5, - textAlign: - TextAlign - .right, // Added this to align text to the right + textAlign: TextAlign.right, decoration: const InputDecoration( border: InputBorder.none, - hintText: - "اكتب السبب", // Added placeholder text + hintText: "اكتب السبب", hintStyle: TextStyle(color: Colors.grey), ), ), ), ), ), - + const SizedBox(height: 20), - - // CONFIRM BUTTON + Center( child: OnboardingButton( text: "تأكيد الطلب", backgroundColor: const Color(0xFFD1FEF0), - textColor: Colors.black, // Changed to black - onPressed: _saveLeaveRequest, // Call the save method + textColor: Colors.black, + onPressed: _saveLeaveRequest, ), ), ], @@ -706,9 +698,6 @@ class _RequestLeaveScreenState extends State { ); } - // =============================================================== - // CUSTOM DATE ROW WIDGET - // =============================================================== Widget _dateRow({ required String label, required DateTime date, @@ -727,9 +716,6 @@ class _RequestLeaveScreenState extends State { ), child: Row( children: [ - // ----------------------- - // DATE PART (can be disabled) - // ----------------------- Opacity( opacity: dateDisabled ? 0.45 : 1, child: IgnorePointer( @@ -757,9 +743,6 @@ class _RequestLeaveScreenState extends State { const Spacer(), - // ----------------------- - // TIME PART (always active) - // ----------------------- GestureDetector( onTap: onTimeTap, child: Text( @@ -779,8 +762,11 @@ class _RequestLeaveScreenState extends State { ); } + // NOTE: DateTime.weekday is 1..7 (Mon..Sun) String _weekday(int day) { switch (day) { + case 7: + return "الأحد"; case 1: return "الإثنين"; case 2: @@ -793,8 +779,6 @@ class _RequestLeaveScreenState extends State { return "الجمعة"; case 6: return "السبت"; - case 7: - return "الأحد"; default: return ""; } diff --git a/lib/presentation/screens/splash_screen.dart b/lib/presentation/screens/splash_screen.dart index 1bdb4da..4a08286 100644 --- a/lib/presentation/screens/splash_screen.dart +++ b/lib/presentation/screens/splash_screen.dart @@ -21,26 +21,37 @@ class _SplashScreenState extends State { } Future _checkTokenAndNavigate() async { - // Wait for splash screen display - await Future.delayed(const Duration(seconds: 2)); + try { + // Wait for splash screen display + await Future.delayed(const Duration(seconds: 2)); - if (!mounted) return; + if (!mounted) return; - // Check if token exists in cache - final token = await sl().getCachedUserToken(); - - if (token != null && token.isNotEmpty) { - // Token exists, navigate directly to MainPage - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (_) => const MainPage()), - ); - } else { - // No token, navigate to OnboardingScreen - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (_) => const OnboardingScreen()), - ); + // Check if token exists in cache + final token = await sl().getCachedUserToken(); + + if (token != null && token.isNotEmpty) { + // Token exists, navigate directly to MainPage + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (_) => const MainPage()), + ); + } else { + // No token, navigate to OnboardingScreen + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (_) => const OnboardingScreen()), + ); + } + } catch (e) { + debugPrint('Error in _checkTokenAndNavigate: $e'); + if (mounted) { + // Fallback to onboarding if anything fails + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (_) => const OnboardingScreen()), + ); + } } }