feat: Add post-save navigation to staff profile for emergency contact and experience, remove a placeholder page, and refine bloc usage and UI rendering.
This commit is contained in:
@@ -64,7 +64,7 @@ extension StaffNavigator on IModularNavigator {
|
||||
/// This is typically called after successful phone verification for new
|
||||
/// staff members. Uses pushReplacement to prevent going back to verification.
|
||||
void toProfileSetup() {
|
||||
pushReplacementNamed(StaffPaths.profileSetup);
|
||||
pushNamed(StaffPaths.profileSetup);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
@@ -76,7 +76,7 @@ extension StaffNavigator on IModularNavigator {
|
||||
/// This is the main landing page for authenticated staff members.
|
||||
/// Displays shift cards, quick actions, and notifications.
|
||||
void toStaffHome() {
|
||||
pushNamed(StaffPaths.home);
|
||||
pushNamedAndRemoveUntil(StaffPaths.home, (_) => false);
|
||||
}
|
||||
|
||||
/// Navigates to the staff main shell.
|
||||
@@ -84,7 +84,7 @@ extension StaffNavigator on IModularNavigator {
|
||||
/// This is the container with bottom navigation. Navigates to home tab
|
||||
/// by default. Usually you'd navigate to a specific tab instead.
|
||||
void toStaffMain() {
|
||||
navigate('${StaffPaths.main}/home/');
|
||||
pushNamedAndRemoveUntil('${StaffPaths.main}/home/', (_) => false);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
@@ -113,8 +113,9 @@ extension StaffNavigator on IModularNavigator {
|
||||
if (refreshAvailable == true) {
|
||||
args['refreshAvailable'] = true;
|
||||
}
|
||||
navigate(
|
||||
pushNamedAndRemoveUntil(
|
||||
StaffPaths.shifts,
|
||||
(_) => false,
|
||||
arguments: args.isEmpty ? null : args,
|
||||
);
|
||||
}
|
||||
@@ -123,21 +124,21 @@ extension StaffNavigator on IModularNavigator {
|
||||
///
|
||||
/// View payment history, earnings breakdown, and tax information.
|
||||
void toPayments() {
|
||||
navigate(StaffPaths.payments);
|
||||
pushNamedAndRemoveUntil(StaffPaths.payments, (_) => false);
|
||||
}
|
||||
|
||||
/// Navigates to the Clock In tab.
|
||||
///
|
||||
/// Access time tracking interface for active shifts.
|
||||
void toClockIn() {
|
||||
navigate(StaffPaths.clockIn);
|
||||
pushNamedAndRemoveUntil(StaffPaths.clockIn, (_) => false);
|
||||
}
|
||||
|
||||
/// Navigates to the Profile tab.
|
||||
///
|
||||
/// Manage personal information, documents, and preferences.
|
||||
void toProfile() {
|
||||
navigate(StaffPaths.profile);
|
||||
pushNamedAndRemoveUntil(StaffPaths.profile, (_) => false);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
@@ -155,10 +156,7 @@ extension StaffNavigator on IModularNavigator {
|
||||
/// The shift object is passed as an argument and can be retrieved
|
||||
/// in the details page.
|
||||
void toShiftDetails(Shift shift) {
|
||||
navigate(
|
||||
StaffPaths.shiftDetails(shift.id),
|
||||
arguments: shift,
|
||||
);
|
||||
navigate(StaffPaths.shiftDetails(shift.id), arguments: shift);
|
||||
}
|
||||
|
||||
/// Pushes the shift details page (alternative method).
|
||||
@@ -167,10 +165,7 @@ extension StaffNavigator on IModularNavigator {
|
||||
/// Use this when you want to add the details page to the stack rather
|
||||
/// than replacing the current route.
|
||||
void pushShiftDetails(Shift shift) {
|
||||
pushNamed(
|
||||
StaffPaths.shiftDetails(shift.id),
|
||||
arguments: shift,
|
||||
);
|
||||
pushNamed(StaffPaths.shiftDetails(shift.id), arguments: shift);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
|
||||
@@ -40,13 +40,13 @@ class HomeCubit extends Cubit<HomeState> with BlocErrorHandler<HomeState> {
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) {
|
||||
if (isClosed) return state; // Avoid state emission if closed, though emit handles it gracefully usually
|
||||
if (isClosed)
|
||||
return state; // Avoid state emission if closed, though emit handles it gracefully usually
|
||||
return state.copyWith(status: HomeStatus.error, errorMessage: errorKey);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void toggleAutoMatch(bool enabled) {
|
||||
emit(state.copyWith(autoMatchEnabled: enabled));
|
||||
}
|
||||
|
||||
@@ -63,9 +63,6 @@ class WorkerHomePage extends StatelessWidget {
|
||||
child: Column(
|
||||
children: [
|
||||
BlocBuilder<HomeCubit, HomeState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.isProfileComplete !=
|
||||
current.isProfileComplete,
|
||||
builder: (context, state) {
|
||||
if (state.isProfileComplete) return const SizedBox();
|
||||
return PlaceholderBanner(
|
||||
|
||||
@@ -39,7 +39,6 @@ class EmergencyContactScreen extends StatelessWidget {
|
||||
body: BlocProvider(
|
||||
create: (context) => Modular.get<EmergencyContactBloc>(),
|
||||
child: BlocConsumer<EmergencyContactBloc, EmergencyContactState>(
|
||||
|
||||
listener: (context, state) {
|
||||
if (state.status == EmergencyContactStatus.failure) {
|
||||
UiSnackbar.show(
|
||||
|
||||
@@ -2,13 +2,17 @@ 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/emergency_contact_bloc.dart';
|
||||
|
||||
class EmergencyContactSaveButton extends StatelessWidget {
|
||||
const EmergencyContactSaveButton({super.key});
|
||||
|
||||
void _onSave(BuildContext context) {
|
||||
context.read<EmergencyContactBloc>().add(EmergencyContactsSaved());
|
||||
BlocProvider.of<EmergencyContactBloc>(
|
||||
context,
|
||||
).add(EmergencyContactsSaved());
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -19,10 +23,13 @@ class EmergencyContactSaveButton extends StatelessWidget {
|
||||
if (state.status == EmergencyContactStatus.saved) {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: t.staff.profile.menu_items.emergency_contact_page.save_success,
|
||||
message:
|
||||
t.staff.profile.menu_items.emergency_contact_page.save_success,
|
||||
type: UiSnackbarType.success,
|
||||
margin: const EdgeInsets.only(bottom: 150, left: 16, right: 16),
|
||||
);
|
||||
|
||||
Modular.to.toProfile();
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
@@ -36,8 +43,9 @@ class EmergencyContactSaveButton extends StatelessWidget {
|
||||
child: SafeArea(
|
||||
child: UiButton.primary(
|
||||
fullWidth: true,
|
||||
onPressed:
|
||||
state.isValid && !isLoading ? () => _onSave(context) : null,
|
||||
onPressed: state.isValid && !isLoading
|
||||
? () => _onSave(context)
|
||||
: null,
|
||||
child: isLoading
|
||||
? const SizedBox(
|
||||
height: 20.0,
|
||||
@@ -49,7 +57,14 @@ class EmergencyContactSaveButton extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
)
|
||||
: Text(t.staff.profile.menu_items.emergency_contact_page.save_continue),
|
||||
: Text(
|
||||
t
|
||||
.staff
|
||||
.profile
|
||||
.menu_items
|
||||
.emergency_contact_page
|
||||
.save_continue,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ 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 '../blocs/experience_bloc.dart';
|
||||
@@ -13,34 +14,57 @@ class ExperiencePage extends StatelessWidget {
|
||||
|
||||
String _getIndustryLabel(dynamic node, Industry industry) {
|
||||
switch (industry) {
|
||||
case Industry.hospitality: return node.hospitality;
|
||||
case Industry.foodService: return node.food_service;
|
||||
case Industry.warehouse: return node.warehouse;
|
||||
case Industry.events: return node.events;
|
||||
case Industry.retail: return node.retail;
|
||||
case Industry.healthcare: return node.healthcare;
|
||||
case Industry.other: return node.other;
|
||||
case Industry.hospitality:
|
||||
return node.hospitality;
|
||||
case Industry.foodService:
|
||||
return node.food_service;
|
||||
case Industry.warehouse:
|
||||
return node.warehouse;
|
||||
case Industry.events:
|
||||
return node.events;
|
||||
case Industry.retail:
|
||||
return node.retail;
|
||||
case Industry.healthcare:
|
||||
return node.healthcare;
|
||||
case Industry.other:
|
||||
return node.other;
|
||||
}
|
||||
}
|
||||
|
||||
String _getSkillLabel(dynamic node, ExperienceSkill skill) {
|
||||
switch (skill) {
|
||||
case ExperienceSkill.foodService: return node.food_service;
|
||||
case ExperienceSkill.bartending: return node.bartending;
|
||||
case ExperienceSkill.eventSetup: return node.event_setup;
|
||||
case ExperienceSkill.hospitality: return node.hospitality;
|
||||
case ExperienceSkill.warehouse: return node.warehouse;
|
||||
case ExperienceSkill.customerService: return node.customer_service;
|
||||
case ExperienceSkill.cleaning: return node.cleaning;
|
||||
case ExperienceSkill.security: return node.security;
|
||||
case ExperienceSkill.retail: return node.retail;
|
||||
case ExperienceSkill.driving: return node.driving;
|
||||
case ExperienceSkill.cooking: return node.cooking;
|
||||
case ExperienceSkill.cashier: return node.cashier;
|
||||
case ExperienceSkill.server: return node.server;
|
||||
case ExperienceSkill.barista: return node.barista;
|
||||
case ExperienceSkill.hostHostess: return node.host_hostess;
|
||||
case ExperienceSkill.busser: return node.busser;
|
||||
case ExperienceSkill.foodService:
|
||||
return node.food_service;
|
||||
case ExperienceSkill.bartending:
|
||||
return node.bartending;
|
||||
case ExperienceSkill.eventSetup:
|
||||
return node.event_setup;
|
||||
case ExperienceSkill.hospitality:
|
||||
return node.hospitality;
|
||||
case ExperienceSkill.warehouse:
|
||||
return node.warehouse;
|
||||
case ExperienceSkill.customerService:
|
||||
return node.customer_service;
|
||||
case ExperienceSkill.cleaning:
|
||||
return node.cleaning;
|
||||
case ExperienceSkill.security:
|
||||
return node.security;
|
||||
case ExperienceSkill.retail:
|
||||
return node.retail;
|
||||
case ExperienceSkill.driving:
|
||||
return node.driving;
|
||||
case ExperienceSkill.cooking:
|
||||
return node.cooking;
|
||||
case ExperienceSkill.cashier:
|
||||
return node.cashier;
|
||||
case ExperienceSkill.server:
|
||||
return node.server;
|
||||
case ExperienceSkill.barista:
|
||||
return node.barista;
|
||||
case ExperienceSkill.hostHostess:
|
||||
return node.host_hostess;
|
||||
case ExperienceSkill.busser:
|
||||
return node.busser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +75,7 @@ class ExperiencePage extends StatelessWidget {
|
||||
return Scaffold(
|
||||
appBar: UiAppBar(
|
||||
title: i18n.title,
|
||||
onLeadingPressed: () => Modular.to.pop(),
|
||||
onLeadingPressed: () => Modular.to.toProfile(),
|
||||
),
|
||||
body: BlocProvider<ExperienceBloc>(
|
||||
create: (context) => Modular.get<ExperienceBloc>(),
|
||||
@@ -68,7 +92,6 @@ class ExperiencePage extends StatelessWidget {
|
||||
right: UiConstants.space4,
|
||||
),
|
||||
);
|
||||
Modular.to.pop();
|
||||
} else if (state.status == ExperienceStatus.failure) {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
@@ -106,13 +129,13 @@ class ExperiencePage extends StatelessWidget {
|
||||
.map(
|
||||
(i) => UiChip(
|
||||
label: _getIndustryLabel(i18n.industries, i),
|
||||
isSelected:
|
||||
state.selectedIndustries.contains(i),
|
||||
onTap: () =>
|
||||
BlocProvider.of<ExperienceBloc>(context)
|
||||
.add(ExperienceIndustryToggled(i)),
|
||||
variant:
|
||||
state.selectedIndustries.contains(i)
|
||||
isSelected: state.selectedIndustries.contains(
|
||||
i,
|
||||
),
|
||||
onTap: () => BlocProvider.of<ExperienceBloc>(
|
||||
context,
|
||||
).add(ExperienceIndustryToggled(i)),
|
||||
variant: state.selectedIndustries.contains(i)
|
||||
? UiChipVariant.primary
|
||||
: UiChipVariant.secondary,
|
||||
),
|
||||
@@ -133,11 +156,12 @@ class ExperiencePage extends StatelessWidget {
|
||||
.map(
|
||||
(s) => UiChip(
|
||||
label: _getSkillLabel(i18n.skills, s),
|
||||
isSelected:
|
||||
state.selectedSkills.contains(s.value),
|
||||
onTap: () =>
|
||||
BlocProvider.of<ExperienceBloc>(context)
|
||||
.add(ExperienceSkillToggled(s.value)),
|
||||
isSelected: state.selectedSkills.contains(
|
||||
s.value,
|
||||
),
|
||||
onTap: () => BlocProvider.of<ExperienceBloc>(
|
||||
context,
|
||||
).add(ExperienceSkillToggled(s.value)),
|
||||
variant:
|
||||
state.selectedSkills.contains(s.value)
|
||||
? UiChipVariant.primary
|
||||
@@ -177,10 +201,7 @@ class ExperiencePage extends StatelessWidget {
|
||||
spacing: UiConstants.space2,
|
||||
runSpacing: UiConstants.space2,
|
||||
children: customSkills.map((skill) {
|
||||
return UiChip(
|
||||
label: skill,
|
||||
variant: UiChipVariant.accent,
|
||||
);
|
||||
return UiChip(label: skill, variant: UiChipVariant.accent);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
@@ -202,8 +223,9 @@ class ExperiencePage extends StatelessWidget {
|
||||
child: UiButton.primary(
|
||||
onPressed: state.status == ExperienceStatus.loading
|
||||
? null
|
||||
: () => BlocProvider.of<ExperienceBloc>(context)
|
||||
.add(ExperienceSubmitted()),
|
||||
: () => BlocProvider.of<ExperienceBloc>(
|
||||
context,
|
||||
).add(ExperienceSubmitted()),
|
||||
fullWidth: true,
|
||||
text: state.status == ExperienceStatus.loading
|
||||
? null
|
||||
|
||||
@@ -12,13 +12,18 @@ class StaffMainCubit extends Cubit<StaffMainState> implements Disposable {
|
||||
super(const StaffMainState()) {
|
||||
Modular.to.addListener(_onRouteChanged);
|
||||
_onRouteChanged();
|
||||
_loadProfileCompletion();
|
||||
}
|
||||
|
||||
final GetProfileCompletionUseCase _getProfileCompletionUsecase;
|
||||
bool _isLoadingCompletion = false;
|
||||
|
||||
void _onRouteChanged() {
|
||||
if (isClosed) return;
|
||||
|
||||
// Refresh completion status whenever route changes to catch profile updates
|
||||
// only if it's not already complete.
|
||||
refreshProfileCompletion();
|
||||
|
||||
final String path = Modular.to.path;
|
||||
int newIndex = state.currentIndex;
|
||||
|
||||
@@ -41,7 +46,10 @@ class StaffMainCubit extends Cubit<StaffMainState> implements Disposable {
|
||||
}
|
||||
|
||||
/// Loads the profile completion status.
|
||||
Future<void> _loadProfileCompletion() async {
|
||||
Future<void> refreshProfileCompletion() async {
|
||||
if (_isLoadingCompletion || isClosed) return;
|
||||
|
||||
_isLoadingCompletion = true;
|
||||
try {
|
||||
final isComplete = await _getProfileCompletionUsecase();
|
||||
if (!isClosed) {
|
||||
@@ -53,6 +61,8 @@ class StaffMainCubit extends Cubit<StaffMainState> implements Disposable {
|
||||
if (!isClosed) {
|
||||
emit(state.copyWith(isProfileComplete: true));
|
||||
}
|
||||
} finally {
|
||||
_isLoadingCompletion = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PlaceholderPage extends StatelessWidget {
|
||||
const PlaceholderPage({required this.title, super.key});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(title)),
|
||||
body: Center(child: Text('$title Page')),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user