2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-05-01 04:56:45 +00:00

[UI] API Context (#8851)

* Create ApiContext provider

* Utilize new context

* Remove api from global context

* Refactor <InvenTreeTable>

- 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
This commit is contained in:
Oliver 2025-01-08 07:34:06 +11:00 committed by GitHub
parent 3a62bdd276
commit 296c54a1d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 127 additions and 66 deletions

View File

@ -682,7 +682,7 @@ class PartSerializer(
Used when displaying all details of a single component. Used when displaying all details of a single component.
""" """
import_exclude_fields = ['duplicate'] import_exclude_fields = ['duplicate', 'tags']
class Meta: class Meta:
"""Metaclass defining serializer fields.""" """Metaclass defining serializer fields."""

View File

@ -4,7 +4,7 @@ import { useQuery } from '@tanstack/react-query';
import { type ReactNode, useCallback } from 'react'; import { type ReactNode, useCallback } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { api } from '../../../App'; import { useApi } from '../../../contexts/ApiContext';
import type { ModelType } from '../../../enums/ModelType'; import type { ModelType } from '../../../enums/ModelType';
import { import {
InvenTreeIcon, InvenTreeIcon,
@ -31,6 +31,7 @@ function QueryCountWidget({
icon?: InvenTreeIconType; icon?: InvenTreeIconType;
params: any; params: any;
}>): ReactNode { }>): ReactNode {
const api = useApi();
const user = useUserState(); const user = useUserState();
const navigate = useNavigate(); const navigate = useNavigate();

View File

@ -14,7 +14,7 @@ import { getValueAtPath } from 'mantine-datatable';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { api } from '../../App'; import { useApi } from '../../contexts/ApiContext';
import { formatDate } from '../../defaults/formatters'; import { formatDate } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import type { ModelType } from '../../enums/ModelType'; import type { ModelType } from '../../enums/ModelType';
@ -101,6 +101,8 @@ function NameBadge({
pk, pk,
type type
}: Readonly<{ pk: string | number; type: BadgeType }>) { }: Readonly<{ pk: string | number; type: BadgeType }>) {
const api = useApi();
const { data } = useQuery({ const { data } = useQuery({
queryKey: ['badge', type, pk], queryKey: ['badge', type, pk],
queryFn: async () => { queryFn: async () => {
@ -217,6 +219,7 @@ function BooleanValue(props: Readonly<FieldProps>) {
} }
function TableAnchorValue(props: Readonly<FieldProps>) { function TableAnchorValue(props: Readonly<FieldProps>) {
const api = useApi();
const navigate = useNavigate(); const navigate = useNavigate();
const { data } = useQuery({ const { data } = useQuery({

View File

@ -7,7 +7,7 @@ import 'easymde/dist/easymde.min.css';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import SimpleMDE from 'react-simplemde-editor'; import SimpleMDE from 'react-simplemde-editor';
import { api } from '../../App'; import { useApi } from '../../contexts/ApiContext';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import type { ModelType } from '../../enums/ModelType'; import type { ModelType } from '../../enums/ModelType';
import { apiUrl } from '../../states/ApiState'; import { apiUrl } from '../../states/ApiState';
@ -31,6 +31,7 @@ export default function NotesEditor({
modelId: number; modelId: number;
editable?: boolean; editable?: boolean;
}>) { }>) {
const api = useApi();
// In addition to the editable prop, we also need to check if the user has "enabled" editing // In addition to the editable prop, we also need to check if the user has "enabled" editing
const [editing, setEditing] = useState<boolean>(false); const [editing, setEditing] = useState<boolean>(false);

View File

@ -12,7 +12,7 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { useId } from '@mantine/hooks'; import { useId } from '@mantine/hooks';
import { notifications } from '@mantine/notifications'; 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 { useCallback, useEffect, useMemo, useState } from 'react';
import { import {
type FieldValues, type FieldValues,
@ -23,7 +23,7 @@ import {
} from 'react-hook-form'; } from 'react-hook-form';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { api, queryClient } from '../../App'; import { useApi } from '../../contexts/ApiContext';
import type { ApiEndpoints } from '../../enums/ApiEndpoints'; import type { ApiEndpoints } from '../../enums/ApiEndpoints';
import type { ModelType } from '../../enums/ModelType'; import type { ModelType } from '../../enums/ModelType';
import { import {
@ -110,6 +110,8 @@ export function OptionsApiForm({
props: ApiFormProps; props: ApiFormProps;
id?: string; id?: string;
}>) { }>) {
const api = useApi();
const props = useMemo( const props = useMemo(
() => ({ () => ({
..._props, ..._props,
@ -205,6 +207,8 @@ export function ApiForm({
props: ApiFormProps; props: ApiFormProps;
optionsLoading: boolean; optionsLoading: boolean;
}>) { }>) {
const api = useApi();
const queryClient = useQueryClient();
const navigate = useNavigate(); const navigate = useNavigate();
const [fields, setFields] = useState<ApiFormFieldSet>( const [fields, setFields] = useState<ApiFormFieldSet>(

View File

@ -5,7 +5,7 @@ import {
useFormContext useFormContext
} from 'react-hook-form'; } from 'react-hook-form';
import { api } from '../../../App'; import { useApi } from '../../../contexts/ApiContext';
import { import {
constructField, constructField,
extractAvailableFields extractAvailableFields
@ -29,6 +29,7 @@ export function DependentField({
url?: string; url?: string;
setFields?: React.Dispatch<React.SetStateAction<ApiFormFieldSet>>; setFields?: React.Dispatch<React.SetStateAction<ApiFormFieldSet>>;
}>) { }>) {
const api = useApi();
const { watch, resetField } = useFormContext(); const { watch, resetField } = useFormContext();
const mappedFieldNames = useMemo( const mappedFieldNames = useMemo(

View File

@ -15,7 +15,7 @@ import {
} from 'react-hook-form'; } from 'react-hook-form';
import Select from 'react-select'; import Select from 'react-select';
import { api } from '../../../App'; import { useApi } from '../../../contexts/ApiContext';
import { vars } from '../../../theme'; import { vars } from '../../../theme';
import { RenderInstance } from '../../render/Instance'; import { RenderInstance } from '../../render/Instance';
import type { ApiFormFieldType } from './ApiFormField'; import type { ApiFormFieldType } from './ApiFormField';
@ -34,6 +34,7 @@ export function RelatedModelField({
fieldName: string; fieldName: string;
limit?: number; limit?: number;
}>) { }>) {
const api = useApi();
const fieldId = useId(); const fieldId = useId();
const { const {
field, field,

View File

@ -9,7 +9,7 @@ import {
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { type ReactNode, useCallback, useMemo, useState } from 'react'; import { type ReactNode, useCallback, useMemo, useState } from 'react';
import { api } from '../../App'; import { useApi } from '../../contexts/ApiContext';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { cancelEvent } from '../../functions/events'; import { cancelEvent } from '../../functions/events';
import { import {
@ -131,6 +131,7 @@ export default function ImporterDataSelector({
}: Readonly<{ }: Readonly<{
session: ImportSessionState; session: ImportSessionState;
}>) { }>) {
const api = useApi();
const table = useTable('dataimporter'); const table = useTable('dataimporter');
const [selectedFieldNames, setSelectedFieldNames] = useState<string[]>([]); const [selectedFieldNames, setSelectedFieldNames] = useState<string[]>([]);

View File

@ -13,7 +13,7 @@ import {
import { IconCheck } from '@tabler/icons-react'; import { IconCheck } from '@tabler/icons-react';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { api } from '../../App'; import { useApi } from '../../contexts/ApiContext';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import type { ImportSessionState } from '../../hooks/UseImportSession'; import type { ImportSessionState } from '../../hooks/UseImportSession';
import { apiUrl } from '../../states/ApiState'; import { apiUrl } from '../../states/ApiState';
@ -24,6 +24,8 @@ function ImporterColumn({
column, column,
options options
}: Readonly<{ column: any; options: any[] }>) { }: Readonly<{ column: any; options: any[] }>) {
const api = useApi();
const [errorMessage, setErrorMessage] = useState<string>(''); const [errorMessage, setErrorMessage] = useState<string>('');
const [selectedColumn, setSelectedColumn] = useState<string>( const [selectedColumn, setSelectedColumn] = useState<string>(
@ -78,6 +80,8 @@ function ImporterDefaultField({
fieldName: string; fieldName: string;
session: ImportSessionState; session: ImportSessionState;
}) { }) {
const api = useApi();
const onChange = useCallback( const onChange = useCallback(
(value: any) => { (value: any) => {
// Update the default value for the field // Update the default value for the field
@ -162,6 +166,8 @@ export default function ImporterColumnSelector({
}: Readonly<{ }: Readonly<{
session: ImportSessionState; session: ImportSessionState;
}>) { }>) {
const api = useApi();
const [errorMessage, setErrorMessage] = useState<string>(''); const [errorMessage, setErrorMessage] = useState<string>('');
const acceptMapping = useCallback(() => { const acceptMapping = useCallback(() => {

View File

@ -21,7 +21,7 @@ import { useQuery } from '@tanstack/react-query';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { api } from '../../App'; import { useApi } from '../../contexts/ApiContext';
import type { ApiEndpoints } from '../../enums/ApiEndpoints'; import type { ApiEndpoints } from '../../enums/ApiEndpoints';
import type { ModelType } from '../../enums/ModelType'; import type { ModelType } from '../../enums/ModelType';
import { navigateToLink } from '../../functions/navigation'; import { navigateToLink } from '../../functions/navigation';
@ -48,6 +48,7 @@ export default function NavigationTree({
modelType: ModelType; modelType: ModelType;
endpoint: ApiEndpoints; endpoint: ApiEndpoints;
}>) { }>) {
const api = useApi();
const navigate = useNavigate(); const navigate = useNavigate();
const treeState = useTree(); const treeState = useTree();

View File

@ -3,7 +3,7 @@ import { Alert, Anchor, Group, Skeleton, Space, Text } from '@mantine/core';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { type ReactNode, useCallback } from 'react'; import { type ReactNode, useCallback } from 'react';
import { api } from '../../App'; import { useApi } from '../../contexts/ApiContext';
import { ModelType } from '../../enums/ModelType'; import { ModelType } from '../../enums/ModelType';
import { navigateToLink } from '../../functions/navigation'; import { navigateToLink } from '../../functions/navigation';
import { shortenString } from '../../functions/tables'; import { shortenString } from '../../functions/tables';
@ -130,6 +130,8 @@ export function RenderRemoteInstance({
model: ModelType; model: ModelType;
pk: number; pk: number;
}>): ReactNode { }>): ReactNode {
const api = useApi();
const { data, isLoading, isFetching } = useQuery({ const { data, isLoading, isFetching } = useQuery({
queryKey: ['model', model, pk], queryKey: ['model', model, pk],
queryFn: async () => { queryFn: async () => {

View File

@ -10,7 +10,7 @@ import React, {
} from 'react'; } from 'react';
import { useStore } from 'zustand'; import { useStore } from 'zustand';
import { api } from '../../App'; import { useApi } from '../../contexts/ApiContext';
import type { ModelType } from '../../enums/ModelType'; import type { ModelType } from '../../enums/ModelType';
import { useEditApiFormModal } from '../../hooks/UseForm'; import { useEditApiFormModal } from '../../hooks/UseForm';
import { apiUrl } from '../../states/ApiState'; import { apiUrl } from '../../states/ApiState';
@ -40,6 +40,8 @@ export function SettingList({
settingsState.fetchSettings(); settingsState.fetchSettings();
}, []); }, []);
const api = useApi();
const allKeys = useMemo( const allKeys = useMemo(
() => settingsState?.settings?.map((s) => s.key) ?? [], () => settingsState?.settings?.map((s) => s.key) ?? [],
[settingsState?.settings] [settingsState?.settings]

View File

@ -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<AxiosInstance | null>(null);
export const ApiProvider = ({
api,
client,
children
}: {
api: AxiosInstance;
client: QueryClient;
children: React.ReactNode;
}) => {
return (
<QueryClientProvider client={client}>
<ApiContext.Provider value={api}>{children}</ApiContext.Provider>
</QueryClientProvider>
);
};
export const useApi = () => {
const context = useContext(ApiContext);
if (!context) {
throw new Error('useApi must be used within an ApiProvider');
}
return context;
};

View File

@ -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 (
<QueryClientProvider client={queryClient}>
<ThemeContext>{children}</ThemeContext>
</QueryClientProvider>
);
};

View File

@ -2,8 +2,8 @@ import { t } from '@lingui/macro';
import { IconPackages } from '@tabler/icons-react'; import { IconPackages } from '@tabler/icons-react';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { api } from '../App';
import type { ApiFormFieldSet } from '../components/forms/fields/ApiFormField'; import type { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
import { useApi } from '../contexts/ApiContext';
import { ApiEndpoints } from '../enums/ApiEndpoints'; import { ApiEndpoints } from '../enums/ApiEndpoints';
import { apiUrl } from '../states/ApiState'; import { apiUrl } from '../states/ApiState';
import { useGlobalSettingsState } from '../states/SettingsState'; import { useGlobalSettingsState } from '../states/SettingsState';
@ -179,6 +179,8 @@ export function usePartParameterFields({
}: { }: {
editTemplate?: boolean; editTemplate?: boolean;
}): ApiFormFieldSet { }): ApiFormFieldSet {
const api = useApi();
// Valid field choices // Valid field choices
const [choices, setChoices] = useState<any[]>([]); const [choices, setChoices] = useState<any[]>([]);

View File

@ -5,7 +5,7 @@
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { api } from '../App'; import { useApi } from '../contexts/ApiContext';
import { ApiEndpoints } from '../enums/ApiEndpoints'; import { ApiEndpoints } from '../enums/ApiEndpoints';
import { resolveItem } from '../functions/conversion'; import { resolveItem } from '../functions/conversion';
import { apiUrl } from '../states/ApiState'; import { apiUrl } from '../states/ApiState';
@ -20,6 +20,8 @@ type UseFilterProps = {
}; };
export function useFilters(props: UseFilterProps) { export function useFilters(props: UseFilterProps) {
const api = useApi();
const query = useQuery({ const query = useQuery({
enabled: true, enabled: true,
gcTime: 500, gcTime: 500,

View File

@ -1,7 +1,7 @@
import { type QueryObserverResult, useQuery } from '@tanstack/react-query'; import { type QueryObserverResult, useQuery } from '@tanstack/react-query';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { api } from '../App'; import { useApi } from '../contexts/ApiContext';
import type { ApiEndpoints } from '../enums/ApiEndpoints'; import type { ApiEndpoints } from '../enums/ApiEndpoints';
import { type PathParams, apiUrl } from '../states/ApiState'; import { type PathParams, apiUrl } from '../states/ApiState';
@ -48,6 +48,8 @@ export function useInstance<T = any>({
throwError?: boolean; throwError?: boolean;
updateInterval?: number; updateInterval?: number;
}): UseInstanceResult { }): UseInstanceResult {
const api = useApi();
const [instance, setInstance] = useState<T | undefined>(defaultValue); const [instance, setInstance] = useState<T | undefined>(defaultValue);
const [requestStatus, setRequestStatus] = useState<number>(0); const [requestStatus, setRequestStatus] = useState<number>(0);

View File

@ -12,7 +12,6 @@ import ReactDOM from 'react-dom/client';
import 'react-grid-layout/css/styles.css'; import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css'; import 'react-resizable/css/styles.css';
import { api } from './App';
import type { HostList } from './states/states'; import type { HostList } from './states/states';
import MainView from './views/MainView'; import MainView from './views/MainView';
@ -27,7 +26,6 @@ declare global {
sentry_dsn?: string; sentry_dsn?: string;
environment?: string; environment?: string;
}; };
InvenTreeAPI: typeof api;
React: typeof React; React: typeof React;
} }
} }
@ -105,4 +103,3 @@ if (window.location.pathname === '/') {
} }
window.React = React; window.React = React;
window.InvenTreeAPI = api;

View File

@ -11,16 +11,17 @@ import {
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { api } from '../App';
import { ActionButton } from '../components/buttons/ActionButton'; import { ActionButton } from '../components/buttons/ActionButton';
import { PageDetail } from '../components/nav/PageDetail'; import { PageDetail } from '../components/nav/PageDetail';
import { PanelGroup } from '../components/panels/PanelGroup'; import { PanelGroup } from '../components/panels/PanelGroup';
import { useApi } from '../contexts/ApiContext';
import { ApiEndpoints } from '../enums/ApiEndpoints'; import { ApiEndpoints } from '../enums/ApiEndpoints';
import { useTable } from '../hooks/UseTable'; import { useTable } from '../hooks/UseTable';
import { apiUrl } from '../states/ApiState'; import { apiUrl } from '../states/ApiState';
import { NotificationTable } from '../tables/notifications/NotificationTable'; import { NotificationTable } from '../tables/notifications/NotificationTable';
export default function NotificationsPage() { export default function NotificationsPage() {
const api = useApi();
const unreadTable = useTable('unreadnotifications'); const unreadTable = useTable('unreadnotifications');
const readTable = useTable('readnotifications'); const readTable = useTable('readnotifications');

View File

@ -34,7 +34,6 @@ import { type ReactNode, useMemo, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import Select from 'react-select'; import Select from 'react-select';
import { api } from '../../App';
import AdminButton from '../../components/buttons/AdminButton'; import AdminButton from '../../components/buttons/AdminButton';
import { PrintingActions } from '../../components/buttons/PrintingActions'; import { PrintingActions } from '../../components/buttons/PrintingActions';
import { import {
@ -63,6 +62,7 @@ import type { PanelType } from '../../components/panels/Panel';
import { PanelGroup } from '../../components/panels/PanelGroup'; import { PanelGroup } from '../../components/panels/PanelGroup';
import { RenderPart } from '../../components/render/Part'; import { RenderPart } from '../../components/render/Part';
import OrderPartsWizard from '../../components/wizards/OrderPartsWizard'; import OrderPartsWizard from '../../components/wizards/OrderPartsWizard';
import { useApi } from '../../contexts/ApiContext';
import { formatPriceRange } from '../../defaults/formatters'; import { formatPriceRange } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType'; import { ModelType } from '../../enums/ModelType';
@ -110,6 +110,7 @@ import PartSupplierDetail from './PartSupplierDetail';
export default function PartDetail() { export default function PartDetail() {
const { id } = useParams(); const { id } = useParams();
const api = useApi();
const navigate = useNavigate(); const navigate = useNavigate();
const user = useUserState(); const user = useUserState();

View File

@ -14,7 +14,6 @@ import { useQuery } from '@tanstack/react-query';
import { type ReactNode, useMemo, useState } from 'react'; import { type ReactNode, useMemo, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { api } from '../../App';
import AdminButton from '../../components/buttons/AdminButton'; import AdminButton from '../../components/buttons/AdminButton';
import { PrintingActions } from '../../components/buttons/PrintingActions'; import { PrintingActions } from '../../components/buttons/PrintingActions';
import { import {
@ -43,6 +42,7 @@ import { PanelGroup } from '../../components/panels/PanelGroup';
import LocateItemButton from '../../components/plugins/LocateItemButton'; import LocateItemButton from '../../components/plugins/LocateItemButton';
import { StatusRenderer } from '../../components/render/StatusRenderer'; import { StatusRenderer } from '../../components/render/StatusRenderer';
import OrderPartsWizard from '../../components/wizards/OrderPartsWizard'; import OrderPartsWizard from '../../components/wizards/OrderPartsWizard';
import { useApi } from '../../contexts/ApiContext';
import { formatCurrency } from '../../defaults/formatters'; import { formatCurrency } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType'; import { ModelType } from '../../enums/ModelType';
@ -78,6 +78,7 @@ import { StockTrackingTable } from '../../tables/stock/StockTrackingTable';
export default function StockDetail() { export default function StockDetail() {
const { id } = useParams(); const { id } = useParams();
const api = useApi();
const user = useUserState(); const user = useUserState();
const globalSettings = useGlobalSettingsState(); const globalSettings = useGlobalSettingsState();
@ -377,7 +378,7 @@ export default function StockDetail() {
); // Must not be installed into another item ); // Must not be installed into another item
}, [stockitem]); }, [stockitem]);
const showSalesAlloctions: boolean = useMemo(() => { const showSalesAllocations: boolean = useMemo(() => {
return stockitem?.part_detail?.salable; return stockitem?.part_detail?.salable;
}, [stockitem]); }, [stockitem]);
@ -452,14 +453,14 @@ export default function StockDetail() {
icon: <IconBookmark />, icon: <IconBookmark />,
hidden: hidden:
!stockitem.in_stock || !stockitem.in_stock ||
(!showSalesAlloctions && !showBuildAllocations), (!showSalesAllocations && !showBuildAllocations),
content: ( content: (
<Accordion <Accordion
multiple={true} multiple={true}
defaultValue={['buildallocations', 'salesallocations']} defaultValue={['buildAllocations', 'salesAllocations']}
> >
{showBuildAllocations && ( {showBuildAllocations && (
<Accordion.Item value='buildallocations' key='buildallocations'> <Accordion.Item value='buildAllocations' key='buildAllocations'>
<Accordion.Control> <Accordion.Control>
<StylishText size='lg'>{t`Build Order Allocations`}</StylishText> <StylishText size='lg'>{t`Build Order Allocations`}</StylishText>
</Accordion.Control> </Accordion.Control>
@ -473,8 +474,8 @@ export default function StockDetail() {
</Accordion.Panel> </Accordion.Panel>
</Accordion.Item> </Accordion.Item>
)} )}
{showSalesAlloctions && ( {showSalesAllocations && (
<Accordion.Item value='salesallocations' key='salesallocations'> <Accordion.Item value='salesAllocations' key='salesAllocations'>
<Accordion.Control> <Accordion.Control>
<StylishText size='lg'>{t`Sales Order Allocations`}</StylishText> <StylishText size='lg'>{t`Sales Order Allocations`}</StylishText>
</Accordion.Control> </Accordion.Control>
@ -536,7 +537,7 @@ export default function StockDetail() {
}) })
]; ];
}, [ }, [
showSalesAlloctions, showSalesAllocations,
showBuildAllocations, showBuildAllocations,
showInstalledItems, showInstalledItems,
stockitem, stockitem,

View File

@ -13,9 +13,9 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { showNotification } from '@mantine/notifications'; import { showNotification } from '@mantine/notifications';
import { api } from '../App';
import { Boundary } from '../components/Boundary'; import { Boundary } from '../components/Boundary';
import type { ApiFormFieldSet } from '../components/forms/fields/ApiFormField'; import type { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
import { useApi } from '../contexts/ApiContext';
import type { ModelType } from '../enums/ModelType'; import type { ModelType } from '../enums/ModelType';
import { resolveItem } from '../functions/conversion'; import { resolveItem } from '../functions/conversion';
import { cancelEvent } from '../functions/events'; import { cancelEvent } from '../functions/events';
@ -139,6 +139,7 @@ export function InvenTreeTable<T extends Record<string, any>>({
const [fieldNames, setFieldNames] = useState<Record<string, string>>({}); const [fieldNames, setFieldNames] = useState<Record<string, string>>({});
const api = useApi();
const navigate = useNavigate(); const navigate = useNavigate();
const { showContextMenu } = useContextMenu(); const { showContextMenu } = useContextMenu();

View File

@ -16,11 +16,11 @@ import {
import { useState } from 'react'; import { useState } from 'react';
import { Fragment } from 'react/jsx-runtime'; import { Fragment } from 'react/jsx-runtime';
import { api } from '../App';
import { Boundary } from '../components/Boundary'; import { Boundary } from '../components/Boundary';
import { ActionButton } from '../components/buttons/ActionButton'; import { ActionButton } from '../components/buttons/ActionButton';
import { ButtonMenu } from '../components/buttons/ButtonMenu'; import { ButtonMenu } from '../components/buttons/ButtonMenu';
import { PrintingActions } from '../components/buttons/PrintingActions'; import { PrintingActions } from '../components/buttons/PrintingActions';
import { useApi } from '../contexts/ApiContext';
import { useDeleteApiFormModal } from '../hooks/UseForm'; import { useDeleteApiFormModal } from '../hooks/UseForm';
import type { TableState } from '../hooks/UseTable'; import type { TableState } from '../hooks/UseTable';
import { TableColumnSelect } from './ColumnSelect'; import { TableColumnSelect } from './ColumnSelect';
@ -50,6 +50,8 @@ export default function InvenTreeTableHeader({
filters: TableFilter[]; filters: TableFilter[];
toggleColumn: (column: string) => void; toggleColumn: (column: string) => void;
}>) { }>) {
const api = useApi();
// Filter list visibility // Filter list visibility
const [filtersVisible, setFiltersVisible] = useState<boolean>(false); const [filtersVisible, setFiltersVisible] = useState<boolean>(false);

View File

@ -11,12 +11,12 @@ import {
import { type ReactNode, useCallback, useMemo, useState } from 'react'; import { type ReactNode, useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { ActionButton } from '../../components/buttons/ActionButton'; import { ActionButton } from '../../components/buttons/ActionButton';
import { AddItemButton } from '../../components/buttons/AddItemButton'; import { AddItemButton } from '../../components/buttons/AddItemButton';
import { YesNoButton } from '../../components/buttons/YesNoButton'; import { YesNoButton } from '../../components/buttons/YesNoButton';
import { Thumbnail } from '../../components/images/Thumbnail'; import { Thumbnail } from '../../components/images/Thumbnail';
import ImporterDrawer from '../../components/importer/ImporterDrawer'; import ImporterDrawer from '../../components/importer/ImporterDrawer';
import { useApi } from '../../contexts/ApiContext';
import { formatDecimal, formatPriceRange } from '../../defaults/formatters'; import { formatDecimal, formatPriceRange } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType'; import { ModelType } from '../../enums/ModelType';
@ -71,6 +71,7 @@ export function BomTable({
partLocked?: boolean; partLocked?: boolean;
params?: any; params?: any;
}>) { }>) {
const api = useApi();
const user = useUserState(); const user = useUserState();
const table = useTable('bom'); const table = useTable('bom');
const navigate = useNavigate(); const navigate = useNavigate();

View File

@ -10,10 +10,10 @@ import {
useState useState
} from 'react'; } from 'react';
import { api } from '../../App';
import { PassFailButton } from '../../components/buttons/YesNoButton'; import { PassFailButton } from '../../components/buttons/YesNoButton';
import type { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField'; import type { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField';
import { RenderUser } from '../../components/render/User'; import { RenderUser } from '../../components/render/User';
import { useApi } from '../../contexts/ApiContext';
import { formatDate } from '../../defaults/formatters'; import { formatDate } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { useTestResultFields } from '../../forms/StockForms'; import { useTestResultFields } from '../../forms/StockForms';
@ -40,6 +40,7 @@ export default function BuildOrderTestTable({
}>) { }>) {
const table = useTable('build-tests'); const table = useTable('build-tests');
const user = useUserState(); const user = useUserState();
const api = useApi();
// Fetch the test templates required for this build order // Fetch the test templates required for this build order
const { data: testTemplates } = useQuery({ const { data: testTemplates } = useQuery({

View File

@ -18,11 +18,11 @@ import { useQuery } from '@tanstack/react-query';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { ActionButton } from '../../components/buttons/ActionButton'; import { ActionButton } from '../../components/buttons/ActionButton';
import { AddItemButton } from '../../components/buttons/AddItemButton'; import { AddItemButton } from '../../components/buttons/AddItemButton';
import { ProgressBar } from '../../components/items/ProgressBar'; import { ProgressBar } from '../../components/items/ProgressBar';
import { StylishText } from '../../components/items/StylishText'; import { StylishText } from '../../components/items/StylishText';
import { useApi } from '../../contexts/ApiContext';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType'; import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles'; import { UserRoles } from '../../enums/Roles';
@ -121,6 +121,7 @@ export default function BuildOutputTable({
build, build,
refreshBuild refreshBuild
}: Readonly<{ build: any; refreshBuild: () => void }>) { }: Readonly<{ build: any; refreshBuild: () => void }>) {
const api = useApi();
const user = useUserState(); const user = useUserState();
const navigate = useNavigate(); const navigate = useNavigate();
const table = useTable('build-outputs'); const table = useTable('build-outputs');

View File

@ -10,10 +10,10 @@ import {
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { type ReactNode, useCallback, useMemo, useState } from 'react'; import { type ReactNode, useCallback, useMemo, useState } from 'react';
import { api } from '../../App';
import { ActionButton } from '../../components/buttons/ActionButton'; import { ActionButton } from '../../components/buttons/ActionButton';
import type { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField'; import type { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField';
import { AttachmentLink } from '../../components/items/AttachmentLink'; import { AttachmentLink } from '../../components/items/AttachmentLink';
import { useApi } from '../../contexts/ApiContext';
import { formatFileSize } from '../../defaults/formatters'; import { formatFileSize } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import type { ModelType } from '../../enums/ModelType'; import type { ModelType } from '../../enums/ModelType';
@ -99,6 +99,7 @@ export function AttachmentTable({
model_type: ModelType; model_type: ModelType;
model_id: number; model_id: number;
}>): ReactNode { }>): ReactNode {
const api = useApi();
const user = useUserState(); const user = useUserState();
const table = useTable(`${model_type}-attachments`); const table = useTable(`${model_type}-attachments`);

View File

@ -20,7 +20,6 @@ import { useQuery } from '@tanstack/react-query';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { AddItemButton } from '../../components/buttons/AddItemButton'; import { AddItemButton } from '../../components/buttons/AddItemButton';
import { YesNoButton } from '../../components/buttons/YesNoButton'; import { YesNoButton } from '../../components/buttons/YesNoButton';
import { import {
@ -40,6 +39,7 @@ import {
TableStatusRenderer TableStatusRenderer
} from '../../components/render/StatusRenderer'; } from '../../components/render/StatusRenderer';
import { MachineSettingList } from '../../components/settings/SettingList'; import { MachineSettingList } from '../../components/settings/SettingList';
import { useApi } from '../../contexts/ApiContext';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { import {
useCreateApiFormModal, useCreateApiFormModal,
@ -103,6 +103,8 @@ export function useMachineTypeDriver({
includeTypes = true, includeTypes = true,
includeDrivers = true includeDrivers = true
}: { includeTypes?: boolean; includeDrivers?: boolean } = {}) { }: { includeTypes?: boolean; includeDrivers?: boolean } = {}) {
const api = useApi();
const { const {
data: machineTypes, data: machineTypes,
isFetching: isMachineTypesFetching, isFetching: isMachineTypesFetching,
@ -146,6 +148,7 @@ function MachineDrawer({
machinePk: string; machinePk: string;
refreshTable: () => void; refreshTable: () => void;
}>) { }>) {
const api = useApi();
const navigate = useNavigate(); const navigate = useNavigate();
const { const {
data: machine, data: machine,

View File

@ -5,9 +5,9 @@ import { useQuery } from '@tanstack/react-query';
import { type ReactNode, useCallback, useMemo, useState } from 'react'; import { type ReactNode, useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { YesNoButton } from '../../components/buttons/YesNoButton'; import { YesNoButton } from '../../components/buttons/YesNoButton';
import type { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField'; import type { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField';
import { useApi } from '../../contexts/ApiContext';
import { formatDecimal } from '../../defaults/formatters'; import { formatDecimal } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType'; import { ModelType } from '../../enums/ModelType';
@ -95,11 +95,12 @@ export default function ParametricPartTable({
}: Readonly<{ }: Readonly<{
categoryId?: any; categoryId?: any;
}>) { }>) {
const api = useApi();
const table = useTable('parametric-parts'); const table = useTable('parametric-parts');
const user = useUserState(); const user = useUserState();
const navigate = useNavigate(); const navigate = useNavigate();
const categoryParmeters = useQuery({ const categoryParameters = useQuery({
queryKey: ['category-parameters', categoryId], queryKey: ['category-parameters', categoryId],
queryFn: async () => { queryFn: async () => {
return api return api
@ -170,13 +171,13 @@ export default function ParametricPartTable({
records[partIndex].parameters[parameterIndex] = parameter; records[partIndex].parameters[parameterIndex] = parameter;
} }
table.setRecords(records); table.updateRecord(records[partIndex]);
}, },
[table.records] [table.updateRecord]
); );
const parameterColumns: TableColumn[] = useMemo(() => { const parameterColumns: TableColumn[] = useMemo(() => {
const data = categoryParmeters.data ?? []; const data = categoryParameters.data ?? [];
return data.map((template: any) => { return data.map((template: any) => {
let title = template.name; 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) => { const onParameterClick = useCallback((template: number, part: any) => {
setSelectedTemplate(template); setSelectedTemplate(template);

View File

@ -13,12 +13,12 @@ import {
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { ActionButton } from '../../components/buttons/ActionButton'; import { ActionButton } from '../../components/buttons/ActionButton';
import { YesNoButton } from '../../components/buttons/YesNoButton'; import { YesNoButton } from '../../components/buttons/YesNoButton';
import { DetailDrawer } from '../../components/nav/DetailDrawer'; import { DetailDrawer } from '../../components/nav/DetailDrawer';
import PluginDrawer from '../../components/plugins/PluginDrawer'; import PluginDrawer from '../../components/plugins/PluginDrawer';
import type { PluginInterface } from '../../components/plugins/PluginInterface'; import type { PluginInterface } from '../../components/plugins/PluginInterface';
import { useApi } from '../../contexts/ApiContext';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { import {
useCreateApiFormModal, useCreateApiFormModal,
@ -63,6 +63,7 @@ function PluginIcon({ plugin }: Readonly<{ plugin: PluginInterface }>) {
* Table displaying list of available plugins * Table displaying list of available plugins
*/ */
export default function PluginListTable() { export default function PluginListTable() {
const api = useApi();
const table = useTable('plugin'); const table = useTable('plugin');
const navigate = useNavigate(); const navigate = useNavigate();
const user = useUserState(); const user = useUserState();

View File

@ -10,12 +10,12 @@ import { useQuery } from '@tanstack/react-query';
import { DataTable } from 'mantine-datatable'; import { DataTable } from 'mantine-datatable';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { api } from '../../App';
import { AddItemButton } from '../../components/buttons/AddItemButton'; import { AddItemButton } from '../../components/buttons/AddItemButton';
import { PassFailButton } from '../../components/buttons/YesNoButton'; import { PassFailButton } from '../../components/buttons/YesNoButton';
import type { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField'; import type { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField';
import { AttachmentLink } from '../../components/items/AttachmentLink'; import { AttachmentLink } from '../../components/items/AttachmentLink';
import { RenderUser } from '../../components/render/User'; import { RenderUser } from '../../components/render/User';
import { useApi } from '../../contexts/ApiContext';
import { formatDate } from '../../defaults/formatters'; import { formatDate } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { UserRoles } from '../../enums/Roles'; import { UserRoles } from '../../enums/Roles';
@ -47,6 +47,7 @@ export default function StockItemTestResultTable({
partId: number; partId: number;
itemId: number; itemId: number;
}>) { }>) {
const api = useApi();
const user = useUserState(); const user = useUserState();
const table = useTable('stocktests'); const table = useTable('stocktests');

View File

@ -1,9 +1,9 @@
import { QueryClientProvider } from '@tanstack/react-query';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import { queryClient } from '../App'; import { api, queryClient } from '../App';
import { BaseContext } from '../contexts/BaseContext'; import { ApiProvider } from '../contexts/ApiContext';
import { ThemeContext } from '../contexts/ThemeContext';
import { defaultHostList } from '../defaults/defaultHostList'; import { defaultHostList } from '../defaults/defaultHostList';
import { base_url } from '../main'; import { base_url } from '../main';
import { routes } from '../router'; import { routes } from '../router';
@ -19,10 +19,10 @@ export default function DesktopAppView() {
}, [hostList]); }, [hostList]);
return ( return (
<BaseContext> <ApiProvider client={queryClient} api={api}>
<QueryClientProvider client={queryClient}> <ThemeContext>
<BrowserRouter basename={base_url}>{routes}</BrowserRouter> <BrowserRouter basename={base_url}>{routes}</BrowserRouter>
</QueryClientProvider> </ThemeContext>
</BaseContext> </ApiProvider>
); );
} }

View File

@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro'; import { Trans } from '@lingui/macro';
import { Anchor, Center, Container, Stack, Text, Title } from '@mantine/core'; 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 { docLinks } from '../defaults/links';
import { IS_DEV } from '../main'; import { IS_DEV } from '../main';
import { useLocalState } from '../states/LocalState'; import { useLocalState } from '../states/LocalState';
@ -14,7 +14,7 @@ export default function MobileAppView() {
window.location.reload(); window.location.reload();
} }
return ( return (
<BaseContext> <ThemeContext>
<Center h='100vh'> <Center h='100vh'>
<Container> <Container>
<Stack> <Stack>
@ -38,6 +38,6 @@ export default function MobileAppView() {
</Stack> </Stack>
</Container> </Container>
</Center> </Center>
</BaseContext> </ThemeContext>
); );
} }