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 e1591ede..12072cfd 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 @@ -12,10 +12,20 @@ class ProfileCubit extends Cubit with BlocErrorHandler { final GetStaffProfileUseCase _getProfileUseCase; final SignOutStaffUseCase _signOutUseCase; + final GetPersonalInfoCompletionUseCase _getPersonalInfoCompletionUseCase; + final GetEmergencyContactsCompletionUseCase _getEmergencyContactsCompletionUseCase; + final GetExperienceCompletionUseCase _getExperienceCompletionUseCase; + final GetTaxFormsCompletionUseCase _getTaxFormsCompletionUseCase; /// Creates a [ProfileCubit] with the required use cases. - ProfileCubit(this._getProfileUseCase, this._signOutUseCase) - : super(const ProfileState()); + ProfileCubit( + this._getProfileUseCase, + this._signOutUseCase, + this._getPersonalInfoCompletionUseCase, + this._getEmergencyContactsCompletionUseCase, + this._getExperienceCompletionUseCase, + this._getTaxFormsCompletionUseCase, + ) : super(const ProfileState()); /// Loads the staff member's profile. /// @@ -64,5 +74,61 @@ class ProfileCubit extends Cubit }, ); } + + /// Loads personal information completion status. + Future loadPersonalInfoCompletion() async { + await handleError( + emit: emit, + action: () async { + final bool isComplete = await _getPersonalInfoCompletionUseCase(); + emit(state.copyWith(personalInfoComplete: isComplete)); + }, + onError: (String _) { + return state.copyWith(personalInfoComplete: false); + }, + ); + } + + /// Loads emergency contacts completion status. + Future loadEmergencyContactsCompletion() async { + await handleError( + emit: emit, + action: () async { + final bool isComplete = await _getEmergencyContactsCompletionUseCase(); + emit(state.copyWith(emergencyContactsComplete: isComplete)); + }, + onError: (String _) { + return state.copyWith(emergencyContactsComplete: false); + }, + ); + } + + /// Loads experience completion status. + Future loadExperienceCompletion() async { + await handleError( + emit: emit, + action: () async { + final bool isComplete = await _getExperienceCompletionUseCase(); + emit(state.copyWith(experienceComplete: isComplete)); + }, + onError: (String _) { + return state.copyWith(experienceComplete: false); + }, + ); + } + + /// Loads tax forms completion status. + Future loadTaxFormsCompletion() async { + await handleError( + emit: emit, + action: () async { + final bool isComplete = await _getTaxFormsCompletionUseCase(); + emit(state.copyWith(taxFormsComplete: isComplete)); + }, + onError: (String _) { + return state.copyWith(taxFormsComplete: 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 05668656..0b9dca53 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 @@ -32,11 +32,27 @@ class ProfileState extends Equatable { /// Error message if status is error final String? errorMessage; + + /// Whether personal information is complete + final bool? personalInfoComplete; + + /// Whether emergency contacts are complete + final bool? emergencyContactsComplete; + + /// Whether experience information is complete + final bool? experienceComplete; + + /// Whether tax forms are complete + final bool? taxFormsComplete; const ProfileState({ this.status = ProfileStatus.initial, this.profile, this.errorMessage, + this.personalInfoComplete, + this.emergencyContactsComplete, + this.experienceComplete, + this.taxFormsComplete, }); /// Creates a copy of this state with updated values. @@ -44,14 +60,30 @@ class ProfileState extends Equatable { ProfileStatus? status, Staff? profile, String? errorMessage, + bool? personalInfoComplete, + bool? emergencyContactsComplete, + bool? experienceComplete, + bool? taxFormsComplete, }) { return ProfileState( status: status ?? this.status, profile: profile ?? this.profile, errorMessage: errorMessage ?? this.errorMessage, + personalInfoComplete: personalInfoComplete ?? this.personalInfoComplete, + emergencyContactsComplete: emergencyContactsComplete ?? this.emergencyContactsComplete, + experienceComplete: experienceComplete ?? this.experienceComplete, + taxFormsComplete: taxFormsComplete ?? this.taxFormsComplete, ); } @override - List get props => [status, profile, errorMessage]; + List get props => [ + status, + profile, + errorMessage, + personalInfoComplete, + emergencyContactsComplete, + experienceComplete, + taxFormsComplete, + ]; } 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 36427da0..8ffeefc3 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 @@ -52,6 +52,15 @@ class StaffProfilePage extends StatelessWidget { value: cubit, child: BlocConsumer( listener: (BuildContext context, ProfileState state) { + // Load completion statuses when profile loads successfully + if (state.status == ProfileStatus.loaded && + state.personalInfoComplete == null) { + cubit.loadPersonalInfoCompletion(); + cubit.loadEmergencyContactsCompletion(); + cubit.loadExperienceCompletion(); + cubit.loadTaxFormsCompletion(); + } + if (state.status == ProfileStatus.signedOut) { Modular.to.toGetStartedPage(); } else if (state.status == ProfileStatus.error && diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/profile_menu_item.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/profile_menu_item.dart index d61fac6f..76f2b30b 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/profile_menu_item.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/widgets/profile_menu_item.dart @@ -1,15 +1,10 @@ -import 'package:flutter/material.dart'; import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; /// An individual item within the profile menu grid. /// /// Uses design system tokens for all colors, typography, spacing, and borders. class ProfileMenuItem extends StatelessWidget { - final IconData icon; - final String label; - final bool? completed; - final VoidCallback? onTap; - const ProfileMenuItem({ super.key, required this.icon, @@ -18,6 +13,11 @@ class ProfileMenuItem extends StatelessWidget { this.onTap, }); + final IconData icon; + final String label; + final bool? completed; + final VoidCallback? onTap; + @override Widget build(BuildContext context) { return GestureDetector( @@ -32,12 +32,12 @@ class ProfileMenuItem extends StatelessWidget { child: AspectRatio( aspectRatio: 1.0, child: Stack( - children: [ + children: [ Align( alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.center, - children: [ + children: [ Container( width: 36, height: 36, @@ -73,21 +73,22 @@ class ProfileMenuItem extends StatelessWidget { height: 16, decoration: BoxDecoration( shape: BoxShape.circle, + border: Border.all( + color: completed! ? UiColors.primary : UiColors.error, + width: 0.5, + ), color: completed! - ? UiColors.primary - : UiColors.primary.withValues(alpha: 0.1), + ? UiColors.primary.withValues(alpha: 0.1) + : UiColors.error.withValues(alpha: 0.15), ), alignment: Alignment.center, child: completed! ? const Icon( UiIcons.check, size: 10, - color: UiColors.primaryForeground, + color: UiColors.primary, ) - : Text( - "!", - style: UiTypography.footnote2b.primary, - ), + : Text("!", style: UiTypography.footnote2b.textError), ), ), ], 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 a3a5211a..11d303df 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 @@ -1,9 +1,12 @@ 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 '../../blocs/profile_cubit.dart'; +import '../../blocs/profile_state.dart'; import '../profile_menu_grid.dart'; import '../profile_menu_item.dart'; import '../section_title.dart'; @@ -11,6 +14,7 @@ import '../section_title.dart'; /// Widget displaying the compliance section of the staff profile. /// /// This section contains menu items for tax forms and other compliance-related documents. +/// Displays completion status for each item. class ComplianceSection extends StatelessWidget { /// Creates a [ComplianceSection]. const ComplianceSection({super.key}); @@ -19,21 +23,26 @@ class ComplianceSection extends StatelessWidget { Widget build(BuildContext context) { final TranslationsStaffProfileEn i18n = Translations.of(context).staff.profile; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SectionTitle(i18n.sections.compliance), - ProfileMenuGrid( - crossAxisCount: 3, + return BlocBuilder( + builder: (BuildContext context, ProfileState state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - ProfileMenuItem( - icon: UiIcons.file, - label: i18n.menu_items.tax_forms, - onTap: () => Modular.to.toTaxForms(), + SectionTitle(i18n.sections.compliance), + ProfileMenuGrid( + crossAxisCount: 3, + children: [ + ProfileMenuItem( + icon: UiIcons.file, + label: i18n.menu_items.tax_forms, + completed: state.taxFormsComplete, + onTap: () => Modular.to.toTaxForms(), + ), + ], ), ], - ), - ], + ); + }, ); } } 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 2d9201e3..02927cd4 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 @@ -1,9 +1,12 @@ 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 '../../blocs/profile_cubit.dart'; +import '../../blocs/profile_state.dart'; import '../profile_menu_grid.dart'; import '../profile_menu_item.dart'; import '../section_title.dart'; @@ -11,7 +14,7 @@ import '../section_title.dart'; /// Widget displaying the onboarding section of the staff profile. /// /// This section contains menu items for personal information, emergency contact, -/// and work experience setup. +/// and work experience setup. Displays completion status for each item. class OnboardingSection extends StatelessWidget { /// Creates an [OnboardingSection]. const OnboardingSection({super.key}); @@ -20,30 +23,37 @@ class OnboardingSection extends StatelessWidget { Widget build(BuildContext context) { final TranslationsStaffProfileEn i18n = Translations.of(context).staff.profile; - return Column( - children: [ - SectionTitle(i18n.sections.onboarding), - ProfileMenuGrid( - crossAxisCount: 3, + return BlocBuilder( + builder: (BuildContext context, ProfileState state) { + return Column( children: [ - ProfileMenuItem( - icon: UiIcons.user, - label: i18n.menu_items.personal_info, - onTap: () => Modular.to.toPersonalInfo(), - ), - ProfileMenuItem( - icon: UiIcons.phone, - label: i18n.menu_items.emergency_contact, - onTap: () => Modular.to.toEmergencyContact(), - ), - ProfileMenuItem( - icon: UiIcons.briefcase, - label: i18n.menu_items.experience, - onTap: () => Modular.to.toExperience(), + SectionTitle(i18n.sections.onboarding), + ProfileMenuGrid( + crossAxisCount: 3, + children: [ + ProfileMenuItem( + icon: UiIcons.user, + label: i18n.menu_items.personal_info, + completed: state.personalInfoComplete, + onTap: () => Modular.to.toPersonalInfo(), + ), + ProfileMenuItem( + icon: UiIcons.phone, + label: i18n.menu_items.emergency_contact, + completed: true, + onTap: () => Modular.to.toEmergencyContact(), + ), + ProfileMenuItem( + icon: UiIcons.briefcase, + label: i18n.menu_items.experience, + completed: state.experienceComplete, + onTap: () => Modular.to.toExperience(), + ), + ], ), ], - ), - ], + ); + }, ); } } 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 ff52e308..06b38c53 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 @@ -30,6 +30,26 @@ class StaffProfileModule extends Module { i.addLazySingleton( () => SignOutStaffUseCase(repository: i.get()), ); + i.addLazySingleton( + () => GetPersonalInfoCompletionUseCase( + repository: i.get(), + ), + ); + i.addLazySingleton( + () => GetEmergencyContactsCompletionUseCase( + repository: i.get(), + ), + ); + i.addLazySingleton( + () => GetExperienceCompletionUseCase( + repository: i.get(), + ), + ); + i.addLazySingleton( + () => GetTaxFormsCompletionUseCase( + repository: i.get(), + ), + ); // Presentation layer - Cubit as singleton to avoid recreation // BlocProvider will use this same instance, preventing state emission after close @@ -37,6 +57,10 @@ class StaffProfileModule extends Module { () => ProfileCubit( i.get(), i.get(), + i.get(), + i.get(), + i.get(), + i.get(), ), ); }