diff --git a/src/frontend/src/components/importer/GlobalImporterDrawer.tsx b/src/frontend/src/components/importer/GlobalImporterDrawer.tsx
new file mode 100644
index 0000000000..de34229b14
--- /dev/null
+++ b/src/frontend/src/components/importer/GlobalImporterDrawer.tsx
@@ -0,0 +1,20 @@
+import { useImporterState } from '../../states/ImporterState';
+import ImporterDrawer from './ImporterDrawer';
+
+export default function GlobalImporterDrawer() {
+ const isOpen = useImporterState((state) => state.isOpen);
+ const sessionId = useImporterState((state) => state.sessionId);
+ const closeImporter = useImporterState((state) => state.closeImporter);
+
+ if (!isOpen || sessionId === null) {
+ return null;
+ }
+
+ return (
+
+ );
+}
diff --git a/src/frontend/src/components/nav/Layout.tsx b/src/frontend/src/components/nav/Layout.tsx
index 9607520db8..741f6d14ad 100644
--- a/src/frontend/src/components/nav/Layout.tsx
+++ b/src/frontend/src/components/nav/Layout.tsx
@@ -21,6 +21,7 @@ import {
} from '../../states/SettingsStates';
import { useUserState } from '../../states/UserState';
import { Boundary } from '../Boundary';
+import GlobalImporterDrawer from '../importer/GlobalImporterDrawer';
import { ApiIcon } from '../items/ApiIcon';
import { useInvenTreeContext } from '../plugins/PluginContext';
import { callExternalPluginFunction } from '../plugins/PluginSource';
@@ -118,31 +119,34 @@ export default function LayoutComponent() {
return (
-
-
-
-
-
-
- {/* */}
-
-
-
- {userSettings.isSet('SHOW_SPOTLIGHT') && (
- ,
- placeholder: t`Search...`
- }}
- shortcut={['mod + K']}
- nothingFound={t`Nothing found...`}
- />
- )}
-
+ <>
+
+
+
+
+
+
+ {/* */}
+
+
+
+ {userSettings.isSet('SHOW_SPOTLIGHT') && (
+ ,
+ placeholder: t`Search...`
+ }}
+ shortcut={['mod + K']}
+ nothingFound={t`Nothing found...`}
+ />
+ )}
+
+
+ >
);
}
diff --git a/src/frontend/src/states/ImporterState.tsx b/src/frontend/src/states/ImporterState.tsx
new file mode 100644
index 0000000000..910970da66
--- /dev/null
+++ b/src/frontend/src/states/ImporterState.tsx
@@ -0,0 +1,62 @@
+/**
+ * This file contains a global state manager for the importer drawer, allowing it to be opened and closed from anywhere in the app without needing to pass props down through the component tree.
+ * The state includes the current session ID for the importer, as well as an optional callback function that can be executed when the importer is closed.
+ * This allows for flexible handling of importer actions, such as refreshing data after an import is completed.
+ * The state is managed using the Zustand library, which provides a simple and efficient way to create global state in React applications.
+ * The `useImporterState` hook can be used to access and manipulate the importer state from any component, while the `openGlobalImporter` and `closeGlobalImporter` functions provide convenient ways to control the importer from outside of React components.
+ */
+
+import { create } from 'zustand';
+
+export interface ImporterOpenOptions {
+ onClose?: () => void;
+}
+
+interface ImporterStateProps {
+ isOpen: boolean;
+ sessionId: number | null;
+ onCloseCallback?: () => void;
+ openImporter: (sessionId: number, options?: ImporterOpenOptions) => void;
+ closeImporter: () => void;
+}
+
+export const useImporterState = create()((set, get) => ({
+ isOpen: false,
+ sessionId: null,
+ onCloseCallback: undefined,
+
+ openImporter: (sessionId: number, options?: ImporterOpenOptions) => {
+ set({
+ sessionId,
+ isOpen: true,
+ onCloseCallback: options?.onClose
+ });
+ },
+
+ closeImporter: () => {
+ const callback = get().onCloseCallback;
+
+ set({
+ sessionId: null,
+ isOpen: false,
+ onCloseCallback: undefined
+ });
+
+ callback?.();
+ }
+}));
+
+export function openGlobalImporter(
+ sessionId: number,
+ options?: ImporterOpenOptions
+) {
+ useImporterState.getState().openImporter(sessionId, options);
+}
+
+export function closeGlobalImporter() {
+ useImporterState.getState().closeImporter();
+}
+
+export function getGlobalImporterState() {
+ return useImporterState.getState();
+}
diff --git a/src/frontend/src/tables/bom/BomTable.tsx b/src/frontend/src/tables/bom/BomTable.tsx
index 9062e12b23..09a2c7ee1c 100644
--- a/src/frontend/src/tables/bom/BomTable.tsx
+++ b/src/frontend/src/tables/bom/BomTable.tsx
@@ -26,7 +26,6 @@ import {
import { type ReactNode, useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Thumbnail } from '../../components/images/Thumbnail';
-import ImporterDrawer from '../../components/importer/ImporterDrawer';
import { ActionDropdown } from '../../components/items/ActionDropdown';
import { RenderPart } from '../../components/render/Part';
import { useApi } from '../../contexts/ApiContext';
@@ -39,6 +38,7 @@ import {
useEditApiFormModal
} from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
+import { useImporterState } from '../../states/ImporterState';
import { useUserState } from '../../states/UserState';
import {
BooleanColumn,
@@ -81,12 +81,7 @@ export function BomTable({
const user = useUserState();
const table = useTable('bom');
const navigate = useNavigate();
-
- const [importOpened, setImportOpened] = useState(false);
-
- const [selectedSession, setSelectedSession] = useState(
- undefined
- );
+ const openImporter = useImporterState((state) => state.openImporter);
const tableColumns: TableColumn[] = useMemo(() => {
return [
@@ -501,8 +496,9 @@ export function BomTable({
title: t`Import BOM Data`,
fields: importSessionFields,
onFormSuccess: (response: any) => {
- setSelectedSession(response.pk);
- setImportOpened(true);
+ openImporter(response.pk, {
+ onClose: table.refreshTable
+ });
}
});
@@ -690,15 +686,6 @@ export function BomTable({
}}
/>
- {
- setSelectedSession(undefined);
- setImportOpened(false);
- table.refreshTable();
- }}
- />
>
);
}
diff --git a/src/frontend/src/tables/general/ParameterTable.tsx b/src/frontend/src/tables/general/ParameterTable.tsx
index d7654e878a..71ced6820f 100644
--- a/src/frontend/src/tables/general/ParameterTable.tsx
+++ b/src/frontend/src/tables/general/ParameterTable.tsx
@@ -12,7 +12,6 @@ import type { TableColumn } from '@lib/types/Tables';
import { t } from '@lingui/core/macro';
import { IconFileUpload, IconPlus } from '@tabler/icons-react';
import { useCallback, useMemo, useState } from 'react';
-import ImporterDrawer from '../../components/importer/ImporterDrawer';
import { ActionDropdown } from '../../components/items/ActionDropdown';
import { useParameterFields } from '../../forms/CommonForms';
import { dataImporterSessionFields } from '../../forms/ImporterForms';
@@ -22,6 +21,7 @@ import {
useEditApiFormModal
} from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
+import { useImporterState } from '../../states/ImporterState';
import { useUserState } from '../../states/UserState';
import {
DateColumn,
@@ -129,12 +129,7 @@ export function ParameterTable({
const [selectedParameter, setSelectedParameter] = useState(
undefined
);
-
- const [importOpened, setImportOpened] = useState(false);
-
- const [selectedSession, setSelectedSession] = useState(
- undefined
- );
+ const openImporter = useImporterState((state) => state.openImporter);
const importSessionFields = useMemo(() => {
const fields = dataImporterSessionFields({
@@ -154,8 +149,9 @@ export function ParameterTable({
title: t`Import Parameters`,
fields: importSessionFields,
onFormSuccess: (response: any) => {
- setSelectedSession(response.pk);
- setImportOpened(true);
+ openImporter(response.pk, {
+ onClose: table.refreshTable
+ });
}
});
@@ -262,15 +258,6 @@ export function ParameterTable({
}
}}
/>
- {
- setSelectedSession(undefined);
- setImportOpened(false);
- table.refreshTable();
- }}
- />
>
);
}
diff --git a/src/frontend/src/tables/part/PartTable.tsx b/src/frontend/src/tables/part/PartTable.tsx
index 2c7defb844..27fa146186 100644
--- a/src/frontend/src/tables/part/PartTable.tsx
+++ b/src/frontend/src/tables/part/PartTable.tsx
@@ -20,7 +20,6 @@ import {
IconShoppingCart
} from '@tabler/icons-react';
import { type ReactNode, useCallback, useMemo, useState } from 'react';
-import ImporterDrawer from '../../components/importer/ImporterDrawer';
import { ActionDropdown } from '../../components/items/ActionDropdown';
import ImportPartWizard from '../../components/wizards/ImportPartWizard';
import OrderPartsWizard from '../../components/wizards/OrderPartsWizard';
@@ -35,6 +34,7 @@ import {
} from '../../hooks/UseForm';
import { usePluginsWithMixin } from '../../hooks/UsePlugins';
import { useTable } from '../../hooks/UseTable';
+import { useImporterState } from '../../states/ImporterState';
import { useGlobalSettingsState } from '../../states/SettingsStates';
import { useUserState } from '../../states/UserState';
import {
@@ -357,12 +357,7 @@ export function PartListTable({
});
const user = useUserState();
const globalSettings = useGlobalSettingsState();
-
- const [importOpened, setImportOpened] = useState(false);
-
- const [selectedSession, setSelectedSession] = useState(
- undefined
- );
+ const openImporter = useImporterState((state) => state.openImporter);
const importSessionFields = useMemo(() => {
const fields = dataImporterSessionFields({
@@ -383,8 +378,9 @@ export function PartListTable({
title: t`Import Parts`,
fields: importSessionFields,
onFormSuccess: (response: any) => {
- setSelectedSession(response.pk);
- setImportOpened(true);
+ openImporter(response.pk, {
+ onClose: table.refreshTable
+ });
}
});
@@ -598,15 +594,6 @@ export function PartListTable({
}
}}
/>
- {
- setSelectedSession(undefined);
- setImportOpened(false);
- table.refreshTable();
- }}
- />
>
);
}
diff --git a/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx b/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx
index f792cec63b..e529f93c34 100644
--- a/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx
+++ b/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx
@@ -21,7 +21,6 @@ import { formatDecimal } from '@lib/functions/Formatting';
import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn } from '@lib/types/Tables';
import { useNavigate } from 'react-router-dom';
-import ImporterDrawer from '../../components/importer/ImporterDrawer';
import { RenderInstance } from '../../components/render/Instance';
import { formatCurrency } from '../../defaults/formatters';
import { dataImporterSessionFields } from '../../forms/ImporterForms';
@@ -36,6 +35,7 @@ import {
} from '../../hooks/UseForm';
import useStatusCodes from '../../hooks/UseStatusCodes';
import { useTable } from '../../hooks/UseTable';
+import { useImporterState } from '../../states/ImporterState';
import { useGlobalSettingsState } from '../../states/SettingsStates';
import { useUserState } from '../../states/UserState';
import {
@@ -77,12 +77,7 @@ export function PurchaseOrderLineItemTable({
const globalSettings = useGlobalSettingsState();
const navigate = useNavigate();
const user = useUserState();
-
- // Data import
- const [importOpened, setImportOpened] = useState(false);
- const [selectedSession, setSelectedSession] = useState(
- undefined
- );
+ const openImporter = useImporterState((state) => state.openImporter);
const importSessionFields = useMemo(() => {
const fields = dataImporterSessionFields({
@@ -115,8 +110,9 @@ export function PurchaseOrderLineItemTable({
title: t`Import Line Items`,
fields: importSessionFields,
onFormSuccess: (response: any) => {
- setSelectedSession(response.pk);
- setImportOpened(true);
+ openImporter(response.pk, {
+ onClose: table.refreshTable
+ });
}
});
@@ -452,15 +448,6 @@ export function PurchaseOrderLineItemTable({
modelField: 'part'
}}
/>
- {
- setSelectedSession(undefined);
- setImportOpened(false);
- table.refreshTable();
- }}
- />
>
);
}
diff --git a/src/frontend/src/tables/settings/ImportSessionTable.tsx b/src/frontend/src/tables/settings/ImportSessionTable.tsx
index 9af6a90159..29d906cdfc 100644
--- a/src/frontend/src/tables/settings/ImportSessionTable.tsx
+++ b/src/frontend/src/tables/settings/ImportSessionTable.tsx
@@ -9,7 +9,6 @@ import { ModelType } from '@lib/enums/ModelType';
import { apiUrl } from '@lib/functions/Api';
import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn } from '@lib/types/Tables';
-import ImporterDrawer from '../../components/importer/ImporterDrawer';
import { AttachmentLink } from '../../components/items/AttachmentLink';
import { RenderUser } from '../../components/render/User';
import { dataImporterSessionFields } from '../../forms/ImporterForms';
@@ -19,14 +18,14 @@ import {
useDeleteApiFormModal
} from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
+import { useImporterState } from '../../states/ImporterState';
import { DateColumn, StatusColumn } from '../ColumnRenderers';
import { StatusFilterOptions, UserFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable';
export default function ImportSessionTable() {
const table = useTable('importsession');
-
- const [opened, setOpened] = useState(false);
+ const openImporter = useImporterState((state) => state.openImporter);
const [selectedSession, setSelectedSession] = useState(
undefined
@@ -47,7 +46,9 @@ export default function ImportSessionTable() {
}),
onFormSuccess: (response: any) => {
setSelectedSession(response.pk);
- setOpened(true);
+ openImporter(response.pk, {
+ onClose: table.refreshTable
+ });
table.refreshTable();
}
});
@@ -159,19 +160,12 @@ export default function ImportSessionTable() {
enableSelection: true,
onRowClick: (record: any) => {
setSelectedSession(record.pk);
- setOpened(true);
+ openImporter(record.pk, {
+ onClose: table.refreshTable
+ });
}
}}
/>
- {
- setSelectedSession(undefined);
- setOpened(false);
- table.refreshTable();
- }}
- />
>
);
}