chnages has been made and net salary is being displayed
This commit is contained in:
@@ -28,7 +28,10 @@ import '../../domain/usecases/get_advances_usecase.dart';
|
||||
import '../../domain/usecases/get_extra_hours_usecase.dart';
|
||||
import '../../domain/usecases/get_rewards_usecase.dart';
|
||||
import '../../domain/usecases/get_punishments_usecase.dart';
|
||||
import '../../domain/usecases/get_salary_summary_usecase.dart';
|
||||
import '../../domain/usecases/change_password_usecase.dart';
|
||||
import '../../presentation/blocs/login/login_bloc.dart';
|
||||
import '../../presentation/blocs/change_password/change_password_bloc.dart';
|
||||
|
||||
final sl = GetIt.instance;
|
||||
|
||||
@@ -61,9 +64,11 @@ Future<void> initializeDependencies() async {
|
||||
|
||||
// Use cases
|
||||
sl.registerLazySingleton(() => LoginUseCase(repository: sl()));
|
||||
sl.registerLazySingleton(() => ChangePasswordUseCase(repository: sl()));
|
||||
|
||||
// Blocs
|
||||
sl.registerFactory(() => LoginBloc(loginUseCase: sl()));
|
||||
sl.registerFactory(() => ChangePasswordBloc(changePasswordUseCase: sl()));
|
||||
|
||||
//Attendence
|
||||
sl.registerLazySingleton<AttendanceRemoteDataSource>(
|
||||
@@ -82,6 +87,7 @@ Future<void> initializeDependencies() async {
|
||||
sl.registerLazySingleton(() => GetExtraHoursUseCase(repository: sl()));
|
||||
sl.registerLazySingleton(() => GetRewardsUseCase(repository: sl()));
|
||||
sl.registerLazySingleton(() => GetPunishmentsUseCase(repository: sl()));
|
||||
sl.registerLazySingleton(() => GetSalarySummaryUseCase(sl()));
|
||||
|
||||
// Finance
|
||||
sl.registerFactory(
|
||||
@@ -90,6 +96,7 @@ Future<void> initializeDependencies() async {
|
||||
getExtraHoursUseCase: sl(),
|
||||
getRewardsUseCase: sl(),
|
||||
getPunishmentsUseCase: sl(),
|
||||
getSalarySummaryUseCase: sl(),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ class ServerException implements Exception {
|
||||
final int? statusCode;
|
||||
|
||||
ServerException({required this.message, this.statusCode});
|
||||
|
||||
@override
|
||||
String toString() => 'ServerException: $message (Status: $statusCode)';
|
||||
}
|
||||
|
||||
class NetworkException implements Exception {
|
||||
|
||||
@@ -7,6 +7,7 @@ import '../dto/attendance_record_dto.dart';
|
||||
import '../dto/overtime_dto.dart';
|
||||
import '../dto/reward_dto.dart';
|
||||
import '../dto/punishment_dto.dart';
|
||||
import '../dto/salary_response_dto.dart';
|
||||
|
||||
abstract class AttendanceRemoteDataSource {
|
||||
Future<AttendanceResponseDto> login({
|
||||
@@ -25,6 +26,11 @@ abstract class AttendanceRemoteDataSource {
|
||||
Future<List<OvertimeDto>> getExtraHours({required String employeeId});
|
||||
Future<List<RewardDto>> getRewards({required String employeeId});
|
||||
Future<List<PunishmentDto>> getPunishments({required String employeeId});
|
||||
Future<SalaryResponseDto> calculateSalary({
|
||||
required String employeeId,
|
||||
required int month,
|
||||
required int year,
|
||||
});
|
||||
}
|
||||
|
||||
class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
||||
@@ -286,4 +292,66 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
||||
throw ServerException(message: 'خطأ غير متوقع');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SalaryResponseDto> calculateSalary({
|
||||
required String employeeId,
|
||||
required int month,
|
||||
required int year,
|
||||
}) async {
|
||||
try {
|
||||
final response = await apiClient.get(
|
||||
'/SalaryRecord/calculate',
|
||||
queryParameters: {
|
||||
'EmployeeId': employeeId,
|
||||
'Month': month,
|
||||
'Year': year,
|
||||
},
|
||||
);
|
||||
|
||||
print('Salary Response Status: ${response.statusCode}');
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
final responseData = response.data;
|
||||
print(
|
||||
'Salary Response Data: $responseData (${responseData.runtimeType})',
|
||||
);
|
||||
|
||||
if (responseData is Map<String, dynamic>) {
|
||||
return SalaryResponseDto.fromJson(responseData);
|
||||
} else if (responseData is num) {
|
||||
// Handle case where API returns raw number
|
||||
return SalaryResponseDto(
|
||||
isSuccess: true,
|
||||
message: 'Success',
|
||||
data: SalaryDataDto(netAmount: responseData.toDouble()),
|
||||
);
|
||||
} else if (responseData is String &&
|
||||
double.tryParse(responseData) != null) {
|
||||
// Handle case where API returns raw numeric string
|
||||
return SalaryResponseDto(
|
||||
isSuccess: true,
|
||||
message: 'Success',
|
||||
data: SalaryDataDto(netAmount: double.parse(responseData)),
|
||||
);
|
||||
} else {
|
||||
throw ServerException(
|
||||
message: 'استجابة غير صحيحة من الخادم: $responseData',
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw ServerException(
|
||||
message: 'فشل في حساب الراتب (Status: ${response.statusCode})',
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
throw ServerException(
|
||||
message: e.message ?? 'Unknown error',
|
||||
statusCode: e.response?.statusCode,
|
||||
);
|
||||
} catch (e) {
|
||||
throw ServerException(message: 'خطأ غير متوقع');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,14 @@ import '../../core/error/exceptions.dart';
|
||||
import '../../core/network/api_client.dart';
|
||||
import '../dto/login_dto.dart';
|
||||
import '../dto/login_response_dto.dart';
|
||||
import '../dto/change_password_request_dto.dart';
|
||||
import '../dto/change_password_response_dto.dart';
|
||||
|
||||
abstract class AuthRemoteDataSource {
|
||||
Future<LoginResponseDto> login(LoginDto dto);
|
||||
Future<ChangePasswordResponseDto> changePassword(
|
||||
ChangePasswordRequestDto dto,
|
||||
);
|
||||
}
|
||||
|
||||
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
||||
@@ -72,4 +77,37 @@ class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
||||
throw ServerException(message: 'خطأ غير متوقع');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ChangePasswordResponseDto> changePassword(
|
||||
ChangePasswordRequestDto dto,
|
||||
) async {
|
||||
try {
|
||||
final response = await apiClient.post(
|
||||
'/Auth/change-password',
|
||||
data: dto.toJson(),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return ChangePasswordResponseDto.fromJson(response.data);
|
||||
} else {
|
||||
throw ServerException(
|
||||
message: response.data['message'] ?? 'فشل تغيير كلمة المرور',
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
final message =
|
||||
e.response?.data?['message'] ??
|
||||
e.response?.data?['error'] ??
|
||||
'فشل تغيير كلمة المرور';
|
||||
throw ServerException(
|
||||
message: message,
|
||||
statusCode: e.response?.statusCode,
|
||||
);
|
||||
} catch (e) {
|
||||
if (e is ServerException) rethrow;
|
||||
throw ServerException(message: 'خطأ غير متوقع: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
lib/data/dto/change_password_request_dto.dart
Normal file
13
lib/data/dto/change_password_request_dto.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
class ChangePasswordRequestDto {
|
||||
final String oldPassword;
|
||||
final String newPassword;
|
||||
|
||||
ChangePasswordRequestDto({
|
||||
required this.oldPassword,
|
||||
required this.newPassword,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {'oldPassword': oldPassword, 'newPassword': newPassword};
|
||||
}
|
||||
}
|
||||
36
lib/data/dto/change_password_response_dto.dart
Normal file
36
lib/data/dto/change_password_response_dto.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'login_response_dto.dart';
|
||||
|
||||
class ChangePasswordResponseDto {
|
||||
final int statusCode;
|
||||
final bool isSuccess;
|
||||
final String message;
|
||||
final LoginDataDto? data;
|
||||
|
||||
ChangePasswordResponseDto({
|
||||
required this.statusCode,
|
||||
required this.isSuccess,
|
||||
required this.message,
|
||||
this.data,
|
||||
});
|
||||
|
||||
factory ChangePasswordResponseDto.fromJson(Map<String, dynamic> json) {
|
||||
return ChangePasswordResponseDto(
|
||||
statusCode: json['statusCode'] ?? 0,
|
||||
isSuccess: json['isSuccess'] ?? false,
|
||||
message: json['message'] ?? '',
|
||||
data:
|
||||
json['data'] != null && json['data'] is Map<String, dynamic>
|
||||
? LoginDataDto.fromJson(json['data'])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'statusCode': statusCode,
|
||||
'isSuccess': isSuccess,
|
||||
'message': message,
|
||||
'data': data?.toJson(),
|
||||
};
|
||||
}
|
||||
}
|
||||
36
lib/data/dto/salary_response_dto.dart
Normal file
36
lib/data/dto/salary_response_dto.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
class SalaryResponseDto {
|
||||
final bool isSuccess;
|
||||
final String message;
|
||||
final SalaryDataDto? data;
|
||||
|
||||
SalaryResponseDto({
|
||||
required this.isSuccess,
|
||||
required this.message,
|
||||
this.data,
|
||||
});
|
||||
|
||||
factory SalaryResponseDto.fromJson(Map<String, dynamic> json) {
|
||||
return SalaryResponseDto(
|
||||
isSuccess: json['isSuccess'] ?? json['IsSuccess'] ?? false,
|
||||
message: json['message'] ?? json['Message'] ?? '',
|
||||
data:
|
||||
json['data'] != null
|
||||
? SalaryDataDto.fromJson(json['data'])
|
||||
: json['Data'] != null
|
||||
? SalaryDataDto.fromJson(json['Data'])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SalaryDataDto {
|
||||
final double netAmount;
|
||||
|
||||
SalaryDataDto({required this.netAmount});
|
||||
|
||||
factory SalaryDataDto.fromJson(Map<String, dynamic> json) {
|
||||
return SalaryDataDto(
|
||||
netAmount: (json['netAmount'] ?? json['NetAmount'] ?? 0.0).toDouble(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import '../../domain/models/attendance_response_model.dart';
|
||||
import '../../domain/models/attendance_model.dart';
|
||||
import '../../domain/models/overtime_model.dart';
|
||||
import '../../domain/models/extra_payment_model.dart';
|
||||
import '../../domain/models/salary_model.dart';
|
||||
import '../../domain/repositories/attendance_repository.dart';
|
||||
import '../datasources/attendance_remote_data_source.dart';
|
||||
|
||||
@@ -131,4 +132,19 @@ class AttendanceRepositoryImpl implements AttendanceRepository {
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SalaryModel> calculateSalary({
|
||||
required String employeeId,
|
||||
required int month,
|
||||
required int year,
|
||||
}) async {
|
||||
final dto = await remoteDataSource.calculateSalary(
|
||||
employeeId: employeeId,
|
||||
month: month,
|
||||
year: year,
|
||||
);
|
||||
|
||||
return SalaryModel(netAmount: dto.data?.netAmount ?? 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ import '../../domain/models/login_request.dart';
|
||||
import '../../domain/models/login_response_model.dart';
|
||||
import '../../domain/repositories/auth_repository.dart';
|
||||
|
||||
import '../dto/change_password_request_dto.dart';
|
||||
import '../../domain/models/change_password_request.dart';
|
||||
import '../../domain/models/general_response_model.dart';
|
||||
|
||||
class AuthRepositoryImpl implements AuthRepository {
|
||||
final AuthRemoteDataSource remoteDataSource;
|
||||
final UserLocalDataSource localDataSource;
|
||||
@@ -71,4 +75,45 @@ class AuthRepositoryImpl implements AuthRepository {
|
||||
return Left(ServerFailure('خطأ غير متوقع: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, GeneralResponseModel>> changePassword(
|
||||
ChangePasswordRequest request,
|
||||
) async {
|
||||
try {
|
||||
final dto = ChangePasswordRequestDto(
|
||||
oldPassword: request.oldPassword,
|
||||
newPassword: request.newPassword,
|
||||
);
|
||||
|
||||
final responseDto = await remoteDataSource.changePassword(dto);
|
||||
|
||||
// Re-cache token and employeeId if provided in response
|
||||
if (responseDto.data != null) {
|
||||
if (responseDto.data!.token != null) {
|
||||
await localDataSource.cacheUserToken(responseDto.data!.token!);
|
||||
}
|
||||
if (responseDto.data!.employeeId != null) {
|
||||
await localDataSource.cacheEmployeeId(responseDto.data!.employeeId!);
|
||||
}
|
||||
}
|
||||
|
||||
final model = GeneralResponseModel(
|
||||
statusCode: responseDto.statusCode,
|
||||
isSuccess: responseDto.isSuccess,
|
||||
message: responseDto.message,
|
||||
data:
|
||||
responseDto
|
||||
.isSuccess, // Use isSuccess as data for the boolean model
|
||||
);
|
||||
|
||||
return Right(model);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message));
|
||||
} on NetworkException catch (e) {
|
||||
return Left(NetworkFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure('خطأ غير متوقع: $e'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
lib/domain/models/change_password_request.dart
Normal file
6
lib/domain/models/change_password_request.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
class ChangePasswordRequest {
|
||||
final String oldPassword;
|
||||
final String newPassword;
|
||||
|
||||
ChangePasswordRequest({required this.oldPassword, required this.newPassword});
|
||||
}
|
||||
13
lib/domain/models/general_response_model.dart
Normal file
13
lib/domain/models/general_response_model.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
class GeneralResponseModel {
|
||||
final int statusCode;
|
||||
final bool isSuccess;
|
||||
final String message;
|
||||
final bool? data;
|
||||
|
||||
GeneralResponseModel({
|
||||
required this.statusCode,
|
||||
required this.isSuccess,
|
||||
required this.message,
|
||||
this.data,
|
||||
});
|
||||
}
|
||||
4
lib/domain/models/salary_model.dart
Normal file
4
lib/domain/models/salary_model.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
class SalaryModel {
|
||||
final double netAmount;
|
||||
SalaryModel({required this.netAmount});
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import '../models/attendance_response_model.dart';
|
||||
import '../models/attendance_model.dart';
|
||||
import '../models/overtime_model.dart';
|
||||
import '../models/extra_payment_model.dart';
|
||||
import '../models/salary_model.dart';
|
||||
|
||||
//in the following polymorphism is being used , a quich recap it is where th esame method but opperate in a different way
|
||||
|
||||
@@ -19,4 +20,9 @@ abstract class AttendanceRepository {
|
||||
Future<List<OvertimeModel>> getExtraHours({required String employeeId});
|
||||
Future<List<ExtraPaymentModel>> getRewards({required String employeeId});
|
||||
Future<List<ExtraPaymentModel>> getPunishments({required String employeeId});
|
||||
Future<SalaryModel> calculateSalary({
|
||||
required String employeeId,
|
||||
required int month,
|
||||
required int year,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,12 @@ import 'package:dartz/dartz.dart';
|
||||
import '../../core/error/failures.dart';
|
||||
import '../models/login_request.dart';
|
||||
import '../models/login_response_model.dart';
|
||||
import '../models/change_password_request.dart';
|
||||
import '../models/general_response_model.dart';
|
||||
|
||||
abstract class AuthRepository {
|
||||
Future<Either<Failure, LoginResponseModel>> login(LoginRequest request);
|
||||
Future<Either<Failure, GeneralResponseModel>> changePassword(
|
||||
ChangePasswordRequest request,
|
||||
);
|
||||
}
|
||||
|
||||
17
lib/domain/usecases/change_password_usecase.dart
Normal file
17
lib/domain/usecases/change_password_usecase.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../core/error/failures.dart';
|
||||
import '../models/change_password_request.dart';
|
||||
import '../models/general_response_model.dart';
|
||||
import '../repositories/auth_repository.dart';
|
||||
|
||||
class ChangePasswordUseCase {
|
||||
final AuthRepository repository;
|
||||
|
||||
ChangePasswordUseCase({required this.repository});
|
||||
|
||||
Future<Either<Failure, GeneralResponseModel>> call(
|
||||
ChangePasswordRequest request,
|
||||
) async {
|
||||
return await repository.changePassword(request);
|
||||
}
|
||||
}
|
||||
20
lib/domain/usecases/get_salary_summary_usecase.dart
Normal file
20
lib/domain/usecases/get_salary_summary_usecase.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import '../repositories/attendance_repository.dart';
|
||||
import '../models/salary_model.dart';
|
||||
|
||||
class GetSalarySummaryUseCase {
|
||||
final AttendanceRepository repository;
|
||||
|
||||
GetSalarySummaryUseCase(this.repository);
|
||||
|
||||
Future<SalaryModel> execute({
|
||||
required String employeeId,
|
||||
required int month,
|
||||
required int year,
|
||||
}) {
|
||||
return repository.calculateSalary(
|
||||
employeeId: employeeId,
|
||||
month: month,
|
||||
year: year,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import '../../../domain/usecases/get_attendance_records_usecase.dart';
|
||||
import '../../../domain/usecases/get_extra_hours_usecase.dart';
|
||||
import '../../../domain/usecases/get_rewards_usecase.dart';
|
||||
import '../../../domain/usecases/get_punishments_usecase.dart';
|
||||
import '../../../domain/usecases/get_salary_summary_usecase.dart';
|
||||
import '../../../domain/models/finance_record.dart';
|
||||
import '../../../domain/models/finance_category.dart';
|
||||
import '../../../core/error/exceptions.dart';
|
||||
@@ -13,8 +14,15 @@ abstract class FinanceEvent {}
|
||||
class LoadFinanceDataEvent extends FinanceEvent {
|
||||
final String employeeId;
|
||||
final FinanceCategory category;
|
||||
final int? month;
|
||||
final int? year;
|
||||
|
||||
LoadFinanceDataEvent({required this.employeeId, required this.category});
|
||||
LoadFinanceDataEvent({
|
||||
required this.employeeId,
|
||||
required this.category,
|
||||
this.month,
|
||||
this.year,
|
||||
});
|
||||
}
|
||||
|
||||
// States
|
||||
@@ -27,8 +35,13 @@ class FinanceLoading extends FinanceState {}
|
||||
class FinanceLoaded extends FinanceState {
|
||||
final List<FinanceRecord> records;
|
||||
final FinanceCategory category;
|
||||
final double netSalary;
|
||||
|
||||
FinanceLoaded({required this.records, required this.category});
|
||||
FinanceLoaded({
|
||||
required this.records,
|
||||
required this.category,
|
||||
this.netSalary = 0.0,
|
||||
});
|
||||
}
|
||||
|
||||
class FinanceError extends FinanceState {
|
||||
@@ -43,12 +56,14 @@ class FinanceBloc extends Bloc<FinanceEvent, FinanceState> {
|
||||
final GetExtraHoursUseCase getExtraHoursUseCase;
|
||||
final GetRewardsUseCase getRewardsUseCase;
|
||||
final GetPunishmentsUseCase getPunishmentsUseCase;
|
||||
final GetSalarySummaryUseCase getSalarySummaryUseCase;
|
||||
|
||||
FinanceBloc({
|
||||
required this.getAttendanceRecordsUseCase,
|
||||
required this.getExtraHoursUseCase,
|
||||
required this.getRewardsUseCase,
|
||||
required this.getPunishmentsUseCase,
|
||||
required this.getSalarySummaryUseCase,
|
||||
}) : super(FinanceInitial()) {
|
||||
on<LoadFinanceDataEvent>(_onLoadFinanceData);
|
||||
}
|
||||
@@ -64,6 +79,27 @@ class FinanceBloc extends Bloc<FinanceEvent, FinanceState> {
|
||||
|
||||
emit(FinanceLoading());
|
||||
try {
|
||||
final now = DateTime.now();
|
||||
final month = event.month ?? now.month;
|
||||
final year = event.year ?? now.year;
|
||||
double netSalary = 0.0;
|
||||
|
||||
print(
|
||||
'Fetching salary for Employee: ${event.employeeId}, Month: $month, Year: $year',
|
||||
);
|
||||
try {
|
||||
final salaryModel = await getSalarySummaryUseCase.execute(
|
||||
employeeId: event.employeeId,
|
||||
month: month,
|
||||
year: year,
|
||||
);
|
||||
print('Salary Model fetched: ${salaryModel.netAmount}');
|
||||
netSalary = salaryModel.netAmount;
|
||||
} catch (e) {
|
||||
print('Error fetching salary: $e');
|
||||
// We continue even if salary fails, to show the list at least
|
||||
}
|
||||
|
||||
List<FinanceRecord> records;
|
||||
switch (event.category) {
|
||||
case FinanceCategory.attendance:
|
||||
@@ -87,7 +123,13 @@ class FinanceBloc extends Bloc<FinanceEvent, FinanceState> {
|
||||
);
|
||||
break;
|
||||
}
|
||||
emit(FinanceLoaded(records: records, category: event.category));
|
||||
emit(
|
||||
FinanceLoaded(
|
||||
records: records,
|
||||
category: event.category,
|
||||
netSalary: netSalary,
|
||||
),
|
||||
);
|
||||
} on ServerException catch (e) {
|
||||
emit(FinanceError(message: e.message));
|
||||
} on NetworkException catch (e) {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../domain/usecases/change_password_usecase.dart';
|
||||
import 'change_password_event.dart';
|
||||
import 'change_password_state.dart';
|
||||
|
||||
class ChangePasswordBloc
|
||||
extends Bloc<ChangePasswordEvent, ChangePasswordState> {
|
||||
final ChangePasswordUseCase changePasswordUseCase;
|
||||
|
||||
ChangePasswordBloc({required this.changePasswordUseCase})
|
||||
: super(const ChangePasswordInitial()) {
|
||||
on<ChangePasswordSubmitted>(_onChangePasswordSubmitted);
|
||||
on<ChangePasswordReset>(_onChangePasswordReset);
|
||||
}
|
||||
|
||||
Future<void> _onChangePasswordSubmitted(
|
||||
ChangePasswordSubmitted event,
|
||||
Emitter<ChangePasswordState> emit,
|
||||
) async {
|
||||
emit(const ChangePasswordLoading());
|
||||
|
||||
final result = await changePasswordUseCase(event.request);
|
||||
|
||||
result.fold((failure) => emit(ChangePasswordError(failure.message)), (
|
||||
response,
|
||||
) {
|
||||
if (response.isSuccess) {
|
||||
emit(ChangePasswordSuccess(response.message));
|
||||
} else {
|
||||
emit(ChangePasswordError(response.message));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onChangePasswordReset(
|
||||
ChangePasswordReset event,
|
||||
Emitter<ChangePasswordState> emit,
|
||||
) {
|
||||
emit(const ChangePasswordInitial());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import '../../../domain/models/change_password_request.dart';
|
||||
|
||||
abstract class ChangePasswordEvent {}
|
||||
|
||||
class ChangePasswordSubmitted extends ChangePasswordEvent {
|
||||
final ChangePasswordRequest request;
|
||||
|
||||
ChangePasswordSubmitted(this.request);
|
||||
}
|
||||
|
||||
class ChangePasswordReset extends ChangePasswordEvent {}
|
||||
@@ -0,0 +1,23 @@
|
||||
abstract class ChangePasswordState {
|
||||
const ChangePasswordState();
|
||||
}
|
||||
|
||||
class ChangePasswordInitial extends ChangePasswordState {
|
||||
const ChangePasswordInitial();
|
||||
}
|
||||
|
||||
class ChangePasswordLoading extends ChangePasswordState {
|
||||
const ChangePasswordLoading();
|
||||
}
|
||||
|
||||
class ChangePasswordSuccess extends ChangePasswordState {
|
||||
final String message;
|
||||
|
||||
const ChangePasswordSuccess(this.message);
|
||||
}
|
||||
|
||||
class ChangePasswordError extends ChangePasswordState {
|
||||
final String message;
|
||||
|
||||
const ChangePasswordError(this.message);
|
||||
}
|
||||
@@ -22,18 +22,22 @@ class FinanceScreen extends StatefulWidget {
|
||||
class _FinanceScreenState extends State<FinanceScreen> {
|
||||
FinanceCategory currentCategory = FinanceCategory.attendance;
|
||||
late ScrollController scrollController;
|
||||
late FinanceBloc _financeBloc;
|
||||
String? _employeeId;
|
||||
DateTime selectedDate = DateTime.now();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
scrollController = ScrollController();
|
||||
_financeBloc = sl<FinanceBloc>();
|
||||
_loadInitialData();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.dispose();
|
||||
_financeBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -41,17 +45,19 @@ class _FinanceScreenState extends State<FinanceScreen> {
|
||||
_employeeId = await sl<UserLocalDataSource>().getCachedEmployeeId();
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
_triggerLoad();
|
||||
}
|
||||
}
|
||||
|
||||
void _triggerLoad(BuildContext context) {
|
||||
void _triggerLoad() {
|
||||
if (_employeeId != null && _employeeId!.isNotEmpty) {
|
||||
final bloc = context.read<FinanceBloc>();
|
||||
if (bloc.state is FinanceInitial) {
|
||||
bloc.add(
|
||||
if (_financeBloc.state is FinanceInitial) {
|
||||
_financeBloc.add(
|
||||
LoadFinanceDataEvent(
|
||||
employeeId: _employeeId!,
|
||||
category: currentCategory,
|
||||
month: selectedDate.month,
|
||||
year: selectedDate.year,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -60,17 +66,12 @@ class _FinanceScreenState extends State<FinanceScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => sl<FinanceBloc>(),
|
||||
return BlocProvider.value(
|
||||
value: _financeBloc,
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: SafeArea(
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
// Trigger initial load when bloc is ready
|
||||
_triggerLoad(context);
|
||||
|
||||
return CustomScrollView(
|
||||
child: CustomScrollView(
|
||||
controller: scrollController,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
slivers: [
|
||||
@@ -105,26 +106,58 @@ class _FinanceScreenState extends State<FinanceScreen> {
|
||||
|
||||
/// SUMMARY CARD
|
||||
SliverToBoxAdapter(
|
||||
child: FinanceSummaryCard(
|
||||
totalAmount: "333,000",
|
||||
child: BlocBuilder<FinanceBloc, FinanceState>(
|
||||
buildWhen: (previous, current) => current is FinanceLoaded,
|
||||
builder: (context, state) {
|
||||
String amount = "0";
|
||||
if (state is FinanceLoaded) {
|
||||
amount = state.netSalary.toStringAsFixed(0);
|
||||
amount = amount.replaceAllMapped(
|
||||
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
|
||||
(Match m) => '${m[1]},',
|
||||
);
|
||||
}
|
||||
|
||||
return FinanceSummaryCard(
|
||||
totalAmount: amount,
|
||||
currentCategory: currentCategory,
|
||||
onCalendarTap:
|
||||
() => showDatePicker(
|
||||
onCalendarTap: () async {
|
||||
final date = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
initialDate: selectedDate,
|
||||
firstDate: DateTime(2020),
|
||||
lastDate: DateTime(2030),
|
||||
),
|
||||
onCategoryChanged: (category) {
|
||||
if (category != null) {
|
||||
setState(() => currentCategory = category);
|
||||
context.read<FinanceBloc>().add(
|
||||
);
|
||||
if (date != null && mounted) {
|
||||
setState(() => selectedDate = date);
|
||||
if (_employeeId != null && _employeeId!.isNotEmpty) {
|
||||
_financeBloc.add(
|
||||
LoadFinanceDataEvent(
|
||||
employeeId: _employeeId ?? '',
|
||||
employeeId: _employeeId!,
|
||||
category: currentCategory,
|
||||
month: selectedDate.month,
|
||||
year: selectedDate.year,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
onCategoryChanged: (category) {
|
||||
if (category != null) {
|
||||
setState(() => currentCategory = category);
|
||||
if (_employeeId != null && _employeeId!.isNotEmpty) {
|
||||
_financeBloc.add(
|
||||
LoadFinanceDataEvent(
|
||||
employeeId: _employeeId!,
|
||||
category: currentCategory,
|
||||
month: selectedDate.month,
|
||||
year: selectedDate.year,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -153,10 +186,7 @@ class _FinanceScreenState extends State<FinanceScreen> {
|
||||
);
|
||||
}
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((
|
||||
context,
|
||||
index,
|
||||
) {
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return WorkDayCard(record: state.records[index]);
|
||||
}, childCount: state.records.length),
|
||||
);
|
||||
@@ -180,8 +210,6 @@ class _FinanceScreenState extends State<FinanceScreen> {
|
||||
|
||||
const SliverToBoxAdapter(child: SizedBox(height: 120)),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -13,11 +13,11 @@ 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/vacations_list_response_model.dart';
|
||||
import '../../domain/models/vacation_response_model.dart';
|
||||
import '../../domain/models/advances_list_response_model.dart';
|
||||
// import '../../domain/models/advances_list_response_model.dart';
|
||||
import '../../domain/models/advance_request_model.dart';
|
||||
import '../../core/error/failures.dart';
|
||||
// import '../../core/error/failures.dart';
|
||||
|
||||
class HolidayScreen extends StatefulWidget {
|
||||
final void Function(bool isScrollingDown)? onScrollEvent;
|
||||
@@ -118,7 +118,8 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
||||
(response) {
|
||||
if (mounted && response.data != null) {
|
||||
setState(() {
|
||||
_leaveRequests = response.data!.items
|
||||
_leaveRequests =
|
||||
response.data!.items
|
||||
.map((vacation) => _convertVacationToLeaveRequest(vacation))
|
||||
.toList();
|
||||
_isLoadingVacations = false;
|
||||
@@ -141,7 +142,8 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
||||
String leaveTypeName = _getArabicVacationTypeName(vacation.type);
|
||||
|
||||
// Check if it's timed leave (same day but different times)
|
||||
bool isTimedLeave = vacation.startDate.year == vacation.endDate.year &&
|
||||
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;
|
||||
@@ -200,7 +202,8 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
||||
(response) {
|
||||
if (mounted && response.data != null) {
|
||||
setState(() {
|
||||
_advanceRequests = response.data!.items
|
||||
_advanceRequests =
|
||||
response.data!.items
|
||||
.map((advance) => _convertAdvanceToAdvanceRequest(advance))
|
||||
.toList();
|
||||
_isLoadingAdvances = false;
|
||||
@@ -417,9 +420,7 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(40.0),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Color(0xFF8EFDC2),
|
||||
),
|
||||
child: CircularProgressIndicator(color: Color(0xFF8EFDC2)),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -458,9 +459,7 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(40.0),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Color(0xFF8EFDC2),
|
||||
),
|
||||
child: CircularProgressIndicator(color: Color(0xFF8EFDC2)),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../core/di/injection_container.dart';
|
||||
import '../../domain/models/change_password_request.dart';
|
||||
import '../blocs/change_password/change_password_bloc.dart';
|
||||
import '../blocs/change_password/change_password_event.dart';
|
||||
import '../blocs/change_password/change_password_state.dart';
|
||||
import 'onboarding_button.dart';
|
||||
|
||||
class ChangePasswordModal extends StatefulWidget {
|
||||
@@ -10,7 +16,17 @@ class ChangePasswordModal extends StatefulWidget {
|
||||
|
||||
class _ChangePasswordModalState extends State<ChangePasswordModal> {
|
||||
bool _oldObscure = true;
|
||||
// bool _newObscure = true;
|
||||
bool _newObscure = true;
|
||||
|
||||
final TextEditingController _oldPasswordController = TextEditingController();
|
||||
final TextEditingController _newPasswordController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_oldPasswordController.dispose();
|
||||
_newPasswordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -73,10 +89,11 @@ class _ChangePasswordModalState extends State<ChangePasswordModal> {
|
||||
const SizedBox(height: 8),
|
||||
|
||||
_buildField(
|
||||
controller: _oldPasswordController,
|
||||
hint: "أدخل كلمة المرور",
|
||||
obscure: _oldObscure,
|
||||
fontSize: fieldFontSize,
|
||||
hasEye: true, // ✅ eye here
|
||||
hasEye: true,
|
||||
onEyeTap:
|
||||
() => setState(() => _oldObscure = !_oldObscure),
|
||||
),
|
||||
@@ -98,21 +115,75 @@ class _ChangePasswordModalState extends State<ChangePasswordModal> {
|
||||
const SizedBox(height: 8),
|
||||
|
||||
_buildField(
|
||||
controller: _newPasswordController,
|
||||
hint: "كلمة المرور",
|
||||
obscure: false,
|
||||
obscure: _newObscure,
|
||||
fontSize: fieldFontSize,
|
||||
hasEye: false,
|
||||
onEyeTap: () {}, // unused
|
||||
hasEye: true,
|
||||
onEyeTap:
|
||||
() => setState(() => _newObscure = !_newObscure),
|
||||
),
|
||||
|
||||
SizedBox(height: buttonSpacing),
|
||||
|
||||
Center(
|
||||
BlocProvider(
|
||||
create: (context) => sl<ChangePasswordBloc>(),
|
||||
child: BlocConsumer<
|
||||
ChangePasswordBloc,
|
||||
ChangePasswordState
|
||||
>(
|
||||
listener: (context, state) {
|
||||
if (state is ChangePasswordSuccess) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.message),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
} else if (state is ChangePasswordError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.message),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state is ChangePasswordLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: OnboardingButton(
|
||||
text: "حفظ التغيير",
|
||||
backgroundColor: const Color(0xEE23574A),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
if (_oldPasswordController.text.isEmpty ||
|
||||
_newPasswordController.text.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text("يرجى ملء جميع الحقول"),
|
||||
backgroundColor: Colors.orange,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
context.read<ChangePasswordBloc>().add(
|
||||
ChangePasswordSubmitted(
|
||||
ChangePasswordRequest(
|
||||
oldPassword: _oldPasswordController.text,
|
||||
newPassword: _newPasswordController.text,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -127,6 +198,7 @@ class _ChangePasswordModalState extends State<ChangePasswordModal> {
|
||||
}
|
||||
|
||||
Widget _buildField({
|
||||
required TextEditingController controller,
|
||||
required String hint,
|
||||
required bool obscure,
|
||||
required double fontSize,
|
||||
@@ -142,6 +214,7 @@ class _ChangePasswordModalState extends State<ChangePasswordModal> {
|
||||
],
|
||||
),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
obscureText: obscure,
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(fontSize: fontSize),
|
||||
|
||||
Reference in New Issue
Block a user