mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-01 19:20:55 +00:00
[UI] Refactor useQuery hooks (#9894)
* Improvements for table loading - Retry table queries on failure - Properly store failure modes - Increasing standoff time on query failure * Add error messages * Better error extraction * Simplify error handling * Update NotesEditor * Update dashboard items * Tweak table refetch * Refactor notifications query * Fix for calendar querty * Other fixes * Allow retry for search query * Further adjustments * Improved dashboard * Upate more useQuery hooks * Fix broken URL (was used for testing) * Remove custom delay * Revert change to noRecordsText
This commit is contained in:
@ -72,9 +72,6 @@ export function PrintingActions({
|
|||||||
.then((response: any) => {
|
.then((response: any) => {
|
||||||
return extractAvailableFields(response, 'POST') || {};
|
return extractAvailableFields(response, 'POST') || {};
|
||||||
})
|
})
|
||||||
.catch(() => {
|
|
||||||
return {};
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const labelFields: ApiFormFieldSet = useMemo(() => {
|
const labelFields: ApiFormFieldSet = useMemo(() => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { Alert, Card, Center, Divider, Loader, Text } from '@mantine/core';
|
import { Alert, Card, Center, Divider, Loader, Text } from '@mantine/core';
|
||||||
import { useDisclosure, useHotkeys } from '@mantine/hooks';
|
import { useDisclosure, useHotkeys } from '@mantine/hooks';
|
||||||
import { IconInfoCircle } from '@tabler/icons-react';
|
import { IconExclamationCircle, IconInfoCircle } from '@tabler/icons-react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { type Layout, Responsive, WidthProvider } from 'react-grid-layout';
|
import { type Layout, Responsive, WidthProvider } from 'react-grid-layout';
|
||||||
|
|
||||||
@ -220,6 +220,11 @@ export default function DashboardLayout() {
|
|||||||
removing={removing}
|
removing={removing}
|
||||||
/>
|
/>
|
||||||
<Divider p='xs' />
|
<Divider p='xs' />
|
||||||
|
{availableWidgets.error && (
|
||||||
|
<Alert color='red' title={t`Error`} icon={<IconExclamationCircle />}>
|
||||||
|
{t`Failed to load dashboard widgets.`}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
{layouts && loaded && availableWidgets.loaded ? (
|
{layouts && loaded && availableWidgets.loaded ? (
|
||||||
<>
|
<>
|
||||||
{widgetLabels.length == 0 ? (
|
{widgetLabels.length == 0 ? (
|
||||||
|
@ -47,8 +47,7 @@ function QueryCountWidget({
|
|||||||
limit: 1
|
limit: 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((res) => res.data)
|
.then((res) => res.data);
|
||||||
.catch(() => {});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -196,19 +196,14 @@ function NameBadge({
|
|||||||
|
|
||||||
const url = apiUrl(path, pk);
|
const url = apiUrl(path, pk);
|
||||||
|
|
||||||
return api
|
return api.get(url).then((response) => {
|
||||||
.get(url)
|
switch (response.status) {
|
||||||
.then((response) => {
|
case 200:
|
||||||
switch (response.status) {
|
return response.data;
|
||||||
case 200:
|
default:
|
||||||
return response.data;
|
return {};
|
||||||
default:
|
}
|
||||||
return {};
|
});
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
return {};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -356,9 +351,6 @@ function TableAnchorValue(props: Readonly<FieldProps>) {
|
|||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
return {};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -92,11 +92,9 @@ export default function NotesEditor({
|
|||||||
|
|
||||||
const dataQuery = useQuery({
|
const dataQuery = useQuery({
|
||||||
queryKey: ['notes-editor', noteUrl, modelType, modelId],
|
queryKey: ['notes-editor', noteUrl, modelType, modelId],
|
||||||
|
retry: 5,
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
api
|
api.get(noteUrl).then((response) => response.data?.notes ?? ''),
|
||||||
.get(noteUrl)
|
|
||||||
.then((response) => response.data?.notes ?? '')
|
|
||||||
.catch(() => ''),
|
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -254,19 +254,14 @@ export function ApiForm({
|
|||||||
props.pathParams
|
props.pathParams
|
||||||
],
|
],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return await api
|
return await api.get(url).then((response: any) => {
|
||||||
.get(url)
|
// Process API response
|
||||||
.then((response: any) => {
|
const fetchedData: any = processFields(fields, response.data);
|
||||||
// Process API response
|
|
||||||
const fetchedData: any = processFields(fields, response.data);
|
|
||||||
|
|
||||||
// Update form values, but only for the fields specified for this form
|
// Update form values, but only for the fields specified for this form
|
||||||
form.reset(fetchedData);
|
form.reset(fetchedData);
|
||||||
return fetchedData;
|
return fetchedData;
|
||||||
})
|
});
|
||||||
.catch(() => {
|
|
||||||
return {};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -179,10 +179,6 @@ export function RelatedModelField({
|
|||||||
setData(values);
|
setData(values);
|
||||||
dataRef.current = values;
|
dataRef.current = values;
|
||||||
return response;
|
return response;
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setData([]);
|
|
||||||
return error;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -57,10 +57,7 @@ export function LicenseModal() {
|
|||||||
queryKey: ['license'],
|
queryKey: ['license'],
|
||||||
refetchOnMount: true,
|
refetchOnMount: true,
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
api
|
api.get(apiUrl(ApiEndpoints.license)).then((res) => res.data ?? {})
|
||||||
.get(apiUrl(ApiEndpoints.license))
|
|
||||||
.then((res) => res.data ?? {})
|
|
||||||
.catch(() => {})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const packageKeys = useMemo(() => {
|
const packageKeys = useMemo(() => {
|
||||||
|
@ -78,23 +78,17 @@ export function Header() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
return api
|
||||||
const params = {
|
.get(apiUrl(ApiEndpoints.notifications_list), {
|
||||||
params: {
|
params: {
|
||||||
read: false,
|
read: false,
|
||||||
limit: 1
|
limit: 1
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
const response = await api
|
.then((response: any) => {
|
||||||
.get(apiUrl(ApiEndpoints.notifications_list), params)
|
setNotificationCount(response?.data?.count ?? 0);
|
||||||
.catch(() => {
|
return response.data ?? null;
|
||||||
return null;
|
});
|
||||||
});
|
|
||||||
setNotificationCount(response?.data?.count ?? 0);
|
|
||||||
return response?.data ?? null;
|
|
||||||
} catch (error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
// Refetch every minute, *if* the tab is visible
|
// Refetch every minute, *if* the tab is visible
|
||||||
refetchInterval: 60 * 1000,
|
refetchInterval: 60 * 1000,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
|
Alert,
|
||||||
Anchor,
|
Anchor,
|
||||||
Divider,
|
Divider,
|
||||||
Drawer,
|
Drawer,
|
||||||
@ -15,6 +16,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
IconChevronRight,
|
IconChevronRight,
|
||||||
|
IconExclamationCircle,
|
||||||
IconSitemap
|
IconSitemap
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
@ -29,6 +31,7 @@ import {
|
|||||||
getDetailUrl,
|
getDetailUrl,
|
||||||
navigateToLink
|
navigateToLink
|
||||||
} from '@lib/functions/Navigation';
|
} from '@lib/functions/Navigation';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import { useApi } from '../../contexts/ApiContext';
|
import { useApi } from '../../contexts/ApiContext';
|
||||||
import { ApiIcon } from '../items/ApiIcon';
|
import { ApiIcon } from '../items/ApiIcon';
|
||||||
import { StylishText } from '../items/StylishText';
|
import { StylishText } from '../items/StylishText';
|
||||||
@ -67,10 +70,6 @@ export default function NavigationTree({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((response) => response.data ?? [])
|
.then((response) => response.data ?? [])
|
||||||
.catch((error) => {
|
|
||||||
console.error(`Error fetching ${modelType} tree`);
|
|
||||||
return [];
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const follow = useCallback(
|
const follow = useCallback(
|
||||||
@ -96,7 +95,7 @@ export default function NavigationTree({
|
|||||||
const nodes: Record<number, any> = {};
|
const nodes: Record<number, any> = {};
|
||||||
const tree: TreeNodeData[] = [];
|
const tree: TreeNodeData[] = [];
|
||||||
|
|
||||||
if (!query?.data?.length) {
|
if (!query || !query?.data?.length) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +206,13 @@ export default function NavigationTree({
|
|||||||
<Stack gap='xs'>
|
<Stack gap='xs'>
|
||||||
<Divider />
|
<Divider />
|
||||||
<LoadingOverlay visible={query.isFetching || query.isLoading} />
|
<LoadingOverlay visible={query.isFetching || query.isLoading} />
|
||||||
<Tree data={data} tree={treeState} renderNode={renderNode} />
|
{query.isError ? (
|
||||||
|
<Alert color='red' title={t`Error`} icon={<IconExclamationCircle />}>
|
||||||
|
{t`Error loading navigation tree.`}
|
||||||
|
</Alert>
|
||||||
|
) : (
|
||||||
|
<Tree data={data} tree={treeState} renderNode={renderNode} />
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
|
@ -122,10 +122,7 @@ export function NotificationDrawer({
|
|||||||
ordering: '-creation'
|
ordering: '-creation'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((response) => response.data)
|
.then((response) => response.data),
|
||||||
.catch((error) => {
|
|
||||||
return error;
|
|
||||||
}),
|
|
||||||
refetchOnMount: false
|
refetchOnMount: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -397,11 +397,7 @@ export function SearchDrawer({
|
|||||||
|
|
||||||
return api
|
return api
|
||||||
.post(apiUrl(ApiEndpoints.api_search), params)
|
.post(apiUrl(ApiEndpoints.api_search), params)
|
||||||
.then((response) => response.data)
|
.then((response) => response.data);
|
||||||
.catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Search query manager
|
// Search query manager
|
||||||
|
@ -137,10 +137,7 @@ export function RenderRemoteInstance({
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const url = apiUrl(ModelInformationDict[model].api_endpoint, pk);
|
const url = apiUrl(ModelInformationDict[model].api_endpoint, pk);
|
||||||
|
|
||||||
return api
|
return api.get(url).then((response) => response.data);
|
||||||
.get(url)
|
|
||||||
.then((response) => response.data)
|
|
||||||
.catch(() => null);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1100,10 +1100,7 @@ function useStockOperationModal({
|
|||||||
.get(url, {
|
.get(url, {
|
||||||
params: params
|
params: params
|
||||||
})
|
})
|
||||||
.then((response) => response.data ?? [])
|
.then((response) => response.data ?? []);
|
||||||
.catch(() => {
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1368,7 +1365,7 @@ export function useFindSerialNumberForm({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
checkClose: (data, form) => {
|
checkClose: (data, form) => {
|
||||||
if (data.length == 0) {
|
if (!data || data?.length == 0) {
|
||||||
form.setError('serial', { message: t`No matching items` });
|
form.setError('serial', { message: t`No matching items` });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ export function extractErrorMessage({
|
|||||||
field?: string;
|
field?: string;
|
||||||
defaultMessage?: string;
|
defaultMessage?: string;
|
||||||
}): string {
|
}): string {
|
||||||
const error_data = error.response?.data ?? null;
|
const error_data = error.response?.data ?? error.data ?? null;
|
||||||
|
|
||||||
let message = '';
|
let message = '';
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ export function extractErrorMessage({
|
|||||||
|
|
||||||
// No message? Look at the response status codes
|
// No message? Look at the response status codes
|
||||||
if (!message) {
|
if (!message) {
|
||||||
const status = error.response?.status ?? null;
|
const status = error.status ?? error.response?.status ?? null;
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@ -48,8 +48,11 @@ export function extractErrorMessage({
|
|||||||
message = t`Internal server error`;
|
message = t`Internal server error`;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
message = t`Unknown error`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message = `${status} - ${message}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,12 +80,14 @@ export function showApiErrorMessage({
|
|||||||
error,
|
error,
|
||||||
title,
|
title,
|
||||||
message,
|
message,
|
||||||
field
|
field,
|
||||||
|
id
|
||||||
}: {
|
}: {
|
||||||
error: any;
|
error: any;
|
||||||
title: string;
|
title: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
field?: string;
|
field?: string;
|
||||||
|
id?: string;
|
||||||
}) {
|
}) {
|
||||||
const errorMessage = extractErrorMessage({
|
const errorMessage = extractErrorMessage({
|
||||||
error: error,
|
error: error,
|
||||||
@ -93,7 +95,10 @@ export function showApiErrorMessage({
|
|||||||
defaultMessage: message
|
defaultMessage: message
|
||||||
});
|
});
|
||||||
|
|
||||||
|
notifications.hide(id ?? 'api-error');
|
||||||
|
|
||||||
notifications.show({
|
notifications.show({
|
||||||
|
id: id ?? 'api-error',
|
||||||
title: title,
|
title: title,
|
||||||
message: errorMessage,
|
message: errorMessage,
|
||||||
color: 'red'
|
color: 'red'
|
||||||
|
@ -103,6 +103,14 @@ export default function useCalendar({
|
|||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
enabled: !!startDate && !!endDate,
|
enabled: !!startDate && !!endDate,
|
||||||
queryKey: ['calendar', name, endpoint, queryFilters],
|
queryKey: ['calendar', name, endpoint, queryFilters],
|
||||||
|
throwOnError: (error: any) => {
|
||||||
|
showApiErrorMessage({
|
||||||
|
error: error,
|
||||||
|
title: 'Error fetching calendar data'
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
// Fetch data from the API
|
// Fetch data from the API
|
||||||
return api
|
return api
|
||||||
@ -111,12 +119,6 @@ export default function useCalendar({
|
|||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response.data ?? [];
|
return response.data ?? [];
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
showApiErrorMessage({
|
|
||||||
error: error,
|
|
||||||
title: 'Error fetching calendar data'
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -173,6 +175,6 @@ export default function useCalendar({
|
|||||||
setEndDate,
|
setEndDate,
|
||||||
exportModal,
|
exportModal,
|
||||||
query: query,
|
query: query,
|
||||||
data: query.data
|
data: query.data ?? []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import { useUserState } from '../states/UserState';
|
|||||||
interface DashboardLibraryProps {
|
interface DashboardLibraryProps {
|
||||||
items: DashboardWidgetProps[];
|
items: DashboardWidgetProps[];
|
||||||
loaded: boolean;
|
loaded: boolean;
|
||||||
|
error: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,13 +52,7 @@ export function useDashboardItems(): DashboardLibraryProps {
|
|||||||
feature_type: PluginUIFeatureType.dashboard
|
feature_type: PluginUIFeatureType.dashboard
|
||||||
});
|
});
|
||||||
|
|
||||||
return api
|
return api.get(url).then((response: any) => response.data);
|
||||||
.get(url)
|
|
||||||
.then((response: any) => response.data)
|
|
||||||
.catch((_error: any) => {
|
|
||||||
console.error('ERR: Failed to fetch plugin dashboard items');
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -90,7 +85,7 @@ export function useDashboardItems(): DashboardLibraryProps {
|
|||||||
};
|
};
|
||||||
}) ?? []
|
}) ?? []
|
||||||
);
|
);
|
||||||
}, [pluginQuery, inventreeContext]);
|
}, [pluginQuery.data, inventreeContext]);
|
||||||
|
|
||||||
const items: DashboardWidgetProps[] = useMemo(() => {
|
const items: DashboardWidgetProps[] = useMemo(() => {
|
||||||
const widgets = [...builtin, ...pluginDashboardItems];
|
const widgets = [...builtin, ...pluginDashboardItems];
|
||||||
@ -113,6 +108,7 @@ export function useDashboardItems(): DashboardLibraryProps {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
items: items,
|
items: items,
|
||||||
loaded: loaded
|
loaded: loaded,
|
||||||
|
error: pluginQuery.error
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -72,9 +72,6 @@ export default function useDataExport({
|
|||||||
.then((response: any) => {
|
.then((response: any) => {
|
||||||
return extractAvailableFields(response, 'GET') || {};
|
return extractAvailableFields(response, 'GET') || {};
|
||||||
})
|
})
|
||||||
.catch(() => {
|
|
||||||
return {};
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Construct a field set for the export form
|
// Construct a field set for the export form
|
||||||
|
@ -39,8 +39,7 @@ export function useFilters(props: UseFilterProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
})
|
});
|
||||||
.catch((error) => []);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -85,6 +85,13 @@ export function useGenerator(props: GeneratorProps): GeneratorState {
|
|||||||
],
|
],
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
|
throwOnError: (error: any) => {
|
||||||
|
console.error(
|
||||||
|
`Error generating ${props.key} @ ${props.endpoint}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const generatorQuery = {
|
const generatorQuery = {
|
||||||
...(props.initialQuery ?? {}),
|
...(props.initialQuery ?? {}),
|
||||||
@ -105,14 +112,6 @@ export function useGenerator(props: GeneratorProps): GeneratorState {
|
|||||||
props.onGenerate?.(value);
|
props.onGenerate?.(value);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(
|
|
||||||
`Error generating ${props.key} @ ${props.endpoint}:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -53,6 +53,10 @@ export function usePluginPanels({
|
|||||||
const pluginQuery = useQuery({
|
const pluginQuery = useQuery({
|
||||||
enabled: pluginPanelsEnabled && !!model && id !== undefined,
|
enabled: pluginPanelsEnabled && !!model && id !== undefined,
|
||||||
queryKey: ['custom-plugin-panels', model, id],
|
queryKey: ['custom-plugin-panels', model, id],
|
||||||
|
throwOnError: (error: any) => {
|
||||||
|
console.error('ERR: Failed to fetch plugin panels');
|
||||||
|
return false;
|
||||||
|
},
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!pluginPanelsEnabled || !model) {
|
if (!pluginPanelsEnabled || !model) {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
@ -69,11 +73,7 @@ export function usePluginPanels({
|
|||||||
target_id: id
|
target_id: id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((response: any) => response.data)
|
.then((response: any) => response.data);
|
||||||
.catch((_error: any) => {
|
|
||||||
console.error('ERR: Failed to fetch plugin panels');
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -723,8 +723,7 @@ export default function PartDetail() {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.catch(() => {});
|
|
||||||
|
|
||||||
return revisions;
|
return revisions;
|
||||||
}
|
}
|
||||||
|
@ -491,9 +491,6 @@ export default function StockDetail() {
|
|||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
return null;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -504,7 +501,7 @@ export default function StockDetail() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackedBomItemQuery.data != null) {
|
if (!!trackedBomItemQuery.data) {
|
||||||
return trackedBomItemQuery.data;
|
return trackedBomItemQuery.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,12 +28,12 @@ import { navigateToLink } from '@lib/functions/Navigation';
|
|||||||
import type { TableFilter } from '@lib/types/Filters';
|
import type { TableFilter } from '@lib/types/Filters';
|
||||||
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
||||||
import type { TableState } from '@lib/types/Tables';
|
import type { TableState } from '@lib/types/Tables';
|
||||||
import { hideNotification, showNotification } from '@mantine/notifications';
|
|
||||||
import { IconArrowRight } from '@tabler/icons-react';
|
import { IconArrowRight } from '@tabler/icons-react';
|
||||||
import { Boundary } from '../components/Boundary';
|
import { Boundary } from '../components/Boundary';
|
||||||
import { useApi } from '../contexts/ApiContext';
|
import { useApi } from '../contexts/ApiContext';
|
||||||
import { resolveItem } from '../functions/conversion';
|
import { resolveItem } from '../functions/conversion';
|
||||||
import { extractAvailableFields, mapFields } from '../functions/forms';
|
import { extractAvailableFields, mapFields } from '../functions/forms';
|
||||||
|
import { showApiErrorMessage } from '../functions/notifications';
|
||||||
import { useLocalState } from '../states/LocalState';
|
import { useLocalState } from '../states/LocalState';
|
||||||
import type { TableColumn } from './Column';
|
import type { TableColumn } from './Column';
|
||||||
import InvenTreeTableHeader from './InvenTreeTableHeader';
|
import InvenTreeTableHeader from './InvenTreeTableHeader';
|
||||||
@ -199,7 +199,16 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
tableProps.params,
|
tableProps.params,
|
||||||
props.enableColumnCaching
|
props.enableColumnCaching
|
||||||
],
|
],
|
||||||
retry: 3,
|
retry: 5,
|
||||||
|
retryDelay: (attempt: number) => (1 + attempt) * 250,
|
||||||
|
throwOnError: (error: any) => {
|
||||||
|
showApiErrorMessage({
|
||||||
|
error: error,
|
||||||
|
title: t`Error loading table options`
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
refetchOnMount: true,
|
refetchOnMount: true,
|
||||||
gcTime: 5000,
|
gcTime: 5000,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@ -240,17 +249,6 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
setTableColumnNames(cacheKey)(names);
|
setTableColumnNames(cacheKey)(names);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
hideNotification('table-options-error');
|
|
||||||
showNotification({
|
|
||||||
id: 'table-options-error',
|
|
||||||
title: t`API Error`,
|
|
||||||
message: t`Failed to load table options`,
|
|
||||||
color: 'red'
|
|
||||||
});
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -270,9 +268,7 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
setFieldNames(cachedNames);
|
setFieldNames(cachedNames);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}, []);
|
||||||
tableOptionQuery.refetch();
|
|
||||||
}, [cacheKey, url, props.params, props.enableColumnCaching]);
|
|
||||||
|
|
||||||
const enableSelection: boolean = useMemo(() => {
|
const enableSelection: boolean = useMemo(() => {
|
||||||
return tableProps.enableSelection || tableProps.enableBulkDelete || false;
|
return tableProps.enableSelection || tableProps.enableBulkDelete || false;
|
||||||
@ -555,54 +551,24 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
return api
|
return api
|
||||||
.get(url, {
|
.get(url, {
|
||||||
params: queryParams,
|
params: queryParams,
|
||||||
timeout: 5 * 1000
|
timeout: 10 * 1000
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
switch (response.status) {
|
let results = response.data?.results ?? response.data ?? [];
|
||||||
case 200:
|
|
||||||
setMissingRecordsText(
|
|
||||||
tableProps.noRecordsText ?? t`No records found`
|
|
||||||
);
|
|
||||||
|
|
||||||
let results = response.data?.results ?? response.data ?? [];
|
if (props.dataFormatter) {
|
||||||
|
// Custom data formatter provided
|
||||||
if (props.dataFormatter) {
|
results = props.dataFormatter(results);
|
||||||
// Custom data formatter provided
|
|
||||||
results = props.dataFormatter(results);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Array.isArray(results)) {
|
|
||||||
setMissingRecordsText(t`Server returned incorrect data type`);
|
|
||||||
results = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
tableState.setRecordCount(response.data?.count ?? results.length);
|
|
||||||
|
|
||||||
return results;
|
|
||||||
case 400:
|
|
||||||
setMissingRecordsText(t`Bad request`);
|
|
||||||
break;
|
|
||||||
case 401:
|
|
||||||
setMissingRecordsText(t`Unauthorized`);
|
|
||||||
break;
|
|
||||||
case 403:
|
|
||||||
setMissingRecordsText(t`Forbidden`);
|
|
||||||
break;
|
|
||||||
case 404:
|
|
||||||
setMissingRecordsText(t`Not found`);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
setMissingRecordsText(
|
|
||||||
`${t`Unknown error`}: ${response.statusText}`
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
if (!Array.isArray(results)) {
|
||||||
})
|
setMissingRecordsText(t`Server returned incorrect data type`);
|
||||||
.catch((error) => {
|
results = [];
|
||||||
setMissingRecordsText(`${t`Error`}: ${error.message}`);
|
}
|
||||||
return [];
|
|
||||||
|
tableState.setRecordCount(response.data?.count ?? results.length);
|
||||||
|
|
||||||
|
return results;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -626,9 +592,18 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
tableState.storedDataLoaded,
|
tableState.storedDataLoaded,
|
||||||
tableState.searchTerm
|
tableState.searchTerm
|
||||||
],
|
],
|
||||||
|
retry: 5,
|
||||||
|
retryDelay: (attempt: number) => (1 + attempt) * 250,
|
||||||
|
throwOnError: (error: any) => {
|
||||||
|
showApiErrorMessage({
|
||||||
|
error: error,
|
||||||
|
title: t`Error loading table data`
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
enabled: !!url && !tableData && tableState.storedDataLoaded,
|
enabled: !!url && !tableData && tableState.storedDataLoaded,
|
||||||
queryFn: fetchTableData,
|
queryFn: fetchTableData
|
||||||
refetchOnMount: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Refetch data when the query parameters change
|
// Refetch data when the query parameters change
|
||||||
|
@ -53,8 +53,7 @@ export default function BuildOrderTestTable({
|
|||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((res) => res.data)
|
.then((res) => res.data);
|
||||||
.catch((err) => []);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -160,8 +160,7 @@ export default function BuildOutputTable({
|
|||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((response) => response.data)
|
.then((response) => response.data);
|
||||||
.catch(() => []);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -184,8 +183,7 @@ export default function BuildOutputTable({
|
|||||||
tracked: true
|
tracked: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((response) => response.data)
|
.then((response) => response.data);
|
||||||
.catch(() => []);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -113,8 +113,7 @@ export default function ParametricPartTable({
|
|||||||
category: categoryId
|
category: categoryId
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((response) => response.data)
|
.then((response) => response.data);
|
||||||
.catch((_error) => []);
|
|
||||||
},
|
},
|
||||||
refetchOnMount: true
|
refetchOnMount: true
|
||||||
});
|
});
|
||||||
@ -290,7 +289,7 @@ export default function ParametricPartTable({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const parameterColumns: TableColumn[] = useMemo(() => {
|
const parameterColumns: TableColumn[] = useMemo(() => {
|
||||||
const data = categoryParameters.data ?? [];
|
const data = categoryParameters?.data || [];
|
||||||
|
|
||||||
return data.map((template: any) => {
|
return data.map((template: any) => {
|
||||||
let title = template.name;
|
let title = template.name;
|
||||||
|
@ -137,6 +137,11 @@ export function PartThumbTable({ pk, setImage }: Readonly<ThumbTableProps>) {
|
|||||||
// Fetch thumbnails from API
|
// Fetch thumbnails from API
|
||||||
const thumbQuery = useQuery({
|
const thumbQuery = useQuery({
|
||||||
queryKey: [ApiEndpoints.part_thumbs_list, page, searchText],
|
queryKey: [ApiEndpoints.part_thumbs_list, page, searchText],
|
||||||
|
throwOnError: (error: any) => {
|
||||||
|
setTotalPages(1);
|
||||||
|
setPage(1);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const offset = Math.max(0, page - 1) * limit;
|
const offset = Math.max(0, page - 1) * limit;
|
||||||
|
|
||||||
@ -152,11 +157,6 @@ export function PartThumbTable({ pk, setImage }: Readonly<ThumbTableProps>) {
|
|||||||
const records = response?.data?.count ?? 1;
|
const records = response?.data?.count ?? 1;
|
||||||
setTotalPages(Math.ceil(records / limit));
|
setTotalPages(Math.ceil(records / limit));
|
||||||
return response.data?.results ?? response.data;
|
return response.data?.results ?? response.data;
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setTotalPages(1);
|
|
||||||
setPage(1);
|
|
||||||
return [];
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -172,7 +172,7 @@ export function PartThumbTable({ pk, setImage }: Readonly<ThumbTableProps>) {
|
|||||||
spacing='xs'
|
spacing='xs'
|
||||||
>
|
>
|
||||||
{!thumbQuery.isFetching
|
{!thumbQuery.isFetching
|
||||||
? thumbQuery?.data.map((data: ImageElement, index: number) => (
|
? thumbQuery?.data?.map((data: ImageElement, index: number) => (
|
||||||
<PartThumbComponent
|
<PartThumbComponent
|
||||||
element={data}
|
element={data}
|
||||||
key={index}
|
key={index}
|
||||||
|
@ -72,8 +72,7 @@ export default function StockItemTestResultTable({
|
|||||||
enabled: true
|
enabled: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((response) => response.data)
|
.then((response) => response.data);
|
||||||
.catch((_error) => []);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -85,13 +84,14 @@ export default function StockItemTestResultTable({
|
|||||||
const formatRecords = useCallback(
|
const formatRecords = useCallback(
|
||||||
(records: any[]): any[] => {
|
(records: any[]): any[] => {
|
||||||
// Construct a list of test templates
|
// Construct a list of test templates
|
||||||
const results = testTemplates.map((template: any) => {
|
const results =
|
||||||
return {
|
testTemplates?.map((template: any) => {
|
||||||
...template,
|
return {
|
||||||
templateId: template.pk,
|
...template,
|
||||||
results: []
|
templateId: template.pk,
|
||||||
};
|
results: []
|
||||||
});
|
};
|
||||||
|
}) ?? [];
|
||||||
|
|
||||||
// If any of the tests results point to templates which we do not have, add them in
|
// If any of the tests results point to templates which we do not have, add them in
|
||||||
records.forEach((record) => {
|
records.forEach((record) => {
|
||||||
|
Reference in New Issue
Block a user