From 299839312ff98dec39c0389bd93f0f1f5e547da7 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 18 Jun 2024 13:12:40 +0000 Subject: [PATCH] Add some dummy data for the plugin panels --- src/backend/InvenTree/plugin/api.py | 27 ++++++- src/backend/InvenTree/plugin/serializers.py | 7 +- src/frontend/src/enums/ApiEndpoints.tsx | 1 + src/frontend/src/functions/icons.tsx | 4 +- src/frontend/src/hooks/UsePluginPanels.tsx | 88 ++++++++++++--------- src/frontend/src/pages/part/PartDetail.tsx | 15 +++- 6 files changed, 97 insertions(+), 45 deletions(-) diff --git a/src/backend/InvenTree/plugin/api.py b/src/backend/InvenTree/plugin/api.py index 79840c3f23..b898eb2c98 100644 --- a/src/backend/InvenTree/plugin/api.py +++ b/src/backend/InvenTree/plugin/api.py @@ -424,8 +424,33 @@ class PluginPanelList(APIView): # Extract all plugins from the registry which provide custom panels for _plugin in registry.with_mixin('panel', active=True): + # TODO: Allow plugins to fill this data out ... + panels = [ + { + 'plugin': 'myplugin', + 'name': 'test-plugin', + 'label': 'My Plugin', + 'icon': 'part', + 'content': '
hello world
', + }, + { + 'plugin': 'myplugin', + 'name': 'test-plugin-2', + 'label': 'My Plugin 2', + 'icon': 'email', + 'content': '
hello world 2
', + }, + { + 'plugin': 'myplugin', + 'name': 'test-plugin-3', + 'label': 'My Plugin 3', + 'icon': 'website', + 'content': '
hello world 3
', + }, + ] + return Response(PluginSerializers.PluginPanelSerializer(panels, many=True).data) @@ -437,7 +462,7 @@ plugin_api_urls = [ 'plugins/', include([ path( - 'panel/', + 'panels/', include([ path('', PluginPanelList.as_view(), name='api-plugin-panel-list') ]), diff --git a/src/backend/InvenTree/plugin/serializers.py b/src/backend/InvenTree/plugin/serializers.py index 968e06728d..8bd385c5e0 100644 --- a/src/backend/InvenTree/plugin/serializers.py +++ b/src/backend/InvenTree/plugin/serializers.py @@ -311,9 +311,10 @@ class PluginPanelSerializer(serializers.Serializer): class Meta: """Meta for serializer.""" - fields = ['plugin', 'title', 'description', 'icon'] + fields = ['plugin', 'name', 'label', 'icon'] plugin = serializers.CharField(label=_('Plugin Key')) - title = serializers.CharField(label=_('Panel Title')) - description = serializers.CharField(label=_('Panel Description')) + name = serializers.CharField(label=_('Panel Name')) + label = serializers.CharField(label=_('Panel Label')) icon = serializers.CharField(label=_('Panel Icon')) + content = serializers.CharField(label=_('Panel Content')) diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index 962da26c49..0ce284415b 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -144,6 +144,7 @@ export enum ApiEndpoints { plugin_reload = 'plugins/reload/', plugin_activate = 'plugins/:key/activate/', plugin_uninstall = 'plugins/:key/uninstall/', + plugin_panel_list = 'plugins/panels/', // Machine API endpoints machine_types_list = 'machine/types/', diff --git a/src/frontend/src/functions/icons.tsx b/src/frontend/src/functions/icons.tsx index 75002f4701..a572b99e3e 100644 --- a/src/frontend/src/functions/icons.tsx +++ b/src/frontend/src/functions/icons.tsx @@ -50,6 +50,7 @@ import { IconPaperclip, IconPhone, IconPhoto, + IconPlug, IconPoint, IconPrinter, IconProgressCheck, @@ -199,7 +200,8 @@ const icons = { destination: IconFlag, repeat_destination: IconFlagShare, unlink: IconUnlink, - success: IconCircleCheck + success: IconCircleCheck, + plugin: IconPlug }; export type InvenTreeIconType = keyof typeof icons; diff --git a/src/frontend/src/hooks/UsePluginPanels.tsx b/src/frontend/src/hooks/UsePluginPanels.tsx index 3f730c4d43..e172c3bb36 100644 --- a/src/frontend/src/hooks/UsePluginPanels.tsx +++ b/src/frontend/src/hooks/UsePluginPanels.tsx @@ -1,58 +1,70 @@ +import { t } from '@lingui/macro'; +import { Alert, Text } from '@mantine/core'; import { useTimeout } from '@mantine/hooks'; import { Icon24Hours } from '@tabler/icons-react'; +import { useQuery } from '@tanstack/react-query'; import { ReactNode, useEffect, useMemo, useState } from 'react'; +import { api } from '../App'; import { PanelType } from '../components/nav/Panel'; +import { ApiEndpoints } from '../enums/ApiEndpoints'; +import { identifierString } from '../functions/conversion'; +import { InvenTreeIcon } from '../functions/icons'; +import { apiUrl } from '../states/ApiState'; -export interface PluginPanelState extends PanelType { - pluginKey: string; - targetType: string; - targetId?: string | number | null; +export type PluginPanelState = { + panels: PanelType[]; +}; + +// Placeholder content for a panel with no content +function PanelNoContent() { + return ( + + {t`No content provided for this plugin`} + + ); } -export function usePluginPanel({ - pluginKey, - panelName, +export function usePluginPanels({ targetModel, targetId }: { - pluginKey: string; - panelName: string; targetModel: string; targetId?: string | number | null; }): PluginPanelState { - // TODO: Query to fetch the "content" for the plugin + const { isFetching, data } = useQuery({ + queryKey: [targetModel, targetId], + queryFn: () => { + return api + .get(apiUrl(ApiEndpoints.plugin_panel_list), { + params: { + target_model: targetModel, + target_id: targetId + } + }) + .then((response: any) => response.data) + .catch((error: any) => { + console.error('Failed to fetch plugin panels:', error); + return []; + }); + } + }); - const [loaded, setLoaded] = useState(false); - - const { start } = useTimeout(() => setLoaded(true), 5000); - - useEffect(() => { - start(); - console.log('starting timer!'); - }, []); - - const content = useMemo(() => { - return loaded ? ( - 'plugin content loaded!' - ) : ( -
-

