feat: address missing features and bugs identified during Milestone 3 demo; improve localization, error handling, and navigation

This commit is contained in:
Achintha Isuru
2026-02-04 16:45:16 -05:00
parent 9f48ed40d7
commit c05261ddd7
28 changed files with 55 additions and 138 deletions

View File

@@ -27,6 +27,8 @@
- fix "dartdepend_on_referenced_packages" warnings.
- fix "dartunnecessary_library_name" warnings.
- fix lint issues.
- fix localization.
- centralise way to handle errors.
- track minimum shift hours in the staff profile and show a warning if they try to apply for shifts that are below their minimum hours.
- this need to be added in the BE and also a FE validation (5 hrs).

View File

@@ -1,4 +1,3 @@
library;
export 'src/presentation/navigation/billing_navigator.dart';
export 'src/billing_module.dart';

View File

@@ -1,7 +0,0 @@
import 'package:flutter_modular/flutter_modular.dart';
/// Extension on [IModularNavigator] to provide typed navigation for the billing feature.
extension BillingNavigator on IModularNavigator {
/// Navigates to the billing page.
void pushBilling() => pushNamed('/billing/');
}

View File

@@ -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 '../blocs/billing_bloc.dart';
import '../blocs/billing_event.dart';
@@ -83,7 +84,7 @@ class _BillingViewState extends State<BillingView> {
leading: Center(
child: UiIconButton.secondary(
icon: UiIcons.arrowLeft,
onTap: () => Modular.to.navigate('/client-main/home/'),
onTap: () => Modular.to.toClientHome()
),
),
title: AnimatedSwitcher(

View File

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:intl/intl.dart';
import 'package:krow_core/core.dart';
import '../blocs/coverage_bloc.dart';
import '../blocs/coverage_event.dart';
import '../blocs/coverage_state.dart';
@@ -68,7 +69,7 @@ class _CoveragePageState extends State<CoveragePage> {
expandedHeight: 300.0,
backgroundColor: UiColors.primary,
leading: IconButton(
onPressed: () => Modular.to.navigate('/client-main/home/'),
onPressed: () => Modular.to.toClientHome(),
icon: Container(
padding: const EdgeInsets.all(UiConstants.space2),
decoration: BoxDecoration(

View File

@@ -1,6 +1,7 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
import 'coverage_calendar_selector.dart';
/// Header widget for the coverage page.
@@ -67,7 +68,7 @@ class CoverageHeader extends StatelessWidget {
Row(
children: <Widget>[
GestureDetector(
onTap: () => Modular.to.navigate('/client-main/home/'),
onTap: () => Modular.to.toClientHome(),
child: Container(
width: UiConstants.space10,
height: UiConstants.space10,

View File

@@ -1,4 +1,3 @@
library;
export 'src/client_main_module.dart';
export 'src/presentation/navigation/client_main_navigator.dart';

View File

@@ -1,5 +1,6 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
import 'client_main_state.dart';
class ClientMainCubit extends Cubit<ClientMainState> implements Disposable {
@@ -14,15 +15,15 @@ class ClientMainCubit extends Cubit<ClientMainState> implements Disposable {
// Detect which tab is active based on the route path
// Using contains() to handle child routes and trailing slashes
if (path.contains('/client-main/coverage')) {
if (path.contains(ClientPaths.coverage)) {
newIndex = 0;
} else if (path.contains('/client-main/billing')) {
} else if (path.contains(ClientPaths.billing)) {
newIndex = 1;
} else if (path.contains('/client-main/home')) {
} else if (path.contains(ClientPaths.home)) {
newIndex = 2;
} else if (path.contains('/client-main/orders')) {
} else if (path.contains(ClientPaths.viewOrders)) {
newIndex = 3;
} else if (path.contains('/client-main/reports')) {
} else if (path.contains(ClientPaths.reports)) {
newIndex = 4;
}
@@ -36,19 +37,19 @@ class ClientMainCubit extends Cubit<ClientMainState> implements Disposable {
switch (index) {
case 0:
Modular.to.navigate('/client-main/coverage');
Modular.to.navigate(ClientPaths.coverage);
break;
case 1:
Modular.to.navigate('/client-main/billing');
Modular.to.navigate(ClientPaths.billing);
break;
case 2:
Modular.to.navigate('/client-main/home');
Modular.to.navigate(ClientPaths.home);
break;
case 3:
Modular.to.navigate('/client-main/orders');
Modular.to.navigate(ClientPaths.viewOrders);
break;
case 4:
Modular.to.navigate('/client-main/reports');
Modular.to.navigate(ClientPaths.reports);
break;
}
// State update will happen via _onRouteChanged

View File

@@ -1,10 +0,0 @@
import 'package:flutter_modular/flutter_modular.dart';
/// Extension to provide typed navigation for the Client Main feature.
extension ClientMainNavigator on IModularNavigator {
/// Navigates to the Client Main Shell (Home).
/// This replaces the current navigation stack.
void navigateClientMain() {
navigate('/client-main/');
}
}

View File

@@ -1,23 +0,0 @@
import 'package:flutter_modular/flutter_modular.dart';
extension ClientCreateOrderNavigator on IModularNavigator {
void pushCreateOrder() {
pushNamed('/client/create-order/');
}
void pushRapidOrder() {
pushNamed('/client/create-order/rapid');
}
void pushOneTimeOrder() {
pushNamed('/client/create-order/one-time');
}
void pushRecurringOrder() {
pushNamed('/client/create-order/recurring');
}
void pushPermanentOrder() {
pushNamed('/client/create-order/permanent');
}
}

View File

@@ -2,6 +2,7 @@ import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
/// Permanent Order Page - Long-term staffing placement.
/// Placeholder for future implementation.
@@ -18,7 +19,7 @@ class PermanentOrderPage extends StatelessWidget {
backgroundColor: UiColors.bgPrimary,
appBar: UiAppBar(
title: labels.title,
onLeadingPressed: () => Modular.to.navigate('/client/create-order/'),
onLeadingPressed: () => Modular.to.navigate(ClientPaths.createOrder),
),
body: Center(
child: Padding(

View File

@@ -2,6 +2,7 @@ import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
/// Recurring Order Page - Ongoing weekly/monthly coverage.
/// Placeholder for future implementation.
@@ -18,7 +19,7 @@ class RecurringOrderPage extends StatelessWidget {
backgroundColor: UiColors.bgPrimary,
appBar: UiAppBar(
title: labels.title,
onLeadingPressed: () => Modular.to.navigate('/client/create-order/'),
onLeadingPressed: () => Modular.to.toClientHome(),
),
body: Center(
child: Padding(

View File

@@ -3,10 +3,10 @@ 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/client_create_order_bloc.dart';
import '../../blocs/client_create_order_state.dart';
import '../../navigation/client_create_order_navigator.dart';
import '../../ui_entities/order_type_ui_metadata.dart';
import '../order_type_card.dart';
@@ -43,7 +43,7 @@ class CreateOrderView extends StatelessWidget {
backgroundColor: UiColors.bgPrimary,
appBar: UiAppBar(
title: t.client_create_order.title,
onLeadingPressed: () => Modular.to.navigate('/client-main/home/'),
onLeadingPressed: () => Modular.to.toClientHome(),
),
body: SafeArea(
child: Padding(
@@ -98,16 +98,16 @@ class CreateOrderView extends StatelessWidget {
onTap: () {
switch (type.id) {
case 'rapid':
Modular.to.pushRapidOrder();
Modular.to.toCreateOrderRapid();
break;
case 'one-time':
Modular.to.pushOneTimeOrder();
Modular.to.toCreateOrderOneTime();
break;
case 'recurring':
Modular.to.pushRecurringOrder();
Modular.to.toCreateOrderRecurring();
break;
case 'permanent':
Modular.to.pushPermanentOrder();
Modular.to.toCreateOrderPermanent();
break;
}
},

View File

@@ -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/one_time_order_bloc.dart';
import '../../blocs/one_time_order_event.dart';
@@ -50,7 +51,7 @@ class OneTimeOrderView extends StatelessWidget {
OneTimeOrderHeader(
title: labels.title,
subtitle: labels.subtitle,
onBack: () => Modular.to.navigate('/client/create-order/'),
onBack: () => Modular.to.navigate(ClientPaths.createOrder),
),
Expanded(
child: Center(
@@ -89,7 +90,7 @@ class OneTimeOrderView extends StatelessWidget {
OneTimeOrderHeader(
title: labels.title,
subtitle: labels.subtitle,
onBack: () => Modular.to.navigate('/client/create-order/'),
onBack: () => Modular.to.navigate(ClientPaths.createOrder),
),
Expanded(
child: Stack(

View File

@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:intl/intl.dart';
import 'package:krow_core/core.dart';
import '../../blocs/rapid_order_bloc.dart';
import '../../blocs/rapid_order_event.dart';
import '../../blocs/rapid_order_state.dart';
@@ -28,7 +29,7 @@ class RapidOrderView extends StatelessWidget {
title: labels.success_title,
message: labels.success_message,
buttonLabel: labels.back_to_orders,
onDone: () => Modular.to.navigate('/client-main/orders/'),
onDone: () => Modular.to.navigate(ClientPaths.orders),
);
}
@@ -82,7 +83,7 @@ class _RapidOrderFormState extends State<_RapidOrderForm> {
subtitle: labels.subtitle,
date: dateStr,
time: timeStr,
onBack: () => Modular.to.navigate('/client/create-order/'),
onBack: () => Modular.to.navigate(ClientPaths.createOrder),
),
// Content

View File

@@ -9,7 +9,6 @@ import 'src/presentation/blocs/client_home_bloc.dart';
import 'src/presentation/pages/client_home_page.dart';
export 'src/presentation/pages/client_home_page.dart';
export 'src/presentation/navigation/client_home_navigator.dart';
/// A [Module] for the client home feature.
///

View File

@@ -2,10 +2,10 @@ 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/client_home_bloc.dart';
import '../blocs/client_home_event.dart';
import '../blocs/client_home_state.dart';
import '../navigation/client_home_navigator.dart';
import 'header_icon_button.dart';
/// The header section of the client home page.
@@ -95,7 +95,7 @@ class ClientHomeHeader extends StatelessWidget {
),
HeaderIconButton(
icon: UiIcons.settings,
onTap: () => Modular.to.pushSettings(),
onTap: () => Modular.to.toClientSettings(),
),
],
),

View File

@@ -1,20 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import '../widgets/shift_order_form_sheet.dart';
extension ClientHomeNavigator on IModularNavigator {
void pushSettings() {
pushNamed('/client-settings/');
}
void pushCreateOrder() {
pushNamed('/client/create-order/');
}
void pushRapidOrder() {
pushNamed('/client/create-order/rapid');
}
}
import 'shift_order_form_sheet.dart';
/// Helper class for showing modal sheets in the client home feature.
class ClientHomeSheets {

View File

@@ -1,14 +1,15 @@
import 'package:core_localization/core_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
import '../blocs/client_home_state.dart';
import '../navigation/client_home_navigator.dart';
import '../widgets/actions_widget.dart';
import '../widgets/coverage_widget.dart';
import '../widgets/draggable_widget_wrapper.dart';
import '../widgets/live_activity_widget.dart';
import '../widgets/reorder_widget.dart';
import '../widgets/spending_widget.dart';
import 'client_home_sheets.dart';
/// A widget that builds dashboard content based on widget ID.
///
@@ -62,8 +63,8 @@ class DashboardWidgetBuilder extends StatelessWidget {
switch (id) {
case 'actions':
return ActionsWidget(
onRapidPressed: () => Modular.to.pushRapidOrder(),
onCreateOrderPressed: () => Modular.to.pushCreateOrder(),
onRapidPressed: () => Modular.to.toCreateOrderRapid(),
onCreateOrderPressed: () => Modular.to.toCreateOrder(),
subtitle: subtitle,
);
case 'reorder':
@@ -116,7 +117,7 @@ class DashboardWidgetBuilder extends StatelessWidget {
);
case 'liveActivity':
return LiveActivityWidget(
onViewAllPressed: () => Modular.to.navigate('/client-main/coverage/'),
onViewAllPressed: () => Modular.to.toClientCoverage(),
subtitle: subtitle,
);
default:

View File

@@ -1,9 +0,0 @@
import 'package:flutter_modular/flutter_modular.dart';
/// Extension on [IModularNavigator] to provide typed navigation for client hubs.
extension ClientHubsNavigator on IModularNavigator {
/// Navigates to the client hubs page.
Future<void> pushClientHubs() async {
await pushNamed('/client-hubs/');
}
}

View File

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:core_localization/core_localization.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../blocs/client_hubs_bloc.dart';
import '../blocs/client_hubs_event.dart';
@@ -179,7 +180,7 @@ class ClientHubsPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
GestureDetector(
onTap: () => Modular.to.navigate('/client-main/home/'),
onTap: () => Modular.to.toClientHome(),
child: Container(
width: 40,
height: 40,

View File

@@ -1,15 +0,0 @@
import 'package:flutter_modular/flutter_modular.dart';
/// Extension on [IModularNavigator] to provide strongly-typed navigation
/// for the client settings feature.
extension ClientSettingsNavigator on IModularNavigator {
/// Navigates to the client settings page.
void pushClientSettings() {
pushNamed('/client/settings/');
}
/// Navigates to the hubs page.
void pushHubs() {
pushNamed('/client-hubs/');
}
}

View File

@@ -1,9 +1,9 @@
import 'package:client_settings/src/presentation/navigation/client_settings_navigator.dart';
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/client_settings_bloc.dart';
/// A widget that displays the primary actions for the settings page.
@@ -30,7 +30,7 @@ class SettingsActions extends StatelessWidget {
// Hubs button
UiButton.primary(
text: labels.hubs,
onPressed: () => Modular.to.pushHubs(),
onPressed: () => Modular.to.toClientHubs(),
),
const SizedBox(height: UiConstants.space4),

View File

@@ -2,6 +2,7 @@ import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
/// A widget that displays the profile header with avatar and company info.
@@ -30,7 +31,7 @@ class SettingsProfileHeader extends StatelessWidget {
shape: const Border(bottom: BorderSide(color: UiColors.border, width: 1)),
leading: IconButton(
icon: const Icon(UiIcons.chevronLeft, color: UiColors.textSecondary),
onPressed: () => Modular.to.navigate('/client-main/home/'),
onPressed: () => Modular.to.toClientHome(),
),
flexibleSpace: FlexibleSpaceBar(
background: Container(

View File

@@ -2,7 +2,7 @@ import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import '../../navigation/client_settings_navigator.dart';
import 'package:krow_core/core.dart';
/// A widget that displays a list of quick links in a card.
class SettingsQuickLinks extends StatelessWidget {
@@ -37,7 +37,7 @@ class SettingsQuickLinks extends StatelessWidget {
_QuickLinkItem(
icon: UiIcons.nfc,
title: labels.clock_in_hubs,
onTap: () => Modular.to.pushHubs(),
onTap: () => Modular.to.toClientHubs(),
),
_QuickLinkItem(

View File

@@ -1,14 +0,0 @@
import 'package:flutter_modular/flutter_modular.dart';
/// Extension to provide typed navigation for the View Orders feature.
extension ViewOrdersNavigator on IModularNavigator {
/// Navigates to the Create Order feature.
void navigateToCreateOrder() {
navigate('/client/create-order/');
}
/// Navigates to the Order Details (placeholder for now).
void navigateToOrderDetails(String orderId) {
// pushNamed('/view-orders/$orderId');
}
}

View File

@@ -5,12 +5,12 @@ import 'package:flutter_modular/flutter_modular.dart';
import 'package:intl/intl.dart';
import 'package:core_localization/core_localization.dart';
import 'package:krow_core/core.dart';
import '../blocs/view_orders_cubit.dart';
import '../blocs/view_orders_state.dart';
import 'package:krow_domain/krow_domain.dart';
import '../widgets/view_order_card.dart';
import '../widgets/view_orders_header.dart';
import '../navigation/view_orders_navigator.dart';
/// The main page for viewing client orders.
///
@@ -191,7 +191,7 @@ class _ViewOrdersViewState extends State<ViewOrdersView> {
UiButton.primary(
text: t.client_view_orders.post_order,
leadingIcon: UiIcons.add,
onPressed: () => Modular.to.navigateToCreateOrder(),
onPressed: () => Modular.to.toCreateOrder(),
),
],
),

View File

@@ -5,10 +5,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:intl/intl.dart';
import 'package:core_localization/core_localization.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../blocs/view_orders_cubit.dart';
import '../blocs/view_orders_state.dart';
import '../navigation/view_orders_navigator.dart';
import 'view_orders_filter_tab.dart';
/// The sticky header section for the View Orders page.
@@ -69,7 +69,7 @@ class ViewOrdersHeader extends StatelessWidget {
UiButton.primary(
text: t.client_view_orders.post_button,
leadingIcon: UiIcons.add,
onPressed: () => Modular.to.navigateToCreateOrder(),
onPressed: () => Modular.to.toCreateOrder(),
size: UiButtonSize.small,
style: ElevatedButton.styleFrom(
minimumSize: const Size(0, 48),