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
This commit is contained in:
Matthias Mair 2026-04-24 00:34:21 +02:00 committed by GitHub
parent 21d5edb56a
commit de3266cdf6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 60 additions and 23 deletions

View File

@ -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'
}

View File

@ -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<PanelProps>): ReactNode {
const localState = useLocalState();
const location = useLocation();
@ -96,9 +102,17 @@ function BasePanelGroup({
const [expanded, setExpanded] = useState<boolean>(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
});

View File

@ -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();

View File

@ -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}
/>
</Stack>
) : (

View File

@ -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() {
<PanelGroup
pageKey='system-settings'
panels={systemSettingsPanels}
model='systemsettings'
id={null}
pluginPanelWithoutId
pluginPanelKey={PluginPanelKey.systemsettings}
/>
</Stack>
) : (

View File

@ -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() {
<PanelGroup
pageKey='user-settings'
panels={userSettingsPanels}
model='usersettings'
id={null}
pluginPanelWithoutId
pluginPanelKey={PluginPanelKey.usersettings}
/>
</Stack>
</>

View File

@ -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() {
<PanelGroup
pageKey='build-index'
panels={panels}
model='manufacturing'
id={null}
pluginPanelWithoutId
pluginPanelKey={PluginPanelKey.manufacturing}
/>
</Stack>
);

View File

@ -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 (
<Stack>
<PageDetail title={t`System Overview`} />
<PanelGroup pageKey='core-index' panels={panels} id={null} />
<PanelGroup
pageKey='core-index'
panels={panels}
pluginPanelWithoutId
pluginPanelKey={PluginPanelKey.core}
/>
</Stack>
);
}

View File

@ -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() {
<PanelGroup
pageKey='purchasing-index'
panels={panels}
model={'purchasing'}
id={null}
pluginPanelWithoutId
pluginPanelKey={PluginPanelKey.purchasing}
/>
</Stack>
);

View File

@ -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() {
<PanelGroup
pageKey='sales-index'
panels={panels}
model={'sales'}
id={null}
pluginPanelWithoutId
pluginPanelKey={PluginPanelKey.sales}
/>
</Stack>
);

View File

@ -481,6 +481,7 @@ export default function Stock() {
reloadInstance={refreshInstance}
id={location?.pk}
instance={location}
pluginPanelWithoutId
/>
</Stack>
{stockAdjustActions.modals.map((modal) => modal.modal)}