mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-05 13:10:57 +00:00
[UI] Reduce flicker when reloading detail instance (#9926)
* UI improvement for column selection in tables - Limit max height of dropdown * Allow retry for instance query * Prevent flickering when reloading instance - Don't hide the children - Just put a loading overlay on top * Enhanced rendering for <InstanceDetail> * Refactor other pages * remove unused attributes
This commit is contained in:
@ -1,37 +1,44 @@
|
||||
import { LoadingOverlay } from '@mantine/core';
|
||||
import { Center, Container, Loader } from '@mantine/core';
|
||||
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import type { UserRoles } from '@lib/enums/Roles';
|
||||
import type { UseQueryResult } from '@tanstack/react-query';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import ClientError from '../errors/ClientError';
|
||||
import PermissionDenied from '../errors/PermissionDenied';
|
||||
import ServerError from '../errors/ServerError';
|
||||
|
||||
export default function InstanceDetail({
|
||||
status,
|
||||
loading,
|
||||
query,
|
||||
children,
|
||||
requiredRole,
|
||||
requiredPermission
|
||||
}: Readonly<{
|
||||
status: number;
|
||||
loading: boolean;
|
||||
query: UseQueryResult;
|
||||
children: React.ReactNode;
|
||||
requiredRole?: UserRoles;
|
||||
requiredPermission?: ModelType;
|
||||
}>) {
|
||||
const user = useUserState();
|
||||
|
||||
if (loading || !user.isLoggedIn()) {
|
||||
return <LoadingOverlay />;
|
||||
}
|
||||
const [loaded, setLoaded] = useState<boolean>(false);
|
||||
|
||||
if (status >= 500) {
|
||||
return <ServerError status={status} />;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (query.isSuccess) {
|
||||
setLoaded(true);
|
||||
}
|
||||
}, [query.isSuccess]);
|
||||
|
||||
if (status >= 400) {
|
||||
return <ClientError status={status} />;
|
||||
if (query.isError) {
|
||||
const reason = query.failureReason as any;
|
||||
const statusCode = reason?.response?.status ?? reason?.status ?? 0;
|
||||
|
||||
if (statusCode >= 500) {
|
||||
return <ServerError status={statusCode} />;
|
||||
}
|
||||
|
||||
return <ClientError status={statusCode} />;
|
||||
}
|
||||
|
||||
if (requiredRole && !user.hasViewRole(requiredRole)) {
|
||||
@ -42,5 +49,16 @@ export default function InstanceDetail({
|
||||
return <PermissionDenied />;
|
||||
}
|
||||
|
||||
if (!loaded || !user.isLoggedIn()) {
|
||||
// Return a loader for the first page load
|
||||
return (
|
||||
<Center>
|
||||
<Container>
|
||||
<Loader />
|
||||
</Container>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ export interface UseInstanceResult {
|
||||
refreshInstance: () => void;
|
||||
refreshInstancePromise: () => Promise<QueryObserverResult<any, any>>;
|
||||
instanceQuery: any;
|
||||
requestStatus: number;
|
||||
isLoaded: boolean;
|
||||
}
|
||||
|
||||
@ -35,7 +34,6 @@ export function useInstance<T = any>({
|
||||
hasPrimaryKey = true,
|
||||
refetchOnMount = true,
|
||||
refetchOnWindowFocus = false,
|
||||
throwError = false,
|
||||
updateInterval
|
||||
}: {
|
||||
endpoint: ApiEndpoints;
|
||||
@ -46,15 +44,12 @@ export function useInstance<T = any>({
|
||||
defaultValue?: any;
|
||||
refetchOnMount?: boolean;
|
||||
refetchOnWindowFocus?: boolean;
|
||||
throwError?: boolean;
|
||||
updateInterval?: number;
|
||||
}): UseInstanceResult {
|
||||
const api = useApi();
|
||||
|
||||
const [instance, setInstance] = useState<T | undefined>(defaultValue);
|
||||
|
||||
const [requestStatus, setRequestStatus] = useState<number>(0);
|
||||
|
||||
const instanceQuery = useQuery<T>({
|
||||
queryKey: [
|
||||
'instance',
|
||||
@ -84,7 +79,6 @@ export function useInstance<T = any>({
|
||||
params: params
|
||||
})
|
||||
.then((response) => {
|
||||
setRequestStatus(response.status);
|
||||
switch (response.status) {
|
||||
case 200:
|
||||
setInstance(response.data);
|
||||
@ -93,15 +87,6 @@ export function useInstance<T = any>({
|
||||
setInstance(defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setRequestStatus(error.response?.status || 0);
|
||||
setInstance(defaultValue);
|
||||
console.error(`ERR: Error fetching instance ${url}:`, error);
|
||||
|
||||
if (throwError) throw error;
|
||||
|
||||
return defaultValue;
|
||||
});
|
||||
},
|
||||
refetchOnMount: refetchOnMount,
|
||||
@ -131,7 +116,6 @@ export function useInstance<T = any>({
|
||||
refreshInstance,
|
||||
refreshInstancePromise,
|
||||
instanceQuery,
|
||||
requestStatus,
|
||||
isLoaded
|
||||
};
|
||||
}
|
||||
|
@ -75,8 +75,7 @@ export default function BuildDetail() {
|
||||
const {
|
||||
instance: build,
|
||||
refreshInstance,
|
||||
instanceQuery,
|
||||
requestStatus
|
||||
instanceQuery
|
||||
} = useInstance({
|
||||
endpoint: ApiEndpoints.build_order_list,
|
||||
pk: id,
|
||||
@ -621,11 +620,7 @@ export default function BuildDetail() {
|
||||
{holdOrder.modal}
|
||||
{issueOrder.modal}
|
||||
{completeOrder.modal}
|
||||
<InstanceDetail
|
||||
status={requestStatus}
|
||||
loading={instanceQuery.isFetching}
|
||||
requiredRole={UserRoles.build}
|
||||
>
|
||||
<InstanceDetail query={instanceQuery} requiredRole={UserRoles.build}>
|
||||
<Stack gap='xs'>
|
||||
<PageDetail
|
||||
title={`${t`Build Order`}: ${build.reference}`}
|
||||
|
@ -73,8 +73,7 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
|
||||
const {
|
||||
instance: company,
|
||||
refreshInstance,
|
||||
instanceQuery,
|
||||
requestStatus
|
||||
instanceQuery
|
||||
} = useInstance({
|
||||
endpoint: ApiEndpoints.company_list,
|
||||
pk: id,
|
||||
@ -326,7 +325,10 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
|
||||
<>
|
||||
{editCompany.modal}
|
||||
{deleteCompany.modal}
|
||||
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
||||
<InstanceDetail
|
||||
query={instanceQuery}
|
||||
requiredPermission={ModelType.company}
|
||||
>
|
||||
<Stack gap='xs'>
|
||||
<PageDetail
|
||||
title={`${t`Company`}: ${company.name}`}
|
||||
|
@ -51,8 +51,7 @@ export default function ManufacturerPartDetail() {
|
||||
const {
|
||||
instance: manufacturerPart,
|
||||
instanceQuery,
|
||||
refreshInstance,
|
||||
requestStatus
|
||||
refreshInstance
|
||||
} = useInstance({
|
||||
endpoint: ApiEndpoints.manufacturer_part_list,
|
||||
pk: id,
|
||||
@ -273,7 +272,10 @@ export default function ManufacturerPartDetail() {
|
||||
{deleteManufacturerPart.modal}
|
||||
{duplicateManufacturerPart.modal}
|
||||
{editManufacturerPart.modal}
|
||||
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
||||
<InstanceDetail
|
||||
query={instanceQuery}
|
||||
requiredPermission={ModelType.manufacturerpart}
|
||||
>
|
||||
<Stack gap='xs'>
|
||||
<PageDetail
|
||||
title={t`ManufacturerPart`}
|
||||
|
@ -56,8 +56,7 @@ export default function SupplierPartDetail() {
|
||||
const {
|
||||
instance: supplierPart,
|
||||
instanceQuery,
|
||||
refreshInstance,
|
||||
requestStatus
|
||||
refreshInstance
|
||||
} = useInstance({
|
||||
endpoint: ApiEndpoints.supplier_part_list,
|
||||
pk: id,
|
||||
@ -401,8 +400,7 @@ export default function SupplierPartDetail() {
|
||||
{duplicateSupplierPart.modal}
|
||||
{editSupplierPart.modal}
|
||||
<InstanceDetail
|
||||
status={requestStatus}
|
||||
loading={instanceQuery.isFetching}
|
||||
query={instanceQuery}
|
||||
requiredRole={UserRoles.purchase_order}
|
||||
>
|
||||
<Stack gap='xs'>
|
||||
|
@ -26,7 +26,7 @@ import { useInstance } from '../../hooks/UseInstance';
|
||||
export default function GroupDetail() {
|
||||
const { id } = useParams();
|
||||
|
||||
const { instance, instanceQuery, requestStatus } = useInstance({
|
||||
const { instance, instanceQuery } = useInstance({
|
||||
endpoint: ApiEndpoints.group_list,
|
||||
pk: id
|
||||
});
|
||||
@ -72,7 +72,7 @@ export default function GroupDetail() {
|
||||
}, [instance, id]);
|
||||
|
||||
return (
|
||||
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
||||
<InstanceDetail query={instanceQuery}>
|
||||
<Stack gap='xs'>
|
||||
<PageDetail
|
||||
title={`${t`Group`}: ${instance.name}`}
|
||||
|
@ -29,7 +29,7 @@ export default function UserDetail() {
|
||||
const user = useUserState();
|
||||
const settings = useGlobalSettingsState();
|
||||
|
||||
const { instance, instanceQuery, requestStatus } = useInstance({
|
||||
const { instance, instanceQuery } = useInstance({
|
||||
endpoint: ApiEndpoints.user_list,
|
||||
pk: id
|
||||
});
|
||||
@ -214,7 +214,7 @@ export default function UserDetail() {
|
||||
}, [instance, instanceQuery]);
|
||||
|
||||
return (
|
||||
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
||||
<InstanceDetail query={instanceQuery}>
|
||||
<Stack gap='xs'>
|
||||
<PageDetail
|
||||
title={`${t`User`}: ${instance.username}`}
|
||||
|
@ -64,8 +64,7 @@ export default function CategoryDetail() {
|
||||
const {
|
||||
instance: category,
|
||||
refreshInstance,
|
||||
instanceQuery,
|
||||
requestStatus
|
||||
instanceQuery
|
||||
} = useInstance({
|
||||
endpoint: ApiEndpoints.category_list,
|
||||
hasPrimaryKey: true,
|
||||
@ -325,8 +324,7 @@ export default function CategoryDetail() {
|
||||
{editCategory.modal}
|
||||
{deleteCategory.modal}
|
||||
<InstanceDetail
|
||||
status={requestStatus}
|
||||
loading={id ? instanceQuery.isFetching : false}
|
||||
query={instanceQuery}
|
||||
requiredRole={UserRoles.part_category}
|
||||
>
|
||||
<Stack gap='xs'>
|
||||
|
@ -130,8 +130,7 @@ export default function PartDetail() {
|
||||
const {
|
||||
instance: part,
|
||||
refreshInstance,
|
||||
instanceQuery,
|
||||
requestStatus
|
||||
instanceQuery
|
||||
} = useInstance({
|
||||
endpoint: ApiEndpoints.part_list,
|
||||
pk: id,
|
||||
@ -1028,11 +1027,7 @@ export default function PartDetail() {
|
||||
{orderPartsWizard.wizard}
|
||||
{findBySerialNumber.modal}
|
||||
{transferStockItems.modal}
|
||||
<InstanceDetail
|
||||
status={requestStatus}
|
||||
loading={instanceQuery.isFetching}
|
||||
requiredRole={UserRoles.part}
|
||||
>
|
||||
<InstanceDetail query={instanceQuery} requiredRole={UserRoles.part}>
|
||||
<Stack gap='xs'>
|
||||
{user.hasViewRole(UserRoles.part_category) && (
|
||||
<NavigationTree
|
||||
|
@ -59,8 +59,7 @@ export default function PurchaseOrderDetail() {
|
||||
const {
|
||||
instance: order,
|
||||
instanceQuery,
|
||||
refreshInstance,
|
||||
requestStatus
|
||||
refreshInstance
|
||||
} = useInstance({
|
||||
endpoint: ApiEndpoints.purchase_order_list,
|
||||
pk: id,
|
||||
@ -514,8 +513,7 @@ export default function PurchaseOrderDetail() {
|
||||
{editPurchaseOrder.modal}
|
||||
{duplicatePurchaseOrder.modal}
|
||||
<InstanceDetail
|
||||
status={requestStatus}
|
||||
loading={instanceQuery.isFetching}
|
||||
query={instanceQuery}
|
||||
requiredRole={UserRoles.purchase_order}
|
||||
>
|
||||
<Stack gap='xs'>
|
||||
|
@ -59,8 +59,7 @@ export default function ReturnOrderDetail() {
|
||||
const {
|
||||
instance: order,
|
||||
instanceQuery,
|
||||
refreshInstance,
|
||||
requestStatus
|
||||
refreshInstance
|
||||
} = useInstance({
|
||||
endpoint: ApiEndpoints.return_order_list,
|
||||
pk: id,
|
||||
@ -499,8 +498,7 @@ export default function ReturnOrderDetail() {
|
||||
{completeOrder.modal}
|
||||
{duplicateReturnOrder.modal}
|
||||
<InstanceDetail
|
||||
status={requestStatus}
|
||||
loading={instanceQuery.isFetching}
|
||||
query={instanceQuery}
|
||||
requiredRole={UserRoles.return_order}
|
||||
>
|
||||
<Stack gap='xs'>
|
||||
|
@ -68,8 +68,7 @@ export default function SalesOrderDetail() {
|
||||
const {
|
||||
instance: order,
|
||||
instanceQuery,
|
||||
refreshInstance,
|
||||
requestStatus
|
||||
refreshInstance
|
||||
} = useInstance({
|
||||
endpoint: ApiEndpoints.sales_order_list,
|
||||
pk: id,
|
||||
@ -563,8 +562,7 @@ export default function SalesOrderDetail() {
|
||||
{editSalesOrder.modal}
|
||||
{duplicateSalesOrder.modal}
|
||||
<InstanceDetail
|
||||
status={requestStatus}
|
||||
loading={instanceQuery.isFetching}
|
||||
query={instanceQuery}
|
||||
requiredRole={UserRoles.sales_order}
|
||||
>
|
||||
<Stack gap='xs'>
|
||||
|
@ -52,8 +52,7 @@ export default function SalesOrderShipmentDetail() {
|
||||
const {
|
||||
instance: shipment,
|
||||
instanceQuery: shipmentQuery,
|
||||
refreshInstance: refreshShipment,
|
||||
requestStatus: shipmentStatus
|
||||
refreshInstance: refreshShipment
|
||||
} = useInstance({
|
||||
endpoint: ApiEndpoints.sales_order_shipment_list,
|
||||
pk: id,
|
||||
@ -65,8 +64,7 @@ export default function SalesOrderShipmentDetail() {
|
||||
const {
|
||||
instance: customer,
|
||||
instanceQuery: customerQuery,
|
||||
refreshInstance: refreshCustomer,
|
||||
requestStatus: customerStatus
|
||||
refreshInstance: refreshCustomer
|
||||
} = useInstance({
|
||||
endpoint: ApiEndpoints.company_list,
|
||||
pk: shipment.order_detail?.customer,
|
||||
@ -351,8 +349,7 @@ export default function SalesOrderShipmentDetail() {
|
||||
{editShipment.modal}
|
||||
{deleteShipment.modal}
|
||||
<InstanceDetail
|
||||
status={shipmentStatus}
|
||||
loading={shipmentQuery.isFetching || customerQuery.isFetching}
|
||||
query={shipmentQuery}
|
||||
requiredRole={UserRoles.sales_order}
|
||||
>
|
||||
<Stack gap='xs'>
|
||||
|
@ -64,8 +64,7 @@ export default function Stock() {
|
||||
const {
|
||||
instance: location,
|
||||
refreshInstance,
|
||||
instanceQuery,
|
||||
requestStatus
|
||||
instanceQuery
|
||||
} = useInstance({
|
||||
endpoint: ApiEndpoints.stock_location_list,
|
||||
hasPrimaryKey: true,
|
||||
@ -423,8 +422,7 @@ export default function Stock() {
|
||||
{scanInStockItem.dialog}
|
||||
{scanInStockLocation.dialog}
|
||||
<InstanceDetail
|
||||
status={requestStatus}
|
||||
loading={id ? instanceQuery.isFetching : false}
|
||||
query={instanceQuery}
|
||||
requiredRole={UserRoles.stock_location}
|
||||
>
|
||||
<Stack>
|
||||
|
@ -114,8 +114,7 @@ export default function StockDetail() {
|
||||
instance: stockitem,
|
||||
refreshInstance,
|
||||
refreshInstancePromise,
|
||||
instanceQuery,
|
||||
requestStatus
|
||||
instanceQuery
|
||||
} = useInstance({
|
||||
endpoint: ApiEndpoints.stock_item_list,
|
||||
pk: id,
|
||||
@ -1053,9 +1052,8 @@ export default function StockDetail() {
|
||||
{findBySerialNumber.modal}
|
||||
{scanIntoLocation.dialog}
|
||||
<InstanceDetail
|
||||
requiredRole={UserRoles.stock}
|
||||
status={requestStatus}
|
||||
loading={instanceQuery.isFetching}
|
||||
query={instanceQuery}
|
||||
requiredPermission={ModelType.stockitem}
|
||||
>
|
||||
<Stack>
|
||||
{user.hasViewRole(UserRoles.stock_location) && (
|
||||
|
@ -45,7 +45,6 @@ export function GroupDrawer({
|
||||
} = useInstance({
|
||||
endpoint: ApiEndpoints.group_list,
|
||||
pk: id,
|
||||
throwError: true,
|
||||
params: {
|
||||
permission_detail: true,
|
||||
role_detail: true,
|
||||
|
@ -80,8 +80,7 @@ export function TemplateDrawer({
|
||||
} = useInstance<TemplateI>({
|
||||
endpoint: templateEndpoint,
|
||||
hasPrimaryKey: true,
|
||||
pk: id,
|
||||
throwError: true
|
||||
pk: id
|
||||
});
|
||||
|
||||
// Editors
|
||||
|
@ -68,8 +68,7 @@ export function UserDrawer({
|
||||
instanceQuery: { isFetching, error }
|
||||
} = useInstance<UserDetailI>({
|
||||
endpoint: ApiEndpoints.user_list,
|
||||
pk: id,
|
||||
throwError: true
|
||||
pk: id
|
||||
});
|
||||
|
||||
const currentUserPk = useUserState(useShallow((s) => s.user?.pk));
|
||||
|
Reference in New Issue
Block a user