From 296c54a1d776a95c67f5283d944c6a9b4a913c53 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 8 Jan 2025 07:34:06 +1100 Subject: [PATCH] [UI] API Context (#8851) * Create ApiContext provider * Utilize new context * Remove api from global context * Refactor - No longer need hard-coded API constant * Refactor useInstance hook * Refactoring - QueryCountDatshboardWidget - NotesEditor - RenderInstance * Refactor multiple tables * Fix typos * Refactor useFilters hook - Allow plugins to use this hook! * Further refactoring * Refactor API forms * Cleanup context routing * Fix provision order --- src/backend/InvenTree/part/serializers.py | 2 +- .../widgets/QueryCountDashboardWidget.tsx | 3 +- .../src/components/details/Details.tsx | 5 ++- .../src/components/editors/NotesEditor.tsx | 3 +- src/frontend/src/components/forms/ApiForm.tsx | 8 +++-- .../forms/fields/DependentField.tsx | 3 +- .../forms/fields/RelatedModelField.tsx | 3 +- .../importer/ImportDataSelector.tsx | 3 +- .../importer/ImporterColumnSelector.tsx | 8 ++++- .../src/components/nav/NavigationTree.tsx | 3 +- .../src/components/render/Instance.tsx | 4 ++- .../src/components/settings/SettingList.tsx | 4 ++- src/frontend/src/contexts/ApiContext.tsx | 31 +++++++++++++++++++ src/frontend/src/contexts/BaseContext.tsx | 12 ------- src/frontend/src/forms/PartForms.tsx | 4 ++- src/frontend/src/hooks/UseFilter.tsx | 4 ++- src/frontend/src/hooks/UseInstance.tsx | 4 ++- src/frontend/src/main.tsx | 3 -- src/frontend/src/pages/Notifications.tsx | 3 +- src/frontend/src/pages/part/PartDetail.tsx | 3 +- src/frontend/src/pages/stock/StockDetail.tsx | 17 +++++----- src/frontend/src/tables/InvenTreeTable.tsx | 3 +- .../src/tables/InvenTreeTableHeader.tsx | 4 ++- src/frontend/src/tables/bom/BomTable.tsx | 3 +- .../src/tables/build/BuildOrderTestTable.tsx | 3 +- .../src/tables/build/BuildOutputTable.tsx | 3 +- .../src/tables/general/AttachmentTable.tsx | 3 +- .../src/tables/machine/MachineListTable.tsx | 5 ++- .../src/tables/part/ParametricPartTable.tsx | 13 ++++---- .../src/tables/plugin/PluginListTable.tsx | 3 +- .../tables/stock/StockItemTestResultTable.tsx | 3 +- src/frontend/src/views/DesktopAppView.tsx | 14 ++++----- src/frontend/src/views/MobileAppView.tsx | 6 ++-- 33 files changed, 127 insertions(+), 66 deletions(-) create mode 100644 src/frontend/src/contexts/ApiContext.tsx delete mode 100644 src/frontend/src/contexts/BaseContext.tsx diff --git a/src/backend/InvenTree/part/serializers.py b/src/backend/InvenTree/part/serializers.py index 24f0b5cf66..68ab21990a 100644 --- a/src/backend/InvenTree/part/serializers.py +++ b/src/backend/InvenTree/part/serializers.py @@ -682,7 +682,7 @@ class PartSerializer( Used when displaying all details of a single component. """ - import_exclude_fields = ['duplicate'] + import_exclude_fields = ['duplicate', 'tags'] class Meta: """Metaclass defining serializer fields.""" diff --git a/src/frontend/src/components/dashboard/widgets/QueryCountDashboardWidget.tsx b/src/frontend/src/components/dashboard/widgets/QueryCountDashboardWidget.tsx index 66e70d8c5c..ced5d7f711 100644 --- a/src/frontend/src/components/dashboard/widgets/QueryCountDashboardWidget.tsx +++ b/src/frontend/src/components/dashboard/widgets/QueryCountDashboardWidget.tsx @@ -4,7 +4,7 @@ import { useQuery } from '@tanstack/react-query'; import { type ReactNode, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; -import { api } from '../../../App'; +import { useApi } from '../../../contexts/ApiContext'; import type { ModelType } from '../../../enums/ModelType'; import { InvenTreeIcon, @@ -31,6 +31,7 @@ function QueryCountWidget({ icon?: InvenTreeIconType; params: any; }>): ReactNode { + const api = useApi(); const user = useUserState(); const navigate = useNavigate(); diff --git a/src/frontend/src/components/details/Details.tsx b/src/frontend/src/components/details/Details.tsx index e1cc70e3d4..5395033a05 100644 --- a/src/frontend/src/components/details/Details.tsx +++ b/src/frontend/src/components/details/Details.tsx @@ -14,7 +14,7 @@ import { getValueAtPath } from 'mantine-datatable'; import { useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; -import { api } from '../../App'; +import { useApi } from '../../contexts/ApiContext'; import { formatDate } from '../../defaults/formatters'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import type { ModelType } from '../../enums/ModelType'; @@ -101,6 +101,8 @@ function NameBadge({ pk, type }: Readonly<{ pk: string | number; type: BadgeType }>) { + const api = useApi(); + const { data } = useQuery({ queryKey: ['badge', type, pk], queryFn: async () => { @@ -217,6 +219,7 @@ function BooleanValue(props: Readonly) { } function TableAnchorValue(props: Readonly) { + const api = useApi(); const navigate = useNavigate(); const { data } = useQuery({ diff --git a/src/frontend/src/components/editors/NotesEditor.tsx b/src/frontend/src/components/editors/NotesEditor.tsx index 87bf4a4310..e05d0dac1b 100644 --- a/src/frontend/src/components/editors/NotesEditor.tsx +++ b/src/frontend/src/components/editors/NotesEditor.tsx @@ -7,7 +7,7 @@ import 'easymde/dist/easymde.min.css'; import { useCallback, useEffect, useMemo, useState } from 'react'; import SimpleMDE from 'react-simplemde-editor'; -import { api } from '../../App'; +import { useApi } from '../../contexts/ApiContext'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import type { ModelType } from '../../enums/ModelType'; import { apiUrl } from '../../states/ApiState'; @@ -31,6 +31,7 @@ export default function NotesEditor({ modelId: number; editable?: boolean; }>) { + const api = useApi(); // In addition to the editable prop, we also need to check if the user has "enabled" editing const [editing, setEditing] = useState(false); diff --git a/src/frontend/src/components/forms/ApiForm.tsx b/src/frontend/src/components/forms/ApiForm.tsx index eaa5cb15cf..dd2622bfa1 100644 --- a/src/frontend/src/components/forms/ApiForm.tsx +++ b/src/frontend/src/components/forms/ApiForm.tsx @@ -12,7 +12,7 @@ import { } from '@mantine/core'; import { useId } from '@mantine/hooks'; import { notifications } from '@mantine/notifications'; -import { useQuery } from '@tanstack/react-query'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { type FieldValues, @@ -23,7 +23,7 @@ import { } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; -import { api, queryClient } from '../../App'; +import { useApi } from '../../contexts/ApiContext'; import type { ApiEndpoints } from '../../enums/ApiEndpoints'; import type { ModelType } from '../../enums/ModelType'; import { @@ -110,6 +110,8 @@ export function OptionsApiForm({ props: ApiFormProps; id?: string; }>) { + const api = useApi(); + const props = useMemo( () => ({ ..._props, @@ -205,6 +207,8 @@ export function ApiForm({ props: ApiFormProps; optionsLoading: boolean; }>) { + const api = useApi(); + const queryClient = useQueryClient(); const navigate = useNavigate(); const [fields, setFields] = useState( diff --git a/src/frontend/src/components/forms/fields/DependentField.tsx b/src/frontend/src/components/forms/fields/DependentField.tsx index c8670300ae..6fc3248507 100644 --- a/src/frontend/src/components/forms/fields/DependentField.tsx +++ b/src/frontend/src/components/forms/fields/DependentField.tsx @@ -5,7 +5,7 @@ import { useFormContext } from 'react-hook-form'; -import { api } from '../../../App'; +import { useApi } from '../../../contexts/ApiContext'; import { constructField, extractAvailableFields @@ -29,6 +29,7 @@ export function DependentField({ url?: string; setFields?: React.Dispatch>; }>) { + const api = useApi(); const { watch, resetField } = useFormContext(); const mappedFieldNames = useMemo( diff --git a/src/frontend/src/components/forms/fields/RelatedModelField.tsx b/src/frontend/src/components/forms/fields/RelatedModelField.tsx index e321345b70..96e624de04 100644 --- a/src/frontend/src/components/forms/fields/RelatedModelField.tsx +++ b/src/frontend/src/components/forms/fields/RelatedModelField.tsx @@ -15,7 +15,7 @@ import { } from 'react-hook-form'; import Select from 'react-select'; -import { api } from '../../../App'; +import { useApi } from '../../../contexts/ApiContext'; import { vars } from '../../../theme'; import { RenderInstance } from '../../render/Instance'; import type { ApiFormFieldType } from './ApiFormField'; @@ -34,6 +34,7 @@ export function RelatedModelField({ fieldName: string; limit?: number; }>) { + const api = useApi(); const fieldId = useId(); const { field, diff --git a/src/frontend/src/components/importer/ImportDataSelector.tsx b/src/frontend/src/components/importer/ImportDataSelector.tsx index 531d60f16a..a6120dad20 100644 --- a/src/frontend/src/components/importer/ImportDataSelector.tsx +++ b/src/frontend/src/components/importer/ImportDataSelector.tsx @@ -9,7 +9,7 @@ import { } from '@tabler/icons-react'; import { type ReactNode, useCallback, useMemo, useState } from 'react'; -import { api } from '../../App'; +import { useApi } from '../../contexts/ApiContext'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { cancelEvent } from '../../functions/events'; import { @@ -131,6 +131,7 @@ export default function ImporterDataSelector({ }: Readonly<{ session: ImportSessionState; }>) { + const api = useApi(); const table = useTable('dataimporter'); const [selectedFieldNames, setSelectedFieldNames] = useState([]); diff --git a/src/frontend/src/components/importer/ImporterColumnSelector.tsx b/src/frontend/src/components/importer/ImporterColumnSelector.tsx index b640f3809b..d31dbc8365 100644 --- a/src/frontend/src/components/importer/ImporterColumnSelector.tsx +++ b/src/frontend/src/components/importer/ImporterColumnSelector.tsx @@ -13,7 +13,7 @@ import { import { IconCheck } from '@tabler/icons-react'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { api } from '../../App'; +import { useApi } from '../../contexts/ApiContext'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import type { ImportSessionState } from '../../hooks/UseImportSession'; import { apiUrl } from '../../states/ApiState'; @@ -24,6 +24,8 @@ function ImporterColumn({ column, options }: Readonly<{ column: any; options: any[] }>) { + const api = useApi(); + const [errorMessage, setErrorMessage] = useState(''); const [selectedColumn, setSelectedColumn] = useState( @@ -78,6 +80,8 @@ function ImporterDefaultField({ fieldName: string; session: ImportSessionState; }) { + const api = useApi(); + const onChange = useCallback( (value: any) => { // Update the default value for the field @@ -162,6 +166,8 @@ export default function ImporterColumnSelector({ }: Readonly<{ session: ImportSessionState; }>) { + const api = useApi(); + const [errorMessage, setErrorMessage] = useState(''); const acceptMapping = useCallback(() => { diff --git a/src/frontend/src/components/nav/NavigationTree.tsx b/src/frontend/src/components/nav/NavigationTree.tsx index 98d2f66708..4f192c9042 100644 --- a/src/frontend/src/components/nav/NavigationTree.tsx +++ b/src/frontend/src/components/nav/NavigationTree.tsx @@ -21,7 +21,7 @@ import { useQuery } from '@tanstack/react-query'; import { useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; -import { api } from '../../App'; +import { useApi } from '../../contexts/ApiContext'; import type { ApiEndpoints } from '../../enums/ApiEndpoints'; import type { ModelType } from '../../enums/ModelType'; import { navigateToLink } from '../../functions/navigation'; @@ -48,6 +48,7 @@ export default function NavigationTree({ modelType: ModelType; endpoint: ApiEndpoints; }>) { + const api = useApi(); const navigate = useNavigate(); const treeState = useTree(); diff --git a/src/frontend/src/components/render/Instance.tsx b/src/frontend/src/components/render/Instance.tsx index 0753fd5499..34786d6362 100644 --- a/src/frontend/src/components/render/Instance.tsx +++ b/src/frontend/src/components/render/Instance.tsx @@ -3,7 +3,7 @@ import { Alert, Anchor, Group, Skeleton, Space, Text } from '@mantine/core'; import { useQuery } from '@tanstack/react-query'; import { type ReactNode, useCallback } from 'react'; -import { api } from '../../App'; +import { useApi } from '../../contexts/ApiContext'; import { ModelType } from '../../enums/ModelType'; import { navigateToLink } from '../../functions/navigation'; import { shortenString } from '../../functions/tables'; @@ -130,6 +130,8 @@ export function RenderRemoteInstance({ model: ModelType; pk: number; }>): ReactNode { + const api = useApi(); + const { data, isLoading, isFetching } = useQuery({ queryKey: ['model', model, pk], queryFn: async () => { diff --git a/src/frontend/src/components/settings/SettingList.tsx b/src/frontend/src/components/settings/SettingList.tsx index 4ae79ed631..d212329e85 100644 --- a/src/frontend/src/components/settings/SettingList.tsx +++ b/src/frontend/src/components/settings/SettingList.tsx @@ -10,7 +10,7 @@ import React, { } from 'react'; import { useStore } from 'zustand'; -import { api } from '../../App'; +import { useApi } from '../../contexts/ApiContext'; import type { ModelType } from '../../enums/ModelType'; import { useEditApiFormModal } from '../../hooks/UseForm'; import { apiUrl } from '../../states/ApiState'; @@ -40,6 +40,8 @@ export function SettingList({ settingsState.fetchSettings(); }, []); + const api = useApi(); + const allKeys = useMemo( () => settingsState?.settings?.map((s) => s.key) ?? [], [settingsState?.settings] diff --git a/src/frontend/src/contexts/ApiContext.tsx b/src/frontend/src/contexts/ApiContext.tsx new file mode 100644 index 0000000000..48cfbbe406 --- /dev/null +++ b/src/frontend/src/contexts/ApiContext.tsx @@ -0,0 +1,31 @@ +import { type QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import type { AxiosInstance } from 'axios'; +import { createContext, useContext } from 'react'; + +const ApiContext = createContext(null); + +export const ApiProvider = ({ + api, + client, + children +}: { + api: AxiosInstance; + client: QueryClient; + children: React.ReactNode; +}) => { + return ( + + {children} + + ); +}; + +export const useApi = () => { + const context = useContext(ApiContext); + + if (!context) { + throw new Error('useApi must be used within an ApiProvider'); + } + + return context; +}; diff --git a/src/frontend/src/contexts/BaseContext.tsx b/src/frontend/src/contexts/BaseContext.tsx deleted file mode 100644 index e7dfb3c32e..0000000000 --- a/src/frontend/src/contexts/BaseContext.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { QueryClientProvider } from '@tanstack/react-query'; - -import { queryClient } from '../App'; -import { ThemeContext } from './ThemeContext'; - -export const BaseContext = ({ children }: { children: any }) => { - return ( - - {children} - - ); -}; diff --git a/src/frontend/src/forms/PartForms.tsx b/src/frontend/src/forms/PartForms.tsx index ecff565731..4b5e57c9eb 100644 --- a/src/frontend/src/forms/PartForms.tsx +++ b/src/frontend/src/forms/PartForms.tsx @@ -2,8 +2,8 @@ import { t } from '@lingui/macro'; import { IconPackages } from '@tabler/icons-react'; import { useMemo, useState } from 'react'; -import { api } from '../App'; import type { ApiFormFieldSet } from '../components/forms/fields/ApiFormField'; +import { useApi } from '../contexts/ApiContext'; import { ApiEndpoints } from '../enums/ApiEndpoints'; import { apiUrl } from '../states/ApiState'; import { useGlobalSettingsState } from '../states/SettingsState'; @@ -179,6 +179,8 @@ export function usePartParameterFields({ }: { editTemplate?: boolean; }): ApiFormFieldSet { + const api = useApi(); + // Valid field choices const [choices, setChoices] = useState([]); diff --git a/src/frontend/src/hooks/UseFilter.tsx b/src/frontend/src/hooks/UseFilter.tsx index 92419dcf00..4595866c98 100644 --- a/src/frontend/src/hooks/UseFilter.tsx +++ b/src/frontend/src/hooks/UseFilter.tsx @@ -5,7 +5,7 @@ import { useQuery } from '@tanstack/react-query'; import { useCallback, useMemo } from 'react'; -import { api } from '../App'; +import { useApi } from '../contexts/ApiContext'; import { ApiEndpoints } from '../enums/ApiEndpoints'; import { resolveItem } from '../functions/conversion'; import { apiUrl } from '../states/ApiState'; @@ -20,6 +20,8 @@ type UseFilterProps = { }; export function useFilters(props: UseFilterProps) { + const api = useApi(); + const query = useQuery({ enabled: true, gcTime: 500, diff --git a/src/frontend/src/hooks/UseInstance.tsx b/src/frontend/src/hooks/UseInstance.tsx index 2345c800fb..08a8ae8a9c 100644 --- a/src/frontend/src/hooks/UseInstance.tsx +++ b/src/frontend/src/hooks/UseInstance.tsx @@ -1,7 +1,7 @@ import { type QueryObserverResult, useQuery } from '@tanstack/react-query'; import { useCallback, useMemo, useState } from 'react'; -import { api } from '../App'; +import { useApi } from '../contexts/ApiContext'; import type { ApiEndpoints } from '../enums/ApiEndpoints'; import { type PathParams, apiUrl } from '../states/ApiState'; @@ -48,6 +48,8 @@ export function useInstance({ throwError?: boolean; updateInterval?: number; }): UseInstanceResult { + const api = useApi(); + const [instance, setInstance] = useState(defaultValue); const [requestStatus, setRequestStatus] = useState(0); diff --git a/src/frontend/src/main.tsx b/src/frontend/src/main.tsx index b8377c396c..69815de890 100644 --- a/src/frontend/src/main.tsx +++ b/src/frontend/src/main.tsx @@ -12,7 +12,6 @@ import ReactDOM from 'react-dom/client'; import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; -import { api } from './App'; import type { HostList } from './states/states'; import MainView from './views/MainView'; @@ -27,7 +26,6 @@ declare global { sentry_dsn?: string; environment?: string; }; - InvenTreeAPI: typeof api; React: typeof React; } } @@ -105,4 +103,3 @@ if (window.location.pathname === '/') { } window.React = React; -window.InvenTreeAPI = api; diff --git a/src/frontend/src/pages/Notifications.tsx b/src/frontend/src/pages/Notifications.tsx index 13fa75e799..4bc99f34c7 100644 --- a/src/frontend/src/pages/Notifications.tsx +++ b/src/frontend/src/pages/Notifications.tsx @@ -11,16 +11,17 @@ import { } from '@tabler/icons-react'; import { useCallback, useMemo } from 'react'; -import { api } from '../App'; import { ActionButton } from '../components/buttons/ActionButton'; import { PageDetail } from '../components/nav/PageDetail'; import { PanelGroup } from '../components/panels/PanelGroup'; +import { useApi } from '../contexts/ApiContext'; import { ApiEndpoints } from '../enums/ApiEndpoints'; import { useTable } from '../hooks/UseTable'; import { apiUrl } from '../states/ApiState'; import { NotificationTable } from '../tables/notifications/NotificationTable'; export default function NotificationsPage() { + const api = useApi(); const unreadTable = useTable('unreadnotifications'); const readTable = useTable('readnotifications'); diff --git a/src/frontend/src/pages/part/PartDetail.tsx b/src/frontend/src/pages/part/PartDetail.tsx index 6b23a647e1..248298612d 100644 --- a/src/frontend/src/pages/part/PartDetail.tsx +++ b/src/frontend/src/pages/part/PartDetail.tsx @@ -34,7 +34,6 @@ import { type ReactNode, useMemo, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import Select from 'react-select'; -import { api } from '../../App'; import AdminButton from '../../components/buttons/AdminButton'; import { PrintingActions } from '../../components/buttons/PrintingActions'; import { @@ -63,6 +62,7 @@ import type { PanelType } from '../../components/panels/Panel'; import { PanelGroup } from '../../components/panels/PanelGroup'; import { RenderPart } from '../../components/render/Part'; import OrderPartsWizard from '../../components/wizards/OrderPartsWizard'; +import { useApi } from '../../contexts/ApiContext'; import { formatPriceRange } from '../../defaults/formatters'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; @@ -110,6 +110,7 @@ import PartSupplierDetail from './PartSupplierDetail'; export default function PartDetail() { const { id } = useParams(); + const api = useApi(); const navigate = useNavigate(); const user = useUserState(); diff --git a/src/frontend/src/pages/stock/StockDetail.tsx b/src/frontend/src/pages/stock/StockDetail.tsx index 79a1552aaa..a4ffb6c3ad 100644 --- a/src/frontend/src/pages/stock/StockDetail.tsx +++ b/src/frontend/src/pages/stock/StockDetail.tsx @@ -14,7 +14,6 @@ import { useQuery } from '@tanstack/react-query'; import { type ReactNode, useMemo, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { api } from '../../App'; import AdminButton from '../../components/buttons/AdminButton'; import { PrintingActions } from '../../components/buttons/PrintingActions'; import { @@ -43,6 +42,7 @@ import { PanelGroup } from '../../components/panels/PanelGroup'; import LocateItemButton from '../../components/plugins/LocateItemButton'; import { StatusRenderer } from '../../components/render/StatusRenderer'; import OrderPartsWizard from '../../components/wizards/OrderPartsWizard'; +import { useApi } from '../../contexts/ApiContext'; import { formatCurrency } from '../../defaults/formatters'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; @@ -78,6 +78,7 @@ import { StockTrackingTable } from '../../tables/stock/StockTrackingTable'; export default function StockDetail() { const { id } = useParams(); + const api = useApi(); const user = useUserState(); const globalSettings = useGlobalSettingsState(); @@ -377,7 +378,7 @@ export default function StockDetail() { ); // Must not be installed into another item }, [stockitem]); - const showSalesAlloctions: boolean = useMemo(() => { + const showSalesAllocations: boolean = useMemo(() => { return stockitem?.part_detail?.salable; }, [stockitem]); @@ -452,14 +453,14 @@ export default function StockDetail() { icon: , hidden: !stockitem.in_stock || - (!showSalesAlloctions && !showBuildAllocations), + (!showSalesAllocations && !showBuildAllocations), content: ( {showBuildAllocations && ( - + {t`Build Order Allocations`} @@ -473,8 +474,8 @@ export default function StockDetail() { )} - {showSalesAlloctions && ( - + {showSalesAllocations && ( + {t`Sales Order Allocations`} @@ -536,7 +537,7 @@ export default function StockDetail() { }) ]; }, [ - showSalesAlloctions, + showSalesAllocations, showBuildAllocations, showInstalledItems, stockitem, diff --git a/src/frontend/src/tables/InvenTreeTable.tsx b/src/frontend/src/tables/InvenTreeTable.tsx index 758dd32d8b..9baf8c52e6 100644 --- a/src/frontend/src/tables/InvenTreeTable.tsx +++ b/src/frontend/src/tables/InvenTreeTable.tsx @@ -13,9 +13,9 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { showNotification } from '@mantine/notifications'; -import { api } from '../App'; import { Boundary } from '../components/Boundary'; import type { ApiFormFieldSet } from '../components/forms/fields/ApiFormField'; +import { useApi } from '../contexts/ApiContext'; import type { ModelType } from '../enums/ModelType'; import { resolveItem } from '../functions/conversion'; import { cancelEvent } from '../functions/events'; @@ -139,6 +139,7 @@ export function InvenTreeTable>({ const [fieldNames, setFieldNames] = useState>({}); + const api = useApi(); const navigate = useNavigate(); const { showContextMenu } = useContextMenu(); diff --git a/src/frontend/src/tables/InvenTreeTableHeader.tsx b/src/frontend/src/tables/InvenTreeTableHeader.tsx index 8fe7f8c397..f8241da173 100644 --- a/src/frontend/src/tables/InvenTreeTableHeader.tsx +++ b/src/frontend/src/tables/InvenTreeTableHeader.tsx @@ -16,11 +16,11 @@ import { import { useState } from 'react'; import { Fragment } from 'react/jsx-runtime'; -import { api } from '../App'; import { Boundary } from '../components/Boundary'; import { ActionButton } from '../components/buttons/ActionButton'; import { ButtonMenu } from '../components/buttons/ButtonMenu'; import { PrintingActions } from '../components/buttons/PrintingActions'; +import { useApi } from '../contexts/ApiContext'; import { useDeleteApiFormModal } from '../hooks/UseForm'; import type { TableState } from '../hooks/UseTable'; import { TableColumnSelect } from './ColumnSelect'; @@ -50,6 +50,8 @@ export default function InvenTreeTableHeader({ filters: TableFilter[]; toggleColumn: (column: string) => void; }>) { + const api = useApi(); + // Filter list visibility const [filtersVisible, setFiltersVisible] = useState(false); diff --git a/src/frontend/src/tables/bom/BomTable.tsx b/src/frontend/src/tables/bom/BomTable.tsx index 6e9509bf58..cbe61fc127 100644 --- a/src/frontend/src/tables/bom/BomTable.tsx +++ b/src/frontend/src/tables/bom/BomTable.tsx @@ -11,12 +11,12 @@ import { import { type ReactNode, useCallback, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { api } from '../../App'; import { ActionButton } from '../../components/buttons/ActionButton'; import { AddItemButton } from '../../components/buttons/AddItemButton'; import { YesNoButton } from '../../components/buttons/YesNoButton'; import { Thumbnail } from '../../components/images/Thumbnail'; import ImporterDrawer from '../../components/importer/ImporterDrawer'; +import { useApi } from '../../contexts/ApiContext'; import { formatDecimal, formatPriceRange } from '../../defaults/formatters'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; @@ -71,6 +71,7 @@ export function BomTable({ partLocked?: boolean; params?: any; }>) { + const api = useApi(); const user = useUserState(); const table = useTable('bom'); const navigate = useNavigate(); diff --git a/src/frontend/src/tables/build/BuildOrderTestTable.tsx b/src/frontend/src/tables/build/BuildOrderTestTable.tsx index df6619c9ae..0a67bcb10f 100644 --- a/src/frontend/src/tables/build/BuildOrderTestTable.tsx +++ b/src/frontend/src/tables/build/BuildOrderTestTable.tsx @@ -10,10 +10,10 @@ import { useState } from 'react'; -import { api } from '../../App'; import { PassFailButton } from '../../components/buttons/YesNoButton'; import type { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField'; import { RenderUser } from '../../components/render/User'; +import { useApi } from '../../contexts/ApiContext'; import { formatDate } from '../../defaults/formatters'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { useTestResultFields } from '../../forms/StockForms'; @@ -40,6 +40,7 @@ export default function BuildOrderTestTable({ }>) { const table = useTable('build-tests'); const user = useUserState(); + const api = useApi(); // Fetch the test templates required for this build order const { data: testTemplates } = useQuery({ diff --git a/src/frontend/src/tables/build/BuildOutputTable.tsx b/src/frontend/src/tables/build/BuildOutputTable.tsx index e6f71bb891..594d9471c1 100644 --- a/src/frontend/src/tables/build/BuildOutputTable.tsx +++ b/src/frontend/src/tables/build/BuildOutputTable.tsx @@ -18,11 +18,11 @@ import { useQuery } from '@tanstack/react-query'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { api } from '../../App'; import { ActionButton } from '../../components/buttons/ActionButton'; import { AddItemButton } from '../../components/buttons/AddItemButton'; import { ProgressBar } from '../../components/items/ProgressBar'; import { StylishText } from '../../components/items/StylishText'; +import { useApi } from '../../contexts/ApiContext'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; @@ -121,6 +121,7 @@ export default function BuildOutputTable({ build, refreshBuild }: Readonly<{ build: any; refreshBuild: () => void }>) { + const api = useApi(); const user = useUserState(); const navigate = useNavigate(); const table = useTable('build-outputs'); diff --git a/src/frontend/src/tables/general/AttachmentTable.tsx b/src/frontend/src/tables/general/AttachmentTable.tsx index 3e19e45adc..18506d8650 100644 --- a/src/frontend/src/tables/general/AttachmentTable.tsx +++ b/src/frontend/src/tables/general/AttachmentTable.tsx @@ -10,10 +10,10 @@ import { } from '@tabler/icons-react'; import { type ReactNode, useCallback, useMemo, useState } from 'react'; -import { api } from '../../App'; import { ActionButton } from '../../components/buttons/ActionButton'; import type { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField'; import { AttachmentLink } from '../../components/items/AttachmentLink'; +import { useApi } from '../../contexts/ApiContext'; import { formatFileSize } from '../../defaults/formatters'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import type { ModelType } from '../../enums/ModelType'; @@ -99,6 +99,7 @@ export function AttachmentTable({ model_type: ModelType; model_id: number; }>): ReactNode { + const api = useApi(); const user = useUserState(); const table = useTable(`${model_type}-attachments`); diff --git a/src/frontend/src/tables/machine/MachineListTable.tsx b/src/frontend/src/tables/machine/MachineListTable.tsx index c6f13a9ed0..09e7c9e299 100644 --- a/src/frontend/src/tables/machine/MachineListTable.tsx +++ b/src/frontend/src/tables/machine/MachineListTable.tsx @@ -20,7 +20,6 @@ import { useQuery } from '@tanstack/react-query'; import { useCallback, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { api } from '../../App'; import { AddItemButton } from '../../components/buttons/AddItemButton'; import { YesNoButton } from '../../components/buttons/YesNoButton'; import { @@ -40,6 +39,7 @@ import { TableStatusRenderer } from '../../components/render/StatusRenderer'; import { MachineSettingList } from '../../components/settings/SettingList'; +import { useApi } from '../../contexts/ApiContext'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { useCreateApiFormModal, @@ -103,6 +103,8 @@ export function useMachineTypeDriver({ includeTypes = true, includeDrivers = true }: { includeTypes?: boolean; includeDrivers?: boolean } = {}) { + const api = useApi(); + const { data: machineTypes, isFetching: isMachineTypesFetching, @@ -146,6 +148,7 @@ function MachineDrawer({ machinePk: string; refreshTable: () => void; }>) { + const api = useApi(); const navigate = useNavigate(); const { data: machine, diff --git a/src/frontend/src/tables/part/ParametricPartTable.tsx b/src/frontend/src/tables/part/ParametricPartTable.tsx index 8381622177..6d40eecfaf 100644 --- a/src/frontend/src/tables/part/ParametricPartTable.tsx +++ b/src/frontend/src/tables/part/ParametricPartTable.tsx @@ -5,9 +5,9 @@ import { useQuery } from '@tanstack/react-query'; import { type ReactNode, useCallback, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { api } from '../../App'; import { YesNoButton } from '../../components/buttons/YesNoButton'; import type { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField'; +import { useApi } from '../../contexts/ApiContext'; import { formatDecimal } from '../../defaults/formatters'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; @@ -95,11 +95,12 @@ export default function ParametricPartTable({ }: Readonly<{ categoryId?: any; }>) { + const api = useApi(); const table = useTable('parametric-parts'); const user = useUserState(); const navigate = useNavigate(); - const categoryParmeters = useQuery({ + const categoryParameters = useQuery({ queryKey: ['category-parameters', categoryId], queryFn: async () => { return api @@ -170,13 +171,13 @@ export default function ParametricPartTable({ records[partIndex].parameters[parameterIndex] = parameter; } - table.setRecords(records); + table.updateRecord(records[partIndex]); }, - [table.records] + [table.updateRecord] ); const parameterColumns: TableColumn[] = useMemo(() => { - const data = categoryParmeters.data ?? []; + const data = categoryParameters.data ?? []; return data.map((template: any) => { let title = template.name; @@ -201,7 +202,7 @@ export default function ParametricPartTable({ ) }; }); - }, [user, categoryParmeters.data]); + }, [user, categoryParameters.data]); const onParameterClick = useCallback((template: number, part: any) => { setSelectedTemplate(template); diff --git a/src/frontend/src/tables/plugin/PluginListTable.tsx b/src/frontend/src/tables/plugin/PluginListTable.tsx index ea589d3e04..2d3af946fb 100644 --- a/src/frontend/src/tables/plugin/PluginListTable.tsx +++ b/src/frontend/src/tables/plugin/PluginListTable.tsx @@ -13,12 +13,12 @@ import { import { useCallback, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { api } from '../../App'; import { ActionButton } from '../../components/buttons/ActionButton'; import { YesNoButton } from '../../components/buttons/YesNoButton'; import { DetailDrawer } from '../../components/nav/DetailDrawer'; import PluginDrawer from '../../components/plugins/PluginDrawer'; import type { PluginInterface } from '../../components/plugins/PluginInterface'; +import { useApi } from '../../contexts/ApiContext'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { useCreateApiFormModal, @@ -63,6 +63,7 @@ function PluginIcon({ plugin }: Readonly<{ plugin: PluginInterface }>) { * Table displaying list of available plugins */ export default function PluginListTable() { + const api = useApi(); const table = useTable('plugin'); const navigate = useNavigate(); const user = useUserState(); diff --git a/src/frontend/src/tables/stock/StockItemTestResultTable.tsx b/src/frontend/src/tables/stock/StockItemTestResultTable.tsx index 8ca28c67a6..bef6ea0b63 100644 --- a/src/frontend/src/tables/stock/StockItemTestResultTable.tsx +++ b/src/frontend/src/tables/stock/StockItemTestResultTable.tsx @@ -10,12 +10,12 @@ import { useQuery } from '@tanstack/react-query'; import { DataTable } from 'mantine-datatable'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { api } from '../../App'; import { AddItemButton } from '../../components/buttons/AddItemButton'; import { PassFailButton } from '../../components/buttons/YesNoButton'; import type { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField'; import { AttachmentLink } from '../../components/items/AttachmentLink'; import { RenderUser } from '../../components/render/User'; +import { useApi } from '../../contexts/ApiContext'; import { formatDate } from '../../defaults/formatters'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { UserRoles } from '../../enums/Roles'; @@ -47,6 +47,7 @@ export default function StockItemTestResultTable({ partId: number; itemId: number; }>) { + const api = useApi(); const user = useUserState(); const table = useTable('stocktests'); diff --git a/src/frontend/src/views/DesktopAppView.tsx b/src/frontend/src/views/DesktopAppView.tsx index eaedb264ac..a0415a93e5 100644 --- a/src/frontend/src/views/DesktopAppView.tsx +++ b/src/frontend/src/views/DesktopAppView.tsx @@ -1,9 +1,9 @@ -import { QueryClientProvider } from '@tanstack/react-query'; import { useEffect } from 'react'; import { BrowserRouter } from 'react-router-dom'; -import { queryClient } from '../App'; -import { BaseContext } from '../contexts/BaseContext'; +import { api, queryClient } from '../App'; +import { ApiProvider } from '../contexts/ApiContext'; +import { ThemeContext } from '../contexts/ThemeContext'; import { defaultHostList } from '../defaults/defaultHostList'; import { base_url } from '../main'; import { routes } from '../router'; @@ -19,10 +19,10 @@ export default function DesktopAppView() { }, [hostList]); return ( - - + + {routes} - - + + ); } diff --git a/src/frontend/src/views/MobileAppView.tsx b/src/frontend/src/views/MobileAppView.tsx index 99c912e5f6..ec1ce5fbe0 100644 --- a/src/frontend/src/views/MobileAppView.tsx +++ b/src/frontend/src/views/MobileAppView.tsx @@ -1,7 +1,7 @@ import { Trans } from '@lingui/macro'; import { Anchor, Center, Container, Stack, Text, Title } from '@mantine/core'; -import { BaseContext } from '../contexts/BaseContext'; +import { ThemeContext } from '../contexts/ThemeContext'; import { docLinks } from '../defaults/links'; import { IS_DEV } from '../main'; import { useLocalState } from '../states/LocalState'; @@ -14,7 +14,7 @@ export default function MobileAppView() { window.location.reload(); } return ( - +
@@ -38,6 +38,6 @@ export default function MobileAppView() {
-
+ ); }