From 33099c44975ba60651cf572fd71c45a0c5160d82 Mon Sep 17 00:00:00 2001 From: Mohammed Al-Samarraie Date: Sun, 18 Jan 2026 19:52:10 +0300 Subject: [PATCH] 1111 --- lib/core/di/injection_container.dart | 19 ++ .../advance_remote_data_source.dart | 124 ++++++++++++ .../vacation_remote_data_source.dart | 54 +++++ lib/data/dto/advance_request_dto.dart | 22 +++ lib/data/dto/advance_response_dto.dart | 86 ++++++++ lib/data/dto/advances_list_response_dto.dart | 54 +++++ lib/data/dto/vacations_list_response_dto.dart | 54 +++++ .../repositories/advance_repository_impl.dart | 109 +++++++++++ .../vacation_repository_impl.dart | 49 +++++ lib/domain/models/advance_request_model.dart | 59 ++++++ .../models/advances_list_response_model.dart | 31 +++ .../models/vacations_list_response_model.dart | 31 +++ .../repositories/advance_repository.dart | 11 ++ .../repositories/vacation_repository.dart | 2 + .../usecases/create_advance_usecase.dart | 14 ++ lib/domain/usecases/get_advances_usecase.dart | 14 ++ .../usecases/get_vacations_usecase.dart | 14 ++ lib/presentation/screens/holiday_screen.dart | 185 +++++++++++++++++- .../screens/request_advance_scrren.dart | 144 ++++++++++---- 19 files changed, 1036 insertions(+), 40 deletions(-) create mode 100644 lib/data/datasources/advance_remote_data_source.dart create mode 100644 lib/data/dto/advance_request_dto.dart create mode 100644 lib/data/dto/advance_response_dto.dart create mode 100644 lib/data/dto/advances_list_response_dto.dart create mode 100644 lib/data/dto/vacations_list_response_dto.dart create mode 100644 lib/data/repositories/advance_repository_impl.dart create mode 100644 lib/domain/models/advance_request_model.dart create mode 100644 lib/domain/models/advances_list_response_model.dart create mode 100644 lib/domain/models/vacations_list_response_model.dart create mode 100644 lib/domain/repositories/advance_repository.dart create mode 100644 lib/domain/usecases/create_advance_usecase.dart create mode 100644 lib/domain/usecases/get_advances_usecase.dart create mode 100644 lib/domain/usecases/get_vacations_usecase.dart diff --git a/lib/core/di/injection_container.dart b/lib/core/di/injection_container.dart index b61c9bd..287dfe5 100644 --- a/lib/core/di/injection_container.dart +++ b/lib/core/di/injection_container.dart @@ -8,15 +8,21 @@ import '../network/api_client.dart'; import '../../data/datasources/auth_remote_data_source.dart'; import '../../data/datasources/user_local_data_source.dart'; import '../../data/datasources/vacation_remote_data_source.dart'; +import '../../data/datasources/advance_remote_data_source.dart'; import '../../data/repositories/auth_repository_impl.dart'; import '../../data/repositories/vacation_repository_impl.dart'; +import '../../data/repositories/advance_repository_impl.dart'; import '../../domain/repositories/auth_repository.dart'; import '../../domain/repositories/vacation_repository.dart'; +import '../../domain/repositories/advance_repository.dart'; import '../../domain/usecases/login_usecase.dart'; import '../../domain/usecases/attendance_login_usecase.dart'; import '../../domain/usecases/attendance_logout_usecase.dart'; import '../../domain/usecases/create_vacation_usecase.dart'; import '../../domain/usecases/get_vacation_types_usecase.dart'; +import '../../domain/usecases/get_vacations_usecase.dart'; +import '../../domain/usecases/create_advance_usecase.dart'; +import '../../domain/usecases/get_advances_usecase.dart'; import '../../presentation/blocs/login/login_bloc.dart'; final sl = GetIt.instance; @@ -78,4 +84,17 @@ Future initializeDependencies() async { sl.registerLazySingleton(() => CreateVacationUseCase(repository: sl())); sl.registerLazySingleton(() => GetVacationTypesUseCase(repository: sl())); + sl.registerLazySingleton(() => GetVacationsUseCase(repository: sl())); + + // Advance + sl.registerLazySingleton( + () => AdvanceRemoteDataSourceImpl(apiClient: sl()), + ); + + sl.registerLazySingleton( + () => AdvanceRepositoryImpl(remoteDataSource: sl()), + ); + + sl.registerLazySingleton(() => CreateAdvanceUseCase(repository: sl())); + sl.registerLazySingleton(() => GetAdvancesUseCase(repository: sl())); } diff --git a/lib/data/datasources/advance_remote_data_source.dart b/lib/data/datasources/advance_remote_data_source.dart new file mode 100644 index 0000000..e5bbd20 --- /dev/null +++ b/lib/data/datasources/advance_remote_data_source.dart @@ -0,0 +1,124 @@ +import 'package:dio/dio.dart'; +import '../../core/error/exceptions.dart'; +import '../../core/network/api_client.dart'; +import '../dto/advance_request_dto.dart'; +import '../dto/advance_response_dto.dart'; +import '../dto/advances_list_response_dto.dart'; + +abstract class AdvanceRemoteDataSource { + Future createAdvance(AdvanceRequestDto request); + Future getAdvances(); +} + +class AdvanceRemoteDataSourceImpl implements AdvanceRemoteDataSource { + final ApiClient apiClient; + + AdvanceRemoteDataSourceImpl({required this.apiClient}); + + @override + Future createAdvance(AdvanceRequestDto request) async { + try { + final response = await apiClient.post( + '/SalaryInAdvance', + data: request.toJson(), + ); + + if (response.statusCode == 200 || response.statusCode == 201) { + final responseData = response.data; + + if (responseData is Map) { + return AdvanceResponseDto.fromJson(responseData); + } else { + throw ServerException( + message: 'استجابة غير صحيحة من الخادم', + statusCode: response.statusCode, + ); + } + } else { + throw ServerException( + message: 'فشل إنشاء طلب السلفة', + statusCode: response.statusCode, + ); + } + } on DioException catch (e) { + if (e.type == DioExceptionType.connectionTimeout || + e.type == DioExceptionType.receiveTimeout) { + throw NetworkException(message: 'انتهت مهلة الاتصال'); + } else if (e.type == DioExceptionType.connectionError) { + throw NetworkException(message: 'لا يوجد اتصال بالانترنيت'); + } else if (e.response?.statusCode == 500) { + throw ServerException(message: 'خطأ في الخادم يرجى المحاولة لاحقا'); + } else if (e.response != null) { + final message = + e.response?.data?['message'] ?? + e.response?.data?['error'] ?? + 'فشل إنشاء طلب السلفة'; + + throw ServerException( + message: message.toString(), + statusCode: e.response?.statusCode, + ); + } else { + throw NetworkException(message: 'خطأ في الانترنيت يرجى المحاولة لاحقا'); + } + } catch (e) { + if (e is ServerException || e is NetworkException) { + rethrow; + } + print('خطأ غير متوقع: $e'); + throw ServerException(message: 'خطأ غير متوقع'); + } + } + + @override + Future getAdvances() async { + try { + final response = await apiClient.get('/SalaryInAdvance'); + + if (response.statusCode == 200) { + final responseData = response.data; + + if (responseData is Map) { + return AdvancesListResponseDto.fromJson(responseData); + } else { + throw ServerException( + message: 'استجابة غير صحيحة من الخادم', + statusCode: response.statusCode, + ); + } + } else { + throw ServerException( + message: 'فشل جلب قائمة السلف', + statusCode: response.statusCode, + ); + } + } on DioException catch (e) { + if (e.type == DioExceptionType.connectionTimeout || + e.type == DioExceptionType.receiveTimeout) { + throw NetworkException(message: 'انتهت مهلة الاتصال'); + } else if (e.type == DioExceptionType.connectionError) { + throw NetworkException(message: 'لا يوجد اتصال بالانترنيت'); + } else if (e.response?.statusCode == 500) { + throw ServerException(message: 'خطأ في الخادم يرجى المحاولة لاحقا'); + } else if (e.response != null) { + final message = + e.response?.data?['message'] ?? + e.response?.data?['error'] ?? + 'فشل جلب قائمة السلف'; + + throw ServerException( + message: message.toString(), + statusCode: e.response?.statusCode, + ); + } else { + throw NetworkException(message: 'خطأ في الانترنيت يرجى المحاولة لاحقا'); + } + } catch (e) { + if (e is ServerException || e is NetworkException) { + rethrow; + } + print('خطأ غير متوقع: $e'); + throw ServerException(message: 'خطأ غير متوقع'); + } + } +} diff --git a/lib/data/datasources/vacation_remote_data_source.dart b/lib/data/datasources/vacation_remote_data_source.dart index af3a7d4..3a9d129 100644 --- a/lib/data/datasources/vacation_remote_data_source.dart +++ b/lib/data/datasources/vacation_remote_data_source.dart @@ -4,10 +4,12 @@ import '../../core/network/api_client.dart'; import '../dto/vacation_request_dto.dart'; import '../dto/vacation_response_dto.dart'; import '../dto/vacation_type_dto.dart'; +import '../dto/vacations_list_response_dto.dart'; abstract class VacationRemoteDataSource { Future createVacation(VacationRequestDto request); Future getVacationTypes(); + Future getVacations(); } class VacationRemoteDataSourceImpl implements VacationRemoteDataSource { @@ -121,4 +123,56 @@ class VacationRemoteDataSourceImpl implements VacationRemoteDataSource { throw ServerException(message: 'خطأ غير متوقع'); } } + + @override + Future getVacations() async { + try { + final response = await apiClient.get('/Vacation'); + + if (response.statusCode == 200) { + final responseData = response.data; + + if (responseData is Map) { + return VacationsListResponseDto.fromJson(responseData); + } else { + throw ServerException( + message: 'استجابة غير صحيحة من الخادم', + statusCode: response.statusCode, + ); + } + } else { + throw ServerException( + message: 'فشل جلب قائمة الإجازات', + statusCode: response.statusCode, + ); + } + } on DioException catch (e) { + if (e.type == DioExceptionType.connectionTimeout || + e.type == DioExceptionType.receiveTimeout) { + throw NetworkException(message: 'انتهت مهلة الاتصال'); + } else if (e.type == DioExceptionType.connectionError) { + throw NetworkException(message: 'لا يوجد اتصال بالانترنيت'); + } else if (e.response?.statusCode == 500) { + throw ServerException(message: 'خطأ في الخادم يرجى المحاولة لاحقا'); + } else if (e.response != null) { + final message = + e.response?.data?['message'] ?? + e.response?.data?['error'] ?? + 'فشل جلب قائمة الإجازات'; + + throw ServerException( + message: message.toString(), + statusCode: e.response?.statusCode, + ); + } else { + throw NetworkException(message: 'خطأ في الانترنيت يرجى المحاولة لاحقا'); + } + } catch (e) { + if (e is ServerException || e is NetworkException) { + rethrow; + } + print('خطأ غير متوقع: $e'); + throw ServerException(message: 'خطأ غير متوقع'); + } + } } diff --git a/lib/data/dto/advance_request_dto.dart b/lib/data/dto/advance_request_dto.dart new file mode 100644 index 0000000..de15a43 --- /dev/null +++ b/lib/data/dto/advance_request_dto.dart @@ -0,0 +1,22 @@ +class AdvanceRequestDto { + final String employeeId; + final DateTime date; + final double amount; + final String reason; + + AdvanceRequestDto({ + required this.employeeId, + required this.date, + required this.amount, + required this.reason, + }); + + Map toJson() { + return { + 'employeeId': employeeId, + 'date': date.toIso8601String(), + 'amount': amount, + 'reason': reason, + }; + } +} diff --git a/lib/data/dto/advance_response_dto.dart b/lib/data/dto/advance_response_dto.dart new file mode 100644 index 0000000..f851d67 --- /dev/null +++ b/lib/data/dto/advance_response_dto.dart @@ -0,0 +1,86 @@ +class AdvanceResponseDto { + final int statusCode; + final bool isSuccess; + final String? message; + final AdvanceDataDto? data; + + AdvanceResponseDto({ + required this.statusCode, + required this.isSuccess, + this.message, + this.data, + }); + + factory AdvanceResponseDto.fromJson(Map json) { + return AdvanceResponseDto( + statusCode: json['statusCode'] ?? 0, + isSuccess: json['isSuccess'] ?? false, + message: json['message'], + data: json['data'] != null ? AdvanceDataDto.fromJson(json['data']) : null, + ); + } +} + +class AdvanceDataDto { + final String employeeId; + final String? employeeFullName; + final DateTime date; + final double amount; + final String? submittedBy; + final String? submittedByUser; + final String reason; + final int state; + final String id; + final DateTime? createdAt; + final DateTime? updatedAt; + final DateTime? deletedAt; + final bool? isDeleted; + + AdvanceDataDto({ + required this.employeeId, + this.employeeFullName, + required this.date, + required this.amount, + this.submittedBy, + this.submittedByUser, + required this.reason, + required this.state, + required this.id, + this.createdAt, + this.updatedAt, + this.deletedAt, + this.isDeleted, + }); + + factory AdvanceDataDto.fromJson(Map json) { + return AdvanceDataDto( + employeeId: json['employeeId']?.toString() ?? '', + employeeFullName: json['employeeFullName'], + date: _parseDateTime(json['date'])!, + amount: (json['amount'] is int) ? (json['amount'] as int).toDouble() : (json['amount'] as num).toDouble(), + submittedBy: json['submittedBy'], + submittedByUser: json['submittedByUser'], + reason: json['reason']?.toString() ?? '', + state: json['state'] ?? 0, + id: json['id']?.toString() ?? '', + createdAt: _parseDateTime(json['createdAt']), + updatedAt: _parseDateTime(json['updatedAt']), + deletedAt: _parseDateTime(json['deletedAt']), + isDeleted: json['isDeleted'], + ); + } + + static DateTime? _parseDateTime(dynamic value) { + if (value == null) return null; + if (value is DateTime) return value; + if (value is String) { + try { + return DateTime.parse(value); + } catch (e) { + print('Error parsing date: $value - $e'); + return null; + } + } + return null; + } +} diff --git a/lib/data/dto/advances_list_response_dto.dart b/lib/data/dto/advances_list_response_dto.dart new file mode 100644 index 0000000..caca833 --- /dev/null +++ b/lib/data/dto/advances_list_response_dto.dart @@ -0,0 +1,54 @@ +import 'advance_response_dto.dart'; + +class AdvancesListResponseDto { + final int statusCode; + final bool isSuccess; + final String? message; + final AdvancesListDataDto? data; + + AdvancesListResponseDto({ + required this.statusCode, + required this.isSuccess, + this.message, + this.data, + }); + + factory AdvancesListResponseDto.fromJson(Map json) { + return AdvancesListResponseDto( + statusCode: json['statusCode'] ?? 0, + isSuccess: json['isSuccess'] ?? false, + message: json['message'], + data: json['data'] != null ? AdvancesListDataDto.fromJson(json['data']) : null, + ); + } +} + +class AdvancesListDataDto { + final List items; + final int pageNumber; + final int pageSize; + final int totalCount; + final int totalPages; + + AdvancesListDataDto({ + required this.items, + required this.pageNumber, + required this.pageSize, + required this.totalCount, + required this.totalPages, + }); + + factory AdvancesListDataDto.fromJson(Map json) { + return AdvancesListDataDto( + items: json['items'] != null + ? (json['items'] as List) + .map((item) => AdvanceDataDto.fromJson(item)) + .toList() + : [], + pageNumber: json['pageNumber'] ?? 1, + pageSize: json['pageSize'] ?? 15, + totalCount: json['totalCount'] ?? 0, + totalPages: json['totalPages'] ?? 1, + ); + } +} diff --git a/lib/data/dto/vacations_list_response_dto.dart b/lib/data/dto/vacations_list_response_dto.dart new file mode 100644 index 0000000..7575ef2 --- /dev/null +++ b/lib/data/dto/vacations_list_response_dto.dart @@ -0,0 +1,54 @@ +import 'vacation_response_dto.dart'; + +class VacationsListResponseDto { + final int statusCode; + final bool isSuccess; + final String? message; + final VacationsListDataDto? data; + + VacationsListResponseDto({ + required this.statusCode, + required this.isSuccess, + this.message, + this.data, + }); + + factory VacationsListResponseDto.fromJson(Map json) { + return VacationsListResponseDto( + statusCode: json['statusCode'] ?? 0, + isSuccess: json['isSuccess'] ?? false, + message: json['message'], + data: json['data'] != null ? VacationsListDataDto.fromJson(json['data']) : null, + ); + } +} + +class VacationsListDataDto { + final List items; + final int pageNumber; + final int pageSize; + final int totalCount; + final int totalPages; + + VacationsListDataDto({ + required this.items, + required this.pageNumber, + required this.pageSize, + required this.totalCount, + required this.totalPages, + }); + + factory VacationsListDataDto.fromJson(Map json) { + return VacationsListDataDto( + items: json['items'] != null + ? (json['items'] as List) + .map((item) => VacationDataDto.fromJson(item)) + .toList() + : [], + pageNumber: json['pageNumber'] ?? 1, + pageSize: json['pageSize'] ?? 15, + totalCount: json['totalCount'] ?? 0, + totalPages: json['totalPages'] ?? 1, + ); + } +} diff --git a/lib/data/repositories/advance_repository_impl.dart b/lib/data/repositories/advance_repository_impl.dart new file mode 100644 index 0000000..092e620 --- /dev/null +++ b/lib/data/repositories/advance_repository_impl.dart @@ -0,0 +1,109 @@ +import 'package:dartz/dartz.dart'; +import '../../core/error/exceptions.dart'; +import '../../core/error/failures.dart'; +import '../datasources/advance_remote_data_source.dart'; +import '../dto/advance_request_dto.dart'; +import '../../domain/models/advance_request_model.dart'; +import '../../domain/models/advances_list_response_model.dart'; +import '../../domain/repositories/advance_repository.dart'; + +class AdvanceRepositoryImpl implements AdvanceRepository { + final AdvanceRemoteDataSource remoteDataSource; + + AdvanceRepositoryImpl({required this.remoteDataSource}); + + @override + Future> createAdvance( + AdvanceRequestModel request, + ) async { + try { + final dto = AdvanceRequestDto( + employeeId: request.employeeId, + date: request.date, + amount: request.amount, + reason: request.reason, + ); + + final responseDto = await remoteDataSource.createAdvance(dto); + + // Convert DTO to Model + final responseModel = AdvanceResponseModel( + 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, + ); + + return Right(responseModel); + } on ServerException catch (e) { + return Left(ServerFailure(e.message)); + } on NetworkException catch (e) { + return Left(NetworkFailure(e.message)); + } catch (e) { + return Left(ServerFailure('خطأ غير متوقع: $e')); + } + } + + @override + Future> getAdvances() async { + try { + final responseDto = await remoteDataSource.getAdvances(); + + // 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, + ); + + return Right(responseModel); + } on ServerException catch (e) { + return Left(ServerFailure(e.message)); + } on NetworkException catch (e) { + return Left(NetworkFailure(e.message)); + } catch (e) { + return Left(ServerFailure('خطأ غير متوقع: $e')); + } + } +} diff --git a/lib/data/repositories/vacation_repository_impl.dart b/lib/data/repositories/vacation_repository_impl.dart index f7bcc74..00d5c7c 100644 --- a/lib/data/repositories/vacation_repository_impl.dart +++ b/lib/data/repositories/vacation_repository_impl.dart @@ -6,6 +6,7 @@ import '../dto/vacation_request_dto.dart'; import '../../domain/models/vacation_request.dart'; import '../../domain/models/vacation_response_model.dart'; import '../../domain/models/vacation_type_model.dart'; +import '../../domain/models/vacations_list_response_model.dart'; import '../../domain/repositories/vacation_repository.dart'; class VacationRepositoryImpl implements VacationRepository { @@ -90,4 +91,52 @@ class VacationRepositoryImpl implements VacationRepository { return Left(ServerFailure('خطأ غير متوقع: $e')); } } + + @override + Future> getVacations() async { + try { + final responseDto = await remoteDataSource.getVacations(); + + // 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, + ); + + return Right(responseModel); + } on ServerException catch (e) { + return Left(ServerFailure(e.message)); + } on NetworkException catch (e) { + return Left(NetworkFailure(e.message)); + } catch (e) { + return Left(ServerFailure('خطأ غير متوقع: $e')); + } + } } diff --git a/lib/domain/models/advance_request_model.dart b/lib/domain/models/advance_request_model.dart new file mode 100644 index 0000000..1b41684 --- /dev/null +++ b/lib/domain/models/advance_request_model.dart @@ -0,0 +1,59 @@ +class AdvanceRequestModel { + final String employeeId; + final DateTime date; + final double amount; + final String reason; + + AdvanceRequestModel({ + required this.employeeId, + required this.date, + required this.amount, + required this.reason, + }); +} + +class AdvanceResponseModel { + final int statusCode; + final bool isSuccess; + final String? message; + final AdvanceDataModel? data; + + AdvanceResponseModel({ + required this.statusCode, + required this.isSuccess, + this.message, + this.data, + }); +} + +class AdvanceDataModel { + final String employeeId; + final String? employeeFullName; + final DateTime date; + final double amount; + final String? submittedBy; + final String? submittedByUser; + final String reason; + final int state; + final String id; + final DateTime? createdAt; + final DateTime? updatedAt; + final DateTime? deletedAt; + final bool? isDeleted; + + AdvanceDataModel({ + required this.employeeId, + this.employeeFullName, + required this.date, + required this.amount, + this.submittedBy, + this.submittedByUser, + required this.reason, + required this.state, + required this.id, + this.createdAt, + this.updatedAt, + this.deletedAt, + this.isDeleted, + }); +} diff --git a/lib/domain/models/advances_list_response_model.dart b/lib/domain/models/advances_list_response_model.dart new file mode 100644 index 0000000..5fad1c2 --- /dev/null +++ b/lib/domain/models/advances_list_response_model.dart @@ -0,0 +1,31 @@ +import 'advance_request_model.dart'; + +class AdvancesListResponseModel { + final int statusCode; + final bool isSuccess; + final String? message; + final AdvancesListDataModel? data; + + AdvancesListResponseModel({ + required this.statusCode, + required this.isSuccess, + this.message, + this.data, + }); +} + +class AdvancesListDataModel { + final List items; + final int pageNumber; + final int pageSize; + final int totalCount; + final int totalPages; + + AdvancesListDataModel({ + required this.items, + required this.pageNumber, + required this.pageSize, + required this.totalCount, + required this.totalPages, + }); +} diff --git a/lib/domain/models/vacations_list_response_model.dart b/lib/domain/models/vacations_list_response_model.dart new file mode 100644 index 0000000..e4cb53c --- /dev/null +++ b/lib/domain/models/vacations_list_response_model.dart @@ -0,0 +1,31 @@ +import 'vacation_response_model.dart'; + +class VacationsListResponseModel { + final int statusCode; + final bool isSuccess; + final String? message; + final VacationsListDataModel? data; + + VacationsListResponseModel({ + required this.statusCode, + required this.isSuccess, + this.message, + this.data, + }); +} + +class VacationsListDataModel { + final List items; + final int pageNumber; + final int pageSize; + final int totalCount; + final int totalPages; + + VacationsListDataModel({ + required this.items, + required this.pageNumber, + required this.pageSize, + required this.totalCount, + required this.totalPages, + }); +} diff --git a/lib/domain/repositories/advance_repository.dart b/lib/domain/repositories/advance_repository.dart new file mode 100644 index 0000000..2abb171 --- /dev/null +++ b/lib/domain/repositories/advance_repository.dart @@ -0,0 +1,11 @@ +import 'package:dartz/dartz.dart'; +import '../../core/error/failures.dart'; +import '../models/advance_request_model.dart'; +import '../models/advances_list_response_model.dart'; + +abstract class AdvanceRepository { + Future> createAdvance( + AdvanceRequestModel request, + ); + Future> getAdvances(); +} diff --git a/lib/domain/repositories/vacation_repository.dart b/lib/domain/repositories/vacation_repository.dart index be5479c..28abad7 100644 --- a/lib/domain/repositories/vacation_repository.dart +++ b/lib/domain/repositories/vacation_repository.dart @@ -3,10 +3,12 @@ import '../../core/error/failures.dart'; import '../models/vacation_request.dart'; import '../models/vacation_response_model.dart'; import '../models/vacation_type_model.dart'; +import '../models/vacations_list_response_model.dart'; abstract class VacationRepository { Future> createVacation( VacationRequest request, ); Future> getVacationTypes(); + Future> getVacations(); } diff --git a/lib/domain/usecases/create_advance_usecase.dart b/lib/domain/usecases/create_advance_usecase.dart new file mode 100644 index 0000000..c31b56c --- /dev/null +++ b/lib/domain/usecases/create_advance_usecase.dart @@ -0,0 +1,14 @@ +import 'package:dartz/dartz.dart'; +import '../../core/error/failures.dart'; +import '../models/advance_request_model.dart'; +import '../repositories/advance_repository.dart'; + +class CreateAdvanceUseCase { + final AdvanceRepository repository; + + CreateAdvanceUseCase({required this.repository}); + + Future> call(AdvanceRequestModel request) { + return repository.createAdvance(request); + } +} diff --git a/lib/domain/usecases/get_advances_usecase.dart b/lib/domain/usecases/get_advances_usecase.dart new file mode 100644 index 0000000..b126b22 --- /dev/null +++ b/lib/domain/usecases/get_advances_usecase.dart @@ -0,0 +1,14 @@ +import 'package:dartz/dartz.dart'; +import '../../core/error/failures.dart'; +import '../models/advances_list_response_model.dart'; +import '../repositories/advance_repository.dart'; + +class GetAdvancesUseCase { + final AdvanceRepository repository; + + GetAdvancesUseCase({required this.repository}); + + Future> call() { + return repository.getAdvances(); + } +} diff --git a/lib/domain/usecases/get_vacations_usecase.dart b/lib/domain/usecases/get_vacations_usecase.dart new file mode 100644 index 0000000..8dd0ef2 --- /dev/null +++ b/lib/domain/usecases/get_vacations_usecase.dart @@ -0,0 +1,14 @@ +import 'package:dartz/dartz.dart'; +import '../../core/error/failures.dart'; +import '../models/vacations_list_response_model.dart'; +import '../repositories/vacation_repository.dart'; + +class GetVacationsUseCase { + final VacationRepository repository; + + GetVacationsUseCase({required this.repository}); + + Future> call() { + return repository.getVacations(); + } +} diff --git a/lib/presentation/screens/holiday_screen.dart b/lib/presentation/screens/holiday_screen.dart index 3b9b316..6d1b571 100644 --- a/lib/presentation/screens/holiday_screen.dart +++ b/lib/presentation/screens/holiday_screen.dart @@ -10,6 +10,14 @@ import 'request_advance_scrren.dart'; import '../../models/leave_request.dart'; import '../../models/advance_request.dart'; import '../../core/services/request_service.dart'; +import '../../core/di/injection_container.dart'; +import '../../domain/usecases/get_vacations_usecase.dart'; +import '../../domain/usecases/get_advances_usecase.dart'; +import '../../domain/models/vacations_list_response_model.dart'; +import '../../domain/models/vacation_response_model.dart'; +import '../../domain/models/advances_list_response_model.dart'; +import '../../domain/models/advance_request_model.dart'; +import '../../core/error/failures.dart'; class HolidayScreen extends StatefulWidget { final void Function(bool isScrollingDown)? onScrollEvent; @@ -24,8 +32,12 @@ class _HolidayScreenState extends State { int activeTab = 0; final RequestService _requestService = RequestService(); + final GetVacationsUseCase _getVacationsUseCase = sl(); + final GetAdvancesUseCase _getAdvancesUseCase = sl(); List _leaveRequests = []; List _advanceRequests = []; + bool _isLoadingVacations = false; + bool _isLoadingAdvances = false; final ScrollController _scrollController = ScrollController(); bool _showActions = true; @@ -62,11 +74,18 @@ class _HolidayScreenState extends State { } void _initializeData() async { - _leaveRequests = await _requestService.getLeaveRequests(); - _advanceRequests = await _requestService.getAdvanceRequests(); + // Load from API + _loadVacationsFromAPI(); + _loadAdvancesFromAPI(); + // Also listen to local changes (for newly created requests) _requestService.leaveRequestsStream.listen((requests) { - if (mounted) setState(() => _leaveRequests = requests); + if (mounted) { + setState(() { + // Merge with API data if needed + _leaveRequests = requests; + }); + } }); _requestService.advanceRequestsStream.listen((requests) { @@ -74,6 +93,140 @@ class _HolidayScreenState extends State { }); } + Future _loadVacationsFromAPI() async { + setState(() { + _isLoadingVacations = true; + }); + + final result = await _getVacationsUseCase(); + result.fold( + (failure) { + if (mounted) { + setState(() { + _isLoadingVacations = false; + }); + // Load from local service as fallback + _requestService.getLeaveRequests().then((requests) { + if (mounted) { + setState(() { + _leaveRequests = requests; + }); + } + }); + } + }, + (response) { + if (mounted && response.data != null) { + setState(() { + _leaveRequests = response.data!.items + .map((vacation) => _convertVacationToLeaveRequest(vacation)) + .toList(); + _isLoadingVacations = false; + }); + } + }, + ); + } + + LeaveRequest _convertVacationToLeaveRequest(VacationDataModel vacation) { + // Convert state (0=waiting, 1=approved, 2=denied) to status string + String status = "waiting"; + if (vacation.state == 1) { + status = "approved"; + } else if (vacation.state == 2) { + status = "denied"; + } + + // Convert type to Arabic name + String leaveTypeName = _getArabicVacationTypeName(vacation.type); + + // Check if it's timed leave (same day but different times) + bool isTimedLeave = vacation.startDate.year == vacation.endDate.year && + vacation.startDate.month == vacation.endDate.month && + vacation.startDate.day == vacation.endDate.day && + vacation.startDate.hour != vacation.endDate.hour; + + return LeaveRequest( + id: vacation.id, + leaveType: leaveTypeName, + isTimedLeave: isTimedLeave, + fromDate: vacation.startDate, + toDate: vacation.endDate, + fromTime: TimeOfDay.fromDateTime(vacation.startDate), + toTime: TimeOfDay.fromDateTime(vacation.endDate), + reason: vacation.reason, + requestDate: vacation.createdAt ?? vacation.startDate, + status: status, + ); + } + + String _getArabicVacationTypeName(int type) { + switch (type) { + case 1: + return 'أجازة زمنية'; + case 2: + return 'إجازة مرضية'; + case 3: + return 'إجازة مدفوعة'; + case 4: + return 'إجازة غير مدفوعة'; + default: + return 'إجازة'; + } + } + + Future _loadAdvancesFromAPI() async { + setState(() { + _isLoadingAdvances = true; + }); + + final result = await _getAdvancesUseCase(); + result.fold( + (failure) { + if (mounted) { + setState(() { + _isLoadingAdvances = false; + }); + // Load from local service as fallback + _requestService.getAdvanceRequests().then((requests) { + if (mounted) { + setState(() { + _advanceRequests = requests; + }); + } + }); + } + }, + (response) { + if (mounted && response.data != null) { + setState(() { + _advanceRequests = response.data!.items + .map((advance) => _convertAdvanceToAdvanceRequest(advance)) + .toList(); + _isLoadingAdvances = false; + }); + } + }, + ); + } + + AdvanceRequest _convertAdvanceToAdvanceRequest(AdvanceDataModel advance) { + // Convert state (0=waiting, 1=approved, 2=denied) to status string + String status = "waiting"; + if (advance.state == 1) { + status = "approved"; + } else if (advance.state == 2) { + status = "denied"; + } + + return AdvanceRequest( + id: advance.id, + amount: advance.amount, + reason: advance.reason, + status: status, + ); + } + @override Widget build(BuildContext context) { return Stack( @@ -259,6 +412,19 @@ class _HolidayScreenState extends State { // ---------------------------------------------------------------- Widget _buildLeaveRequestsSliver() { + if (_isLoadingVacations) { + return SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(40.0), + child: Center( + child: CircularProgressIndicator( + color: Color(0xFF8EFDC2), + ), + ), + ), + ); + } + if (_leaveRequests.isEmpty) { return SliverToBoxAdapter( child: Padding( @@ -287,6 +453,19 @@ class _HolidayScreenState extends State { } Widget _buildAdvanceRequestsSliver() { + if (_isLoadingAdvances) { + return SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(40.0), + child: Center( + child: CircularProgressIndicator( + color: Color(0xFF8EFDC2), + ), + ), + ), + ); + } + if (_advanceRequests.isEmpty) { return SliverToBoxAdapter( child: Padding( diff --git a/lib/presentation/screens/request_advance_scrren.dart b/lib/presentation/screens/request_advance_scrren.dart index a57faad..6caf2a2 100644 --- a/lib/presentation/screens/request_advance_scrren.dart +++ b/lib/presentation/screens/request_advance_scrren.dart @@ -5,6 +5,11 @@ import '../widgets/settings_bar.dart'; import '../widgets/onboarding_button.dart'; import '../../models/advance_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_advance_usecase.dart'; +import '../../domain/models/advance_request_model.dart'; +import '../../core/error/failures.dart'; class RequestAdvanceScreen extends StatefulWidget { const RequestAdvanceScreen({super.key}); @@ -23,16 +28,30 @@ class _RequestAdvanceScreenState extends State { // Use the singleton instance final RequestService _requestService = RequestService(); + // Use case + final CreateAdvanceUseCase _createAdvanceUseCase = sl(); + + String _getFailureMessage(Failure failure) { + if (failure is ServerFailure) { + return failure.message; + } else if (failure is NetworkFailure) { + return failure.message; + } + return 'حدث خطأ غير متوقع'; + } + // 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, - ), - ); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('الرجاء إدخال جميع الحقول'), + backgroundColor: Colors.red, + ), + ); + } return; } @@ -40,45 +59,96 @@ class _RequestAdvanceScreenState extends State { 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, - ), - ); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('الرجاء إدخال مبلغ صحيح'), + backgroundColor: Colors.red, + ), + ); + } return; } - // Create a new advance request with default status "waiting" - final advanceRequest = AdvanceRequest( - id: DateTime.now().millisecondsSinceEpoch.toString(), - amount: amount, - reason: reasonController.text, - status: "waiting", // Default status - ); + // Get employee ID + final employeeId = await sl().getCachedEmployeeId(); + if (employeeId == null) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('خطأ: لم يتم العثور على رقم الموظف'), + backgroundColor: Colors.red, + ), + ); + } + return; + } + + // Show loading indicator + if (mounted) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const Center(child: CircularProgressIndicator()), + ); + } try { - // Save the advance request - await _requestService.addAdvanceRequest(advanceRequest); - - // Show a success message - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('تم إرسال طلب السلفة بنجاح'), - backgroundColor: Colors.green, - ), + // Create advance request model + final advanceRequestModel = AdvanceRequestModel( + employeeId: employeeId, + date: DateTime.now(), + amount: amount, + reason: reasonController.text, ); - // Navigate back to the previous screen - Navigator.pop(context); + final result = await _createAdvanceUseCase(advanceRequestModel); + + 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 advanceRequest = AdvanceRequest( + id: response.data?.id ?? DateTime.now().millisecondsSinceEpoch.toString(), + amount: amount, + reason: reasonController.text, + status: "waiting", // Default status + ); + _requestService.addAdvanceRequest(advanceRequest); + + // 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, + ), + ); + } } }