2025-09-22 21:21:46 +03:30
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
|
import '../../core/auth_store.dart';
|
2025-09-25 01:01:27 +03:30
|
|
|
import '../../core/locale_controller.dart';
|
2025-09-22 21:21:46 +03:30
|
|
|
import '../../core/calendar_controller.dart';
|
2025-09-25 01:01:27 +03:30
|
|
|
import '../../theme/theme_controller.dart';
|
|
|
|
|
import '../../widgets/settings_menu_button.dart';
|
|
|
|
|
import '../../widgets/user_account_menu_button.dart';
|
|
|
|
|
import 'package:hesabix_ui/l10n/app_localizations.dart';
|
2025-09-22 21:21:46 +03:30
|
|
|
|
|
|
|
|
class BusinessShell extends StatefulWidget {
|
|
|
|
|
final int businessId;
|
|
|
|
|
final Widget child;
|
2025-09-25 01:01:27 +03:30
|
|
|
final AuthStore authStore;
|
|
|
|
|
final LocaleController? localeController;
|
|
|
|
|
final CalendarController? calendarController;
|
|
|
|
|
final ThemeController? themeController;
|
2025-09-22 21:21:46 +03:30
|
|
|
|
|
|
|
|
const BusinessShell({
|
|
|
|
|
super.key,
|
|
|
|
|
required this.businessId,
|
|
|
|
|
required this.child,
|
2025-09-25 01:01:27 +03:30
|
|
|
required this.authStore,
|
|
|
|
|
this.localeController,
|
|
|
|
|
this.calendarController,
|
|
|
|
|
this.themeController,
|
2025-09-22 21:21:46 +03:30
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
State<BusinessShell> createState() => _BusinessShellState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _BusinessShellState extends State<BusinessShell> {
|
2025-09-25 01:01:27 +03:30
|
|
|
int _hoverIndex = -1;
|
|
|
|
|
bool _isBasicToolsExpanded = false;
|
|
|
|
|
bool _isPeopleExpanded = false;
|
2025-09-25 02:17:52 +03:30
|
|
|
bool _isProductsAndServicesExpanded = false;
|
|
|
|
|
bool _isBankingExpanded = false;
|
|
|
|
|
bool _isAccountingMenuExpanded = false;
|
|
|
|
|
bool _isWarehouseManagementExpanded = false;
|
2025-09-22 21:21:46 +03:30
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
// اضافه کردن listener برای AuthStore
|
|
|
|
|
widget.authStore.addListener(() {
|
|
|
|
|
if (mounted) {
|
|
|
|
|
setState(() {});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
final width = MediaQuery.of(context).size.width;
|
|
|
|
|
final bool useRail = width >= 700;
|
|
|
|
|
final bool railExtended = width >= 1100;
|
|
|
|
|
final ColorScheme scheme = Theme.of(context).colorScheme;
|
2025-09-25 01:01:27 +03:30
|
|
|
String location = '/business/${widget.businessId}/dashboard'; // default location
|
|
|
|
|
try {
|
|
|
|
|
location = GoRouterState.of(context).uri.toString();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// اگر GoRouterState در دسترس نیست، از default استفاده کن
|
|
|
|
|
}
|
2025-09-22 21:21:46 +03:30
|
|
|
final bool isDark = Theme.of(context).brightness == Brightness.dark;
|
|
|
|
|
final String logoAsset = isDark
|
|
|
|
|
? 'assets/images/logo-light.png'
|
|
|
|
|
: 'assets/images/logo-light.png';
|
|
|
|
|
|
|
|
|
|
final t = AppLocalizations.of(context);
|
2025-09-25 01:01:27 +03:30
|
|
|
|
|
|
|
|
// ساختار متمرکز منو
|
|
|
|
|
final menuItems = <_MenuItem>[
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.businessDashboard,
|
|
|
|
|
icon: Icons.dashboard_outlined,
|
|
|
|
|
selectedIcon: Icons.dashboard,
|
|
|
|
|
path: '/business/${widget.businessId}/dashboard',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.practicalTools,
|
|
|
|
|
icon: Icons.category,
|
|
|
|
|
selectedIcon: Icons.category,
|
|
|
|
|
path: null, // آیتم جداکننده
|
|
|
|
|
type: _MenuItemType.separator,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.people,
|
|
|
|
|
icon: Icons.people,
|
|
|
|
|
selectedIcon: Icons.people,
|
|
|
|
|
path: null, // برای منوی بازشونده
|
|
|
|
|
type: _MenuItemType.expandable,
|
|
|
|
|
children: [
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.peopleList,
|
|
|
|
|
icon: Icons.list,
|
|
|
|
|
selectedIcon: Icons.list,
|
|
|
|
|
path: '/business/${widget.businessId}/people-list',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.receipts,
|
|
|
|
|
icon: Icons.receipt,
|
|
|
|
|
selectedIcon: Icons.receipt,
|
|
|
|
|
path: '/business/${widget.businessId}/receipts',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: true,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.payments,
|
|
|
|
|
icon: Icons.payment,
|
|
|
|
|
selectedIcon: Icons.payment,
|
|
|
|
|
path: '/business/${widget.businessId}/payments',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: true,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
2025-09-25 02:17:52 +03:30
|
|
|
_MenuItem(
|
|
|
|
|
label: t.productsAndServices,
|
|
|
|
|
icon: Icons.inventory_2,
|
|
|
|
|
selectedIcon: Icons.inventory_2,
|
|
|
|
|
path: null, // برای منوی بازشونده
|
|
|
|
|
type: _MenuItemType.expandable,
|
|
|
|
|
children: [
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.products,
|
|
|
|
|
icon: Icons.shopping_cart,
|
|
|
|
|
selectedIcon: Icons.shopping_cart,
|
|
|
|
|
path: '/business/${widget.businessId}/products',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: true,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.priceLists,
|
|
|
|
|
icon: Icons.list_alt,
|
|
|
|
|
selectedIcon: Icons.list_alt,
|
|
|
|
|
path: '/business/${widget.businessId}/price-lists',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: false,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.categories,
|
|
|
|
|
icon: Icons.category,
|
|
|
|
|
selectedIcon: Icons.category,
|
|
|
|
|
path: '/business/${widget.businessId}/categories',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: false,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.productAttributes,
|
|
|
|
|
icon: Icons.tune,
|
|
|
|
|
selectedIcon: Icons.tune,
|
|
|
|
|
path: '/business/${widget.businessId}/product-attributes',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: false,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.banking,
|
|
|
|
|
icon: Icons.account_balance,
|
|
|
|
|
selectedIcon: Icons.account_balance,
|
|
|
|
|
path: null, // برای منوی بازشونده
|
|
|
|
|
type: _MenuItemType.expandable,
|
|
|
|
|
children: [
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.accounts,
|
|
|
|
|
icon: Icons.account_balance_wallet,
|
|
|
|
|
selectedIcon: Icons.account_balance_wallet,
|
|
|
|
|
path: '/business/${widget.businessId}/accounts',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: true,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.pettyCash,
|
|
|
|
|
icon: Icons.money,
|
|
|
|
|
selectedIcon: Icons.money,
|
|
|
|
|
path: '/business/${widget.businessId}/petty-cash',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: true,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.cashBox,
|
|
|
|
|
icon: Icons.savings,
|
|
|
|
|
selectedIcon: Icons.savings,
|
|
|
|
|
path: '/business/${widget.businessId}/cash-box',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: true,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.wallet,
|
|
|
|
|
icon: Icons.wallet,
|
|
|
|
|
selectedIcon: Icons.wallet,
|
|
|
|
|
path: '/business/${widget.businessId}/wallet',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: true,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.checks,
|
|
|
|
|
icon: Icons.receipt_long,
|
|
|
|
|
selectedIcon: Icons.receipt_long,
|
|
|
|
|
path: '/business/${widget.businessId}/checks',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: true,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.transfers,
|
|
|
|
|
icon: Icons.swap_horiz,
|
|
|
|
|
selectedIcon: Icons.swap_horiz,
|
|
|
|
|
path: '/business/${widget.businessId}/transfers',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: true,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.accounting,
|
|
|
|
|
icon: Icons.calculate,
|
|
|
|
|
selectedIcon: Icons.calculate,
|
|
|
|
|
path: null, // آیتم جداکننده
|
|
|
|
|
type: _MenuItemType.separator,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.invoice,
|
|
|
|
|
icon: Icons.receipt,
|
|
|
|
|
selectedIcon: Icons.receipt,
|
|
|
|
|
path: '/business/${widget.businessId}/invoice',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: true,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.expenseAndIncome,
|
|
|
|
|
icon: Icons.account_balance_wallet,
|
|
|
|
|
selectedIcon: Icons.account_balance_wallet,
|
|
|
|
|
path: '/business/${widget.businessId}/expense-income',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: true,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.accountingMenu,
|
|
|
|
|
icon: Icons.calculate,
|
|
|
|
|
selectedIcon: Icons.calculate,
|
|
|
|
|
path: null, // برای منوی بازشونده
|
|
|
|
|
type: _MenuItemType.expandable,
|
|
|
|
|
children: [
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.documents,
|
|
|
|
|
icon: Icons.description,
|
|
|
|
|
selectedIcon: Icons.description,
|
|
|
|
|
path: '/business/${widget.businessId}/documents',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: true,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.chartOfAccounts,
|
|
|
|
|
icon: Icons.table_chart,
|
|
|
|
|
selectedIcon: Icons.table_chart,
|
|
|
|
|
path: '/business/${widget.businessId}/chart-of-accounts',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: false,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.openingBalance,
|
|
|
|
|
icon: Icons.play_arrow,
|
|
|
|
|
selectedIcon: Icons.play_arrow,
|
|
|
|
|
path: '/business/${widget.businessId}/opening-balance',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: false,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.yearEndClosing,
|
|
|
|
|
icon: Icons.stop,
|
|
|
|
|
selectedIcon: Icons.stop,
|
|
|
|
|
path: '/business/${widget.businessId}/year-end-closing',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: false,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.accountingSettings,
|
|
|
|
|
icon: Icons.settings,
|
|
|
|
|
selectedIcon: Icons.settings,
|
|
|
|
|
path: '/business/${widget.businessId}/accounting-settings',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: false,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.reports,
|
|
|
|
|
icon: Icons.assessment,
|
|
|
|
|
selectedIcon: Icons.assessment,
|
|
|
|
|
path: '/business/${widget.businessId}/reports',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: false,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.servicesAndPlugins,
|
|
|
|
|
icon: Icons.extension,
|
|
|
|
|
selectedIcon: Icons.extension,
|
|
|
|
|
path: null, // آیتم جداکننده
|
|
|
|
|
type: _MenuItemType.separator,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.warehouseManagement,
|
|
|
|
|
icon: Icons.warehouse,
|
|
|
|
|
selectedIcon: Icons.warehouse,
|
|
|
|
|
path: null, // برای منوی بازشونده
|
|
|
|
|
type: _MenuItemType.expandable,
|
|
|
|
|
children: [
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.warehouses,
|
|
|
|
|
icon: Icons.store,
|
|
|
|
|
selectedIcon: Icons.store,
|
|
|
|
|
path: '/business/${widget.businessId}/warehouses',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: true,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.shipments,
|
|
|
|
|
icon: Icons.local_shipping,
|
|
|
|
|
selectedIcon: Icons.local_shipping,
|
|
|
|
|
path: '/business/${widget.businessId}/shipments',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: true,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.inquiries,
|
|
|
|
|
icon: Icons.search,
|
|
|
|
|
selectedIcon: Icons.search,
|
|
|
|
|
path: '/business/${widget.businessId}/inquiries',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: false,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.storageSpace,
|
|
|
|
|
icon: Icons.storage,
|
|
|
|
|
selectedIcon: Icons.storage,
|
|
|
|
|
path: '/business/${widget.businessId}/storage-space',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: false,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.taxpayers,
|
|
|
|
|
icon: Icons.account_balance,
|
|
|
|
|
selectedIcon: Icons.account_balance,
|
|
|
|
|
path: '/business/${widget.businessId}/taxpayers',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: false,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.others,
|
|
|
|
|
icon: Icons.more_horiz,
|
|
|
|
|
selectedIcon: Icons.more_horiz,
|
|
|
|
|
path: null, // آیتم جداکننده
|
|
|
|
|
type: _MenuItemType.separator,
|
|
|
|
|
),
|
2025-09-25 01:01:27 +03:30
|
|
|
_MenuItem(
|
|
|
|
|
label: t.settings,
|
|
|
|
|
icon: Icons.settings,
|
|
|
|
|
selectedIcon: Icons.settings,
|
|
|
|
|
path: null, // برای منوی بازشونده
|
|
|
|
|
type: _MenuItemType.expandable,
|
|
|
|
|
children: [
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.businessSettings,
|
|
|
|
|
icon: Icons.business,
|
|
|
|
|
selectedIcon: Icons.business,
|
|
|
|
|
path: '/business/${widget.businessId}/business-settings',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.printDocuments,
|
|
|
|
|
icon: Icons.print,
|
|
|
|
|
selectedIcon: Icons.print,
|
|
|
|
|
path: '/business/${widget.businessId}/print-documents',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
),
|
|
|
|
|
_MenuItem(
|
|
|
|
|
label: t.usersAndPermissions,
|
|
|
|
|
icon: Icons.people_outline,
|
|
|
|
|
selectedIcon: Icons.people,
|
|
|
|
|
path: '/business/${widget.businessId}/users-permissions',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
2025-09-25 03:16:45 +03:30
|
|
|
_MenuItem(
|
|
|
|
|
label: t.pluginMarketplace,
|
|
|
|
|
icon: Icons.store,
|
|
|
|
|
selectedIcon: Icons.store,
|
|
|
|
|
path: '/business/${widget.businessId}/plugin-marketplace',
|
|
|
|
|
type: _MenuItemType.simple,
|
|
|
|
|
hasAddButton: false,
|
|
|
|
|
),
|
2025-09-22 21:21:46 +03:30
|
|
|
];
|
|
|
|
|
|
|
|
|
|
int selectedIndex = 0;
|
2025-09-25 01:01:27 +03:30
|
|
|
for (int i = 0; i < menuItems.length; i++) {
|
|
|
|
|
final item = menuItems[i];
|
|
|
|
|
if (item.type == _MenuItemType.separator) continue; // نادیده گرفتن آیتم جداکننده
|
|
|
|
|
|
|
|
|
|
if (item.type == _MenuItemType.simple && item.path != null && location.startsWith(item.path!)) {
|
2025-09-22 21:21:46 +03:30
|
|
|
selectedIndex = i;
|
|
|
|
|
break;
|
2025-09-25 01:01:27 +03:30
|
|
|
} else if (item.type == _MenuItemType.expandable && item.children != null) {
|
|
|
|
|
for (int j = 0; j < item.children!.length; j++) {
|
|
|
|
|
final child = item.children![j];
|
|
|
|
|
if (child.path != null && location.startsWith(child.path!)) {
|
|
|
|
|
selectedIndex = i;
|
|
|
|
|
// تنظیم وضعیت باز بودن منو
|
|
|
|
|
if (i == 2) _isPeopleExpanded = true; // اشخاص در ایندکس 2
|
2025-09-25 02:17:52 +03:30
|
|
|
if (i == 3) _isProductsAndServicesExpanded = true; // کالا و خدمات در ایندکس 3
|
|
|
|
|
if (i == 4) _isBankingExpanded = true; // بانکداری در ایندکس 4
|
|
|
|
|
if (i == 6) _isAccountingMenuExpanded = true; // حسابداری در ایندکس 6
|
|
|
|
|
if (i == 8) _isWarehouseManagementExpanded = true; // انبارداری در ایندکس 8
|
|
|
|
|
if (i == 9) _isBasicToolsExpanded = true; // تنظیمات در ایندکس 9
|
2025-09-25 01:01:27 +03:30
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-22 21:21:46 +03:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> onSelect(int index) async {
|
2025-09-25 01:01:27 +03:30
|
|
|
final item = menuItems[index];
|
|
|
|
|
if (item.type == _MenuItemType.separator) return; // آیتم جداکننده قابل کلیک نیست
|
|
|
|
|
|
|
|
|
|
if (item.type == _MenuItemType.simple && item.path != null) {
|
|
|
|
|
try {
|
|
|
|
|
if (GoRouterState.of(context).uri.toString() != item.path!) {
|
|
|
|
|
context.go(item.path!);
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// اگر GoRouterState در دسترس نیست، مستقیماً به مسیر برود
|
|
|
|
|
context.go(item.path!);
|
|
|
|
|
}
|
|
|
|
|
} else if (item.type == _MenuItemType.expandable) {
|
|
|
|
|
// تغییر وضعیت باز/بسته بودن منو
|
|
|
|
|
if (item.label == t.people) _isPeopleExpanded = !_isPeopleExpanded;
|
2025-09-25 02:17:52 +03:30
|
|
|
if (item.label == t.productsAndServices) _isProductsAndServicesExpanded = !_isProductsAndServicesExpanded;
|
|
|
|
|
if (item.label == t.banking) _isBankingExpanded = !_isBankingExpanded;
|
|
|
|
|
if (item.label == t.accountingMenu) _isAccountingMenuExpanded = !_isAccountingMenuExpanded;
|
|
|
|
|
if (item.label == t.warehouseManagement) _isWarehouseManagementExpanded = !_isWarehouseManagementExpanded;
|
2025-09-25 01:01:27 +03:30
|
|
|
if (item.label == t.settings) _isBasicToolsExpanded = !_isBasicToolsExpanded;
|
|
|
|
|
setState(() {});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> onSelectChild(int parentIndex, int childIndex) async {
|
|
|
|
|
final parent = menuItems[parentIndex];
|
|
|
|
|
if (parent.type == _MenuItemType.expandable && parent.children != null) {
|
|
|
|
|
final child = parent.children![childIndex];
|
|
|
|
|
if (child.path != null) {
|
|
|
|
|
try {
|
|
|
|
|
if (GoRouterState.of(context).uri.toString() != child.path!) {
|
|
|
|
|
context.go(child.path!);
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// اگر GoRouterState در دسترس نیست، مستقیماً به مسیر برود
|
|
|
|
|
context.go(child.path!);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Future<void> onLogout() async {
|
|
|
|
|
await widget.authStore.saveApiKey(null);
|
|
|
|
|
if (!context.mounted) return;
|
|
|
|
|
ScaffoldMessenger.of(context)
|
|
|
|
|
..hideCurrentSnackBar()
|
2025-09-25 01:18:59 +03:30
|
|
|
..showSnackBar(SnackBar(content: Text(t.logoutDone)));
|
2025-09-25 01:01:27 +03:30
|
|
|
context.go('/login');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isExpanded(_MenuItem item) {
|
|
|
|
|
if (item.label == t.people) return _isPeopleExpanded;
|
2025-09-25 02:17:52 +03:30
|
|
|
if (item.label == t.productsAndServices) return _isProductsAndServicesExpanded;
|
|
|
|
|
if (item.label == t.banking) return _isBankingExpanded;
|
|
|
|
|
if (item.label == t.accountingMenu) return _isAccountingMenuExpanded;
|
|
|
|
|
if (item.label == t.warehouseManagement) return _isWarehouseManagementExpanded;
|
2025-09-25 01:01:27 +03:30
|
|
|
if (item.label == t.settings) return _isBasicToolsExpanded;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int getTotalMenuItemsCount() {
|
|
|
|
|
int count = 0;
|
|
|
|
|
for (final item in menuItems) {
|
|
|
|
|
if (item.type == _MenuItemType.separator) {
|
|
|
|
|
count++; // آیتم جداکننده هم شمرده میشود
|
|
|
|
|
} else {
|
|
|
|
|
count++; // آیتم اصلی
|
|
|
|
|
if (item.type == _MenuItemType.expandable && isExpanded(item) && railExtended) {
|
|
|
|
|
count += item.children?.length ?? 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-22 21:21:46 +03:30
|
|
|
}
|
2025-09-25 01:01:27 +03:30
|
|
|
return count;
|
2025-09-22 21:21:46 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Brand top bar with contrast color
|
|
|
|
|
final Color appBarBg = Theme.of(context).brightness == Brightness.dark
|
|
|
|
|
? scheme.surfaceContainerHighest
|
|
|
|
|
: scheme.primary;
|
|
|
|
|
final Color appBarFg = Theme.of(context).brightness == Brightness.dark
|
|
|
|
|
? scheme.onSurfaceVariant
|
|
|
|
|
: scheme.onPrimary;
|
|
|
|
|
|
|
|
|
|
final appBar = AppBar(
|
|
|
|
|
backgroundColor: appBarBg,
|
|
|
|
|
foregroundColor: appBarFg,
|
2025-09-25 01:01:27 +03:30
|
|
|
automaticallyImplyLeading: !useRail,
|
|
|
|
|
titleSpacing: 0,
|
2025-09-22 21:21:46 +03:30
|
|
|
title: Row(
|
|
|
|
|
children: [
|
|
|
|
|
const SizedBox(width: 12),
|
2025-09-25 01:01:27 +03:30
|
|
|
Image.asset(logoAsset, height: 28),
|
|
|
|
|
const SizedBox(width: 12),
|
|
|
|
|
Text(t.appTitle, style: TextStyle(color: appBarFg, fontWeight: FontWeight.w700)),
|
2025-09-22 21:21:46 +03:30
|
|
|
],
|
|
|
|
|
),
|
2025-09-25 01:01:27 +03:30
|
|
|
leading: useRail
|
|
|
|
|
? null
|
|
|
|
|
: Builder(
|
|
|
|
|
builder: (ctx) => IconButton(
|
|
|
|
|
icon: Icon(Icons.menu, color: appBarFg),
|
|
|
|
|
onPressed: () => Scaffold.of(ctx).openDrawer(),
|
|
|
|
|
tooltip: t.menu,
|
|
|
|
|
),
|
|
|
|
|
),
|
2025-09-22 21:21:46 +03:30
|
|
|
actions: [
|
2025-09-25 01:01:27 +03:30
|
|
|
SettingsMenuButton(
|
|
|
|
|
localeController: widget.localeController,
|
|
|
|
|
calendarController: widget.calendarController,
|
|
|
|
|
themeController: widget.themeController,
|
2025-09-22 21:21:46 +03:30
|
|
|
),
|
|
|
|
|
const SizedBox(width: 8),
|
2025-09-25 01:01:27 +03:30
|
|
|
UserAccountMenuButton(authStore: widget.authStore),
|
|
|
|
|
const SizedBox(width: 8),
|
2025-09-22 21:21:46 +03:30
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
|
2025-09-25 01:01:27 +03:30
|
|
|
final content = Container(
|
|
|
|
|
color: scheme.surface,
|
|
|
|
|
child: SafeArea(
|
|
|
|
|
child: widget.child,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Side colors and styles
|
|
|
|
|
final Color sideBg = Theme.of(context).brightness == Brightness.dark
|
|
|
|
|
? scheme.surfaceContainerHighest
|
|
|
|
|
: scheme.surfaceContainerLow;
|
|
|
|
|
final Color sideFg = scheme.onSurfaceVariant;
|
|
|
|
|
final Color activeBg = scheme.primaryContainer;
|
|
|
|
|
final Color activeFg = scheme.onPrimaryContainer;
|
|
|
|
|
|
2025-09-22 21:21:46 +03:30
|
|
|
if (useRail) {
|
|
|
|
|
return Scaffold(
|
|
|
|
|
appBar: appBar,
|
|
|
|
|
body: Row(
|
|
|
|
|
children: [
|
2025-09-25 01:01:27 +03:30
|
|
|
Container(
|
|
|
|
|
width: railExtended ? 240 : 88,
|
|
|
|
|
height: double.infinity,
|
|
|
|
|
color: sideBg,
|
|
|
|
|
child: ListView.builder(
|
|
|
|
|
padding: EdgeInsets.zero,
|
|
|
|
|
itemCount: getTotalMenuItemsCount(),
|
|
|
|
|
itemBuilder: (ctx, index) {
|
2025-09-25 03:16:45 +03:30
|
|
|
// محاسبه ایندکس منو و تشخیص نوع آیتم
|
|
|
|
|
int menuIndex = 0;
|
|
|
|
|
int childIndex = -1;
|
|
|
|
|
bool isChildItem = false;
|
|
|
|
|
|
|
|
|
|
int currentIndex = 0;
|
|
|
|
|
for (int i = 0; i < menuItems.length; i++) {
|
|
|
|
|
final item = menuItems[i];
|
|
|
|
|
|
|
|
|
|
if (currentIndex == index) {
|
|
|
|
|
menuIndex = i;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
currentIndex++;
|
|
|
|
|
|
|
|
|
|
if (item.type == _MenuItemType.expandable && isExpanded(item) && railExtended) {
|
|
|
|
|
final childrenCount = item.children?.length ?? 0;
|
|
|
|
|
if (index >= currentIndex && index < currentIndex + childrenCount) {
|
|
|
|
|
menuIndex = i;
|
|
|
|
|
childIndex = index - currentIndex;
|
|
|
|
|
isChildItem = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
currentIndex += childrenCount;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 01:01:27 +03:30
|
|
|
final item = menuItems[menuIndex];
|
|
|
|
|
final bool isHovered = index == _hoverIndex;
|
|
|
|
|
final bool isSelected = menuIndex == selectedIndex;
|
|
|
|
|
final bool active = isSelected || isHovered;
|
|
|
|
|
final BorderRadius br = (isSelected && useRail)
|
|
|
|
|
? BorderRadius.zero
|
|
|
|
|
: (isHovered ? BorderRadius.zero : BorderRadius.circular(8));
|
|
|
|
|
final Color bgColor = active
|
|
|
|
|
? (isHovered && !isSelected ? activeBg.withValues(alpha: 0.85) : activeBg)
|
|
|
|
|
: Colors.transparent;
|
|
|
|
|
|
2025-09-25 03:16:45 +03:30
|
|
|
if (isChildItem && item.children != null && childIndex >= 0 && childIndex < item.children!.length) {
|
|
|
|
|
// زیرآیتم
|
|
|
|
|
final child = item.children![childIndex];
|
|
|
|
|
return MouseRegion(
|
|
|
|
|
onEnter: (_) => setState(() => _hoverIndex = index),
|
|
|
|
|
onExit: (_) => setState(() => _hoverIndex = -1),
|
|
|
|
|
child: InkWell(
|
|
|
|
|
borderRadius: br,
|
|
|
|
|
onTap: () => onSelectChild(menuIndex, childIndex),
|
|
|
|
|
child: Container(
|
|
|
|
|
margin: EdgeInsets.zero,
|
|
|
|
|
padding: EdgeInsets.symmetric(
|
|
|
|
|
horizontal: railExtended ? 24 : 16, // بیشتر indent برای زیرآیتم
|
|
|
|
|
vertical: 8,
|
2025-09-25 01:01:27 +03:30
|
|
|
),
|
2025-09-25 03:16:45 +03:30
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: bgColor,
|
2025-09-25 01:01:27 +03:30
|
|
|
borderRadius: br,
|
2025-09-25 03:16:45 +03:30
|
|
|
),
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
Icon(
|
|
|
|
|
child.icon,
|
|
|
|
|
color: sideFg,
|
|
|
|
|
size: 20,
|
2025-09-25 01:01:27 +03:30
|
|
|
),
|
2025-09-25 03:16:45 +03:30
|
|
|
if (railExtended) ...[
|
|
|
|
|
const SizedBox(width: 12),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: Text(
|
|
|
|
|
child.label,
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
color: sideFg,
|
|
|
|
|
fontWeight: FontWeight.w400,
|
|
|
|
|
),
|
2025-09-25 01:01:27 +03:30
|
|
|
),
|
2025-09-25 03:16:45 +03:30
|
|
|
),
|
|
|
|
|
if (child.hasAddButton)
|
|
|
|
|
GestureDetector(
|
|
|
|
|
onTap: () {
|
|
|
|
|
// Navigate to add new item
|
|
|
|
|
if (child.label == t.receipts) {
|
|
|
|
|
// Navigate to add receipt
|
|
|
|
|
} else if (child.label == t.payments) {
|
|
|
|
|
// Navigate to add payment
|
|
|
|
|
} else if (child.label == t.products) {
|
|
|
|
|
// Navigate to add product
|
|
|
|
|
} else if (child.label == t.priceLists) {
|
|
|
|
|
// Navigate to add price list
|
|
|
|
|
} else if (child.label == t.categories) {
|
|
|
|
|
// Navigate to add category
|
|
|
|
|
} else if (child.label == t.productAttributes) {
|
|
|
|
|
// Navigate to add product attribute
|
|
|
|
|
} else if (child.label == t.accounts) {
|
|
|
|
|
// Navigate to add account
|
|
|
|
|
} else if (child.label == t.pettyCash) {
|
|
|
|
|
// Navigate to add petty cash
|
|
|
|
|
} else if (child.label == t.cashBox) {
|
|
|
|
|
// Navigate to add cash box
|
|
|
|
|
} else if (child.label == t.wallet) {
|
|
|
|
|
// Navigate to add wallet
|
|
|
|
|
} else if (child.label == t.checks) {
|
|
|
|
|
// Navigate to add check
|
|
|
|
|
} else if (child.label == t.transfers) {
|
|
|
|
|
// Navigate to add transfer
|
|
|
|
|
} else if (child.label == t.invoice) {
|
|
|
|
|
// Navigate to add invoice
|
|
|
|
|
} else if (child.label == t.expenseAndIncome) {
|
|
|
|
|
// Navigate to add expense/income
|
|
|
|
|
} else if (child.label == t.documents) {
|
|
|
|
|
// Navigate to add document
|
|
|
|
|
} else if (child.label == t.warehouses) {
|
|
|
|
|
// Navigate to add warehouse
|
|
|
|
|
} else if (child.label == t.shipments) {
|
|
|
|
|
// Navigate to add shipment
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
child: Container(
|
|
|
|
|
width: 20,
|
|
|
|
|
height: 20,
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: sideFg.withValues(alpha: 0.1),
|
|
|
|
|
borderRadius: BorderRadius.circular(3),
|
2025-09-25 01:01:27 +03:30
|
|
|
),
|
2025-09-25 03:16:45 +03:30
|
|
|
child: Icon(
|
|
|
|
|
Icons.add,
|
|
|
|
|
size: 14,
|
|
|
|
|
color: sideFg,
|
2025-09-25 01:01:27 +03:30
|
|
|
),
|
2025-09-25 03:16:45 +03:30
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
],
|
2025-09-25 01:01:27 +03:30
|
|
|
),
|
2025-09-25 03:16:45 +03:30
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
2025-09-25 01:01:27 +03:30
|
|
|
} else {
|
2025-09-25 03:16:45 +03:30
|
|
|
// آیتم اصلی (ساده، بازشونده، یا جداکننده)
|
2025-09-25 01:01:27 +03:30
|
|
|
if (item.type == _MenuItemType.separator) {
|
|
|
|
|
// آیتم جداکننده
|
|
|
|
|
return Container(
|
|
|
|
|
margin: EdgeInsets.symmetric(
|
|
|
|
|
horizontal: railExtended ? 16 : 8,
|
|
|
|
|
vertical: 8,
|
|
|
|
|
),
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
if (railExtended) ...[
|
|
|
|
|
Expanded(
|
|
|
|
|
child: Divider(
|
|
|
|
|
color: sideFg.withValues(alpha: 0.3),
|
|
|
|
|
thickness: 1,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(width: 12),
|
|
|
|
|
Text(
|
|
|
|
|
item.label,
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
color: sideFg.withValues(alpha: 0.7),
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(width: 12),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: Divider(
|
|
|
|
|
color: sideFg.withValues(alpha: 0.3),
|
|
|
|
|
thickness: 1,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
] else ...[
|
|
|
|
|
Expanded(
|
|
|
|
|
child: Divider(
|
|
|
|
|
color: sideFg.withValues(alpha: 0.3),
|
|
|
|
|
thickness: 1,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
} else {
|
2025-09-25 03:16:45 +03:30
|
|
|
// آیتم ساده یا آیتم بازشونده
|
2025-09-25 01:01:27 +03:30
|
|
|
return MouseRegion(
|
|
|
|
|
onEnter: (_) => setState(() => _hoverIndex = index),
|
|
|
|
|
onExit: (_) => setState(() => _hoverIndex = -1),
|
|
|
|
|
child: InkWell(
|
|
|
|
|
borderRadius: br,
|
|
|
|
|
onTap: () {
|
|
|
|
|
if (item.type == _MenuItemType.expandable) {
|
|
|
|
|
setState(() {
|
|
|
|
|
if (item.label == t.people) _isPeopleExpanded = !_isPeopleExpanded;
|
2025-09-25 02:17:52 +03:30
|
|
|
if (item.label == t.productsAndServices) _isProductsAndServicesExpanded = !_isProductsAndServicesExpanded;
|
|
|
|
|
if (item.label == t.banking) _isBankingExpanded = !_isBankingExpanded;
|
|
|
|
|
if (item.label == t.accountingMenu) _isAccountingMenuExpanded = !_isAccountingMenuExpanded;
|
|
|
|
|
if (item.label == t.warehouseManagement) _isWarehouseManagementExpanded = !_isWarehouseManagementExpanded;
|
2025-09-25 01:01:27 +03:30
|
|
|
if (item.label == t.settings) _isBasicToolsExpanded = !_isBasicToolsExpanded;
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
onSelect(menuIndex);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
child: Container(
|
|
|
|
|
margin: EdgeInsets.zero,
|
|
|
|
|
padding: EdgeInsets.symmetric(
|
|
|
|
|
horizontal: railExtended ? 16 : 8,
|
|
|
|
|
vertical: 8,
|
|
|
|
|
),
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: bgColor,
|
|
|
|
|
borderRadius: br,
|
|
|
|
|
),
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
Icon(
|
|
|
|
|
active ? item.selectedIcon : item.icon,
|
|
|
|
|
color: active ? activeFg : sideFg,
|
|
|
|
|
size: 24,
|
|
|
|
|
),
|
|
|
|
|
if (railExtended) ...[
|
|
|
|
|
const SizedBox(width: 12),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: Text(
|
|
|
|
|
item.label,
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
color: active ? activeFg : sideFg,
|
|
|
|
|
fontWeight: active ? FontWeight.w600 : FontWeight.w400,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
if (item.type == _MenuItemType.expandable)
|
|
|
|
|
Icon(
|
|
|
|
|
isExpanded(item) ? Icons.expand_less : Icons.expand_more,
|
|
|
|
|
color: sideFg,
|
|
|
|
|
size: 20,
|
2025-09-25 03:16:45 +03:30
|
|
|
)
|
|
|
|
|
else if (item.hasAddButton)
|
|
|
|
|
GestureDetector(
|
|
|
|
|
onTap: () {
|
|
|
|
|
// Navigate to add new item
|
|
|
|
|
if (item.label == t.invoice) {
|
|
|
|
|
// Navigate to add invoice
|
|
|
|
|
} else if (item.label == t.expenseAndIncome) {
|
|
|
|
|
// Navigate to add expense/income
|
|
|
|
|
} else if (item.label == t.reports) {
|
|
|
|
|
// Navigate to add report
|
|
|
|
|
} else if (item.label == t.inquiries) {
|
|
|
|
|
// Navigate to add inquiry
|
|
|
|
|
} else if (item.label == t.storageSpace) {
|
|
|
|
|
// Navigate to add storage space
|
|
|
|
|
} else if (item.label == t.taxpayers) {
|
|
|
|
|
// Navigate to add taxpayer
|
|
|
|
|
} else if (item.label == t.pluginMarketplace) {
|
|
|
|
|
// Navigate to add plugin
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
child: Container(
|
|
|
|
|
width: 24,
|
|
|
|
|
height: 24,
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: sideFg.withValues(alpha: 0.1),
|
|
|
|
|
borderRadius: BorderRadius.circular(4),
|
|
|
|
|
),
|
|
|
|
|
child: Icon(
|
|
|
|
|
Icons.add,
|
|
|
|
|
size: 16,
|
|
|
|
|
color: sideFg,
|
|
|
|
|
),
|
|
|
|
|
),
|
2025-09-25 01:01:27 +03:30
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
),
|
2025-09-22 21:21:46 +03:30
|
|
|
),
|
|
|
|
|
const VerticalDivider(thickness: 1, width: 1),
|
2025-09-25 01:01:27 +03:30
|
|
|
Expanded(child: content),
|
2025-09-22 21:21:46 +03:30
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
2025-09-25 01:01:27 +03:30
|
|
|
}
|
|
|
|
|
|
2025-09-22 21:21:46 +03:30
|
|
|
return Scaffold(
|
|
|
|
|
appBar: appBar,
|
2025-09-25 01:01:27 +03:30
|
|
|
drawer: Drawer(
|
|
|
|
|
backgroundColor: sideBg,
|
|
|
|
|
child: SafeArea(
|
|
|
|
|
child: ListView(
|
|
|
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
|
|
|
children: [
|
|
|
|
|
// آیتمهای منو
|
|
|
|
|
for (int i = 0; i < menuItems.length; i++) ...[
|
|
|
|
|
Builder(builder: (ctx) {
|
|
|
|
|
final item = menuItems[i];
|
|
|
|
|
final bool active = i == selectedIndex;
|
|
|
|
|
|
|
|
|
|
if (item.type == _MenuItemType.separator) {
|
|
|
|
|
// آیتم جداکننده در منوی موبایل
|
|
|
|
|
return Container(
|
|
|
|
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
Expanded(
|
|
|
|
|
child: Divider(
|
|
|
|
|
color: sideFg.withValues(alpha: 0.3),
|
|
|
|
|
thickness: 1,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(width: 12),
|
|
|
|
|
Text(
|
|
|
|
|
item.label,
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
color: sideFg.withValues(alpha: 0.7),
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
const SizedBox(width: 12),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: Divider(
|
|
|
|
|
color: sideFg.withValues(alpha: 0.3),
|
|
|
|
|
thickness: 1,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
} else if (item.type == _MenuItemType.simple) {
|
|
|
|
|
return ListTile(
|
|
|
|
|
leading: Icon(item.selectedIcon, color: active ? activeFg : sideFg),
|
|
|
|
|
title: Text(item.label, style: TextStyle(color: active ? activeFg : sideFg, fontWeight: active ? FontWeight.w600 : FontWeight.w400)),
|
|
|
|
|
selected: active,
|
|
|
|
|
selectedTileColor: activeBg,
|
|
|
|
|
onTap: () {
|
|
|
|
|
context.pop();
|
|
|
|
|
onSelect(i);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
} else if (item.type == _MenuItemType.expandable) {
|
|
|
|
|
return ExpansionTile(
|
|
|
|
|
leading: Icon(item.icon, color: sideFg),
|
|
|
|
|
title: Text(item.label, style: TextStyle(color: sideFg)),
|
|
|
|
|
initiallyExpanded: isExpanded(item),
|
|
|
|
|
onExpansionChanged: (expanded) {
|
|
|
|
|
setState(() {
|
|
|
|
|
if (item.label == t.people) _isPeopleExpanded = expanded;
|
2025-09-25 02:17:52 +03:30
|
|
|
if (item.label == t.productsAndServices) _isProductsAndServicesExpanded = expanded;
|
|
|
|
|
if (item.label == t.banking) _isBankingExpanded = expanded;
|
|
|
|
|
if (item.label == t.accountingMenu) _isAccountingMenuExpanded = expanded;
|
|
|
|
|
if (item.label == t.warehouseManagement) _isWarehouseManagementExpanded = expanded;
|
2025-09-25 01:01:27 +03:30
|
|
|
if (item.label == t.settings) _isBasicToolsExpanded = expanded;
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
children: item.children?.map((child) => ListTile(
|
|
|
|
|
leading: const SizedBox(width: 24),
|
|
|
|
|
title: Text(child.label),
|
2025-09-25 03:16:45 +03:30
|
|
|
trailing: child.hasAddButton ? GestureDetector(
|
|
|
|
|
onTap: () {
|
2025-09-25 01:01:27 +03:30
|
|
|
context.pop();
|
2025-09-25 02:17:52 +03:30
|
|
|
// Navigate to add new item
|
2025-09-25 01:01:27 +03:30
|
|
|
if (child.label == t.receipts) {
|
|
|
|
|
// Navigate to add receipt
|
|
|
|
|
} else if (child.label == t.payments) {
|
|
|
|
|
// Navigate to add payment
|
2025-09-25 02:17:52 +03:30
|
|
|
} else if (child.label == t.products) {
|
|
|
|
|
// Navigate to add product
|
|
|
|
|
} else if (child.label == t.priceLists) {
|
|
|
|
|
// Navigate to add price list
|
|
|
|
|
} else if (child.label == t.categories) {
|
|
|
|
|
// Navigate to add category
|
|
|
|
|
} else if (child.label == t.productAttributes) {
|
|
|
|
|
// Navigate to add product attribute
|
|
|
|
|
} else if (child.label == t.accounts) {
|
|
|
|
|
// Navigate to add account
|
|
|
|
|
} else if (child.label == t.pettyCash) {
|
|
|
|
|
// Navigate to add petty cash
|
|
|
|
|
} else if (child.label == t.cashBox) {
|
|
|
|
|
// Navigate to add cash box
|
|
|
|
|
} else if (child.label == t.wallet) {
|
|
|
|
|
// Navigate to add wallet
|
|
|
|
|
} else if (child.label == t.checks) {
|
|
|
|
|
// Navigate to add check
|
|
|
|
|
} else if (child.label == t.transfers) {
|
|
|
|
|
// Navigate to add transfer
|
|
|
|
|
} else if (child.label == t.invoice) {
|
|
|
|
|
// Navigate to add invoice
|
|
|
|
|
} else if (child.label == t.expenseAndIncome) {
|
|
|
|
|
// Navigate to add expense/income
|
|
|
|
|
} else if (child.label == t.documents) {
|
|
|
|
|
// Navigate to add document
|
|
|
|
|
} else if (child.label == t.warehouses) {
|
|
|
|
|
// Navigate to add warehouse
|
|
|
|
|
} else if (child.label == t.shipments) {
|
|
|
|
|
// Navigate to add shipment
|
2025-09-25 01:01:27 +03:30
|
|
|
}
|
|
|
|
|
},
|
2025-09-25 03:16:45 +03:30
|
|
|
child: Container(
|
|
|
|
|
width: 24,
|
|
|
|
|
height: 24,
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: sideFg.withValues(alpha: 0.1),
|
|
|
|
|
borderRadius: BorderRadius.circular(4),
|
|
|
|
|
),
|
|
|
|
|
child: Icon(
|
|
|
|
|
Icons.add,
|
|
|
|
|
size: 16,
|
|
|
|
|
color: sideFg,
|
|
|
|
|
),
|
|
|
|
|
),
|
2025-09-25 01:01:27 +03:30
|
|
|
) : null,
|
|
|
|
|
onTap: () {
|
|
|
|
|
context.pop();
|
|
|
|
|
onSelectChild(i, item.children!.indexOf(child));
|
|
|
|
|
},
|
|
|
|
|
)).toList() ?? [],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return const SizedBox.shrink();
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
const Divider(),
|
|
|
|
|
ListTile(
|
|
|
|
|
leading: const Icon(Icons.logout),
|
|
|
|
|
title: Text(t.logout),
|
|
|
|
|
onTap: onLogout,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
2025-09-22 21:21:46 +03:30
|
|
|
),
|
2025-09-25 01:01:27 +03:30
|
|
|
),
|
|
|
|
|
body: content,
|
|
|
|
|
);
|
2025-09-22 21:21:46 +03:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 01:01:27 +03:30
|
|
|
enum _MenuItemType { simple, expandable, separator }
|
|
|
|
|
|
|
|
|
|
class _MenuItem {
|
2025-09-22 21:21:46 +03:30
|
|
|
final String label;
|
|
|
|
|
final IconData icon;
|
|
|
|
|
final IconData selectedIcon;
|
2025-09-25 01:01:27 +03:30
|
|
|
final String? path;
|
|
|
|
|
final _MenuItemType type;
|
|
|
|
|
final List<_MenuItem>? children;
|
|
|
|
|
final bool hasAddButton;
|
|
|
|
|
|
|
|
|
|
const _MenuItem({
|
|
|
|
|
required this.label,
|
|
|
|
|
required this.icon,
|
|
|
|
|
required this.selectedIcon,
|
|
|
|
|
this.path,
|
|
|
|
|
required this.type,
|
|
|
|
|
this.children,
|
|
|
|
|
this.hasAddButton = false,
|
|
|
|
|
});
|
|
|
|
|
}
|