Plugin content goes here...

-

Plugin Key: {pluginKey}

-

Panel Name: {panelName}

-

Target Model: {targetModel}

-

Target ID: {targetId}

-
+ const panels: PanelType[] = useMemo(() => { + return ( + data?.map((panel: any) => { + const pluginKey = panel.plugin || 'plugin'; + return { + name: identifierString(`${pluginKey}-${panel.name}`), + label: panel.label || t`Plugin Panel`, + icon: , + content: panel.content || + }; + }) ?? [] ); - }, [loaded, pluginKey, panelName, targetModel, targetId]); + }, [data]); return { - content: content, - name: panelName, - pluginKey: pluginKey, - targetType: targetModel, - targetId: targetId, - label: 'A plugin panel', - icon: + panels: panels }; } diff --git a/src/frontend/src/pages/part/PartDetail.tsx b/src/frontend/src/pages/part/PartDetail.tsx index 08b330bde8..2372b5d873 100644 --- a/src/frontend/src/pages/part/PartDetail.tsx +++ b/src/frontend/src/pages/part/PartDetail.tsx @@ -56,7 +56,8 @@ import { import { PlaceholderPanel } from '../../components/items/Placeholder'; import NavigationTree from '../../components/nav/NavigationTree'; import { PageDetail } from '../../components/nav/PageDetail'; -import { PanelGroup, PanelType } from '../../components/nav/PanelGroup'; +import { PanelType } from '../../components/nav/Panel'; +import { PanelGroup } from '../../components/nav/PanelGroup'; import { formatPriceRange } from '../../defaults/formatters'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; @@ -75,6 +76,7 @@ import { useEditApiFormModal } from '../../hooks/UseForm'; import { useInstance } from '../../hooks/UseInstance'; +import { usePluginPanels } from '../../hooks/UsePluginPanels'; import { apiUrl } from '../../states/ApiState'; import { useUserState } from '../../states/UserState'; import { BomTable } from '../../tables/bom/BomTable'; @@ -640,6 +642,15 @@ export default function PartDetail() { ]; }, [id, part, user]); + const pluginPanels = usePluginPanels({ + targetModel: ModelType.part, + targetId: id + }); + + const panels: PanelType[] = useMemo(() => { + return [...partPanels, ...pluginPanels.panels]; + }, [partPanels, pluginPanels]); + const breadcrumbs = useMemo( () => [ { name: t`Parts`, url: '/part' }, @@ -844,7 +855,7 @@ export default function PartDetail() { }} actions={partActions} /> - + {transferStockItems.modal} {countStockItems.modal}