barcode_scanner/lib/components/drawer_component.dart
mandreshope 4682be5b62 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.
2025-07-24 11:07:10 +03:00

304 lines
9.6 KiB
Dart

import 'package:barcode_scanner/backend/schema/user/user_struct.dart';
import 'package:barcode_scanner/pages/login/login_page_model.dart';
import 'package:barcode_scanner/pages/operation/reception/reception_page_model.dart';
import 'package:barcode_scanner/router/go_router_builder.dart';
import 'package:barcode_scanner/router/go_secure_router_builder.dart';
import 'package:barcode_scanner/themes/app_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class DrawerComponent extends ConsumerStatefulWidget {
const DrawerComponent({
super.key,
this.isOperationExpanded = true,
this.isSettingsExpanded = false,
this.selectedState = const SelectedDrawerState(
menuIndex: 0,
subMenuIndex: 0,
),
});
final bool isOperationExpanded;
final bool isSettingsExpanded;
final SelectedDrawerState selectedState;
@override
ConsumerState<DrawerComponent> createState() => _DrawerComponentState();
}
class _DrawerComponentState extends ConsumerState<DrawerComponent> {
late bool _isOperationExpanded;
late bool _isSettingsExpanded;
@override
void initState() {
super.initState();
_isOperationExpanded = widget.isOperationExpanded;
_isSettingsExpanded = widget.isSettingsExpanded;
}
@override
Widget build(BuildContext context) {
final selected = widget.selectedState;
return Drawer(
backgroundColor: AppTheme.of(context).primaryBackground,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16),
child: Column(
children: [
const SizedBox(height: 50),
// === Profil utilisateur
Consumer(
builder: (context, ref, child) {
final state = ref.watch(receptionPageModelProvider);
return ListTile(
onTap: () => ProfileRoute().push(context),
contentPadding: EdgeInsets.zero,
leading: Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: AppTheme.of(context).secondaryBackground,
borderRadius: BorderRadius.circular(60),
),
),
title: Text(
state.user?.fullName ?? '',
style: AppTheme.of(context).titleMedium,
),
subtitle: Text(
state.user?.email ?? '',
style: AppTheme.of(context).bodyMedium,
),
);
},
),
const Divider(),
// === Opérations
_ExpansionTileComponent(
leadingIcon: Icons.inbox,
isExpanded: _isOperationExpanded,
onExpansionChanged: (expanded) =>
setState(() => _isOperationExpanded = expanded),
title: 'Opérations',
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),
),
],
),
// === Produits
_ListTile(
title: "Produits",
leadingIcon: Icons.insert_chart,
trailing: const Icon(Icons.keyboard_arrow_right),
isActive: selected.isSelected(menu: 1),
contentPadding: const EdgeInsetsDirectional.symmetric(
horizontal: 10,
),
),
// === Historique
_ListTile(
title: "Historiques",
leadingIcon: Icons.history,
trailing: const Icon(Icons.keyboard_arrow_right),
isActive: selected.isSelected(menu: 2),
contentPadding: const EdgeInsetsDirectional.symmetric(
horizontal: 10,
),
),
// === Paramètres
_ExpansionTileComponent(
title: "Paramètres",
isExpanded: _isSettingsExpanded,
onExpansionChanged: (expanded) =>
setState(() => _isSettingsExpanded = expanded),
isActive: selected.isSelected(menu: 3),
leadingIcon: Icons.settings,
children: [
_ListTile(
title: 'Connexion Odoo',
isActive: selected.isSelected(menu: 3, sub: 3),
leadingIcon: Icons.lock_open,
contentPadding: const EdgeInsets.symmetric(horizontal: 40),
),
_ListTile(
title: 'Gestion des utilisateurs',
isActive: selected.isSelected(menu: 3, sub: 4),
leadingIcon: Icons.group,
contentPadding: const EdgeInsets.symmetric(horizontal: 40),
),
],
),
const Spacer(),
// === Déconnexion
SafeArea(
child: ListTile(
onTap: () async {
await ref.read(loginPageModelProvider.notifier).logOut();
await UserStruct(id: '1').deleteLocalStorage();
WidgetsBinding.instance.addPostFrameCallback((_) {
SplashRoute().go(context);
});
},
leading: const Icon(Icons.logout),
title: Text(
'Se déconnecter',
style: AppTheme.of(context).bodyLarge,
),
),
),
],
),
),
);
}
}
class SelectedDrawerState {
const SelectedDrawerState({
required this.menuIndex,
required this.subMenuIndex,
});
final int menuIndex;
final int subMenuIndex;
bool isSelected({required int menu, int? sub}) {
if (sub == null) return menuIndex == menu;
return menuIndex == menu && subMenuIndex == sub;
}
}
class _ListTile extends StatelessWidget {
const _ListTile({
Key? key,
required this.title,
this.isActive = false,
this.onTap,
this.leadingIcon,
this.contentPadding,
this.trailing,
}) : super(key: key);
final bool isActive;
final String title;
final VoidCallback? onTap;
final IconData? leadingIcon;
final EdgeInsetsGeometry? contentPadding;
final Widget? trailing;
@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
: AppTheme.of(context).primaryText,
),
title: Text(
title,
style: AppTheme.of(context).bodyLarge.copyWith(
color: isActive ? AppTheme.of(context).primary : null,
),
),
trailing: trailing,
);
}
}
class _ExpansionTileComponent extends StatelessWidget {
const _ExpansionTileComponent({
required this.title,
this.isExpanded = false,
this.onExpansionChanged,
this.isActive = false,
this.children = const <Widget>[],
this.leadingIcon,
});
final bool isExpanded;
final void Function(bool)? onExpansionChanged;
final bool isActive;
final List<Widget> children;
final String title;
final IconData? leadingIcon;
@override
Widget build(BuildContext context) {
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,
),
);
}
}