From 30c0007f40594e679f5d997219985107de784cf4 Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Thu, 11 Jun 2026 00:04:40 +0200 Subject: [PATCH] feat(accounting): Accounting nav section + relocate Tax report out of CRM Adds the `accounting` feature flag to the frontend (type, context default) and a Settings -> Features toggle card. When enabled: - A new top-level "Accounting" sidebar entry appears (gated by `accounting` + accounting.view), with an AccountingLayout sub-nav mirroring ClientsLayout. - The Tax report relocates: it is HIDDEN from the CRM (Clients) sub-nav and shown under Accounting instead, at /admin/accounting/tax-report. When accounting is OFF, Tax stays under CRM exactly as before. Tax visibility still depends on `taxReport` (which depends on `bills`), so the relocation only changes WHERE the menu item lives, not whether it exists. Files: featureFlags.service.ts (+'accounting'), FeatureFlagsContext default, AdminSidebar entry, new AccountingLayout, ClientsLayout filter, App.tsx route, FeaturesTab card, en/de i18n (navigation.accounting, accounting.*, settings.features.accounting; DE authored natively). Verified: `npm run build` green; en/de JSON valid. --- frontend/src/App.tsx | 15 ++ .../src/components/admin/AccountingLayout.tsx | 141 ++++++++++++++++++ .../src/components/admin/AdminSidebar.tsx | 11 ++ .../src/components/admin/ClientsLayout.tsx | 8 +- frontend/src/contexts/FeatureFlagsContext.tsx | 4 + .../features/settings/tabs/FeaturesTab.tsx | 15 ++ frontend/src/i18n/locales/de.json | 18 +++ frontend/src/i18n/locales/en.json | 18 +++ frontend/src/services/featureFlags.service.ts | 7 +- 9 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/admin/AccountingLayout.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a4d72da6..e78f7e4c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -64,6 +64,7 @@ import { import { CustomerAuthProvider } from './contexts/CustomerAuthContext'; import { AdminLayout, AdminAuthWrapper } from './components/admin'; import { ClientsLayout } from './components/admin/ClientsLayout'; +import { AccountingLayout } from './components/admin/AccountingLayout'; import { RequireFeature } from './components/admin/RequireFeature'; import { PageErrorBoundary, OfflineIndicator, SkipLink, DynamicFavicon, RobotsMetaTags, CMSContentBlock, Loading } from './components/common'; import { MaintenanceWrapper } from './components/MaintenanceWrapper'; @@ -260,6 +261,20 @@ function App() { + {/* Accounting section (migration 122). Parent gated by + the `accounting` flag. Hosts the Tax report — which + relocates here from the CRM sub-nav when accounting + is on — plus the future inbound-invoice / expenses + pages. Each sub-route is independently flagged. */} + }> + }> + }> + } /> + + } /> + + + {/* Old /admin/customers paths now live under /admin/clients/accounts. Kept indefinitely as redirects so existing bookmarks and email links diff --git a/frontend/src/components/admin/AccountingLayout.tsx b/frontend/src/components/admin/AccountingLayout.tsx new file mode 100644 index 00000000..e098b44b --- /dev/null +++ b/frontend/src/components/admin/AccountingLayout.tsx @@ -0,0 +1,141 @@ +/** + * Accounting section layout (migration 122). + * + * Wraps /admin/accounting/* routes with a Settings-style left sub-nav, + * mirroring ClientsLayout. Today it hosts the Tax report (relocated here + * from CRM when the `accounting` flag is on); the inbound-document inbox and + * expenses pages slot in as additional sub-nav entries when their UIs land. + */ +import React from 'react'; +import { NavLink, Outlet, useLocation, useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { Landmark, Calculator } from 'lucide-react'; +import type { LucideIcon } from 'lucide-react'; +import { useFeatureFlags, type FeatureKey } from '../../contexts/FeatureFlagsContext'; + +interface NavItem { + key: string; + to: string; + label: string; + icon: LucideIcon; + /** Feature flag that must be ON for this entry to render. */ + featureFlag: FeatureKey; +} + +export const AccountingLayout: React.FC = () => { + const { t } = useTranslation(); + const location = useLocation(); + const navigate = useNavigate(); + const { flags } = useFeatureFlags(); + + const navItems: NavItem[] = [ + { + key: 'tax-report', + to: '/admin/accounting/tax-report', + label: t('accounting.subnav.taxReport', 'Tax'), + icon: Calculator, + featureFlag: 'taxReport', + }, + // Future Accounting sub-features (inbound inbox, expenses) slot in here + // once their pages land, e.g.: + // { key: 'inbox', to: '/admin/accounting/inbox', featureFlag: 'accounting' } + // { key: 'expenses', to: '/admin/accounting/expenses', featureFlag: 'accounting' } + ]; + + const enabledItems = navItems.filter((item) => flags[item.featureFlag]); + + const header = ( +
+

+ {t('accounting.title', 'Accounting')} +

+

+ {t('accounting.subtitle', 'Inbound supplier invoices, expenses and reporting.')} +

+
+ ); + + if (enabledItems.length === 0) { + return ( +
+ {header} +
+ +

+ {t('accounting.empty.title', 'No accounting features enabled')} +

+

+ {t('accounting.empty.body', 'Enable the Tax report (or another accounting sub-feature) under Settings → Features to get started.')} +

+
+
+ ); + } + + return ( +
+ {header} + +
+ {/* Mobile: native select dropdown */} +
+ + +
+ + {/* Desktop: sticky left rail */} + + +
+ +
+
+
+ ); +}; diff --git a/frontend/src/components/admin/AdminSidebar.tsx b/frontend/src/components/admin/AdminSidebar.tsx index 79aefddf..01a4d837 100644 --- a/frontend/src/components/admin/AdminSidebar.tsx +++ b/frontend/src/components/admin/AdminSidebar.tsx @@ -10,6 +10,7 @@ import { X, Users, Briefcase, + Landmark, PanelLeftClose, PanelLeftOpen, } from 'lucide-react'; @@ -96,6 +97,16 @@ const navigation: NavItem[] = [ 'taxReport', 'hoursLogging', 'contracts', 'calendar', ], }, + // Accounting section (migration 122) — inbound supplier invoices, + // expenses + re-bill, and the tax report (which relocates here from + // the CRM sub-nav when `accounting` is on). Gated by the `accounting` + // master flag; the sub-pages inside AccountingLayout are each + // independently feature-gated. + { + nameKey: 'navigation.accounting', href: '/admin/accounting', icon: Landmark, + permission: 'accounting.view', + featureFlag: 'accounting', + }, ]; export const AdminSidebar: React.FC = ({ isOpen, onClose, collapsed = false, onToggleCollapse }) => { diff --git a/frontend/src/components/admin/ClientsLayout.tsx b/frontend/src/components/admin/ClientsLayout.tsx index d3de2e1a..be53b2e4 100644 --- a/frontend/src/components/admin/ClientsLayout.tsx +++ b/frontend/src/components/admin/ClientsLayout.tsx @@ -99,7 +99,13 @@ export const ClientsLayout: React.FC = () => { }, ]; - const enabledItems = navItems.filter((item) => flags[item.featureFlag]); + const enabledItems = navItems.filter((item) => { + if (!flags[item.featureFlag]) return false; + // When the Accounting area is enabled, the tax report relocates out + // of CRM and under Accounting — hide it here so it isn't in both. + if (item.key === 'tax-report' && flags.accounting) return false; + return true; + }); // When the parent `clients` flag is on but no sub-feature is enabled, // there's nothing to render. Settings → Features is one click away diff --git a/frontend/src/contexts/FeatureFlagsContext.tsx b/frontend/src/contexts/FeatureFlagsContext.tsx index b0da55dc..4b80eb53 100644 --- a/frontend/src/contexts/FeatureFlagsContext.tsx +++ b/frontend/src/contexts/FeatureFlagsContext.tsx @@ -45,6 +45,10 @@ export const DEFAULT_FLAGS: FeatureFlags = { // Settings → Features once they've reviewed the seeded block // library with their lawyer. contracts: false, + // Accounting (migration 122). Top-level Accounting area (inbound + // supplier invoices, expenses + re-bill). When ON, the tax report + // moves out of the CRM sub-nav and under Accounting. Strictly opt-in. + accounting: false, }; export const FEATURE_FLAGS_QUERY_KEY = ['feature-flags'] as const; diff --git a/frontend/src/features/settings/tabs/FeaturesTab.tsx b/frontend/src/features/settings/tabs/FeaturesTab.tsx index b5cefdc7..6563af5c 100644 --- a/frontend/src/features/settings/tabs/FeaturesTab.tsx +++ b/frontend/src/features/settings/tabs/FeaturesTab.tsx @@ -16,6 +16,7 @@ import { Briefcase, Wrench, Calculator, + Landmark, } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { Button, Card } from '../../../components/common'; @@ -278,6 +279,20 @@ export const FeaturesTab: React.FC = () => { ) : undefined} /> + setFlag('accounting', next)} + /> + ;