1111
This commit is contained in:
@@ -7,11 +7,16 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
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/repositories/auth_repository_impl.dart';
|
||||
import '../../data/repositories/vacation_repository_impl.dart';
|
||||
import '../../domain/repositories/auth_repository.dart';
|
||||
import '../../domain/repositories/vacation_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 '../../presentation/blocs/login/login_bloc.dart';
|
||||
|
||||
final sl = GetIt.instance;
|
||||
@@ -61,4 +66,16 @@ Future<void> initializeDependencies() async {
|
||||
sl.registerLazySingleton(() => AttendanceLoginUsecase(repository: sl()));
|
||||
|
||||
sl.registerLazySingleton(() => AttendanceLogoutUseCase(repository: sl()));
|
||||
|
||||
// Vacation
|
||||
sl.registerLazySingleton<VacationRemoteDataSource>(
|
||||
() => VacationRemoteDataSourceImpl(apiClient: sl()),
|
||||
);
|
||||
|
||||
sl.registerLazySingleton<VacationRepository>(
|
||||
() => VacationRepositoryImpl(remoteDataSource: sl()),
|
||||
);
|
||||
|
||||
sl.registerLazySingleton(() => CreateVacationUseCase(repository: sl()));
|
||||
sl.registerLazySingleton(() => GetVacationTypesUseCase(repository: sl()));
|
||||
}
|
||||
|
||||
124
lib/data/datasources/vacation_remote_data_source.dart
Normal file
124
lib/data/datasources/vacation_remote_data_source.dart
Normal file
@@ -0,0 +1,124 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import '../../core/error/exceptions.dart';
|
||||
import '../../core/network/api_client.dart';
|
||||
import '../dto/vacation_request_dto.dart';
|
||||
import '../dto/vacation_response_dto.dart';
|
||||
import '../dto/vacation_type_dto.dart';
|
||||
|
||||
abstract class VacationRemoteDataSource {
|
||||
Future<VacationResponseDto> createVacation(VacationRequestDto request);
|
||||
Future<VacationTypesResponseDto> getVacationTypes();
|
||||
}
|
||||
|
||||
class VacationRemoteDataSourceImpl implements VacationRemoteDataSource {
|
||||
final ApiClient apiClient;
|
||||
|
||||
VacationRemoteDataSourceImpl({required this.apiClient});
|
||||
|
||||
@override
|
||||
Future<VacationResponseDto> createVacation(VacationRequestDto request) async {
|
||||
try {
|
||||
final response = await apiClient.post(
|
||||
'/Vacation',
|
||||
data: request.toJson(),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
final responseData = response.data;
|
||||
|
||||
if (responseData is Map<String, dynamic>) {
|
||||
return VacationResponseDto.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<VacationTypesResponseDto> getVacationTypes() async {
|
||||
try {
|
||||
final response = await apiClient.get('/enums/vacation-types');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final responseData = response.data;
|
||||
|
||||
if (responseData is Map<String, dynamic>) {
|
||||
return VacationTypesResponseDto.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: 'خطأ غير متوقع');
|
||||
}
|
||||
}
|
||||
}
|
||||
25
lib/data/dto/vacation_request_dto.dart
Normal file
25
lib/data/dto/vacation_request_dto.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
class VacationRequestDto {
|
||||
final String employeeId;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
final String reason;
|
||||
final int type;
|
||||
|
||||
VacationRequestDto({
|
||||
required this.employeeId,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
required this.reason,
|
||||
required this.type,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'employeeId': employeeId,
|
||||
'startDate': startDate.toIso8601String(),
|
||||
'endDate': endDate.toIso8601String(),
|
||||
'reason': reason,
|
||||
'type': type,
|
||||
};
|
||||
}
|
||||
}
|
||||
89
lib/data/dto/vacation_response_dto.dart
Normal file
89
lib/data/dto/vacation_response_dto.dart
Normal file
@@ -0,0 +1,89 @@
|
||||
class VacationResponseDto {
|
||||
final int statusCode;
|
||||
final bool isSuccess;
|
||||
final String message;
|
||||
final VacationDataDto? data;
|
||||
|
||||
VacationResponseDto({
|
||||
required this.statusCode,
|
||||
required this.isSuccess,
|
||||
required this.message,
|
||||
this.data,
|
||||
});
|
||||
|
||||
factory VacationResponseDto.fromJson(Map<String, dynamic> json) {
|
||||
return VacationResponseDto(
|
||||
statusCode: json['statusCode'] ?? 0,
|
||||
isSuccess: json['isSuccess'] ?? false,
|
||||
message: json['message'] ?? '',
|
||||
data: json['data'] != null ? VacationDataDto.fromJson(json['data']) : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VacationDataDto {
|
||||
final String employeeId;
|
||||
final String? employeeFullName;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
final String reason;
|
||||
final String? submittedBy;
|
||||
final String? submittedByUser;
|
||||
final int state;
|
||||
final int type;
|
||||
final String id;
|
||||
final DateTime? createdAt;
|
||||
final DateTime? updatedAt;
|
||||
final DateTime? deletedAt;
|
||||
final bool? isDeleted;
|
||||
|
||||
VacationDataDto({
|
||||
required this.employeeId,
|
||||
this.employeeFullName,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
required this.reason,
|
||||
this.submittedBy,
|
||||
this.submittedByUser,
|
||||
required this.state,
|
||||
required this.type,
|
||||
required this.id,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
this.deletedAt,
|
||||
this.isDeleted,
|
||||
});
|
||||
|
||||
factory VacationDataDto.fromJson(Map<String, dynamic> json) {
|
||||
return VacationDataDto(
|
||||
employeeId: json['employeeId']?.toString() ?? '',
|
||||
employeeFullName: json['employeeFullName'],
|
||||
startDate: _parseDateTime(json['startDate'])!,
|
||||
endDate: _parseDateTime(json['endDate'])!,
|
||||
reason: json['reason']?.toString() ?? '',
|
||||
submittedBy: json['submittedBy'],
|
||||
submittedByUser: json['submittedByUser'],
|
||||
state: json['state'] ?? 0,
|
||||
type: json['type'] ?? 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;
|
||||
}
|
||||
}
|
||||
43
lib/data/dto/vacation_type_dto.dart
Normal file
43
lib/data/dto/vacation_type_dto.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
class VacationTypesResponseDto {
|
||||
final int statusCode;
|
||||
final bool isSuccess;
|
||||
final String? message;
|
||||
final List<VacationTypeDto> data;
|
||||
|
||||
VacationTypesResponseDto({
|
||||
required this.statusCode,
|
||||
required this.isSuccess,
|
||||
this.message,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
factory VacationTypesResponseDto.fromJson(Map<String, dynamic> json) {
|
||||
return VacationTypesResponseDto(
|
||||
statusCode: json['statusCode'] ?? 0,
|
||||
isSuccess: json['isSuccess'] ?? false,
|
||||
message: json['message'],
|
||||
data: json['data'] != null
|
||||
? (json['data'] as List)
|
||||
.map((item) => VacationTypeDto.fromJson(item))
|
||||
.toList()
|
||||
: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VacationTypeDto {
|
||||
final int value;
|
||||
final String name;
|
||||
|
||||
VacationTypeDto({
|
||||
required this.value,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
factory VacationTypeDto.fromJson(Map<String, dynamic> json) {
|
||||
return VacationTypeDto(
|
||||
value: json['value'] ?? 0,
|
||||
name: json['name']?.toString() ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
93
lib/data/repositories/vacation_repository_impl.dart
Normal file
93
lib/data/repositories/vacation_repository_impl.dart
Normal file
@@ -0,0 +1,93 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../core/error/exceptions.dart';
|
||||
import '../../core/error/failures.dart';
|
||||
import '../datasources/vacation_remote_data_source.dart';
|
||||
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/repositories/vacation_repository.dart';
|
||||
|
||||
class VacationRepositoryImpl implements VacationRepository {
|
||||
final VacationRemoteDataSource remoteDataSource;
|
||||
|
||||
VacationRepositoryImpl({required this.remoteDataSource});
|
||||
|
||||
@override
|
||||
Future<Either<Failure, VacationResponseModel>> createVacation(
|
||||
VacationRequest request,
|
||||
) async {
|
||||
try {
|
||||
final dto = VacationRequestDto(
|
||||
employeeId: request.employeeId,
|
||||
startDate: request.startDate,
|
||||
endDate: request.endDate,
|
||||
reason: request.reason,
|
||||
type: request.type,
|
||||
);
|
||||
|
||||
final responseDto = await remoteDataSource.createVacation(dto);
|
||||
|
||||
// Convert DTO to Model
|
||||
final responseModel = VacationResponseModel(
|
||||
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,
|
||||
);
|
||||
|
||||
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<Either<Failure, VacationTypesResponseModel>> getVacationTypes() async {
|
||||
try {
|
||||
final responseDto = await remoteDataSource.getVacationTypes();
|
||||
|
||||
// Convert DTO to Model
|
||||
final responseModel = VacationTypesResponseModel(
|
||||
statusCode: responseDto.statusCode,
|
||||
isSuccess: responseDto.isSuccess,
|
||||
message: responseDto.message,
|
||||
data: responseDto.data
|
||||
.map((dto) => VacationTypeModel(
|
||||
value: dto.value,
|
||||
name: dto.name,
|
||||
))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
15
lib/domain/models/vacation_request.dart
Normal file
15
lib/domain/models/vacation_request.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
class VacationRequest {
|
||||
final String employeeId;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
final String reason;
|
||||
final int type;
|
||||
|
||||
VacationRequest({
|
||||
required this.employeeId,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
required this.reason,
|
||||
required this.type,
|
||||
});
|
||||
}
|
||||
47
lib/domain/models/vacation_response_model.dart
Normal file
47
lib/domain/models/vacation_response_model.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
class VacationResponseModel {
|
||||
final int statusCode;
|
||||
final bool isSuccess;
|
||||
final String message;
|
||||
final VacationDataModel? data;
|
||||
|
||||
VacationResponseModel({
|
||||
required this.statusCode,
|
||||
required this.isSuccess,
|
||||
required this.message,
|
||||
this.data,
|
||||
});
|
||||
}
|
||||
|
||||
class VacationDataModel {
|
||||
final String employeeId;
|
||||
final String? employeeFullName;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
final String reason;
|
||||
final String? submittedBy;
|
||||
final String? submittedByUser;
|
||||
final int state;
|
||||
final int type;
|
||||
final String id;
|
||||
final DateTime? createdAt;
|
||||
final DateTime? updatedAt;
|
||||
final DateTime? deletedAt;
|
||||
final bool? isDeleted;
|
||||
|
||||
VacationDataModel({
|
||||
required this.employeeId,
|
||||
this.employeeFullName,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
required this.reason,
|
||||
this.submittedBy,
|
||||
this.submittedByUser,
|
||||
required this.state,
|
||||
required this.type,
|
||||
required this.id,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
this.deletedAt,
|
||||
this.isDeleted,
|
||||
});
|
||||
}
|
||||
23
lib/domain/models/vacation_type_model.dart
Normal file
23
lib/domain/models/vacation_type_model.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
class VacationTypeModel {
|
||||
final int value;
|
||||
final String name;
|
||||
|
||||
VacationTypeModel({
|
||||
required this.value,
|
||||
required this.name,
|
||||
});
|
||||
}
|
||||
|
||||
class VacationTypesResponseModel {
|
||||
final int statusCode;
|
||||
final bool isSuccess;
|
||||
final String? message;
|
||||
final List<VacationTypeModel> data;
|
||||
|
||||
VacationTypesResponseModel({
|
||||
required this.statusCode,
|
||||
required this.isSuccess,
|
||||
this.message,
|
||||
required this.data,
|
||||
});
|
||||
}
|
||||
12
lib/domain/repositories/vacation_repository.dart
Normal file
12
lib/domain/repositories/vacation_repository.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../core/error/failures.dart';
|
||||
import '../models/vacation_request.dart';
|
||||
import '../models/vacation_response_model.dart';
|
||||
import '../models/vacation_type_model.dart';
|
||||
|
||||
abstract class VacationRepository {
|
||||
Future<Either<Failure, VacationResponseModel>> createVacation(
|
||||
VacationRequest request,
|
||||
);
|
||||
Future<Either<Failure, VacationTypesResponseModel>> getVacationTypes();
|
||||
}
|
||||
15
lib/domain/usecases/create_vacation_usecase.dart
Normal file
15
lib/domain/usecases/create_vacation_usecase.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../core/error/failures.dart';
|
||||
import '../models/vacation_request.dart';
|
||||
import '../models/vacation_response_model.dart';
|
||||
import '../repositories/vacation_repository.dart';
|
||||
|
||||
class CreateVacationUseCase {
|
||||
final VacationRepository repository;
|
||||
|
||||
CreateVacationUseCase({required this.repository});
|
||||
|
||||
Future<Either<Failure, VacationResponseModel>> call(VacationRequest request) {
|
||||
return repository.createVacation(request);
|
||||
}
|
||||
}
|
||||
14
lib/domain/usecases/get_vacation_types_usecase.dart
Normal file
14
lib/domain/usecases/get_vacation_types_usecase.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../core/error/failures.dart';
|
||||
import '../models/vacation_type_model.dart';
|
||||
import '../repositories/vacation_repository.dart';
|
||||
|
||||
class GetVacationTypesUseCase {
|
||||
final VacationRepository repository;
|
||||
|
||||
GetVacationTypesUseCase({required this.repository});
|
||||
|
||||
Future<Either<Failure, VacationTypesResponseModel>> call() {
|
||||
return repository.getVacationTypes();
|
||||
}
|
||||
}
|
||||
@@ -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,23 +94,177 @@ 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
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('الرجاء إدخال السبب'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new leave request with default status "waiting"
|
||||
// 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 {
|
||||
// Create vacation request
|
||||
final vacationRequest = VacationRequest(
|
||||
employeeId: employeeId,
|
||||
startDate: finalStartDate,
|
||||
endDate: finalEndDate,
|
||||
reason: reasonController.text,
|
||||
type: typeValue,
|
||||
);
|
||||
|
||||
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: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
leaveType: leaveType, // Use the current leaveType value
|
||||
id: response.data?.id ?? DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
leaveType: leaveType,
|
||||
isTimedLeave: isTimedLeave,
|
||||
fromDate: fromDate!,
|
||||
toDate: toDate!,
|
||||
@@ -105,12 +274,9 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
requestDate: DateTime.now(),
|
||||
status: "waiting", // Default status
|
||||
);
|
||||
_requestService.addLeaveRequest(leaveRequest);
|
||||
|
||||
try {
|
||||
// Save the leave request
|
||||
await _requestService.addLeaveRequest(leaveRequest);
|
||||
|
||||
// Show a success message
|
||||
// Show success message
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('تم إرسال طلب الأجازة بنجاح'),
|
||||
@@ -120,12 +286,20 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
|
||||
// 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),
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
Navigator.pop(context); // Close loading dialog
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('حدث خطأ غير متوقع: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -216,8 +390,15 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
],
|
||||
),
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<String>(
|
||||
value: leaveType,
|
||||
child: _vacationTypes.isEmpty
|
||||
? const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
: DropdownButton<int>(
|
||||
value: _selectedVacationTypeValue,
|
||||
icon: const Icon(
|
||||
Icons.keyboard_arrow_down_rounded,
|
||||
color: Colors.black,
|
||||
@@ -229,54 +410,30 @@ class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
|
||||
),
|
||||
isExpanded: true,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
setState(() {
|
||||
leaveType = value!;
|
||||
// Set toggle based on selected value
|
||||
isTimedLeave = value == "أجازة زمنية";
|
||||
_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: [
|
||||
DropdownMenuItem(
|
||||
value: "إجازة مرضية ",
|
||||
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("إجازة مرضية "),
|
||||
child: Text(arabicName),
|
||||
),
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: "إجازة مدفوعة",
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text("إجازة مدفوعة"),
|
||||
),
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: "إجازة غير مدفوعة",
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text("إجازة غير مدفوعة"),
|
||||
),
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: "أجازة زمنية",
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text("أجازة زمنية"),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user