feat: Implement completion status tracking for personal info, emergency contacts, experience, and tax forms in profile management
This commit is contained in:
@@ -12,10 +12,20 @@ class ProfileCubit extends Cubit<ProfileState>
|
||||
with BlocErrorHandler<ProfileState> {
|
||||
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<ProfileState>
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Loads personal information completion status.
|
||||
Future<void> 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<void> 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<void> 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<void> 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);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,10 +33,26 @@ 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<Object?> get props => [status, profile, errorMessage];
|
||||
List<Object?> get props => <Object?>[
|
||||
status,
|
||||
profile,
|
||||
errorMessage,
|
||||
personalInfoComplete,
|
||||
emergencyContactsComplete,
|
||||
experienceComplete,
|
||||
taxFormsComplete,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -52,6 +52,15 @@ class StaffProfilePage extends StatelessWidget {
|
||||
value: cubit,
|
||||
child: BlocConsumer<ProfileCubit, ProfileState>(
|
||||
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 &&
|
||||
|
||||
@@ -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: <Widget>[
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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,6 +23,8 @@ class ComplianceSection extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final TranslationsStaffProfileEn i18n = Translations.of(context).staff.profile;
|
||||
|
||||
return BlocBuilder<ProfileCubit, ProfileState>(
|
||||
builder: (BuildContext context, ProfileState state) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
@@ -29,11 +35,14 @@ class ComplianceSection extends StatelessWidget {
|
||||
ProfileMenuItem(
|
||||
icon: UiIcons.file,
|
||||
label: i18n.menu_items.tax_forms,
|
||||
completed: state.taxFormsComplete,
|
||||
onTap: () => Modular.to.toTaxForms(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,6 +23,8 @@ class OnboardingSection extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final TranslationsStaffProfileEn i18n = Translations.of(context).staff.profile;
|
||||
|
||||
return BlocBuilder<ProfileCubit, ProfileState>(
|
||||
builder: (BuildContext context, ProfileState state) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
SectionTitle(i18n.sections.onboarding),
|
||||
@@ -29,21 +34,26 @@ class OnboardingSection extends StatelessWidget {
|
||||
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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,26 @@ class StaffProfileModule extends Module {
|
||||
i.addLazySingleton<SignOutStaffUseCase>(
|
||||
() => SignOutStaffUseCase(repository: i.get<StaffConnectorRepository>()),
|
||||
);
|
||||
i.addLazySingleton<GetPersonalInfoCompletionUseCase>(
|
||||
() => GetPersonalInfoCompletionUseCase(
|
||||
repository: i.get<StaffConnectorRepository>(),
|
||||
),
|
||||
);
|
||||
i.addLazySingleton<GetEmergencyContactsCompletionUseCase>(
|
||||
() => GetEmergencyContactsCompletionUseCase(
|
||||
repository: i.get<StaffConnectorRepository>(),
|
||||
),
|
||||
);
|
||||
i.addLazySingleton<GetExperienceCompletionUseCase>(
|
||||
() => GetExperienceCompletionUseCase(
|
||||
repository: i.get<StaffConnectorRepository>(),
|
||||
),
|
||||
);
|
||||
i.addLazySingleton<GetTaxFormsCompletionUseCase>(
|
||||
() => GetTaxFormsCompletionUseCase(
|
||||
repository: i.get<StaffConnectorRepository>(),
|
||||
),
|
||||
);
|
||||
|
||||
// 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<GetStaffProfileUseCase>(),
|
||||
i.get<SignOutStaffUseCase>(),
|
||||
i.get<GetPersonalInfoCompletionUseCase>(),
|
||||
i.get<GetEmergencyContactsCompletionUseCase>(),
|
||||
i.get<GetExperienceCompletionUseCase>(),
|
||||
i.get<GetTaxFormsCompletionUseCase>(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user