From dd542e15c9219e0ef7be2d498caa8ec0e6b13e11 Mon Sep 17 00:00:00 2001 From: Pratik-canopas Date: Thu, 13 Jul 2023 17:14:22 +0530 Subject: [PATCH] Create apply hr_request screens --- lib/data/di/service_locator.config.dart | 181 ++++++++++-------- lib/data/l10n/app_en.arb | 28 ++- lib/data/model/hr_request/hr_request.dart | 26 +-- lib/data/model/hr_request/hr_request.g.dart | 48 +++-- lib/data/services/hr_request_service.dart | 30 +-- lib/ui/navigation/app_router.dart | 8 + .../home/home_screen/user_home_screen.dart | 2 +- .../bloc/hr_request_form_bloc.dart | 53 +++++ .../bloc/hr_request_form_events.dart | 17 ++ .../bloc/hr_request_form_states.dart | 34 ++++ .../apply_hr_request/hr_request_form.dart | 95 +++++++++ .../hr_request_form_description_view.dart | 43 +++++ .../widgets/hr_request_form_type_view.dart | 65 +++++++ .../user/hr_requests/hr_requests_screen.dart | 86 ++------- lib/ui/widget/employee_details_textfield.dart | 6 +- lib/ui/widget/hr_request_card.dart | 122 ++++++++++++ 16 files changed, 651 insertions(+), 193 deletions(-) create mode 100644 lib/ui/user/hr_requests/apply_hr_request/bloc/hr_request_form_bloc.dart create mode 100644 lib/ui/user/hr_requests/apply_hr_request/bloc/hr_request_form_events.dart create mode 100644 lib/ui/user/hr_requests/apply_hr_request/bloc/hr_request_form_states.dart create mode 100644 lib/ui/user/hr_requests/apply_hr_request/hr_request_form.dart create mode 100644 lib/ui/user/hr_requests/apply_hr_request/widgets/hr_request_form_description_view.dart create mode 100644 lib/ui/user/hr_requests/apply_hr_request/widgets/hr_request_form_type_view.dart create mode 100644 lib/ui/widget/hr_request_card.dart diff --git a/lib/data/di/service_locator.config.dart b/lib/data/di/service_locator.config.dart index 8191e64ef..34ac5b56f 100644 --- a/lib/data/di/service_locator.config.dart +++ b/lib/data/di/service_locator.config.dart @@ -21,18 +21,19 @@ import 'package:injectable/injectable.dart' as _i2; import 'package:projectunity/data/bloc/network/network_connection_bloc.dart' as _i15; import 'package:projectunity/data/bloc/user_state/user_state_controller_bloc.dart' - as _i36; -import 'package:projectunity/data/di/app_module.dart' as _i56; + as _i38; +import 'package:projectunity/data/di/app_module.dart' as _i59; import 'package:projectunity/data/pref/user_preference.dart' as _i21; import 'package:projectunity/data/provider/device_info.dart' as _i7; import 'package:projectunity/data/provider/user_state.dart' as _i22; -import 'package:projectunity/data/Repo/employee_repo.dart' as _i44; -import 'package:projectunity/data/Repo/leave_repo.dart' as _i46; +import 'package:projectunity/data/Repo/employee_repo.dart' as _i46; +import 'package:projectunity/data/Repo/leave_repo.dart' as _i49; import 'package:projectunity/data/services/account_service.dart' as _i23; import 'package:projectunity/data/services/auth_service.dart' as _i25; import 'package:projectunity/data/services/employee_service.dart' as _i27; +import 'package:projectunity/data/services/hr_request_service.dart' as _i28; import 'package:projectunity/data/services/invitation_services.dart' as _i13; -import 'package:projectunity/data/services/leave_service.dart' as _i30; +import 'package:projectunity/data/services/leave_service.dart' as _i32; import 'package:projectunity/data/services/mail_notification_service.dart' as _i16; import 'package:projectunity/data/services/space_service.dart' as _i19; @@ -42,57 +43,61 @@ import 'package:projectunity/data/state_manager/auth/desktop/desktop_auth_manage import 'package:projectunity/ui/admin/drawer_options/edit_space/bloc/edit_space_bloc.dart' as _i26; import 'package:projectunity/ui/admin/home/home_screen/bloc/admin_home_bloc.dart' - as _i53; + as _i56; import 'package:projectunity/ui/admin/home/invite_member/bloc/invite_member_bloc.dart' - as _i28; + as _i30; import 'package:projectunity/ui/admin/leaves/details/bloc/admin_leave_details_bloc.dart' - as _i38; + as _i40; import 'package:projectunity/ui/admin/leaves/leave_screen/bloc%20/admin_leaves_bloc.dart' - as _i54; + as _i57; import 'package:projectunity/ui/admin/members/detail/bloc/employee_detail_bloc.dart' - as _i42; + as _i44; import 'package:projectunity/ui/admin/members/details_leaves/bloc/admin_employee_details_leave_bloc.dart' - as _i52; + as _i55; import 'package:projectunity/ui/admin/members/edit_employee/bloc/admin_edit_employee_bloc.dart' - as _i37; + as _i39; import 'package:projectunity/ui/admin/members/list/bloc/member_list_bloc.dart' - as _i55; + as _i58; import 'package:projectunity/ui/navigation/app_router.dart' as _i24; import 'package:projectunity/ui/shared/appbar_drawer/drawer/bloc/app_drawer_bloc.dart' - as _i41; + as _i43; import 'package:projectunity/ui/shared/employees_calendar/bloc/calendar_bloc/employees_calendar_bloc.dart' as _i8; import 'package:projectunity/ui/shared/employees_calendar/bloc/calendar_leaves_bloc/employees_calendar_leaves_bloc.dart' - as _i45; + as _i47; import 'package:projectunity/ui/shared/profile/edit_profile/bloc/employee_edit_profile_bloc.dart' - as _i43; + as _i45; import 'package:projectunity/ui/shared/profile/view_profile/bloc/view_profile_bloc.dart' - as _i50; + as _i53; import 'package:projectunity/ui/shared/who_is_out_card/bloc/who_is_out_card_bloc.dart' - as _i51; -import 'package:projectunity/ui/sign_in/bloc/sign_in_view_bloc.dart' as _i31; + as _i54; +import 'package:projectunity/ui/sign_in/bloc/sign_in_view_bloc.dart' as _i33; import 'package:projectunity/ui/space/create_space/bloc/create_workspace_bloc.dart' - as _i40; + as _i42; import 'package:projectunity/ui/space/join_space/bloc/join_space_bloc.dart' - as _i29; + as _i31; import 'package:projectunity/ui/user/home/home_screen/bloc/user_home_bloc.dart' - as _i48; + as _i51; import 'package:projectunity/ui/user/home/leave_calendar/bloc/calendar_bloc/leave_calendar_bloc.dart' as _i14; import 'package:projectunity/ui/user/home/leave_calendar/bloc/user_leave_calendar_view_bloc/user_leave_calendar_bloc.dart' - as _i33; + as _i35; +import 'package:projectunity/ui/user/hr_requests/apply_hr_request/bloc/hr_request_form_bloc.dart' + as _i48; +import 'package:projectunity/ui/user/hr_requests/bloc/hr_requests_bloc.dart' + as _i29; import 'package:projectunity/ui/user/leaves/apply_leave/bloc/apply_leave_bloc.dart' - as _i39; + as _i41; import 'package:projectunity/ui/user/leaves/detail/bloc/user_leave_detail_bloc.dart' - as _i35; + as _i37; import 'package:projectunity/ui/user/leaves/leaves_screen/bloc/leave_count/user_leave_count_bloc.dart' - as _i34; + as _i36; import 'package:projectunity/ui/user/leaves/leaves_screen/bloc/leaves/user_leave_bloc.dart' - as _i49; + as _i52; import 'package:projectunity/ui/user/members/detail/bloc/user_employee_detail_bloc.dart' - as _i32; + as _i34; import 'package:projectunity/ui/user/members/members_screen/bloc/user_members_bloc.dart' - as _i47; + as _i50; import 'package:projectunity/ui/widget/pick_profile_image/bloc/pick_image_bloc.dart' as _i17; import 'package:shared_preferences/shared_preferences.dart' as _i18; @@ -164,12 +169,20 @@ extension GetItInjectableX on _i1.GetIt { gh<_i22.UserStateNotifier>(), gh<_i10.FirebaseFirestore>(), )); - gh.factory<_i28.InviteMemberBloc>(() => _i28.InviteMemberBloc( + gh.lazySingleton<_i28.HrRequestService>(() => _i28.HrRequestService( + gh<_i22.UserStateNotifier>(), + gh<_i10.FirebaseFirestore>(), + )); + gh.factory<_i29.HrRequestsBloc>(() => _i29.HrRequestsBloc( + gh<_i28.HrRequestService>(), + gh<_i22.UserStateNotifier>(), + )); + gh.factory<_i30.InviteMemberBloc>(() => _i30.InviteMemberBloc( gh<_i13.InvitationService>(), gh<_i22.UserStateNotifier>(), gh<_i27.EmployeeService>(), )); - gh.factory<_i29.JoinSpaceBloc>(() => _i29.JoinSpaceBloc( + gh.factory<_i31.JoinSpaceBloc>(() => _i31.JoinSpaceBloc( gh<_i13.InvitationService>(), gh<_i19.SpaceService>(), gh<_i22.UserStateNotifier>(), @@ -177,122 +190,126 @@ extension GetItInjectableX on _i1.GetIt { gh<_i27.EmployeeService>(), gh<_i25.AuthService>(), )); - gh.lazySingleton<_i30.LeaveService>(() => _i30.LeaveService( + gh.lazySingleton<_i32.LeaveService>(() => _i32.LeaveService( gh<_i22.UserStateNotifier>(), gh<_i10.FirebaseFirestore>(), )); - gh.factory<_i31.SignInBloc>(() => _i31.SignInBloc( + gh.factory<_i33.SignInBloc>(() => _i33.SignInBloc( gh<_i22.UserStateNotifier>(), gh<_i25.AuthService>(), gh<_i23.AccountService>(), )); - gh.factory<_i32.UserEmployeeDetailBloc>( - () => _i32.UserEmployeeDetailBloc(gh<_i30.LeaveService>())); - gh.factory<_i33.UserLeaveCalendarBloc>(() => _i33.UserLeaveCalendarBloc( - gh<_i30.LeaveService>(), + gh.factory<_i34.UserEmployeeDetailBloc>( + () => _i34.UserEmployeeDetailBloc(gh<_i32.LeaveService>())); + gh.factory<_i35.UserLeaveCalendarBloc>(() => _i35.UserLeaveCalendarBloc( + gh<_i32.LeaveService>(), gh<_i27.EmployeeService>(), gh<_i22.UserStateNotifier>(), gh<_i19.SpaceService>(), )); - gh.factory<_i34.UserLeaveCountBloc>(() => _i34.UserLeaveCountBloc( - gh<_i30.LeaveService>(), + gh.factory<_i36.UserLeaveCountBloc>(() => _i36.UserLeaveCountBloc( + gh<_i32.LeaveService>(), gh<_i22.UserStateNotifier>(), gh<_i19.SpaceService>(), )); - gh.factory<_i35.UserLeaveDetailBloc>( - () => _i35.UserLeaveDetailBloc(gh<_i30.LeaveService>())); - gh.factory<_i36.UserStateControllerBloc>(() => _i36.UserStateControllerBloc( + gh.factory<_i37.UserLeaveDetailBloc>( + () => _i37.UserLeaveDetailBloc(gh<_i32.LeaveService>())); + gh.factory<_i38.UserStateControllerBloc>(() => _i38.UserStateControllerBloc( gh<_i27.EmployeeService>(), gh<_i22.UserStateNotifier>(), gh<_i19.SpaceService>(), )); - gh.factory<_i37.AdminEditEmployeeDetailsBloc>( - () => _i37.AdminEditEmployeeDetailsBloc( + gh.factory<_i39.AdminEditEmployeeDetailsBloc>( + () => _i39.AdminEditEmployeeDetailsBloc( gh<_i27.EmployeeService>(), gh<_i22.UserStateNotifier>(), gh<_i20.StorageService>(), )); - gh.factory<_i38.AdminLeaveDetailsBloc>(() => _i38.AdminLeaveDetailsBloc( - gh<_i30.LeaveService>(), + gh.factory<_i40.AdminLeaveDetailsBloc>(() => _i40.AdminLeaveDetailsBloc( + gh<_i32.LeaveService>(), gh<_i16.NotificationService>(), )); - gh.factory<_i39.ApplyLeaveBloc>(() => _i39.ApplyLeaveBloc( + gh.factory<_i41.ApplyLeaveBloc>(() => _i41.ApplyLeaveBloc( gh<_i22.UserStateNotifier>(), - gh<_i30.LeaveService>(), + gh<_i32.LeaveService>(), gh<_i16.NotificationService>(), )); - gh.factory<_i40.CreateSpaceBLoc>(() => _i40.CreateSpaceBLoc( + gh.factory<_i42.CreateSpaceBLoc>(() => _i42.CreateSpaceBLoc( gh<_i19.SpaceService>(), gh<_i22.UserStateNotifier>(), gh<_i27.EmployeeService>(), gh<_i12.ImagePicker>(), gh<_i20.StorageService>(), )); - gh.factory<_i41.DrawerBloc>(() => _i41.DrawerBloc( + gh.factory<_i43.DrawerBloc>(() => _i43.DrawerBloc( gh<_i19.SpaceService>(), gh<_i22.UserStateNotifier>(), gh<_i23.AccountService>(), gh<_i27.EmployeeService>(), )); - gh.factory<_i42.EmployeeDetailBloc>(() => _i42.EmployeeDetailBloc( + gh.factory<_i44.EmployeeDetailBloc>(() => _i44.EmployeeDetailBloc( gh<_i23.AccountService>(), gh<_i19.SpaceService>(), gh<_i22.UserStateNotifier>(), gh<_i27.EmployeeService>(), - gh<_i30.LeaveService>(), + gh<_i32.LeaveService>(), )); - gh.factory<_i43.EmployeeEditProfileBloc>(() => _i43.EmployeeEditProfileBloc( + gh.factory<_i45.EmployeeEditProfileBloc>(() => _i45.EmployeeEditProfileBloc( gh<_i27.EmployeeService>(), gh<_i21.UserPreference>(), gh<_i22.UserStateNotifier>(), gh<_i20.StorageService>(), )); - gh.lazySingleton<_i44.EmployeeRepo>( - () => _i44.EmployeeRepo( + gh.lazySingleton<_i46.EmployeeRepo>( + () => _i46.EmployeeRepo( gh<_i27.EmployeeService>(), gh<_i22.UserStateNotifier>(), ), dispose: (i) => i.dispose(), ); - gh.factory<_i45.EmployeesCalendarLeavesBloc>( - () => _i45.EmployeesCalendarLeavesBloc( + gh.factory<_i47.EmployeesCalendarLeavesBloc>( + () => _i47.EmployeesCalendarLeavesBloc( gh<_i27.EmployeeService>(), - gh<_i30.LeaveService>(), + gh<_i32.LeaveService>(), )); - gh.lazySingleton<_i46.LeaveRepo>( - () => _i46.LeaveRepo(gh<_i30.LeaveService>()), + gh.factory<_i48.HrRequestFormBloc>(() => _i48.HrRequestFormBloc( + gh<_i22.UserStateNotifier>(), + gh<_i28.HrRequestService>(), + )); + gh.lazySingleton<_i49.LeaveRepo>( + () => _i49.LeaveRepo(gh<_i32.LeaveService>()), dispose: (i) => i.dispose(), ); - gh.factory<_i47.UserEmployeesBloc>( - () => _i47.UserEmployeesBloc(gh<_i44.EmployeeRepo>())); - gh.factory<_i48.UserHomeBloc>(() => _i48.UserHomeBloc( + gh.factory<_i50.UserEmployeesBloc>( + () => _i50.UserEmployeesBloc(gh<_i46.EmployeeRepo>())); + gh.factory<_i51.UserHomeBloc>(() => _i51.UserHomeBloc( gh<_i22.UserStateNotifier>(), - gh<_i46.LeaveRepo>(), + gh<_i49.LeaveRepo>(), )); - gh.factory<_i49.UserLeaveBloc>(() => _i49.UserLeaveBloc( + gh.factory<_i52.UserLeaveBloc>(() => _i52.UserLeaveBloc( gh<_i22.UserStateNotifier>(), - gh<_i46.LeaveRepo>(), + gh<_i49.LeaveRepo>(), )); - gh.factory<_i50.ViewProfileBloc>(() => _i50.ViewProfileBloc( + gh.factory<_i53.ViewProfileBloc>(() => _i53.ViewProfileBloc( gh<_i22.UserStateNotifier>(), - gh<_i44.EmployeeRepo>(), + gh<_i46.EmployeeRepo>(), )); - gh.factory<_i51.WhoIsOutCardBloc>(() => _i51.WhoIsOutCardBloc( - gh<_i44.EmployeeRepo>(), - gh<_i46.LeaveRepo>(), + gh.factory<_i54.WhoIsOutCardBloc>(() => _i54.WhoIsOutCardBloc( + gh<_i46.EmployeeRepo>(), + gh<_i49.LeaveRepo>(), )); - gh.factory<_i52.AdminEmployeeDetailsLeavesBLoc>( - () => _i52.AdminEmployeeDetailsLeavesBLoc(gh<_i46.LeaveRepo>())); - gh.factory<_i53.AdminHomeBloc>(() => _i53.AdminHomeBloc( - gh<_i46.LeaveRepo>(), - gh<_i44.EmployeeRepo>(), + gh.factory<_i55.AdminEmployeeDetailsLeavesBLoc>( + () => _i55.AdminEmployeeDetailsLeavesBLoc(gh<_i49.LeaveRepo>())); + gh.factory<_i56.AdminHomeBloc>(() => _i56.AdminHomeBloc( + gh<_i49.LeaveRepo>(), + gh<_i46.EmployeeRepo>(), )); - gh.factory<_i54.AdminLeavesBloc>(() => _i54.AdminLeavesBloc( - gh<_i46.LeaveRepo>(), - gh<_i44.EmployeeRepo>(), + gh.factory<_i57.AdminLeavesBloc>(() => _i57.AdminLeavesBloc( + gh<_i49.LeaveRepo>(), + gh<_i46.EmployeeRepo>(), )); - gh.factory<_i55.AdminMembersBloc>(() => _i55.AdminMembersBloc( - gh<_i44.EmployeeRepo>(), + gh.factory<_i58.AdminMembersBloc>(() => _i58.AdminMembersBloc( + gh<_i46.EmployeeRepo>(), gh<_i13.InvitationService>(), gh<_i22.UserStateNotifier>(), )); @@ -300,4 +317,4 @@ extension GetItInjectableX on _i1.GetIt { } } -class _$AppModule extends _i56.AppModule {} +class _$AppModule extends _i59.AppModule {} diff --git a/lib/data/l10n/app_en.arb b/lib/data/l10n/app_en.arb index a08c5a409..7f3504317 100644 --- a/lib/data/l10n/app_en.arb +++ b/lib/data/l10n/app_en.arb @@ -37,6 +37,9 @@ "alert_cancel_action":"Cancel", "all_tag":"All", "ok_tag":"OK", + "type_tag":"Type", + "description_tag":"Description", + "spaces_title":"Spaces", "delete_space_text":"Delete Space", @@ -281,7 +284,7 @@ "user_leaves_apply_appbar_tag":"Leave Request", "user_leaves_apply_enter_reason_tag":"Enter reason", - "user_leaves_apply_leave_type_tag":"Type", + "user_leaves_apply_leave_start_tag": "Start", "user_leaves_apply_leave_end_tag": "End", "user_leaves_apply_leave_button_tag": "Apply Leave", @@ -384,5 +387,28 @@ "type":"String" } } + }, + + "hr_requests_title":"HR Requests", + "new_request_tag":"New Request", + + "hr_request_status":"{status, select, 0{Pending} 1{Resolved} 2{Canceled} other{Other}}", + "@hr_request_status":{ + "description":"get hr request status", + "placeholders":{ + "status":{ + "type": "String" + } + } + }, + + "hr_request_types":"{type, select, 0{Employee Relations} 1{Training} 2{payroll} 3{Time And Attendance} 4{HR Benefits} 5{Technical Issue} other{Other}}", + "@hr_request_types":{ + "description":"get hr request type", + "placeholders":{ + "type":{ + "type":"String" + } + } } } \ No newline at end of file diff --git a/lib/data/model/hr_request/hr_request.dart b/lib/data/model/hr_request/hr_request.dart index 69d6fc78d..77a4a0fe8 100644 --- a/lib/data/model/hr_request/hr_request.dart +++ b/lib/data/model/hr_request/hr_request.dart @@ -16,14 +16,17 @@ class HrRequest extends Equatable { final HrRequestType type; final String description; final HrRequestStatus status; + final String? response; - HrRequest({ + const HrRequest({ required this.id, required this.type, required this.uid, required this.description, + required this.requestedAt, + this.response, this.status = HrRequestStatus.pending, - }) : requestedAt = DateTime.now(); + }); factory HrRequest.fromFireStore( DocumentSnapshot> snapshot, @@ -43,13 +46,13 @@ class HrRequest extends Equatable { @JsonEnum(valueField: 'value') enum HrRequestType { - employeeRelations(1), - training(2), - payroll(3), - timeAndAttendance(4), - hrBenefits(5), - technicalIssue(6), - other(7); + employeeRelations(0), + training(1), + payroll(2), + timeAndAttendance(3), + hrBenefits(4), + technicalIssue(5), + other(6); final int value; @@ -58,8 +61,9 @@ enum HrRequestType { @JsonEnum(valueField: 'value') enum HrRequestStatus { - pending(1), - done(2); + pending(0), + resolved(1), + canceled(2); final int value; diff --git a/lib/data/model/hr_request/hr_request.g.dart b/lib/data/model/hr_request/hr_request.g.dart index 6bdf3d9d4..e5db024f4 100644 --- a/lib/data/model/hr_request/hr_request.g.dart +++ b/lib/data/model/hr_request/hr_request.g.dart @@ -11,29 +11,45 @@ HrRequest _$HrRequestFromJson(Map json) => HrRequest( type: $enumDecode(_$HrRequestTypeEnumMap, json['type']), uid: json['uid'] as String, description: json['description'] as String, + requestedAt: + const DateTimeConverter().fromJson(json['requested_at'] as int), + response: json['response'] as String?, status: $enumDecodeNullable(_$HrRequestStatusEnumMap, json['status']) ?? HrRequestStatus.pending, ); -Map _$HrRequestToJson(HrRequest instance) => { - 'id': instance.id, - 'uid': instance.uid, - 'type': _$HrRequestTypeEnumMap[instance.type]!, - 'description': instance.description, - 'status': _$HrRequestStatusEnumMap[instance.status]!, - }; +Map _$HrRequestToJson(HrRequest instance) { + final val = { + 'id': instance.id, + 'uid': instance.uid, + 'requested_at': const DateTimeConverter().toJson(instance.requestedAt), + 'type': _$HrRequestTypeEnumMap[instance.type]!, + 'description': instance.description, + 'status': _$HrRequestStatusEnumMap[instance.status]!, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('response', instance.response); + return val; +} const _$HrRequestTypeEnumMap = { - HrRequestType.employeeRelations: 1, - HrRequestType.training: 2, - HrRequestType.payroll: 3, - HrRequestType.timeAndAttendance: 4, - HrRequestType.hrBenefits: 5, - HrRequestType.technicalIssue: 6, - HrRequestType.other: 7, + HrRequestType.employeeRelations: 0, + HrRequestType.training: 1, + HrRequestType.payroll: 2, + HrRequestType.timeAndAttendance: 3, + HrRequestType.hrBenefits: 4, + HrRequestType.technicalIssue: 5, + HrRequestType.other: 6, }; const _$HrRequestStatusEnumMap = { - HrRequestStatus.pending: 1, - HrRequestStatus.done: 2, + HrRequestStatus.pending: 0, + HrRequestStatus.resolved: 1, + HrRequestStatus.canceled: 2, }; diff --git a/lib/data/services/hr_request_service.dart b/lib/data/services/hr_request_service.dart index accec6200..11e7f7acb 100644 --- a/lib/data/services/hr_request_service.dart +++ b/lib/data/services/hr_request_service.dart @@ -22,9 +22,8 @@ class HrRequestService { hrRequest.toFireStore()); } - Future setHrRequest( - HrRequest hrDeskRequest) async { - await _hrRequestDB().doc(hrDeskRequest.id).set(hrDeskRequest); + Future setHrRequest(HrRequest hrRequest) async { + await _hrRequestDB().doc(hrRequest.id).set(hrRequest); } String get generateNewId => _hrRequestDB().doc().id; @@ -34,17 +33,24 @@ class HrRequestService { return hrDeskRequestsCollection.docs.map((e) => e.data()).toList(); } - Future> getHrRequestsOfUser( - String uid) async { - final hrDeskRequestsCollection = await _hrRequestDB() - .where(FireStoreConst.uid, isEqualTo: uid) - .get(); + Future> getHrRequestsOfUser(String uid) async { + final hrDeskRequestsCollection = + await _hrRequestDB().where(FireStoreConst.uid, isEqualTo: uid).get(); return hrDeskRequestsCollection.docs.map((e) => e.data()).toList(); } - Future setHrRequestDone(String id) async { - await _hrRequestDB().doc(id).update({ - FireStoreConst.status: HrRequestStatus.done, - }); + Future setHrRequestDone( + {required String id, + required HrRequestStatus status, + String response = ''}) async { + Map data = { + FireStoreConst.status: status.value, + }; + + if (response.trim().isNotEmpty) { + data.addEntries([MapEntry(FireStoreConst.response, response)]); + } + + await _hrRequestDB().doc(id).update(data); } } diff --git a/lib/ui/navigation/app_router.dart b/lib/ui/navigation/app_router.dart index f4462890a..f5634ece3 100644 --- a/lib/ui/navigation/app_router.dart +++ b/lib/ui/navigation/app_router.dart @@ -5,6 +5,7 @@ import 'package:projectunity/ui/admin/dashboard/admin_dashboard.dart'; import 'package:projectunity/ui/admin/leaves/leave_screen/admin_leaves_screen.dart'; import 'package:projectunity/ui/shared/profile/view_profile/view_profle_screen.dart'; import 'package:projectunity/ui/sign_in/sign_in_screen.dart'; +import 'package:projectunity/ui/user/hr_requests/apply_hr_request/hr_request_form.dart'; import 'package:projectunity/ui/user/hr_requests/hr_requests_screen.dart'; import 'package:projectunity/ui/user/leaves/detail/user_leave_detail_screen.dart'; import 'package:projectunity/ui/user/leaves/leaves_screen/user_leave_screen.dart'; @@ -236,6 +237,13 @@ class AppRouter { path: Routes.hrRequests, name: Routes.hrRequests, pageBuilder: (context, state) => const CupertinoPage(child: HrRequestsPage()), + routes: [ + GoRoute( + path: Routes.applyHrRequests, + name: Routes.applyHrRequests, + pageBuilder: (context, state) => const CupertinoPage(child: HrRequestFormPage()), + ), + ] ), GoRoute( parentNavigatorKey: _employeeShellNavigatorKey, diff --git a/lib/ui/user/home/home_screen/user_home_screen.dart b/lib/ui/user/home/home_screen/user_home_screen.dart index b95e6e41f..cab7bd441 100644 --- a/lib/ui/user/home/home_screen/user_home_screen.dart +++ b/lib/ui/user/home/home_screen/user_home_screen.dart @@ -98,7 +98,7 @@ class _UserHomeScreenState extends State { const WhoIsOutCard(), ElevatedButton(onPressed: (){ context.goNamed(Routes.hrRequests); - }, child: const Text("HR Service Request")), + }, child: Text(locale.hr_requests_title)), BlocConsumer( buildWhen: (previous, current) => current is! UserHomeErrorState, diff --git a/lib/ui/user/hr_requests/apply_hr_request/bloc/hr_request_form_bloc.dart b/lib/ui/user/hr_requests/apply_hr_request/bloc/hr_request_form_bloc.dart new file mode 100644 index 000000000..a485827eb --- /dev/null +++ b/lib/ui/user/hr_requests/apply_hr_request/bloc/hr_request_form_bloc.dart @@ -0,0 +1,53 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:injectable/injectable.dart'; +import '../../../../../data/core/exception/error_const.dart'; +import '../../../../../data/model/hr_request/hr_request.dart'; +import 'hr_request_form_events.dart'; +import 'hr_request_form_states.dart'; +import '../../../../../data/core/utils/bloc_status.dart'; +import '../../../../../data/provider/user_state.dart'; +import '../../../../../data/services/hr_request_service.dart'; + +@Injectable() +class HrRequestFormBloc extends Bloc { + final UserStateNotifier _userStateNotifier; + final HrRequestService _hrRequestService; + + HrRequestFormBloc(this._userStateNotifier, this._hrRequestService) + : super(const HrRequestFormState()) { + on(_changeType); + on(_changeDescription); + on(_applyHrRequest); + } + + void _changeType(ChangeType event, Emitter emit) { + emit(state.copyWith(type: event.type)); + } + + void _changeDescription( + ChangeDescription event, Emitter emit) { + emit(state.copyWith(description: event.description)); + } + + Future _applyHrRequest( + ApplyHrRequest event, Emitter emit) async { + emit(state.copyWith(status: Status.loading)); + try { + if (state.type != null && state.description.isNotEmpty) { + final hrRequest = HrRequest( + id: _hrRequestService.generateNewId, + type: state.type!, + uid: _userStateNotifier.employeeId, + description: state.description, + requestedAt: DateTime.now()); + await _hrRequestService.setHrRequest(hrRequest); + emit(state.copyWith(status: Status.success)); + } else { + emit(state.copyWith(error: fillDetailsError)); + } + } on Exception { + emit( + state.copyWith(status: Status.error, error: firestoreFetchDataError)); + } + } +} diff --git a/lib/ui/user/hr_requests/apply_hr_request/bloc/hr_request_form_events.dart b/lib/ui/user/hr_requests/apply_hr_request/bloc/hr_request_form_events.dart new file mode 100644 index 000000000..f85a67fcc --- /dev/null +++ b/lib/ui/user/hr_requests/apply_hr_request/bloc/hr_request_form_events.dart @@ -0,0 +1,17 @@ +import '../../../../../data/model/hr_request/hr_request.dart'; + +abstract class HrRequestFormEvents {} + +class ChangeType extends HrRequestFormEvents { + final HrRequestType? type; + + ChangeType(this.type); +} + +class ChangeDescription extends HrRequestFormEvents { + final String description; + + ChangeDescription(this.description); +} + +class ApplyHrRequest extends HrRequestFormEvents {} diff --git a/lib/ui/user/hr_requests/apply_hr_request/bloc/hr_request_form_states.dart b/lib/ui/user/hr_requests/apply_hr_request/bloc/hr_request_form_states.dart new file mode 100644 index 000000000..4d0f1cf44 --- /dev/null +++ b/lib/ui/user/hr_requests/apply_hr_request/bloc/hr_request_form_states.dart @@ -0,0 +1,34 @@ +import 'package:equatable/equatable.dart'; +import '../../../../../data/core/utils/bloc_status.dart'; +import '../../../../../data/model/hr_request/hr_request.dart'; + +class HrRequestFormState extends Equatable { + final HrRequestType? type; + final String description; + final Status status; + final String? error; + + const HrRequestFormState({ + this.type, + this.description = "", + this.status = Status.initial, + this.error, + }); + + HrRequestFormState copyWith( + {HrRequestType? type, + String? description, + Status? status, + String? error}) => + HrRequestFormState( + error: error, + status: status ?? this.status, + description: description ?? this.description, + type: type ?? this.type, + ); + + bool get isProvidedDataValid => type != null && description.trim().isNotEmpty; + + @override + List get props => [type, description, status, error]; +} diff --git a/lib/ui/user/hr_requests/apply_hr_request/hr_request_form.dart b/lib/ui/user/hr_requests/apply_hr_request/hr_request_form.dart new file mode 100644 index 000000000..ff36c96ea --- /dev/null +++ b/lib/ui/user/hr_requests/apply_hr_request/hr_request_form.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localization.dart'; +import 'package:go_router/go_router.dart'; +import '../../../widget/error_snack_bar.dart'; +import 'widgets/hr_request_form_description_view.dart'; +import 'widgets/hr_request_form_type_view.dart'; +import '../../../../data/core/utils/bloc_status.dart'; +import '../../../widget/circular_progress_indicator.dart'; +import 'bloc/hr_request_form_bloc.dart'; +import 'bloc/hr_request_form_events.dart'; +import 'bloc/hr_request_form_states.dart'; +import '../../../../data/configs/text_style.dart'; +import '../../../../data/di/service_locator.dart'; + +class HrRequestFormPage extends StatelessWidget { + const HrRequestFormPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => getIt(), + child: const HrRequestFormScreen()); + } +} + +class HrRequestFormScreen extends StatefulWidget { + const HrRequestFormScreen({super.key}); + + @override + State createState() => _HrRequestFormScreenState(); +} + +class _HrRequestFormScreenState extends State { + @override + Widget build(BuildContext context) { + final locale = AppLocalizations.of(context); + return Scaffold( + appBar: AppBar( + title: const Text('Hr Request Form'), + actions: [ + BlocBuilder( + buildWhen: (previous, current) => + previous.status != current.status || + previous.isProvidedDataValid != current.isProvidedDataValid, + builder: (context, state) => state.status == Status.loading + ? const Padding( + padding: EdgeInsets.only(right: 30), + child: AppCircularProgressIndicator(size: 20), + ) + : Padding( + padding: const EdgeInsets.only(right: 10), + child: TextButton( + onPressed: state.isProvidedDataValid + ? () { + context + .read() + .add(ApplyHrRequest()); + } + : null, + child: Text(AppLocalizations.of(context).save_tag)), + )), + ], + ), + body: BlocListener( + listenWhen: (previous, current) => + current.error != null || current.status == Status.success, + listener: (context, state) { + if (state.error != null) { + showSnackBar(context: context, error: state.error); + } else if (state.status == Status.success) { + context.pop(true); + } + }, + child: ListView( + padding: const EdgeInsets.all(16), + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Text(locale.type_tag, + textAlign: TextAlign.start, style: AppFontStyle.labelGrey), + ), + const HrRequestTypeView(), + Padding( + padding: const EdgeInsets.only(top: 16, bottom: 8), + child: Text(locale.description_tag, + textAlign: TextAlign.start, style: AppFontStyle.labelGrey), + ), + const HrRequestDescriptionView(), + ], + ), + ), + ); + } +} diff --git a/lib/ui/user/hr_requests/apply_hr_request/widgets/hr_request_form_description_view.dart b/lib/ui/user/hr_requests/apply_hr_request/widgets/hr_request_form_description_view.dart new file mode 100644 index 000000000..96df2f77e --- /dev/null +++ b/lib/ui/user/hr_requests/apply_hr_request/widgets/hr_request_form_description_view.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../../../data/configs/colors.dart'; +import '../../../../../data/configs/space_constant.dart'; +import '../../../../../data/configs/text_style.dart'; +import '../../../../../data/configs/theme.dart'; +import '../bloc/hr_request_form_bloc.dart'; +import '../bloc/hr_request_form_events.dart'; +import '../bloc/hr_request_form_states.dart'; + +class HrRequestDescriptionView extends StatelessWidget { + const HrRequestDescriptionView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: AppTheme.commonBorderRadius, + boxShadow: AppTheme.commonBoxShadow), + padding: const EdgeInsets.all(primaryHorizontalSpacing) + .copyWith(top: 0, bottom: primaryVerticalSpacing), + child: BlocBuilder( + buildWhen: (previous, current) => + current.description != previous.description, + builder: (context, state) => TextField( + style: AppFontStyle.bodySmallRegular, + cursorColor: AppColors.secondaryText, + maxLines: 10, + decoration: const InputDecoration( + border: InputBorder.none, + hintText: "Enter description", + hintStyle: AppFontStyle.labelGrey, + ), + onChanged: (description) { + context + .read() + .add(ChangeDescription(description)); + }, + )), + ); + } +} diff --git a/lib/ui/user/hr_requests/apply_hr_request/widgets/hr_request_form_type_view.dart b/lib/ui/user/hr_requests/apply_hr_request/widgets/hr_request_form_type_view.dart new file mode 100644 index 000000000..e213fc63d --- /dev/null +++ b/lib/ui/user/hr_requests/apply_hr_request/widgets/hr_request_form_type_view.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localization.dart'; + +import '../../../../../data/configs/colors.dart'; +import '../../../../../data/configs/text_style.dart'; +import '../../../../../data/configs/theme.dart'; +import '../../../../../data/model/hr_request/hr_request.dart'; +import '../bloc/hr_request_form_bloc.dart'; +import '../bloc/hr_request_form_events.dart'; +import '../bloc/hr_request_form_states.dart'; + +class HrRequestTypeView extends StatelessWidget { + const HrRequestTypeView({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + var localization = AppLocalizations.of(context); + return Container( + height: 60, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: AppTheme.commonBorderRadius, + boxShadow: AppTheme.commonBoxShadow), + child: Material( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(12), + child: BlocBuilder( + buildWhen: (previous, current) => previous.type != current.type, + builder: (context, state) => DropdownButtonHideUnderline( + child: DropdownButton( + isExpanded: true, + style: AppFontStyle.bodyLarge, + icon: const Padding( + padding: EdgeInsets.only(right: 8.0), + child: Icon(Icons.arrow_drop_down), + ), + borderRadius: BorderRadius.circular(12), + hint: Padding( + padding: const EdgeInsets.only(left: 16), + child: Text(localization.user_settings_edit_select_tag), + ), + items: HrRequestType.values.map((type) { + return DropdownMenuItem( + value: type, + child: Padding( + padding: const EdgeInsets.only(left: 16), + child: Text( + localization.hr_request_types(type.value.toString())), + ), + ); + }).toList(), + value: state.type, + onChanged: (HrRequestType? type) { + context.read().add(ChangeType(type)); + }, + ), + ), + ), + ), + ); + } +} diff --git a/lib/ui/user/hr_requests/hr_requests_screen.dart b/lib/ui/user/hr_requests/hr_requests_screen.dart index 6aebe9957..0142d596b 100644 --- a/lib/ui/user/hr_requests/hr_requests_screen.dart +++ b/lib/ui/user/hr_requests/hr_requests_screen.dart @@ -1,18 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:projectunity/data/configs/text_style.dart'; -import 'package:projectunity/data/core/extensions/date_time.dart'; -import 'package:projectunity/data/model/hr_request/hr_request.dart'; +import 'package:go_router/go_router.dart'; +import '../../navigation/app_router.dart'; +import '../../widget/hr_request_card.dart'; import 'bloc/hr_requests_bloc.dart'; import 'bloc/hr_requests_events.dart'; import 'bloc/hr_requests_states.dart'; import 'package:projectunity/ui/widget/circular_progress_indicator.dart'; import 'package:projectunity/ui/widget/error_snack_bar.dart'; import 'package:flutter_gen/gen_l10n/app_localization.dart'; -import '../../../data/configs/colors.dart'; -import '../../../data/configs/theme.dart'; import '../../../data/core/utils/bloc_status.dart'; -import '../../../data/core/utils/date_formatter.dart'; import '../../../data/di/service_locator.dart'; class HrRequestsPage extends StatelessWidget { @@ -21,8 +18,7 @@ class HrRequestsPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => getIt() - ..add(HrRequestsInit()), + create: (context) => getIt()..add(HrRequestsInit()), child: const HrRequestsScreen()); } } @@ -31,17 +27,16 @@ class HrRequestsScreen extends StatefulWidget { const HrRequestsScreen({super.key}); @override - State createState() => - _HrServiceDeskRequestScreenState(); + State createState() => _HrServiceDeskRequestScreenState(); } -class _HrServiceDeskRequestScreenState - extends State { +class _HrServiceDeskRequestScreenState extends State { @override Widget build(BuildContext context) { + final locale = AppLocalizations.of(context); return Scaffold( appBar: AppBar( - title: const Text("HR Requests"), + title: Text(locale.hr_requests_title), ), body: BlocConsumer( listenWhen: (previous, current) => @@ -62,7 +57,9 @@ class _HrServiceDeskRequestScreenState itemCount: state.hrServiceDeskRequests.length, separatorBuilder: (context, index) => const SizedBox(height: 16), itemBuilder: (context, index) => HrServiceDeskRequestCard( - onTap: () {}, + onTap: () { + ///TODO: Open hr request details screen + }, hrRequest: state.hrServiceDeskRequests[index]), ); } @@ -70,63 +67,16 @@ class _HrServiceDeskRequestScreenState }, ), floatingActionButton: FloatingActionButton.extended( - onPressed: () {}, - label: const Text("New Request"), + onPressed: () async { + bool? value = await context.pushNamed(Routes.applyHrRequests); + if(value == true){ + context.read().add(HrRequestsInit()); + } + }, + label: Text(locale.new_request_tag), icon: const Icon(Icons.add), ), ); } } -class HrServiceDeskRequestCard extends StatelessWidget { - final void Function()? onTap; - final HrRequest hrRequest; - - const HrServiceDeskRequestCard( - {super.key, this.onTap, required this.hrRequest}); - - @override - Widget build(BuildContext context) { - return Container( - width: MediaQuery.of(context).size.width, - decoration: BoxDecoration( - color: AppColors.whiteColor, - borderRadius: AppTheme.commonBorderRadius, - boxShadow: AppTheme.commonBoxShadow, - ), - child: Material( - borderRadius: AppTheme.commonBorderRadius, - color: AppColors.whiteColor, - child: InkWell( - borderRadius: AppTheme.commonBorderRadius, - onTap: onTap, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(hrRequest.type.name, - style: AppFontStyle.titleDark), - Text( - DateFormatter(AppLocalizations.of(context)) - .timeAgoPresentation( - hrRequest.requestedAt), - style: AppFontStyle.bodyMedium, - overflow: TextOverflow.ellipsis), - ], - ), - const Divider(height: 32), - Text(hrRequest.description, - style: AppFontStyle.labelGrey, - overflow: TextOverflow.ellipsis) - ], - ), - ), - ), - ), - ); - } -} diff --git a/lib/ui/widget/employee_details_textfield.dart b/lib/ui/widget/employee_details_textfield.dart index 5aa433c6c..ce75cedfe 100644 --- a/lib/ui/widget/employee_details_textfield.dart +++ b/lib/ui/widget/employee_details_textfield.dart @@ -26,6 +26,7 @@ class FieldEntry extends StatelessWidget { final String? errorText; final String? hintText; final int? maxLine; + final TextInputAction textInputAction; final List? inputFormatters; final TextEditingController? controller; final int? maxLength; @@ -39,7 +40,8 @@ class FieldEntry extends StatelessWidget { this.controller, this.keyboardType, this.maxLength, - this.inputFormatters}) + this.inputFormatters, + this.textInputAction = TextInputAction.next}) : super(key: key); @override @@ -47,7 +49,7 @@ class FieldEntry extends StatelessWidget { return TextField( keyboardType: keyboardType, inputFormatters: inputFormatters, - textInputAction: TextInputAction.next, + textInputAction: textInputAction, onChanged: onChanged, maxLines: maxLine, maxLength: maxLength, diff --git a/lib/ui/widget/hr_request_card.dart b/lib/ui/widget/hr_request_card.dart new file mode 100644 index 000000000..ebdb63b6e --- /dev/null +++ b/lib/ui/widget/hr_request_card.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import '../../data/configs/colors.dart'; +import '../../data/configs/text_style.dart'; +import '../../data/configs/theme.dart'; +import '../../data/core/utils/date_formatter.dart'; +import 'package:flutter_gen/gen_l10n/app_localization.dart'; +import '../../data/model/hr_request/hr_request.dart'; + +class HrServiceDeskRequestCard extends StatelessWidget { + final void Function()? onTap; + final HrRequest hrRequest; + + const HrServiceDeskRequestCard( + {super.key, this.onTap, required this.hrRequest}); + + @override + Widget build(BuildContext context) { + return Container( + width: MediaQuery.of(context).size.width, + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: AppTheme.commonBorderRadius, + boxShadow: AppTheme.commonBoxShadow, + ), + child: Material( + borderRadius: AppTheme.commonBorderRadius, + color: AppColors.whiteColor, + child: InkWell( + borderRadius: AppTheme.commonBorderRadius, + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + HRStatusView(status: hrRequest.status), + Text( + DateFormatter(AppLocalizations.of(context)) + .getDateRepresentation(hrRequest.requestedAt), + style: AppFontStyle.bodyMedium, + overflow: TextOverflow.ellipsis), + ], + ), + const Divider(height: 32), + Text( + AppLocalizations.of(context) + .hr_request_types(hrRequest.type.value.toString()), + style: AppFontStyle.titleDark), + ], + ), + ), + ), + ), + ); + } +} + +class HRStatusView extends StatelessWidget { + final double verticalPadding; + final double horizontalPadding; + final HrRequestStatus status; + + const HRStatusView( + {Key? key, + required this.status, + this.verticalPadding = 4, + this.horizontalPadding = 10}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: hrRequestStatusColor(status), + ), + padding: EdgeInsets.symmetric( + vertical: verticalPadding, horizontal: horizontalPadding), + child: Row( + children: [ + HrRequestStatusIcon(status: status), + const SizedBox(width: 5), + Text( + AppLocalizations.of(context).hr_request_status(status.value.toString()), + style: AppFontStyle.labelRegular, + ), + ], + ), + ); + } +} + +Color hrRequestStatusColor(HrRequestStatus status) { + if (status == HrRequestStatus.approved) { + return const Color(0xffB6F5D4); + } else if (status == HrRequestStatus.pending) { + return const Color(0xffF5F5F5); + } + return const Color(0xffFFE5E1); +} + +class HrRequestStatusIcon extends StatelessWidget { + final HrRequestStatus status; + + const HrRequestStatusIcon({Key? key, required this.status}) : super(key: key); + + @override + Widget build(BuildContext context) { + if (status == HrRequestStatus.approved) { + return const Icon(Icons.done_all_rounded, + color: AppColors.greenColor, size: 20); + } else if (status == HrRequestStatus.rejected) { + return const Icon(Icons.clear_rounded, + color: AppColors.redColor, size: 20); + } + return const Icon(Icons.query_builder, + color: AppColors.blackColor, size: 20); + } +}