diff --git a/src/frontend/CHANGELOG.md b/src/frontend/CHANGELOG.md index deec3c3251..97db95266d 100644 --- a/src/frontend/CHANGELOG.md +++ b/src/frontend/CHANGELOG.md @@ -2,6 +2,10 @@ This file contains historical changelog information for the InvenTree UI components library. +### 0.8.0 - March 2026 + +Exposes the `monitorDataOutput` hook, which allows plugins to monitor the output of a long-running task and display notifications when the task is complete. This is useful for plugins that need to perform long-running tasks and want to provide feedback to the user when the task is complete. + ### 0.7.0 - October 2025 Exposes stock adjustment forms to plugins, allowing plugins to adjust stock adjustments using the common InvenTree UI form components. diff --git a/src/frontend/lib/hooks/MonitorDataOutput.tsx b/src/frontend/lib/hooks/MonitorDataOutput.tsx new file mode 100644 index 0000000000..a03654cd69 --- /dev/null +++ b/src/frontend/lib/hooks/MonitorDataOutput.tsx @@ -0,0 +1,122 @@ +import { t } from '@lingui/core/macro'; +import { useDocumentVisibility } from '@mantine/hooks'; +import { notifications, showNotification } from '@mantine/notifications'; +import { IconCircleCheck, IconExclamationCircle } from '@tabler/icons-react'; +import { useQuery } from '@tanstack/react-query'; +import type { AxiosInstance } from 'axios'; +import { useEffect, useState } from 'react'; +import { ProgressBar } from '../components/ProgressBar'; +import { ApiEndpoints } from '../enums/ApiEndpoints'; +import { apiUrl } from '../functions/Api'; + +/** + * Hook for monitoring a data output process running on the server + */ +export default function monitorDataOutput({ + api, + title, + hostname, + id +}: { + api: AxiosInstance; + title: string; + hostname?: string; + id?: number; +}) { + const visibility = useDocumentVisibility(); + + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (!!id) { + setLoading(true); + showNotification({ + id: `data-output-${id}`, + title: title, + loading: true, + autoClose: false, + withCloseButton: false, + message: + }); + } else setLoading(false); + }, [id, title]); + + useQuery({ + enabled: !!id && loading && visibility === 'visible', + refetchInterval: 500, + queryKey: ['data-output', id, title], + queryFn: () => + api + .get(apiUrl(ApiEndpoints.data_output, id)) + .then((response) => { + const data = response?.data ?? {}; + + if (!!data.errors || !!data.error) { + setLoading(false); + + const error: string = + data?.error ?? data?.errors?.error ?? t`Process failed`; + + notifications.update({ + id: `data-output-${id}`, + loading: false, + icon: , + autoClose: 2500, + title: title, + message: error, + color: 'red' + }); + } else if (data.complete) { + setLoading(false); + notifications.update({ + id: `data-output-${id}`, + loading: false, + autoClose: 2500, + title: title, + message: t`Process completed successfully`, + color: 'green', + icon: + }); + + if (data.output) { + const url = data.output; + const base = hostname ?? window.location.hostname; + + const downloadUrl = new URL(url, base); + + window.open(downloadUrl.toString(), '_blank'); + } + } else { + notifications.update({ + id: `data-output-${id}`, + loading: true, + autoClose: false, + withCloseButton: false, + message: ( + 0} + animated + /> + ) + }); + } + + return data; + }) + .catch(() => { + setLoading(false); + notifications.update({ + id: `data-output-${id}`, + loading: false, + autoClose: 2500, + title: title, + message: t`Process failed`, + color: 'red' + }); + return {}; + }) + }); +} diff --git a/src/frontend/lib/index.ts b/src/frontend/lib/index.ts index 17e7e20f36..625e65c436 100644 --- a/src/frontend/lib/index.ts +++ b/src/frontend/lib/index.ts @@ -71,3 +71,6 @@ export { RowCancelAction, RowActions } from './components/RowActions'; + +// Shared hooks +export { default as monitorDataOutput } from './hooks/MonitorDataOutput'; diff --git a/src/frontend/package.json b/src/frontend/package.json index 1e6d57d0b3..4c283164f9 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -1,7 +1,7 @@ { "name": "@inventreedb/ui", "description": "UI components for the InvenTree project", - "version": "0.7.0", + "version": "0.8.0", "private": false, "type": "module", "license": "MIT", diff --git a/src/frontend/src/hooks/UseDataOutput.tsx b/src/frontend/src/hooks/UseDataOutput.tsx index 84be903f70..0dbc622d62 100644 --- a/src/frontend/src/hooks/UseDataOutput.tsx +++ b/src/frontend/src/hooks/UseDataOutput.tsx @@ -1,14 +1,6 @@ -import { ProgressBar } from '@lib/components/ProgressBar'; -import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; -import { apiUrl } from '@lib/functions/Api'; -import { t } from '@lingui/core/macro'; -import { useDocumentVisibility } from '@mantine/hooks'; -import { notifications, showNotification } from '@mantine/notifications'; -import { IconCircleCheck, IconExclamationCircle } from '@tabler/icons-react'; -import { useQuery } from '@tanstack/react-query'; -import { useEffect, useState } from 'react'; +import monitorDataOutput from '@lib/hooks/MonitorDataOutput'; import { useApi } from '../contexts/ApiContext'; -import { generateUrl } from '../functions/urls'; +import { useLocalState } from '../states/LocalState'; /** * Hook for monitoring a data output process running on the server @@ -21,97 +13,12 @@ export default function useDataOutput({ id?: number; }) { const api = useApi(); + const { getHost } = useLocalState.getState(); - const visibility = useDocumentVisibility(); - - const [loading, setLoading] = useState(false); - - useEffect(() => { - if (!!id) { - setLoading(true); - showNotification({ - id: `data-output-${id}`, - title: title, - loading: true, - autoClose: false, - withCloseButton: false, - message: - }); - } else setLoading(false); - }, [id, title]); - - useQuery({ - enabled: !!id && loading && visibility === 'visible', - refetchInterval: 500, - queryKey: ['data-output', id, title], - queryFn: () => - api - .get(apiUrl(ApiEndpoints.data_output, id)) - .then((response) => { - const data = response?.data ?? {}; - - if (!!data.errors || !!data.error) { - setLoading(false); - - const error: string = - data?.error ?? data?.errors?.error ?? t`Process failed`; - - notifications.update({ - id: `data-output-${id}`, - loading: false, - icon: , - autoClose: 2500, - title: title, - message: error, - color: 'red' - }); - } else if (data.complete) { - setLoading(false); - notifications.update({ - id: `data-output-${id}`, - loading: false, - autoClose: 2500, - title: title, - message: t`Process completed successfully`, - color: 'green', - icon: - }); - - if (data.output) { - const url = generateUrl(data.output); - window.open(url.toString(), '_blank'); - } - } else { - notifications.update({ - id: `data-output-${id}`, - loading: true, - autoClose: false, - withCloseButton: false, - message: ( - 0} - animated - /> - ) - }); - } - - return data; - }) - .catch(() => { - setLoading(false); - notifications.update({ - id: `data-output-${id}`, - loading: false, - autoClose: 2500, - title: title, - message: t`Process failed`, - color: 'red' - }); - return {}; - }) + return monitorDataOutput({ + api: api, + title: title, + id: id, + hostname: getHost() }); }