diff --git a/apps/mobile/packages/data_connect/lib/krow_data_connect.dart b/apps/mobile/packages/data_connect/lib/krow_data_connect.dart index 378eb395..7f6f5187 100644 --- a/apps/mobile/packages/data_connect/lib/krow_data_connect.dart +++ b/apps/mobile/packages/data_connect/lib/krow_data_connect.dart @@ -24,6 +24,9 @@ export 'src/connectors/staff/domain/usecases/get_personal_info_completion_usecas export 'src/connectors/staff/domain/usecases/get_emergency_contacts_completion_usecase.dart'; export 'src/connectors/staff/domain/usecases/get_experience_completion_usecase.dart'; export 'src/connectors/staff/domain/usecases/get_tax_forms_completion_usecase.dart'; +export 'src/connectors/staff/domain/usecases/get_attire_options_completion_usecase.dart'; +export 'src/connectors/staff/domain/usecases/get_staff_documents_completion_usecase.dart'; +export 'src/connectors/staff/domain/usecases/get_staff_certificates_completion_usecase.dart'; export 'src/connectors/staff/domain/usecases/get_staff_profile_usecase.dart'; export 'src/connectors/staff/domain/usecases/sign_out_staff_usecase.dart'; export 'src/connectors/staff/data/repositories/staff_connector_repository_impl.dart'; diff --git a/apps/mobile/packages/data_connect/lib/src/connectors/staff/data/repositories/staff_connector_repository_impl.dart b/apps/mobile/packages/data_connect/lib/src/connectors/staff/data/repositories/staff_connector_repository_impl.dart index 634a4dcb..0b679eb1 100644 --- a/apps/mobile/packages/data_connect/lib/src/connectors/staff/data/repositories/staff_connector_repository_impl.dart +++ b/apps/mobile/packages/data_connect/lib/src/connectors/staff/data/repositories/staff_connector_repository_impl.dart @@ -2,6 +2,7 @@ import 'package:firebase_data_connect/firebase_data_connect.dart'; import 'package:krow_core/core.dart'; import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_domain/krow_domain.dart' as domain; + import '../../domain/repositories/staff_connector_repository.dart'; /// Implementation of [StaffConnectorRepository]. @@ -122,6 +123,125 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository { }); } + @override + Future getAttireOptionsCompletion() async { + return _service.run(() async { + final String staffId = await _service.getStaffId(); + + final List> results = + await Future.wait>( + >>[ + _service.connector.listAttireOptions().execute(), + _service.connector.getStaffAttire(staffId: staffId).execute(), + ], + ); + + final QueryResult optionsRes = + results[0] as QueryResult; + final QueryResult + staffAttireRes = + results[1] + as QueryResult; + + final List attireOptions = + optionsRes.data.attireOptions; + final List staffAttire = + staffAttireRes.data.staffAttires; + + // Get only mandatory attire options + final List mandatoryOptions = + attireOptions + .where((dc.ListAttireOptionsAttireOptions opt) => + opt.isMandatory ?? false) + .toList(); + + // Return null if no mandatory attire options + if (mandatoryOptions.isEmpty) return null; + + // Return true only if all mandatory attire items are verified + return mandatoryOptions.every( + (dc.ListAttireOptionsAttireOptions mandatoryOpt) { + final dc.GetStaffAttireStaffAttires? currentAttire = staffAttire + .where( + (dc.GetStaffAttireStaffAttires a) => + a.attireOptionId == mandatoryOpt.id, + ) + .firstOrNull; + + if (currentAttire == null) return false; // Not uploaded + if (currentAttire.verificationStatus is dc.Unknown) return false; + final dc.AttireVerificationStatus status = + (currentAttire.verificationStatus + as dc.Known) + .value; + return status == dc.AttireVerificationStatus.APPROVED; + }, + ); + }); + } + + @override + Future getStaffDocumentsCompletion() async { + return _service.run(() async { + final String staffId = await _service.getStaffId(); + + final QueryResult< + dc.ListStaffDocumentsByStaffIdData, + dc.ListStaffDocumentsByStaffIdVariables + > + response = await _service.connector + .listStaffDocumentsByStaffId(staffId: staffId) + .execute(); + + final List staffDocs = + response.data.staffDocuments; + + // Return null if no documents + if (staffDocs.isEmpty) return null; + + // Return true only if all documents are verified + return staffDocs.every( + (dc.ListStaffDocumentsByStaffIdStaffDocuments doc) { + if (doc.status is dc.Unknown) return false; + final dc.DocumentStatus status = + (doc.status as dc.Known).value; + return status == dc.DocumentStatus.VERIFIED; + }, + ); + }); + } + + @override + Future getStaffCertificatesCompletion() async { + return _service.run(() async { + final String staffId = await _service.getStaffId(); + + final QueryResult< + dc.ListCertificatesByStaffIdData, + dc.ListCertificatesByStaffIdVariables + > + response = await _service.connector + .listCertificatesByStaffId(staffId: staffId) + .execute(); + + final List certificates = + response.data.certificates; + + // Return false if no certificates + if (certificates.isEmpty) return null; + + // Return true only if all certificates are fully validated + return certificates.every( + (dc.ListCertificatesByStaffIdCertificates cert) { + if (cert.validationStatus is dc.Unknown) return false; + final dc.ValidationStatus status = + (cert.validationStatus as dc.Known).value; + return status == dc.ValidationStatus.APPROVED; + }, + ); + }); + } + /// Checks if personal info is complete. bool _isPersonalInfoComplete(dc.GetStaffPersonalInfoCompletionStaff? staff) { if (staff == null) return false; @@ -208,8 +328,8 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository { return response.data.benefitsDatas.map(( dc.ListBenefitsDataByStaffIdBenefitsDatas e, ) { - final total = e.vendorBenefitPlan.total?.toDouble() ?? 0.0; - final remaining = e.current.toDouble(); + final double total = e.vendorBenefitPlan.total?.toDouble() ?? 0.0; + final double remaining = e.current.toDouble(); return domain.Benefit( title: e.vendorBenefitPlan.title, entitlementHours: total, diff --git a/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/repositories/staff_connector_repository.dart b/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/repositories/staff_connector_repository.dart index 7a54aea0..f3ba6ac6 100644 --- a/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/repositories/staff_connector_repository.dart +++ b/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/repositories/staff_connector_repository.dart @@ -33,6 +33,21 @@ abstract interface class StaffConnectorRepository { /// Returns true if at least one tax form exists. Future getTaxFormsCompletion(); + /// Fetches attire options completion status. + /// + /// Returns true if all mandatory attire options are verified. + Future getAttireOptionsCompletion(); + + /// Fetches documents completion status. + /// + /// Returns true if all mandatory documents are verified. + Future getStaffDocumentsCompletion(); + + /// Fetches certificates completion status. + /// + /// Returns true if all certificates are validated. + Future getStaffCertificatesCompletion(); + /// Fetches the full staff profile for the current authenticated user. /// /// Returns a [Staff] entity containing all profile information. diff --git a/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/usecases/get_attire_options_completion_usecase.dart b/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/usecases/get_attire_options_completion_usecase.dart new file mode 100644 index 00000000..acf51396 --- /dev/null +++ b/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/usecases/get_attire_options_completion_usecase.dart @@ -0,0 +1,27 @@ +import 'package:krow_core/core.dart'; + +import '../repositories/staff_connector_repository.dart'; + +/// Use case for retrieving attire options completion status. +/// +/// This use case encapsulates the business logic for determining whether +/// a staff member has fully uploaded and verified all mandatory attire options. +/// It delegates to the repository for data access. +class GetAttireOptionsCompletionUseCase extends NoInputUseCase { + /// Creates a [GetAttireOptionsCompletionUseCase]. + /// + /// Requires a [StaffConnectorRepository] for data access. + GetAttireOptionsCompletionUseCase({ + required StaffConnectorRepository repository, + }) : _repository = repository; + + final StaffConnectorRepository _repository; + + /// Executes the use case to get attire options completion status. + /// + /// Returns true if all mandatory attire options are verified, false otherwise. + /// + /// Throws an exception if the operation fails. + @override + Future call() => _repository.getAttireOptionsCompletion(); +} diff --git a/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/usecases/get_staff_certificates_completion_usecase.dart b/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/usecases/get_staff_certificates_completion_usecase.dart new file mode 100644 index 00000000..a77238c7 --- /dev/null +++ b/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/usecases/get_staff_certificates_completion_usecase.dart @@ -0,0 +1,27 @@ +import 'package:krow_core/core.dart'; + +import '../repositories/staff_connector_repository.dart'; + +/// Use case for retrieving certificates completion status. +/// +/// This use case encapsulates the business logic for determining whether +/// a staff member has fully validated all certificates. +/// It delegates to the repository for data access. +class GetStaffCertificatesCompletionUseCase extends NoInputUseCase { + /// Creates a [GetStaffCertificatesCompletionUseCase]. + /// + /// Requires a [StaffConnectorRepository] for data access. + GetStaffCertificatesCompletionUseCase({ + required StaffConnectorRepository repository, + }) : _repository = repository; + + final StaffConnectorRepository _repository; + + /// Executes the use case to get certificates completion status. + /// + /// Returns true if all certificates are validated, false otherwise. + /// + /// Throws an exception if the operation fails. + @override + Future call() => _repository.getStaffCertificatesCompletion(); +} diff --git a/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/usecases/get_staff_documents_completion_usecase.dart b/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/usecases/get_staff_documents_completion_usecase.dart new file mode 100644 index 00000000..4bbe85db --- /dev/null +++ b/apps/mobile/packages/data_connect/lib/src/connectors/staff/domain/usecases/get_staff_documents_completion_usecase.dart @@ -0,0 +1,27 @@ +import 'package:krow_core/core.dart'; + +import '../repositories/staff_connector_repository.dart'; + +/// Use case for retrieving documents completion status. +/// +/// This use case encapsulates the business logic for determining whether +/// a staff member has fully uploaded and verified all mandatory documents. +/// It delegates to the repository for data access. +class GetStaffDocumentsCompletionUseCase extends NoInputUseCase { + /// Creates a [GetStaffDocumentsCompletionUseCase]. + /// + /// Requires a [StaffConnectorRepository] for data access. + GetStaffDocumentsCompletionUseCase({ + required StaffConnectorRepository repository, + }) : _repository = repository; + + final StaffConnectorRepository _repository; + + /// Executes the use case to get documents completion status. + /// + /// Returns true if all mandatory documents are verified, false otherwise. + /// + /// Throws an exception if the operation fails. + @override + Future call() => _repository.getStaffDocumentsCompletion(); +} diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/benefits_overview_page.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/benefits_overview_page.dart index 0fc38f98..b017c9f2 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/benefits_overview_page.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/benefits_overview_page.dart @@ -1,12 +1,12 @@ +import 'dart:math' as math; + +import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_modular/flutter_modular.dart'; -import 'package:krow_core/core.dart'; import 'package:krow_domain/krow_domain.dart'; -import 'package:core_localization/core_localization.dart'; import 'package:staff_home/src/presentation/blocs/home_cubit.dart'; -import 'dart:math' as math; /// Page displaying a detailed overview of the worker's benefits. class BenefitsOverviewPage extends StatelessWidget { diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/blocs/profile_cubit.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/blocs/profile_cubit.dart index 141a9c2b..db64fa43 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/presentation/blocs/profile_cubit.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/blocs/profile_cubit.dart @@ -19,6 +19,9 @@ class ProfileCubit extends Cubit this._getEmergencyContactsCompletionUseCase, this._getExperienceCompletionUseCase, this._getTaxFormsCompletionUseCase, + this._getAttireOptionsCompletionUseCase, + this._getStaffDocumentsCompletionUseCase, + this._getStaffCertificatesCompletionUseCase, ) : super(const ProfileState()); final GetStaffProfileUseCase _getProfileUseCase; final SignOutStaffUseCase _signOutUseCase; @@ -26,6 +29,9 @@ class ProfileCubit extends Cubit final GetEmergencyContactsCompletionUseCase _getEmergencyContactsCompletionUseCase; final GetExperienceCompletionUseCase _getExperienceCompletionUseCase; final GetTaxFormsCompletionUseCase _getTaxFormsCompletionUseCase; + final GetAttireOptionsCompletionUseCase _getAttireOptionsCompletionUseCase; + final GetStaffDocumentsCompletionUseCase _getStaffDocumentsCompletionUseCase; + final GetStaffCertificatesCompletionUseCase _getStaffCertificatesCompletionUseCase; /// Loads the staff member's profile. /// @@ -130,5 +136,47 @@ class ProfileCubit extends Cubit }, ); } + + /// Loads attire options completion status. + Future loadAttireCompletion() async { + await handleError( + emit: emit, + action: () async { + final bool? isComplete = await _getAttireOptionsCompletionUseCase(); + emit(state.copyWith(attireComplete: isComplete)); + }, + onError: (String _) { + return state.copyWith(attireComplete: false); + }, + ); + } + + /// Loads documents completion status. + Future loadDocumentsCompletion() async { + await handleError( + emit: emit, + action: () async { + final bool? isComplete = await _getStaffDocumentsCompletionUseCase(); + emit(state.copyWith(documentsComplete: isComplete)); + }, + onError: (String _) { + return state.copyWith(documentsComplete: false); + }, + ); + } + + /// Loads certificates completion status. + Future loadCertificatesCompletion() async { + await handleError( + emit: emit, + action: () async { + final bool? isComplete = await _getStaffCertificatesCompletionUseCase(); + emit(state.copyWith(certificatesComplete: isComplete)); + }, + onError: (String _) { + return state.copyWith(certificatesComplete: false); + }, + ); + } } diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/blocs/profile_state.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/blocs/profile_state.dart index 39994b97..5c0b3903 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/presentation/blocs/profile_state.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/blocs/profile_state.dart @@ -33,6 +33,9 @@ class ProfileState extends Equatable { this.emergencyContactsComplete, this.experienceComplete, this.taxFormsComplete, + this.attireComplete, + this.documentsComplete, + this.certificatesComplete, }); /// Current status of the profile feature final ProfileStatus status; @@ -54,6 +57,15 @@ class ProfileState extends Equatable { /// Whether tax forms are complete final bool? taxFormsComplete; + + /// Whether attire options are complete + final bool? attireComplete; + + /// Whether documents are complete + final bool? documentsComplete; + + /// Whether certificates are complete + final bool? certificatesComplete; /// Creates a copy of this state with updated values. ProfileState copyWith({ @@ -64,6 +76,9 @@ class ProfileState extends Equatable { bool? emergencyContactsComplete, bool? experienceComplete, bool? taxFormsComplete, + bool? attireComplete, + bool? documentsComplete, + bool? certificatesComplete, }) { return ProfileState( status: status ?? this.status, @@ -73,6 +88,9 @@ class ProfileState extends Equatable { emergencyContactsComplete: emergencyContactsComplete ?? this.emergencyContactsComplete, experienceComplete: experienceComplete ?? this.experienceComplete, taxFormsComplete: taxFormsComplete ?? this.taxFormsComplete, + attireComplete: attireComplete ?? this.attireComplete, + documentsComplete: documentsComplete ?? this.documentsComplete, + certificatesComplete: certificatesComplete ?? this.certificatesComplete, ); } @@ -85,5 +103,8 @@ class ProfileState extends Equatable { emergencyContactsComplete, experienceComplete, taxFormsComplete, + attireComplete, + documentsComplete, + certificatesComplete, ]; } diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/pages/staff_profile_page.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/pages/staff_profile_page.dart index 49767da9..8bec14f2 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/presentation/pages/staff_profile_page.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/pages/staff_profile_page.dart @@ -46,6 +46,9 @@ class StaffProfilePage extends StatelessWidget { cubit.loadEmergencyContactsCompletion(); cubit.loadExperienceCompletion(); cubit.loadTaxFormsCompletion(); + cubit.loadAttireCompletion(); + cubit.loadDocumentsCompletion(); + cubit.loadCertificatesCompletion(); } if (state.status == ProfileStatus.signedOut) { diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/compliance_section.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/compliance_section.dart index b3a700e4..8db16a18 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/compliance_section.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/compliance_section.dart @@ -43,11 +43,13 @@ class ComplianceSection extends StatelessWidget { ProfileMenuItem( icon: UiIcons.file, label: i18n.menu_items.documents, + completed: state.documentsComplete, onTap: () => Modular.to.toDocuments(), ), ProfileMenuItem( icon: UiIcons.certificate, label: i18n.menu_items.certificates, + completed: state.certificatesComplete, onTap: () => Modular.to.toCertificates(), ), ], diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/onboarding_section.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/onboarding_section.dart index 327e58ea..0cb1e574 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/onboarding_section.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/sections/onboarding_section.dart @@ -54,6 +54,7 @@ class OnboardingSection extends StatelessWidget { ProfileMenuItem( icon: UiIcons.shirt, label: i18n.menu_items.attire, + completed: state.attireComplete, onTap: () => Modular.to.toAttire(), ), ], diff --git a/apps/mobile/packages/features/staff/profile/lib/src/staff_profile_module.dart b/apps/mobile/packages/features/staff/profile/lib/src/staff_profile_module.dart index 06b38c53..f9b720cb 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/staff_profile_module.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/staff_profile_module.dart @@ -50,6 +50,21 @@ class StaffProfileModule extends Module { repository: i.get(), ), ); + i.addLazySingleton( + () => GetAttireOptionsCompletionUseCase( + repository: i.get(), + ), + ); + i.addLazySingleton( + () => GetStaffDocumentsCompletionUseCase( + repository: i.get(), + ), + ); + i.addLazySingleton( + () => GetStaffCertificatesCompletionUseCase( + repository: i.get(), + ), + ); // Presentation layer - Cubit as singleton to avoid recreation // BlocProvider will use this same instance, preventing state emission after close @@ -61,6 +76,9 @@ class StaffProfileModule extends Module { i.get(), i.get(), i.get(), + i.get(), + i.get(), + i.get(), ), ); }