feat: Adds operation pages and refactors navigation
Introduces dedicated pages and routes for Delivery and Inventory operations. Refactors the application drawer to include navigation links for these new operations, ensuring proper route transitions and closing the drawer on tap. Enhances the drawer's active state styling for list and expansion tiles, providing better visual feedback. Extracts common app bar logic into a reusable `MainAppbarComponent` and integrates it into operation pages, starting with Reception, to standardize the application's header. Standardizes routing paths for operation pages and ensures consistent page transitions across them.
This commit is contained in:
parent
48556dbeed
commit
4682be5b62
@ -4,3 +4,4 @@ export 'primary_button_component.dart';
|
||||
export 'product_scanned_component.dart';
|
||||
export 'outline_button_component.dart';
|
||||
export 'quick_action_component.dart';
|
||||
export 'main_appbar_component.dart';
|
||||
|
@ -88,26 +88,34 @@ class _DrawerComponentState extends ConsumerState<DrawerComponent> {
|
||||
isActive: selected.isSelected(menu: 0),
|
||||
children: [
|
||||
_ListTile(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
ReceptionRoute().go(context);
|
||||
},
|
||||
title: 'Réceptions',
|
||||
isActive: selected.isSelected(menu: 0, sub: 0),
|
||||
leadingIcon: Icons.move_to_inbox,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
),
|
||||
_ListTile(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
DeliveryRoute().go(context);
|
||||
},
|
||||
title: 'Livraisons',
|
||||
isActive: selected.isSelected(menu: 0, sub: 1),
|
||||
leadingIcon: Icons.local_shipping,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
),
|
||||
_ListTile(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
InventoryRoute().go(context);
|
||||
},
|
||||
title: 'Inventaires',
|
||||
isActive: selected.isSelected(menu: 0, sub: 2),
|
||||
leadingIcon: Icons.inventory,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
ProductListRoute().push(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -218,11 +226,15 @@ class _ListTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
selected: isActive,
|
||||
selectedTileColor: AppTheme.of(context).primary.withValues(alpha: 0.1),
|
||||
onTap: onTap,
|
||||
contentPadding: contentPadding,
|
||||
leading: Icon(
|
||||
leadingIcon,
|
||||
color: isActive ? AppTheme.of(context).primary : null,
|
||||
color: isActive
|
||||
? AppTheme.of(context).primary
|
||||
: AppTheme.of(context).primaryText,
|
||||
),
|
||||
title: Text(
|
||||
title,
|
||||
@ -253,32 +265,39 @@ class _ExpansionTileComponent extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpansionTile(
|
||||
initiallyExpanded: isExpanded,
|
||||
onExpansionChanged: onExpansionChanged,
|
||||
tilePadding: const EdgeInsetsDirectional.symmetric(horizontal: 10),
|
||||
shape: LinearBorder.none,
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(
|
||||
leadingIcon,
|
||||
color: isActive ? AppTheme.of(context).primary : null,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: AppTheme.of(context).bodyLarge.copyWith(
|
||||
color: isActive ? AppTheme.of(context).primary : null,
|
||||
return Container(
|
||||
color: isActive
|
||||
? AppTheme.of(context).primary.withValues(alpha: 0.05)
|
||||
: null,
|
||||
child: ExpansionTile(
|
||||
initiallyExpanded: isExpanded,
|
||||
onExpansionChanged: onExpansionChanged,
|
||||
tilePadding: const EdgeInsetsDirectional.symmetric(horizontal: 10),
|
||||
shape: LinearBorder.none,
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(
|
||||
leadingIcon,
|
||||
color: isActive
|
||||
? AppTheme.of(context).primary
|
||||
: AppTheme.of(context).primaryText,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: AppTheme.of(context).bodyLarge.copyWith(
|
||||
color: isActive ? AppTheme.of(context).primary : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
trailing: Icon(
|
||||
isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_right,
|
||||
),
|
||||
children: children,
|
||||
),
|
||||
trailing: Icon(
|
||||
isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_right,
|
||||
),
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
57
lib/components/main_appbar_component.dart
Normal file
57
lib/components/main_appbar_component.dart
Normal file
@ -0,0 +1,57 @@
|
||||
import 'package:barcode_scanner/themes/app_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MainAppbarComponent extends StatelessWidget
|
||||
implements PreferredSizeWidget {
|
||||
const MainAppbarComponent({
|
||||
super.key,
|
||||
this.title,
|
||||
this.subTitle,
|
||||
this.scaffoledKey,
|
||||
});
|
||||
final GlobalKey<ScaffoldState>? scaffoledKey;
|
||||
final String? title;
|
||||
final String? subTitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBar(
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.menu, color: AppTheme.of(context).white),
|
||||
onPressed: () {
|
||||
scaffoledKey?.currentState?.openDrawer();
|
||||
},
|
||||
),
|
||||
title: title == null || subTitle == null
|
||||
? null
|
||||
: Row(
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title ?? '',
|
||||
style: AppTheme.of(
|
||||
context,
|
||||
).titleMedium.copyWith(color: AppTheme.of(context).white),
|
||||
),
|
||||
Text(
|
||||
subTitle ?? '',
|
||||
style: AppTheme.of(
|
||||
context,
|
||||
).bodyMedium.copyWith(color: AppTheme.of(context).white),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
toolbarHeight: 60,
|
||||
backgroundColor: AppTheme.of(context).primary,
|
||||
elevation: 4,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Size get preferredSize => Size.fromHeight(kToolbarHeight);
|
||||
}
|
59
lib/pages/operation/delivery/delivery_page.dart
Normal file
59
lib/pages/operation/delivery/delivery_page.dart
Normal file
@ -0,0 +1,59 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:barcode_scanner/components/components.dart';
|
||||
import 'package:barcode_scanner/themes/app_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DeliveryPage extends ConsumerStatefulWidget {
|
||||
const DeliveryPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<DeliveryPage> createState() => _DeliveryPageState();
|
||||
}
|
||||
|
||||
class _DeliveryPageState extends ConsumerState<DeliveryPage> {
|
||||
final globalKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppTheme.of(context).primaryBackground,
|
||||
key: globalKey,
|
||||
drawer: DrawerComponent(
|
||||
isOperationExpanded: true,
|
||||
selectedState: SelectedDrawerState(menuIndex: 0, subMenuIndex: 1),
|
||||
),
|
||||
appBar: MainAppbarComponent(
|
||||
scaffoledKey: globalKey,
|
||||
title: "Livraisons",
|
||||
subTitle: "Opérations d'Entrepôt",
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ListView(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
QuickActionComponent(),
|
||||
const SizedBox(height: 16),
|
||||
Card(
|
||||
color: AppTheme.of(context).secondaryBackground,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
side: BorderSide(color: AppTheme.of(context).alternate),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text(
|
||||
'Activité Récente',
|
||||
style: AppTheme.of(
|
||||
context,
|
||||
).bodyMedium.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
59
lib/pages/operation/inventory/inventory_page.dart
Normal file
59
lib/pages/operation/inventory/inventory_page.dart
Normal file
@ -0,0 +1,59 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:barcode_scanner/components/components.dart';
|
||||
import 'package:barcode_scanner/themes/app_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class InventoryPage extends ConsumerStatefulWidget {
|
||||
const InventoryPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<InventoryPage> createState() => _InventoryPageState();
|
||||
}
|
||||
|
||||
class _InventoryPageState extends ConsumerState<InventoryPage> {
|
||||
final globalKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppTheme.of(context).primaryBackground,
|
||||
key: globalKey,
|
||||
drawer: DrawerComponent(
|
||||
isOperationExpanded: true,
|
||||
selectedState: SelectedDrawerState(menuIndex: 0, subMenuIndex: 2),
|
||||
),
|
||||
appBar: MainAppbarComponent(
|
||||
scaffoledKey: globalKey,
|
||||
title: "Inventaires",
|
||||
subTitle: "Opérations d'Entrepôt",
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ListView(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
QuickActionComponent(),
|
||||
const SizedBox(height: 16),
|
||||
Card(
|
||||
color: AppTheme.of(context).secondaryBackground,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
side: BorderSide(color: AppTheme.of(context).alternate),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text(
|
||||
'Activité Récente',
|
||||
style: AppTheme.of(
|
||||
context,
|
||||
).bodyMedium.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1 +1,3 @@
|
||||
export 'reception/reception_page.dart';
|
||||
export 'delivery/delivery_page.dart';
|
||||
export 'inventory/inventory_page.dart';
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:barcode_scanner/components/components.dart';
|
||||
import 'package:barcode_scanner/components/drawer_component.dart';
|
||||
import 'package:barcode_scanner/pages/operation/reception/reception_page_model.dart';
|
||||
import 'package:barcode_scanner/themes/app_theme.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
@ -29,38 +28,10 @@ class _ReceptionPageState extends ConsumerState<ReceptionPage> {
|
||||
backgroundColor: AppTheme.of(context).primaryBackground,
|
||||
key: globalKey,
|
||||
drawer: DrawerComponent(isOperationExpanded: true),
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.menu, color: AppTheme.of(context).white),
|
||||
onPressed: () {
|
||||
globalKey.currentState?.openDrawer();
|
||||
},
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Réceptions",
|
||||
style: AppTheme.of(
|
||||
context,
|
||||
).titleMedium.copyWith(color: AppTheme.of(context).white),
|
||||
),
|
||||
Text(
|
||||
"Opérations d'Entrepôt",
|
||||
style: AppTheme.of(
|
||||
context,
|
||||
).bodyMedium.copyWith(color: AppTheme.of(context).white),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
toolbarHeight: 60,
|
||||
backgroundColor: AppTheme.of(context).primary,
|
||||
elevation: 4,
|
||||
appBar: MainAppbarComponent(
|
||||
scaffoledKey: globalKey,
|
||||
title: "Réceptions",
|
||||
subTitle: "Opérations d'Entrepôt",
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
|
@ -2,6 +2,7 @@ import 'package:barcode_scanner/pages/login/login_page_model.dart';
|
||||
import 'package:barcode_scanner/provider_container.dart';
|
||||
import 'package:barcode_scanner/router/go_router_builder.dart';
|
||||
import 'package:barcode_scanner/pages/pages.dart';
|
||||
import 'package:barcode_scanner/router/router.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
export 'package:go_router/go_router.dart';
|
||||
part 'go_secure_router_builder.g.dart';
|
||||
@ -16,7 +17,9 @@ final appSecureRoutes = $appRoutes;
|
||||
TypedGoRoute<ProductFormRoute>(path: 'ProductFormPage'),
|
||||
TypedGoRoute<ProductListRoute>(path: 'ProductListPage'),
|
||||
TypedGoRoute<ProfileRoute>(path: 'ProfilePage'),
|
||||
TypedGoRoute<ReceptionRoute>(path: 'ReceptionRoute'),
|
||||
TypedGoRoute<ReceptionRoute>(path: 'ReceptionPage'),
|
||||
TypedGoRoute<DeliveryRoute>(path: 'DeliveryPage'),
|
||||
TypedGoRoute<InventoryRoute>(path: 'InventoryPage'),
|
||||
],
|
||||
)
|
||||
class SecureRoute extends GoRouteData with _$SecureRoute {
|
||||
@ -70,5 +73,40 @@ class ReceptionRoute extends GoRouteData with _$ReceptionRoute {
|
||||
const ReceptionRoute();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) => ReceptionPage();
|
||||
CustomTransitionPage<void> buildPage(
|
||||
BuildContext context,
|
||||
GoRouterState state,
|
||||
) => buildPageWithDefaultTransition(
|
||||
context: context,
|
||||
state: state,
|
||||
child: ReceptionPage(),
|
||||
);
|
||||
}
|
||||
|
||||
class DeliveryRoute extends GoRouteData with _$DeliveryRoute {
|
||||
const DeliveryRoute();
|
||||
|
||||
@override
|
||||
CustomTransitionPage<void> buildPage(
|
||||
BuildContext context,
|
||||
GoRouterState state,
|
||||
) => buildPageWithDefaultTransition(
|
||||
context: context,
|
||||
state: state,
|
||||
child: DeliveryPage(),
|
||||
);
|
||||
}
|
||||
|
||||
class InventoryRoute extends GoRouteData with _$InventoryRoute {
|
||||
const InventoryRoute();
|
||||
|
||||
@override
|
||||
CustomTransitionPage<void> buildPage(
|
||||
BuildContext context,
|
||||
GoRouterState state,
|
||||
) => buildPageWithDefaultTransition(
|
||||
context: context,
|
||||
state: state,
|
||||
child: InventoryPage(),
|
||||
);
|
||||
}
|
||||
|
@ -26,10 +26,20 @@ RouteBase get $secureRoute => GoRouteData.$route(
|
||||
),
|
||||
GoRouteData.$route(path: 'ProfilePage', factory: _$ProfileRoute._fromState),
|
||||
GoRouteData.$route(
|
||||
path: 'ReceptionRoute',
|
||||
path: 'ReceptionPage',
|
||||
|
||||
factory: _$ReceptionRoute._fromState,
|
||||
),
|
||||
GoRouteData.$route(
|
||||
path: 'DeliveryPage',
|
||||
|
||||
factory: _$DeliveryRoute._fromState,
|
||||
),
|
||||
GoRouteData.$route(
|
||||
path: 'InventoryPage',
|
||||
|
||||
factory: _$InventoryRoute._fromState,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@ -145,7 +155,48 @@ mixin _$ReceptionRoute on GoRouteData {
|
||||
const ReceptionRoute();
|
||||
|
||||
@override
|
||||
String get location => GoRouteData.$location('/SecurePage/ReceptionRoute');
|
||||
String get location => GoRouteData.$location('/SecurePage/ReceptionPage');
|
||||
|
||||
@override
|
||||
void go(BuildContext context) => context.go(location);
|
||||
|
||||
@override
|
||||
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||
|
||||
@override
|
||||
void pushReplacement(BuildContext context) =>
|
||||
context.pushReplacement(location);
|
||||
|
||||
@override
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
|
||||
mixin _$DeliveryRoute on GoRouteData {
|
||||
static DeliveryRoute _fromState(GoRouterState state) => const DeliveryRoute();
|
||||
|
||||
@override
|
||||
String get location => GoRouteData.$location('/SecurePage/DeliveryPage');
|
||||
|
||||
@override
|
||||
void go(BuildContext context) => context.go(location);
|
||||
|
||||
@override
|
||||
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||
|
||||
@override
|
||||
void pushReplacement(BuildContext context) =>
|
||||
context.pushReplacement(location);
|
||||
|
||||
@override
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
|
||||
mixin _$InventoryRoute on GoRouteData {
|
||||
static InventoryRoute _fromState(GoRouterState state) =>
|
||||
const InventoryRoute();
|
||||
|
||||
@override
|
||||
String get location => GoRouteData.$location('/SecurePage/InventoryPage');
|
||||
|
||||
@override
|
||||
void go(BuildContext context) => context.go(location);
|
||||
|
Loading…
x
Reference in New Issue
Block a user