From de3266cdf6dd3ba93a978e276e7a5a9fcd665fef Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 24 Apr 2026 00:34:21 +0200 Subject: [PATCH] refactor(frontend): make plugin support for panels more generic (#11787) * feat(frontend): add more generic plugin support on PanelGroup * refactor exsisting use cases * add plugin panel support to StockLocation closes https://github.com/inventree/InvenTree/issues/11782 * stop re-using model field * re-order types * add panel support for core * change keys see https://github.com/inventree/InvenTree/pull/11787#discussion_r3127410867 --- src/frontend/lib/enums/ModelType.tsx | 13 ++++++++++ .../src/components/panels/PanelGroup.tsx | 24 +++++++++++++++---- src/frontend/src/hooks/UsePluginPanels.tsx | 4 ++-- .../Index/Settings/AdminCenter/Index.tsx | 5 ++-- .../pages/Index/Settings/SystemSettings.tsx | 5 ++-- .../src/pages/Index/Settings/UserSettings.tsx | 5 ++-- src/frontend/src/pages/build/BuildIndex.tsx | 6 ++--- src/frontend/src/pages/core/CoreIndex.tsx | 8 ++++++- .../src/pages/purchasing/PurchasingIndex.tsx | 6 ++--- src/frontend/src/pages/sales/SalesIndex.tsx | 6 ++--- .../src/pages/stock/LocationDetail.tsx | 1 + 11 files changed, 60 insertions(+), 23 deletions(-) diff --git a/src/frontend/lib/enums/ModelType.tsx b/src/frontend/lib/enums/ModelType.tsx index ea85b542fc..191d04545e 100644 --- a/src/frontend/lib/enums/ModelType.tsx +++ b/src/frontend/lib/enums/ModelType.tsx @@ -38,3 +38,16 @@ export enum ModelType { selectionentry = 'selectionentry', error = 'error' } + +export enum PluginPanelKey { + // settings / admin + admincenter = 'admincenter', + systemsettings = 'systemsettings', + usersettings = 'usersettings', + // generic + core = 'core', + // landing pages + purchasing = 'purchasing', + sales = 'sales', + manufacturing = 'manufacturing' +} diff --git a/src/frontend/src/components/panels/PanelGroup.tsx b/src/frontend/src/components/panels/PanelGroup.tsx index 80b13c80fb..c60d92cb10 100644 --- a/src/frontend/src/components/panels/PanelGroup.tsx +++ b/src/frontend/src/components/panels/PanelGroup.tsx @@ -33,7 +33,7 @@ import { } from 'react-router-dom'; import { Boundary } from '@lib/components/Boundary'; -import type { ModelType } from '@lib/enums/ModelType'; +import type { ModelType, PluginPanelKey } from '@lib/enums/ModelType'; import { identifierString } from '@lib/functions/Conversion'; import { cancelEvent } from '@lib/functions/Events'; import { eventModified, getBaseUrl } from '@lib/functions/Navigation'; @@ -61,6 +61,8 @@ import * as classes from './PanelGroup.css'; * @param selectedPanel - The currently selected panel * @param onPanelChange - Callback when the active panel changes * @param collapsible - If true, the panel group can be collapsed (defaults to true) + * @param pluginPanelWithoutId - If true, the panel group will load plugin panels even with no id provided + * @param pluginPanelKey - The plugin panel key to use when loading plugin panels for this group from the backend */ export type PanelProps = { pageKey: string; @@ -68,11 +70,13 @@ export type PanelProps = { groups?: PanelGroupType[]; instance?: any; reloadInstance?: () => void; - model?: ModelType | string; + model?: ModelType; id?: number | null; selectedPanel?: string; onPanelChange?: (panel: string) => void; collapsible?: boolean; + pluginPanelWithoutId?: boolean; + pluginPanelKey?: PluginPanelKey; }; function BasePanelGroup({ @@ -85,7 +89,9 @@ function BasePanelGroup({ instance, model, id, - collapsible = true + collapsible = true, + pluginPanelWithoutId = false, + pluginPanelKey }: Readonly): ReactNode { const localState = useLocalState(); const location = useLocation(); @@ -96,9 +102,17 @@ function BasePanelGroup({ const [expanded, setExpanded] = useState(true); // Hook to load plugins for this panel + const _pluginId = useMemo(() => { + if (id === undefined && pluginPanelWithoutId) return null; + return id; + }, [id, pluginPanelWithoutId]); + const _pluginKey = useMemo(() => { + if (model === undefined && pluginPanelWithoutId) return pluginPanelKey; + return model; + }, [model, pluginPanelWithoutId, pluginPanelKey]); const pluginPanelSet = usePluginPanels({ - id: id, - model: model, + id: _pluginId, + model: _pluginKey, instance: instance, reloadFunc: reloadInstance }); diff --git a/src/frontend/src/hooks/UsePluginPanels.tsx b/src/frontend/src/hooks/UsePluginPanels.tsx index c8559703e3..d94deea967 100644 --- a/src/frontend/src/hooks/UsePluginPanels.tsx +++ b/src/frontend/src/hooks/UsePluginPanels.tsx @@ -2,7 +2,7 @@ import { type UseQueryResult, useQuery } from '@tanstack/react-query'; import { useMemo } from 'react'; import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; -import type { ModelType } from '@lib/enums/ModelType'; +import type { ModelType, PluginPanelKey } from '@lib/enums/ModelType'; import { apiUrl } from '@lib/functions/Api'; import type { InvenTreePluginContext } from '@lib/types/Plugins'; import { api } from '../App'; @@ -39,7 +39,7 @@ export function usePluginPanels({ }: { instance?: any; reloadFunc?: () => void; - model?: ModelType | string; + model?: ModelType | PluginPanelKey; id?: string | number | null; }): PluginPanelSet { const globalSettings = useGlobalSettingsState(); diff --git a/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx b/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx index 4c700b4ed1..5fca95bd72 100644 --- a/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx +++ b/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx @@ -22,6 +22,7 @@ import { } from '@tabler/icons-react'; import { lazy, useMemo } from 'react'; +import { PluginPanelKey } from '@lib/enums/ModelType'; import { UserRoles } from '@lib/enums/Roles'; import PermissionDenied from '../../../../components/errors/PermissionDenied'; import PageTitle from '../../../../components/nav/PageTitle'; @@ -303,8 +304,8 @@ export default function AdminCenter() { panels={adminCenterPanels} groups={grouping} collapsible={true} - model='admincenter' - id={null} + pluginPanelWithoutId + pluginPanelKey={PluginPanelKey.admincenter} /> ) : ( diff --git a/src/frontend/src/pages/Index/Settings/SystemSettings.tsx b/src/frontend/src/pages/Index/Settings/SystemSettings.tsx index 3ded92663d..35855567cb 100644 --- a/src/frontend/src/pages/Index/Settings/SystemSettings.tsx +++ b/src/frontend/src/pages/Index/Settings/SystemSettings.tsx @@ -20,6 +20,7 @@ import { } from '@tabler/icons-react'; import { lazy, useMemo } from 'react'; +import { PluginPanelKey } from '@lib/enums/ModelType'; import { useShallow } from 'zustand/react/shallow'; import PermissionDenied from '../../../components/errors/PermissionDenied'; import PageTitle from '../../../components/nav/PageTitle'; @@ -376,8 +377,8 @@ export default function SystemSettings() { ) : ( diff --git a/src/frontend/src/pages/Index/Settings/UserSettings.tsx b/src/frontend/src/pages/Index/Settings/UserSettings.tsx index 55a62ede4b..a30397e373 100644 --- a/src/frontend/src/pages/Index/Settings/UserSettings.tsx +++ b/src/frontend/src/pages/Index/Settings/UserSettings.tsx @@ -11,6 +11,7 @@ import { } from '@tabler/icons-react'; import { lazy, useMemo } from 'react'; +import { PluginPanelKey } from '@lib/enums/ModelType'; import { useShallow } from 'zustand/react/shallow'; import PageTitle from '../../../components/nav/PageTitle'; import { SettingsHeader } from '../../../components/nav/SettingsHeader'; @@ -154,8 +155,8 @@ export default function UserSettings() { diff --git a/src/frontend/src/pages/build/BuildIndex.tsx b/src/frontend/src/pages/build/BuildIndex.tsx index 2b8f4e681c..b1034b699c 100644 --- a/src/frontend/src/pages/build/BuildIndex.tsx +++ b/src/frontend/src/pages/build/BuildIndex.tsx @@ -8,7 +8,7 @@ import { } from '@tabler/icons-react'; import { useMemo } from 'react'; -import { ModelType } from '@lib/enums/ModelType'; +import { ModelType, PluginPanelKey } from '@lib/enums/ModelType'; import { UserRoles } from '@lib/enums/Roles'; import type { TableFilter } from '@lib/types/Filters'; import { useLocalStorage } from '@mantine/hooks'; @@ -102,8 +102,8 @@ export default function BuildIndex() { ); diff --git a/src/frontend/src/pages/core/CoreIndex.tsx b/src/frontend/src/pages/core/CoreIndex.tsx index fbc47e28aa..c588849461 100644 --- a/src/frontend/src/pages/core/CoreIndex.tsx +++ b/src/frontend/src/pages/core/CoreIndex.tsx @@ -3,6 +3,7 @@ import { Stack } from '@mantine/core'; import { IconUser, IconUsersGroup } from '@tabler/icons-react'; import { useMemo } from 'react'; +import { PluginPanelKey } from '@lib/enums/ModelType'; import PermissionDenied from '../../components/errors/PermissionDenied'; import { PageDetail } from '../../components/nav/PageDetail'; import { PanelGroup } from '../../components/panels/PanelGroup'; @@ -44,7 +45,12 @@ export default function CoreIndex() { return ( - + ); } diff --git a/src/frontend/src/pages/purchasing/PurchasingIndex.tsx b/src/frontend/src/pages/purchasing/PurchasingIndex.tsx index a4a2f70d47..74e8280931 100644 --- a/src/frontend/src/pages/purchasing/PurchasingIndex.tsx +++ b/src/frontend/src/pages/purchasing/PurchasingIndex.tsx @@ -12,7 +12,7 @@ import { } from '@tabler/icons-react'; import { useMemo } from 'react'; -import { ModelType } from '@lib/enums/ModelType'; +import { ModelType, PluginPanelKey } from '@lib/enums/ModelType'; import { UserRoles } from '@lib/enums/Roles'; import { useLocalStorage } from '@mantine/hooks'; import OrderCalendar from '../../components/calendar/OrderCalendar'; @@ -215,8 +215,8 @@ export default function PurchasingIndex() { ); diff --git a/src/frontend/src/pages/sales/SalesIndex.tsx b/src/frontend/src/pages/sales/SalesIndex.tsx index d225081ec4..80285f4483 100644 --- a/src/frontend/src/pages/sales/SalesIndex.tsx +++ b/src/frontend/src/pages/sales/SalesIndex.tsx @@ -11,7 +11,7 @@ import { } from '@tabler/icons-react'; import { useMemo } from 'react'; -import { ModelType } from '@lib/enums/ModelType'; +import { ModelType, PluginPanelKey } from '@lib/enums/ModelType'; import { UserRoles } from '@lib/enums/Roles'; import { useLocalStorage } from '@mantine/hooks'; import OrderCalendar from '../../components/calendar/OrderCalendar'; @@ -170,8 +170,8 @@ export default function SalesIndex() { ); diff --git a/src/frontend/src/pages/stock/LocationDetail.tsx b/src/frontend/src/pages/stock/LocationDetail.tsx index b8eb574561..d0d68287fe 100644 --- a/src/frontend/src/pages/stock/LocationDetail.tsx +++ b/src/frontend/src/pages/stock/LocationDetail.tsx @@ -481,6 +481,7 @@ export default function Stock() { reloadInstance={refreshInstance} id={location?.pk} instance={location} + pluginPanelWithoutId /> {stockAdjustActions.modals.map((modal) => modal.modal)}