mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
[PUI] Error pages (#7554)
* Add <PermissionDenied /> page * Check permissions for admin center * Wrap <PartDetail> page in an error handler - Display client or server errors * Add error handlers to other detail pages * Refactor error pages * Add playwright tests * Refactor component locations * Get test to work
This commit is contained in:
parent
0c293fa896
commit
b15eb35273
28
src/frontend/src/components/errors/ClientError.tsx
Normal file
28
src/frontend/src/components/errors/ClientError.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
|
import GenericErrorPage from './GenericErrorPage';
|
||||||
|
import NotAuthenticated from './NotAuthenticated';
|
||||||
|
import NotFound from './NotFound';
|
||||||
|
import PermissionDenied from './PermissionDenied';
|
||||||
|
|
||||||
|
export default function ClientError({ status }: { status?: number }) {
|
||||||
|
switch (status) {
|
||||||
|
case 401:
|
||||||
|
return <NotAuthenticated />;
|
||||||
|
case 403:
|
||||||
|
return <PermissionDenied />;
|
||||||
|
case 404:
|
||||||
|
return <NotFound />;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic client error
|
||||||
|
return (
|
||||||
|
<GenericErrorPage
|
||||||
|
title={t`Client Error`}
|
||||||
|
message={t`Client error occurred`}
|
||||||
|
status={status}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
74
src/frontend/src/components/errors/GenericErrorPage.tsx
Normal file
74
src/frontend/src/components/errors/GenericErrorPage.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { Trans } from '@lingui/macro';
|
||||||
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Alert,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Center,
|
||||||
|
Container,
|
||||||
|
Divider,
|
||||||
|
Group,
|
||||||
|
Stack,
|
||||||
|
Text
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconExclamationCircle } from '@tabler/icons-react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { LanguageContext } from '../../contexts/LanguageContext';
|
||||||
|
|
||||||
|
export default function ErrorPage({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
status
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
status?: number;
|
||||||
|
redirectMessage?: string;
|
||||||
|
redirectTarget?: string;
|
||||||
|
}) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LanguageContext>
|
||||||
|
<Center>
|
||||||
|
<Container w="md" miw={400}>
|
||||||
|
<Card withBorder shadow="xs" padding="xl" radius="sm">
|
||||||
|
<Card.Section p="lg">
|
||||||
|
<Group gap="xs">
|
||||||
|
<ActionIcon color="red" variant="transparent" size="xl">
|
||||||
|
<IconExclamationCircle />
|
||||||
|
</ActionIcon>
|
||||||
|
<Text size="xl">{title}</Text>
|
||||||
|
</Group>
|
||||||
|
</Card.Section>
|
||||||
|
<Divider />
|
||||||
|
<Card.Section p="lg">
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text size="lg">{message}</Text>
|
||||||
|
{status && (
|
||||||
|
<Text>
|
||||||
|
<Trans>Status Code</Trans>: {status}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Card.Section>
|
||||||
|
<Divider />
|
||||||
|
<Card.Section p="lg">
|
||||||
|
<Center>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
color="green"
|
||||||
|
onClick={() => navigate('/')}
|
||||||
|
>
|
||||||
|
<Trans>Return to the index page</Trans>
|
||||||
|
<IconArrowBack />
|
||||||
|
</Button>
|
||||||
|
</Center>
|
||||||
|
</Card.Section>
|
||||||
|
</Card>
|
||||||
|
</Container>
|
||||||
|
</Center>
|
||||||
|
</LanguageContext>
|
||||||
|
);
|
||||||
|
}
|
12
src/frontend/src/components/errors/NotAuthenticated.tsx
Normal file
12
src/frontend/src/components/errors/NotAuthenticated.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
|
import GenericErrorPage from './GenericErrorPage';
|
||||||
|
|
||||||
|
export default function NotAuthenticated() {
|
||||||
|
return (
|
||||||
|
<GenericErrorPage
|
||||||
|
title={t`Not Authenticated`}
|
||||||
|
message={t`You are not logged in.`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
12
src/frontend/src/components/errors/NotFound.tsx
Normal file
12
src/frontend/src/components/errors/NotFound.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
|
import GenericErrorPage from './GenericErrorPage';
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<GenericErrorPage
|
||||||
|
title={t`Page Not Found`}
|
||||||
|
message={t`This page does not exist`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
12
src/frontend/src/components/errors/PermissionDenied.tsx
Normal file
12
src/frontend/src/components/errors/PermissionDenied.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
|
import GenericErrorPage from './GenericErrorPage';
|
||||||
|
|
||||||
|
export default function PermissionDenied() {
|
||||||
|
return (
|
||||||
|
<GenericErrorPage
|
||||||
|
title={t`Permission Denied`}
|
||||||
|
message={t`You do not have permission to view this page.`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
13
src/frontend/src/components/errors/ServerError.tsx
Normal file
13
src/frontend/src/components/errors/ServerError.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
|
import GenericErrorPage from './GenericErrorPage';
|
||||||
|
|
||||||
|
export default function ServerError({ status }: { status?: number }) {
|
||||||
|
return (
|
||||||
|
<GenericErrorPage
|
||||||
|
title={t`Server Error`}
|
||||||
|
message={t`A server error occurred`}
|
||||||
|
status={status}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -308,7 +308,7 @@ export function ApiForm({
|
|||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching initial data:', error);
|
console.error('ERR: Error fetching initial data:', error);
|
||||||
// Re-throw error to allow react-query to handle error
|
// Re-throw error to allow react-query to handle error
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
28
src/frontend/src/components/nav/InstanceDetail.tsx
Normal file
28
src/frontend/src/components/nav/InstanceDetail.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { LoadingOverlay } from '@mantine/core';
|
||||||
|
|
||||||
|
import ClientError from '../errors/ClientError';
|
||||||
|
import ServerError from '../errors/ServerError';
|
||||||
|
|
||||||
|
export default function InstanceDetail({
|
||||||
|
status,
|
||||||
|
loading,
|
||||||
|
children
|
||||||
|
}: {
|
||||||
|
status: number;
|
||||||
|
loading: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
if (loading) {
|
||||||
|
return <LoadingOverlay />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status >= 500) {
|
||||||
|
return <ServerError status={status} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status >= 400) {
|
||||||
|
return <ClientError status={status} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { api } from '../App';
|
import { api } from '../App';
|
||||||
import { ApiEndpoints } from '../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../enums/ApiEndpoints';
|
||||||
@ -39,6 +39,8 @@ export function useInstance<T = any>({
|
|||||||
}) {
|
}) {
|
||||||
const [instance, setInstance] = useState<T | undefined>(defaultValue);
|
const [instance, setInstance] = useState<T | undefined>(defaultValue);
|
||||||
|
|
||||||
|
const [requestStatus, setRequestStatus] = useState<number>(0);
|
||||||
|
|
||||||
const instanceQuery = useQuery<T>({
|
const instanceQuery = useQuery<T>({
|
||||||
queryKey: ['instance', endpoint, pk, params, pathParams],
|
queryKey: ['instance', endpoint, pk, params, pathParams],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@ -62,6 +64,7 @@ export function useInstance<T = any>({
|
|||||||
params: params
|
params: params
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
setRequestStatus(response.status);
|
||||||
switch (response.status) {
|
switch (response.status) {
|
||||||
case 200:
|
case 200:
|
||||||
setInstance(response.data);
|
setInstance(response.data);
|
||||||
@ -72,8 +75,9 @@ export function useInstance<T = any>({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
setRequestStatus(error.response?.status || 0);
|
||||||
setInstance(defaultValue);
|
setInstance(defaultValue);
|
||||||
console.error(`Error fetching instance ${url}:`, error);
|
console.error(`ERR: Error fetching instance ${url}:`, error);
|
||||||
|
|
||||||
if (throwError) throw error;
|
if (throwError) throw error;
|
||||||
|
|
||||||
@ -89,5 +93,5 @@ export function useInstance<T = any>({
|
|||||||
instanceQuery.refetch();
|
instanceQuery.refetch();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return { instance, refreshInstance, instanceQuery };
|
return { instance, refreshInstance, instanceQuery, requestStatus };
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { Trans, t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Container, Text, Title } from '@mantine/core';
|
|
||||||
import { useDocumentTitle } from '@mantine/hooks';
|
import { useDocumentTitle } from '@mantine/hooks';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useRouteError } from 'react-router-dom';
|
import { useRouteError } from 'react-router-dom';
|
||||||
|
|
||||||
import { LanguageContext } from '../contexts/LanguageContext';
|
import GenericErrorPage from '../components/errors/GenericErrorPage';
|
||||||
import { ErrorResponse } from '../states/states';
|
import { ErrorResponse } from '../states/states';
|
||||||
|
|
||||||
export default function ErrorPage() {
|
export default function ErrorPage() {
|
||||||
@ -19,18 +18,9 @@ export default function ErrorPage() {
|
|||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LanguageContext>
|
<GenericErrorPage
|
||||||
<Container>
|
title={title}
|
||||||
<Title>
|
message={t`An unexpected error has occurred`}
|
||||||
<Trans>Error</Trans>
|
/>
|
||||||
</Title>
|
|
||||||
<Text>
|
|
||||||
<Trans>Sorry, an unexpected error has occurred.</Trans>
|
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
<i>{error.statusText || error.message}</i>
|
|
||||||
</Text>
|
|
||||||
</Container>
|
|
||||||
</LanguageContext>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,13 @@ import {
|
|||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { lazy, useMemo } from 'react';
|
import { lazy, useMemo } from 'react';
|
||||||
|
|
||||||
|
import PermissionDenied from '../../../../components/errors/PermissionDenied';
|
||||||
import { PlaceholderPill } from '../../../../components/items/Placeholder';
|
import { PlaceholderPill } from '../../../../components/items/Placeholder';
|
||||||
import { PanelGroup, PanelType } from '../../../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../../../components/nav/PanelGroup';
|
||||||
import { SettingsHeader } from '../../../../components/nav/SettingsHeader';
|
import { SettingsHeader } from '../../../../components/nav/SettingsHeader';
|
||||||
import { GlobalSettingList } from '../../../../components/settings/SettingList';
|
import { GlobalSettingList } from '../../../../components/settings/SettingList';
|
||||||
import { Loadable } from '../../../../functions/loading';
|
import { Loadable } from '../../../../functions/loading';
|
||||||
|
import { useUserState } from '../../../../states/UserState';
|
||||||
|
|
||||||
const ReportTemplatePanel = Loadable(
|
const ReportTemplatePanel = Loadable(
|
||||||
lazy(() => import('./ReportTemplatePanel'))
|
lazy(() => import('./ReportTemplatePanel'))
|
||||||
@ -74,6 +76,8 @@ const CurrencyTable = Loadable(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export default function AdminCenter() {
|
export default function AdminCenter() {
|
||||||
|
const user = useUserState();
|
||||||
|
|
||||||
const adminCenterPanels: PanelType[] = useMemo(() => {
|
const adminCenterPanels: PanelType[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -187,19 +191,25 @@ export default function AdminCenter() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="xs">
|
<>
|
||||||
<SettingsHeader
|
{user.isStaff() ? (
|
||||||
title={t`Admin Center`}
|
<Stack gap="xs">
|
||||||
subtitle={t`Advanced Options`}
|
<SettingsHeader
|
||||||
switch_link="/settings/system"
|
title={t`Admin Center`}
|
||||||
switch_text="System Settings"
|
subtitle={t`Advanced Options`}
|
||||||
/>
|
switch_link="/settings/system"
|
||||||
<QuickAction />
|
switch_text="System Settings"
|
||||||
<PanelGroup
|
/>
|
||||||
pageKey="admin-center"
|
<QuickAction />
|
||||||
panels={adminCenterPanels}
|
<PanelGroup
|
||||||
collapsible={true}
|
pageKey="admin-center"
|
||||||
/>
|
panels={adminCenterPanels}
|
||||||
</Stack>
|
collapsible={true}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<PermissionDenied />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,13 @@ import {
|
|||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import PermissionDenied from '../../../components/errors/PermissionDenied';
|
||||||
import { PlaceholderPanel } from '../../../components/items/Placeholder';
|
import { PlaceholderPanel } from '../../../components/items/Placeholder';
|
||||||
import { PanelGroup, PanelType } from '../../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../../components/nav/PanelGroup';
|
||||||
import { SettingsHeader } from '../../../components/nav/SettingsHeader';
|
import { SettingsHeader } from '../../../components/nav/SettingsHeader';
|
||||||
import { GlobalSettingList } from '../../../components/settings/SettingList';
|
import { GlobalSettingList } from '../../../components/settings/SettingList';
|
||||||
import { useServerApiState } from '../../../states/ApiState';
|
import { useServerApiState } from '../../../states/ApiState';
|
||||||
|
import { useUserState } from '../../../states/UserState';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* System settings page
|
* System settings page
|
||||||
@ -295,19 +297,26 @@ export default function SystemSettings() {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const user = useUserState();
|
||||||
|
|
||||||
const [server] = useServerApiState((state) => [state.server]);
|
const [server] = useServerApiState((state) => [state.server]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack gap="xs">
|
{user.isStaff() ? (
|
||||||
<SettingsHeader
|
<Stack gap="xs">
|
||||||
title={t`System Settings`}
|
<SettingsHeader
|
||||||
subtitle={server.instance || ''}
|
title={t`System Settings`}
|
||||||
switch_link="/settings/user"
|
subtitle={server.instance || ''}
|
||||||
switch_text={<Trans>Switch to User Setting</Trans>}
|
switch_link="/settings/user"
|
||||||
/>
|
switch_text={<Trans>Switch to User Setting</Trans>}
|
||||||
<PanelGroup pageKey="system-settings" panels={systemSettingsPanels} />
|
/>
|
||||||
</Stack>
|
<PanelGroup pageKey="system-settings" panels={systemSettingsPanels} />
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<PermissionDenied />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
import { Trans } from '@lingui/macro';
|
|
||||||
import { Button, Center, Container, Stack, Text, Title } from '@mantine/core';
|
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { LanguageContext } from '../contexts/LanguageContext';
|
|
||||||
|
|
||||||
export default function NotFound() {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LanguageContext>
|
|
||||||
<Center mih="100vh">
|
|
||||||
<Container w="md" miw={400}>
|
|
||||||
<Stack>
|
|
||||||
<Title>
|
|
||||||
<Trans>Not Found</Trans>
|
|
||||||
</Title>
|
|
||||||
<Text>
|
|
||||||
<Trans>Sorry, this page is not known or was moved.</Trans>
|
|
||||||
</Text>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
color="green"
|
|
||||||
onClick={() => navigate('/')}
|
|
||||||
>
|
|
||||||
<Trans>Go to the start page</Trans>
|
|
||||||
<IconArrowBack />
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Container>
|
|
||||||
</Center>
|
|
||||||
</LanguageContext>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
|
import { Grid, Skeleton, Stack } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
IconClipboardCheck,
|
IconClipboardCheck,
|
||||||
IconClipboardList,
|
IconClipboardList,
|
||||||
@ -30,6 +30,7 @@ import {
|
|||||||
UnlinkBarcodeAction,
|
UnlinkBarcodeAction,
|
||||||
ViewBarcodeAction
|
ViewBarcodeAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
import { StatusRenderer } from '../../components/render/StatusRenderer';
|
import { StatusRenderer } from '../../components/render/StatusRenderer';
|
||||||
@ -61,7 +62,8 @@ export default function BuildDetail() {
|
|||||||
const {
|
const {
|
||||||
instance: build,
|
instance: build,
|
||||||
refreshInstance,
|
refreshInstance,
|
||||||
instanceQuery
|
instanceQuery,
|
||||||
|
requestStatus
|
||||||
} = useInstance({
|
} = useInstance({
|
||||||
endpoint: ApiEndpoints.build_order_list,
|
endpoint: ApiEndpoints.build_order_list,
|
||||||
pk: id,
|
pk: id,
|
||||||
@ -410,21 +412,22 @@ export default function BuildDetail() {
|
|||||||
{editBuild.modal}
|
{editBuild.modal}
|
||||||
{duplicateBuild.modal}
|
{duplicateBuild.modal}
|
||||||
{cancelBuild.modal}
|
{cancelBuild.modal}
|
||||||
<Stack gap="xs">
|
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<Stack gap="xs">
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={build.reference}
|
title={build.reference}
|
||||||
subtitle={build.title}
|
subtitle={build.title}
|
||||||
badges={buildBadges}
|
badges={buildBadges}
|
||||||
imageUrl={build.part_detail?.image ?? build.part_detail?.thumbnail}
|
imageUrl={build.part_detail?.image ?? build.part_detail?.thumbnail}
|
||||||
breadcrumbs={[
|
breadcrumbs={[
|
||||||
{ name: t`Build Orders`, url: '/build' },
|
{ name: t`Build Orders`, url: '/build' },
|
||||||
{ name: build.reference, url: `/build/${build.pk}` }
|
{ name: build.reference, url: `/build/${build.pk}` }
|
||||||
]}
|
]}
|
||||||
actions={buildActions}
|
actions={buildActions}
|
||||||
/>
|
/>
|
||||||
<PanelGroup pageKey="build" panels={buildPanels} />
|
<PanelGroup pageKey="build" panels={buildPanels} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</InstanceDetail>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
|
import { Grid, Skeleton, Stack } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
IconBuildingFactory2,
|
IconBuildingFactory2,
|
||||||
IconBuildingWarehouse,
|
IconBuildingWarehouse,
|
||||||
@ -30,6 +30,7 @@ import {
|
|||||||
EditItemAction
|
EditItemAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { Breadcrumb } from '../../components/nav/BreadcrumbList';
|
import { Breadcrumb } from '../../components/nav/BreadcrumbList';
|
||||||
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
@ -66,7 +67,8 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
|
|||||||
const {
|
const {
|
||||||
instance: company,
|
instance: company,
|
||||||
refreshInstance,
|
refreshInstance,
|
||||||
instanceQuery
|
instanceQuery,
|
||||||
|
requestStatus
|
||||||
} = useInstance({
|
} = useInstance({
|
||||||
endpoint: ApiEndpoints.company_list,
|
endpoint: ApiEndpoints.company_list,
|
||||||
pk: id,
|
pk: id,
|
||||||
@ -320,18 +322,19 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{editCompany.modal}
|
{editCompany.modal}
|
||||||
<Stack gap="xs">
|
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<Stack gap="xs">
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={t`Company` + `: ${company.name}`}
|
title={t`Company` + `: ${company.name}`}
|
||||||
subtitle={company.description}
|
subtitle={company.description}
|
||||||
actions={companyActions}
|
actions={companyActions}
|
||||||
imageUrl={company.image}
|
imageUrl={company.image}
|
||||||
breadcrumbs={props.breadcrumbs}
|
breadcrumbs={props.breadcrumbs}
|
||||||
badges={badges}
|
badges={badges}
|
||||||
/>
|
/>
|
||||||
<PanelGroup pageKey="company" panels={companyPanels} />
|
<PanelGroup pageKey="company" panels={companyPanels} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</InstanceDetail>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
|
import { Grid, Skeleton, Stack } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
IconBuildingWarehouse,
|
IconBuildingWarehouse,
|
||||||
IconDots,
|
IconDots,
|
||||||
@ -20,6 +20,7 @@ import {
|
|||||||
DuplicateItemAction,
|
DuplicateItemAction,
|
||||||
EditItemAction
|
EditItemAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
@ -44,7 +45,8 @@ export default function ManufacturerPartDetail() {
|
|||||||
const {
|
const {
|
||||||
instance: manufacturerPart,
|
instance: manufacturerPart,
|
||||||
instanceQuery,
|
instanceQuery,
|
||||||
refreshInstance
|
refreshInstance,
|
||||||
|
requestStatus
|
||||||
} = useInstance({
|
} = useInstance({
|
||||||
endpoint: ApiEndpoints.manufacturer_part_list,
|
endpoint: ApiEndpoints.manufacturer_part_list,
|
||||||
pk: id,
|
pk: id,
|
||||||
@ -244,17 +246,18 @@ export default function ManufacturerPartDetail() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{editManufacturerPart.modal}
|
{editManufacturerPart.modal}
|
||||||
<Stack gap="xs">
|
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<Stack gap="xs">
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={t`ManufacturerPart`}
|
title={t`ManufacturerPart`}
|
||||||
subtitle={`${manufacturerPart.MPN} - ${manufacturerPart.part_detail?.name}`}
|
subtitle={`${manufacturerPart.MPN} - ${manufacturerPart.part_detail?.name}`}
|
||||||
breadcrumbs={breadcrumbs}
|
breadcrumbs={breadcrumbs}
|
||||||
actions={manufacturerPartActions}
|
actions={manufacturerPartActions}
|
||||||
imageUrl={manufacturerPart?.part_detail?.thumbnail}
|
imageUrl={manufacturerPart?.part_detail?.thumbnail}
|
||||||
/>
|
/>
|
||||||
<PanelGroup pageKey="manufacturerpart" panels={panels} />
|
<PanelGroup pageKey="manufacturerpart" panels={panels} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</InstanceDetail>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
|
import { Grid, Skeleton, Stack } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
IconCurrencyDollar,
|
IconCurrencyDollar,
|
||||||
IconDots,
|
IconDots,
|
||||||
@ -21,6 +21,7 @@ import {
|
|||||||
DuplicateItemAction,
|
DuplicateItemAction,
|
||||||
EditItemAction
|
EditItemAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
@ -46,7 +47,8 @@ export default function SupplierPartDetail() {
|
|||||||
const {
|
const {
|
||||||
instance: supplierPart,
|
instance: supplierPart,
|
||||||
instanceQuery,
|
instanceQuery,
|
||||||
refreshInstance
|
refreshInstance,
|
||||||
|
requestStatus
|
||||||
} = useInstance({
|
} = useInstance({
|
||||||
endpoint: ApiEndpoints.supplier_part_list,
|
endpoint: ApiEndpoints.supplier_part_list,
|
||||||
pk: id,
|
pk: id,
|
||||||
@ -312,18 +314,19 @@ export default function SupplierPartDetail() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{editSuppliertPart.modal}
|
{editSuppliertPart.modal}
|
||||||
<Stack gap="xs">
|
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<Stack gap="xs">
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={t`Supplier Part`}
|
title={t`Supplier Part`}
|
||||||
subtitle={`${supplierPart.SKU} - ${supplierPart?.part_detail?.name}`}
|
subtitle={`${supplierPart.SKU} - ${supplierPart?.part_detail?.name}`}
|
||||||
breadcrumbs={breadcrumbs}
|
breadcrumbs={breadcrumbs}
|
||||||
badges={badges}
|
badges={badges}
|
||||||
actions={supplierPartActions}
|
actions={supplierPartActions}
|
||||||
imageUrl={supplierPart?.part_detail?.thumbnail}
|
imageUrl={supplierPart?.part_detail?.thumbnail}
|
||||||
/>
|
/>
|
||||||
<PanelGroup pageKey="supplierpart" panels={panels} />
|
<PanelGroup pageKey="supplierpart" panels={panels} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</InstanceDetail>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
DeleteItemAction,
|
DeleteItemAction,
|
||||||
EditItemAction
|
EditItemAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
import NavigationTree from '../../components/nav/NavigationTree';
|
import NavigationTree from '../../components/nav/NavigationTree';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
@ -56,7 +57,8 @@ export default function CategoryDetail({}: {}) {
|
|||||||
const {
|
const {
|
||||||
instance: category,
|
instance: category,
|
||||||
refreshInstance,
|
refreshInstance,
|
||||||
instanceQuery
|
instanceQuery,
|
||||||
|
requestStatus
|
||||||
} = useInstance({
|
} = useInstance({
|
||||||
endpoint: ApiEndpoints.category_list,
|
endpoint: ApiEndpoints.category_list,
|
||||||
hasPrimaryKey: true,
|
hasPrimaryKey: true,
|
||||||
@ -275,29 +277,34 @@ export default function CategoryDetail({}: {}) {
|
|||||||
<>
|
<>
|
||||||
{editCategory.modal}
|
{editCategory.modal}
|
||||||
{deleteCategory.modal}
|
{deleteCategory.modal}
|
||||||
<Stack gap="xs">
|
<InstanceDetail
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
status={requestStatus}
|
||||||
<NavigationTree
|
loading={id ? instanceQuery.isFetching : false}
|
||||||
modelType={ModelType.partcategory}
|
>
|
||||||
title={t`Part Categories`}
|
<Stack gap="xs">
|
||||||
endpoint={ApiEndpoints.category_tree}
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
opened={treeOpen}
|
<NavigationTree
|
||||||
onClose={() => {
|
modelType={ModelType.partcategory}
|
||||||
setTreeOpen(false);
|
title={t`Part Categories`}
|
||||||
}}
|
endpoint={ApiEndpoints.category_tree}
|
||||||
selectedId={category?.pk}
|
opened={treeOpen}
|
||||||
/>
|
onClose={() => {
|
||||||
<PageDetail
|
setTreeOpen(false);
|
||||||
title={t`Part Category`}
|
}}
|
||||||
subtitle={category?.name}
|
selectedId={category?.pk}
|
||||||
breadcrumbs={breadcrumbs}
|
/>
|
||||||
breadcrumbAction={() => {
|
<PageDetail
|
||||||
setTreeOpen(true);
|
title={t`Part Category`}
|
||||||
}}
|
subtitle={category?.name}
|
||||||
actions={categoryActions}
|
breadcrumbs={breadcrumbs}
|
||||||
/>
|
breadcrumbAction={() => {
|
||||||
<PanelGroup pageKey="partcategory" panels={categoryPanels} />
|
setTreeOpen(true);
|
||||||
</Stack>
|
}}
|
||||||
|
actions={categoryActions}
|
||||||
|
/>
|
||||||
|
<PanelGroup pageKey="partcategory" panels={categoryPanels} />
|
||||||
|
</Stack>
|
||||||
|
</InstanceDetail>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import {
|
import { Alert, Grid, Skeleton, Stack, Table } from '@mantine/core';
|
||||||
Alert,
|
|
||||||
Divider,
|
|
||||||
Grid,
|
|
||||||
LoadingOverlay,
|
|
||||||
Skeleton,
|
|
||||||
Stack,
|
|
||||||
Table
|
|
||||||
} from '@mantine/core';
|
|
||||||
import {
|
import {
|
||||||
IconBookmarks,
|
IconBookmarks,
|
||||||
IconBuilding,
|
IconBuilding,
|
||||||
@ -31,7 +23,7 @@ import {
|
|||||||
IconVersions
|
IconVersions
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||||
import { ReactNode, useMemo, useState } from 'react';
|
import { ReactNode, useEffect, useMemo, useState } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { api } from '../../App';
|
import { api } from '../../App';
|
||||||
@ -54,6 +46,7 @@ import {
|
|||||||
ViewBarcodeAction
|
ViewBarcodeAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { PlaceholderPanel } from '../../components/items/Placeholder';
|
import { PlaceholderPanel } from '../../components/items/Placeholder';
|
||||||
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
import NavigationTree from '../../components/nav/NavigationTree';
|
import NavigationTree from '../../components/nav/NavigationTree';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
@ -105,7 +98,8 @@ export default function PartDetail() {
|
|||||||
const {
|
const {
|
||||||
instance: part,
|
instance: part,
|
||||||
refreshInstance,
|
refreshInstance,
|
||||||
instanceQuery
|
instanceQuery,
|
||||||
|
requestStatus
|
||||||
} = useInstance({
|
} = useInstance({
|
||||||
endpoint: ApiEndpoints.part_list,
|
endpoint: ApiEndpoints.part_list,
|
||||||
pk: id,
|
pk: id,
|
||||||
@ -821,33 +815,34 @@ export default function PartDetail() {
|
|||||||
{duplicatePart.modal}
|
{duplicatePart.modal}
|
||||||
{editPart.modal}
|
{editPart.modal}
|
||||||
{deletePart.modal}
|
{deletePart.modal}
|
||||||
<Stack gap="xs">
|
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<Stack gap="xs">
|
||||||
<NavigationTree
|
<NavigationTree
|
||||||
title={t`Part Categories`}
|
title={t`Part Categories`}
|
||||||
modelType={ModelType.partcategory}
|
modelType={ModelType.partcategory}
|
||||||
endpoint={ApiEndpoints.category_tree}
|
endpoint={ApiEndpoints.category_tree}
|
||||||
opened={treeOpen}
|
opened={treeOpen}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setTreeOpen(false);
|
setTreeOpen(false);
|
||||||
}}
|
}}
|
||||||
selectedId={part?.category}
|
selectedId={part?.category}
|
||||||
/>
|
/>
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={t`Part` + ': ' + part.full_name}
|
title={t`Part` + ': ' + part.full_name}
|
||||||
subtitle={part.description}
|
subtitle={part.description}
|
||||||
imageUrl={part.image}
|
imageUrl={part.image}
|
||||||
badges={badges}
|
badges={badges}
|
||||||
breadcrumbs={breadcrumbs}
|
breadcrumbs={breadcrumbs}
|
||||||
breadcrumbAction={() => {
|
breadcrumbAction={() => {
|
||||||
setTreeOpen(true);
|
setTreeOpen(true);
|
||||||
}}
|
}}
|
||||||
actions={partActions}
|
actions={partActions}
|
||||||
/>
|
/>
|
||||||
<PanelGroup pageKey="part" panels={partPanels} />
|
<PanelGroup pageKey="part" panels={partPanels} />
|
||||||
{transferStockItems.modal}
|
{transferStockItems.modal}
|
||||||
{countStockItems.modal}
|
{countStockItems.modal}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</InstanceDetail>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
|
import { Grid, Skeleton, Stack } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
IconDots,
|
IconDots,
|
||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
@ -27,6 +27,7 @@ import {
|
|||||||
UnlinkBarcodeAction,
|
UnlinkBarcodeAction,
|
||||||
ViewBarcodeAction
|
ViewBarcodeAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
import { StatusRenderer } from '../../components/render/StatusRenderer';
|
import { StatusRenderer } from '../../components/render/StatusRenderer';
|
||||||
@ -40,7 +41,6 @@ import {
|
|||||||
useEditApiFormModal
|
useEditApiFormModal
|
||||||
} from '../../hooks/UseForm';
|
} from '../../hooks/UseForm';
|
||||||
import { useInstance } from '../../hooks/UseInstance';
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
||||||
import { PurchaseOrderLineItemTable } from '../../tables/purchasing/PurchaseOrderLineItemTable';
|
import { PurchaseOrderLineItemTable } from '../../tables/purchasing/PurchaseOrderLineItemTable';
|
||||||
@ -57,7 +57,8 @@ export default function PurchaseOrderDetail() {
|
|||||||
const {
|
const {
|
||||||
instance: order,
|
instance: order,
|
||||||
instanceQuery,
|
instanceQuery,
|
||||||
refreshInstance
|
refreshInstance,
|
||||||
|
requestStatus
|
||||||
} = useInstance({
|
} = useInstance({
|
||||||
endpoint: ApiEndpoints.purchase_order_list,
|
endpoint: ApiEndpoints.purchase_order_list,
|
||||||
pk: id,
|
pk: id,
|
||||||
@ -355,18 +356,19 @@ export default function PurchaseOrderDetail() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{editPurchaseOrder.modal}
|
{editPurchaseOrder.modal}
|
||||||
<Stack gap="xs">
|
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<Stack gap="xs">
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={t`Purchase Order` + `: ${order.reference}`}
|
title={t`Purchase Order` + `: ${order.reference}`}
|
||||||
subtitle={order.description}
|
subtitle={order.description}
|
||||||
imageUrl={order.supplier_detail?.image}
|
imageUrl={order.supplier_detail?.image}
|
||||||
breadcrumbs={[{ name: t`Purchasing`, url: '/purchasing/' }]}
|
breadcrumbs={[{ name: t`Purchasing`, url: '/purchasing/' }]}
|
||||||
actions={poActions}
|
actions={poActions}
|
||||||
badges={orderBadges}
|
badges={orderBadges}
|
||||||
/>
|
/>
|
||||||
<PanelGroup pageKey="purchaseorder" panels={orderPanels} />
|
<PanelGroup pageKey="purchaseorder" panels={orderPanels} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</InstanceDetail>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
|
import { Grid, Skeleton, Stack } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
IconDots,
|
IconDots,
|
||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
@ -23,6 +23,7 @@ import {
|
|||||||
EditItemAction
|
EditItemAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { PlaceholderPanel } from '../../components/items/Placeholder';
|
import { PlaceholderPanel } from '../../components/items/Placeholder';
|
||||||
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
import { StatusRenderer } from '../../components/render/StatusRenderer';
|
import { StatusRenderer } from '../../components/render/StatusRenderer';
|
||||||
@ -36,7 +37,6 @@ import {
|
|||||||
useEditApiFormModal
|
useEditApiFormModal
|
||||||
} from '../../hooks/UseForm';
|
} from '../../hooks/UseForm';
|
||||||
import { useInstance } from '../../hooks/UseInstance';
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
||||||
|
|
||||||
@ -51,7 +51,8 @@ export default function ReturnOrderDetail() {
|
|||||||
const {
|
const {
|
||||||
instance: order,
|
instance: order,
|
||||||
instanceQuery,
|
instanceQuery,
|
||||||
refreshInstance
|
refreshInstance,
|
||||||
|
requestStatus
|
||||||
} = useInstance({
|
} = useInstance({
|
||||||
endpoint: ApiEndpoints.return_order_list,
|
endpoint: ApiEndpoints.return_order_list,
|
||||||
pk: id,
|
pk: id,
|
||||||
@ -320,18 +321,19 @@ export default function ReturnOrderDetail() {
|
|||||||
<>
|
<>
|
||||||
{editReturnOrder.modal}
|
{editReturnOrder.modal}
|
||||||
{duplicateReturnOrder.modal}
|
{duplicateReturnOrder.modal}
|
||||||
<Stack gap="xs">
|
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<Stack gap="xs">
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={t`Return Order` + `: ${order.reference}`}
|
title={t`Return Order` + `: ${order.reference}`}
|
||||||
subtitle={order.description}
|
subtitle={order.description}
|
||||||
imageUrl={order.customer_detail?.image}
|
imageUrl={order.customer_detail?.image}
|
||||||
badges={orderBadges}
|
badges={orderBadges}
|
||||||
actions={orderActions}
|
actions={orderActions}
|
||||||
breadcrumbs={[{ name: t`Sales`, url: '/sales/' }]}
|
breadcrumbs={[{ name: t`Sales`, url: '/sales/' }]}
|
||||||
/>
|
/>
|
||||||
<PanelGroup pageKey="returnorder" panels={orderPanels} />
|
<PanelGroup pageKey="returnorder" panels={orderPanels} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</InstanceDetail>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
|
import { Grid, Skeleton, Stack } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
IconDots,
|
IconDots,
|
||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
@ -26,6 +26,7 @@ import {
|
|||||||
EditItemAction
|
EditItemAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { PlaceholderPanel } from '../../components/items/Placeholder';
|
import { PlaceholderPanel } from '../../components/items/Placeholder';
|
||||||
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
import { StatusRenderer } from '../../components/render/StatusRenderer';
|
import { StatusRenderer } from '../../components/render/StatusRenderer';
|
||||||
@ -39,7 +40,6 @@ import {
|
|||||||
useEditApiFormModal
|
useEditApiFormModal
|
||||||
} from '../../hooks/UseForm';
|
} from '../../hooks/UseForm';
|
||||||
import { useInstance } from '../../hooks/UseInstance';
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
|
import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
|
||||||
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
||||||
@ -55,7 +55,8 @@ export default function SalesOrderDetail() {
|
|||||||
const {
|
const {
|
||||||
instance: order,
|
instance: order,
|
||||||
instanceQuery,
|
instanceQuery,
|
||||||
refreshInstance
|
refreshInstance,
|
||||||
|
requestStatus
|
||||||
} = useInstance({
|
} = useInstance({
|
||||||
endpoint: ApiEndpoints.sales_order_list,
|
endpoint: ApiEndpoints.sales_order_list,
|
||||||
pk: id,
|
pk: id,
|
||||||
@ -344,18 +345,19 @@ export default function SalesOrderDetail() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{editSalesOrder.modal}
|
{editSalesOrder.modal}
|
||||||
<Stack gap="xs">
|
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<Stack gap="xs">
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={t`Sales Order` + `: ${order.reference}`}
|
title={t`Sales Order` + `: ${order.reference}`}
|
||||||
subtitle={order.description}
|
subtitle={order.description}
|
||||||
imageUrl={order.customer_detail?.image}
|
imageUrl={order.customer_detail?.image}
|
||||||
badges={orderBadges}
|
badges={orderBadges}
|
||||||
actions={soActions}
|
actions={soActions}
|
||||||
breadcrumbs={[{ name: t`Sales`, url: '/sales/' }]}
|
breadcrumbs={[{ name: t`Sales`, url: '/sales/' }]}
|
||||||
/>
|
/>
|
||||||
<PanelGroup pageKey="salesorder" panels={orderPanels} />
|
<PanelGroup pageKey="salesorder" panels={orderPanels} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</InstanceDetail>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { LoadingOverlay, Skeleton, Stack, Text } from '@mantine/core';
|
import { Skeleton, Stack, Text } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
IconDots,
|
IconDots,
|
||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
@ -23,6 +23,7 @@ import {
|
|||||||
UnlinkBarcodeAction,
|
UnlinkBarcodeAction,
|
||||||
ViewBarcodeAction
|
ViewBarcodeAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
import NavigationTree from '../../components/nav/NavigationTree';
|
import NavigationTree from '../../components/nav/NavigationTree';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
@ -63,7 +64,8 @@ export default function Stock() {
|
|||||||
const {
|
const {
|
||||||
instance: location,
|
instance: location,
|
||||||
refreshInstance,
|
refreshInstance,
|
||||||
instanceQuery
|
instanceQuery,
|
||||||
|
requestStatus
|
||||||
} = useInstance({
|
} = useInstance({
|
||||||
endpoint: ApiEndpoints.stock_location_list,
|
endpoint: ApiEndpoints.stock_location_list,
|
||||||
hasPrimaryKey: true,
|
hasPrimaryKey: true,
|
||||||
@ -355,29 +357,33 @@ export default function Stock() {
|
|||||||
<>
|
<>
|
||||||
{editLocation.modal}
|
{editLocation.modal}
|
||||||
{deleteLocation.modal}
|
{deleteLocation.modal}
|
||||||
<Stack>
|
<InstanceDetail
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
status={requestStatus}
|
||||||
<NavigationTree
|
loading={id ? instanceQuery.isFetching : false}
|
||||||
title={t`Stock Locations`}
|
>
|
||||||
modelType={ModelType.stocklocation}
|
<Stack>
|
||||||
endpoint={ApiEndpoints.stock_location_tree}
|
<NavigationTree
|
||||||
opened={treeOpen}
|
title={t`Stock Locations`}
|
||||||
onClose={() => setTreeOpen(false)}
|
modelType={ModelType.stocklocation}
|
||||||
selectedId={location?.pk}
|
endpoint={ApiEndpoints.stock_location_tree}
|
||||||
/>
|
opened={treeOpen}
|
||||||
<PageDetail
|
onClose={() => setTreeOpen(false)}
|
||||||
title={t`Stock Items`}
|
selectedId={location?.pk}
|
||||||
subtitle={location?.name}
|
/>
|
||||||
actions={locationActions}
|
<PageDetail
|
||||||
breadcrumbs={breadcrumbs}
|
title={t`Stock Items`}
|
||||||
breadcrumbAction={() => {
|
subtitle={location?.name}
|
||||||
setTreeOpen(true);
|
actions={locationActions}
|
||||||
}}
|
breadcrumbs={breadcrumbs}
|
||||||
/>
|
breadcrumbAction={() => {
|
||||||
<PanelGroup pageKey="stocklocation" panels={locationPanels} />
|
setTreeOpen(true);
|
||||||
{transferStockItems.modal}
|
}}
|
||||||
{countStockItems.modal}
|
/>
|
||||||
</Stack>
|
<PanelGroup pageKey="stocklocation" panels={locationPanels} />
|
||||||
|
{transferStockItems.modal}
|
||||||
|
{countStockItems.modal}
|
||||||
|
</Stack>
|
||||||
|
</InstanceDetail>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
|
import { Grid, Skeleton, Stack } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
IconBookmark,
|
IconBookmark,
|
||||||
IconBoxPadding,
|
IconBoxPadding,
|
||||||
@ -33,6 +33,7 @@ import {
|
|||||||
ViewBarcodeAction
|
ViewBarcodeAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { PlaceholderPanel } from '../../components/items/Placeholder';
|
import { PlaceholderPanel } from '../../components/items/Placeholder';
|
||||||
|
import InstanceDetail from '../../components/nav/InstanceDetail';
|
||||||
import NavigationTree from '../../components/nav/NavigationTree';
|
import NavigationTree from '../../components/nav/NavigationTree';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
@ -76,7 +77,8 @@ export default function StockDetail() {
|
|||||||
const {
|
const {
|
||||||
instance: stockitem,
|
instance: stockitem,
|
||||||
refreshInstance,
|
refreshInstance,
|
||||||
instanceQuery
|
instanceQuery,
|
||||||
|
requestStatus
|
||||||
} = useInstance({
|
} = useInstance({
|
||||||
endpoint: ApiEndpoints.stock_item_list,
|
endpoint: ApiEndpoints.stock_item_list,
|
||||||
pk: id,
|
pk: id,
|
||||||
@ -548,35 +550,36 @@ export default function StockDetail() {
|
|||||||
}, [stockitem, instanceQuery]);
|
}, [stockitem, instanceQuery]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<Stack>
|
||||||
<NavigationTree
|
<NavigationTree
|
||||||
title={t`Stock Locations`}
|
title={t`Stock Locations`}
|
||||||
modelType={ModelType.stocklocation}
|
modelType={ModelType.stocklocation}
|
||||||
endpoint={ApiEndpoints.stock_location_tree}
|
endpoint={ApiEndpoints.stock_location_tree}
|
||||||
opened={treeOpen}
|
opened={treeOpen}
|
||||||
onClose={() => setTreeOpen(false)}
|
onClose={() => setTreeOpen(false)}
|
||||||
selectedId={stockitem?.location}
|
selectedId={stockitem?.location}
|
||||||
/>
|
/>
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={t`Stock Item`}
|
title={t`Stock Item`}
|
||||||
subtitle={stockitem.part_detail?.full_name}
|
subtitle={stockitem.part_detail?.full_name}
|
||||||
imageUrl={stockitem.part_detail?.thumbnail}
|
imageUrl={stockitem.part_detail?.thumbnail}
|
||||||
badges={stockBadges}
|
badges={stockBadges}
|
||||||
breadcrumbs={breadcrumbs}
|
breadcrumbs={breadcrumbs}
|
||||||
breadcrumbAction={() => {
|
breadcrumbAction={() => {
|
||||||
setTreeOpen(true);
|
setTreeOpen(true);
|
||||||
}}
|
}}
|
||||||
actions={stockActions}
|
actions={stockActions}
|
||||||
/>
|
/>
|
||||||
<PanelGroup pageKey="stockitem" panels={stockPanels} />
|
<PanelGroup pageKey="stockitem" panels={stockPanels} />
|
||||||
{editStockItem.modal}
|
{editStockItem.modal}
|
||||||
{duplicateStockItem.modal}
|
{duplicateStockItem.modal}
|
||||||
{deleteStockItem.modal}
|
{deleteStockItem.modal}
|
||||||
{countStockItem.modal}
|
{countStockItem.modal}
|
||||||
{addStockItem.modal}
|
{addStockItem.modal}
|
||||||
{removeStockItem.modal}
|
{removeStockItem.modal}
|
||||||
{transferStockItem.modal}
|
{transferStockItem.modal}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</InstanceDetail>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,9 @@ export const AdminCenter = Loadable(
|
|||||||
lazy(() => import('./pages/Index/Settings/AdminCenter/Index'))
|
lazy(() => import('./pages/Index/Settings/AdminCenter/Index'))
|
||||||
);
|
);
|
||||||
|
|
||||||
export const NotFound = Loadable(lazy(() => import('./pages/NotFound')));
|
export const NotFound = Loadable(
|
||||||
|
lazy(() => import('./components/errors/NotFound'))
|
||||||
|
);
|
||||||
export const Login = Loadable(lazy(() => import('./pages/Auth/Login')));
|
export const Login = Loadable(lazy(() => import('./pages/Auth/Login')));
|
||||||
export const Logout = Loadable(lazy(() => import('./pages/Auth/Logout')));
|
export const Logout = Loadable(lazy(() => import('./pages/Auth/Logout')));
|
||||||
export const Logged_In = Loadable(lazy(() => import('./pages/Auth/Logged-In')));
|
export const Logged_In = Loadable(lazy(() => import('./pages/Auth/Logged-In')));
|
||||||
|
@ -64,7 +64,8 @@ export const test = baseTest.extend({
|
|||||||
url != 'http://localhost:8000/api/barcode/' &&
|
url != 'http://localhost:8000/api/barcode/' &&
|
||||||
url != 'http://localhost:8000/api/news/?search=&offset=0&limit=25' &&
|
url != 'http://localhost:8000/api/news/?search=&offset=0&limit=25' &&
|
||||||
url != 'https://docs.inventree.org/en/versions.json' &&
|
url != 'https://docs.inventree.org/en/versions.json' &&
|
||||||
!url.startsWith('chrome://')
|
!url.startsWith('chrome://') &&
|
||||||
|
url.indexOf('99999') < 0
|
||||||
)
|
)
|
||||||
messages.push(msg);
|
messages.push(msg);
|
||||||
});
|
});
|
||||||
|
@ -31,10 +31,14 @@ export const doQuickLogin = async (
|
|||||||
password = password ?? user.password;
|
password = password ?? user.password;
|
||||||
url = url ?? baseUrl;
|
url = url ?? baseUrl;
|
||||||
|
|
||||||
// await page.goto(logoutUrl);
|
|
||||||
await page.goto(`${url}/login/?login=${username}&password=${password}`);
|
await page.goto(`${url}/login/?login=${username}&password=${password}`);
|
||||||
await page.waitForURL('**/platform/home');
|
await page.waitForURL('**/platform/home');
|
||||||
await page
|
await page
|
||||||
.getByRole('heading', { name: 'Welcome to your Dashboard,' })
|
.getByRole('heading', { name: 'Welcome to your Dashboard,' })
|
||||||
.waitFor();
|
.waitFor();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const doLogout = async (page) => {
|
||||||
|
await page.goto(`${baseUrl}/logout/`);
|
||||||
|
await page.waitForURL('**/platform/login');
|
||||||
|
};
|
||||||
|
@ -224,3 +224,13 @@ test('PUI - Pages - Part - Notes', async ({ page }) => {
|
|||||||
// Check that the original notes are still present
|
// Check that the original notes are still present
|
||||||
await page.getByText('This is some data').waitFor();
|
await page.getByText('This is some data').waitFor();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('PUI - Pages - Part - 404', async ({ page }) => {
|
||||||
|
await doQuickLogin(page);
|
||||||
|
|
||||||
|
await page.goto(`${baseUrl}/part/99999/`);
|
||||||
|
await page.getByText('Page Not Found', { exact: true }).waitFor();
|
||||||
|
|
||||||
|
// Clear out any console error messages
|
||||||
|
await page.evaluate(() => console.clear());
|
||||||
|
});
|
||||||
|
@ -54,4 +54,5 @@ test('PUI - Quick Login Test', async ({ page }) => {
|
|||||||
// Logout (via URL)
|
// Logout (via URL)
|
||||||
await page.goto(`${baseUrl}/logout/`);
|
await page.goto(`${baseUrl}/logout/`);
|
||||||
await page.waitForURL('**/platform/login');
|
await page.waitForURL('**/platform/login');
|
||||||
|
await page.getByLabel('username');
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { test } from './baseFixtures.js';
|
import { test } from './baseFixtures.js';
|
||||||
import { baseUrl } from './defaults.js';
|
import { baseUrl } from './defaults.js';
|
||||||
import { doQuickLogin } from './login.js';
|
import { doLogout, doQuickLogin } from './login.js';
|
||||||
|
|
||||||
test('PUI - Parts', async ({ page }) => {
|
test('PUI - Parts', async ({ page }) => {
|
||||||
await doQuickLogin(page);
|
await doQuickLogin(page);
|
||||||
@ -141,57 +141,6 @@ test('PUI - Scanning', async ({ page }) => {
|
|||||||
await page.getByRole('option', { name: 'Manual input' }).click();
|
await page.getByRole('option', { name: 'Manual input' }).click();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PUI - Admin', async ({ page }) => {
|
|
||||||
// Note here we login with admin access
|
|
||||||
await doQuickLogin(page, 'admin', 'inventree');
|
|
||||||
|
|
||||||
// User settings
|
|
||||||
await page.getByRole('button', { name: 'admin' }).click();
|
|
||||||
await page.getByRole('menuitem', { name: 'Account settings' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Security' }).click();
|
|
||||||
//await page.getByRole('tab', { name: 'Dashboard' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Display Options' }).click();
|
|
||||||
await page.getByText('Date Format').waitFor();
|
|
||||||
await page.getByRole('tab', { name: 'Search' }).click();
|
|
||||||
await page.getByText('Regex Search').waitFor();
|
|
||||||
await page.getByRole('tab', { name: 'Notifications' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Reporting' }).click();
|
|
||||||
await page.getByText('Inline report display').waitFor();
|
|
||||||
|
|
||||||
// System Settings
|
|
||||||
await page.getByRole('link', { name: 'Switch to System Setting' }).click();
|
|
||||||
await page.getByText('Base URL', { exact: true }).waitFor();
|
|
||||||
await page.getByRole('tab', { name: 'Login' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Barcodes' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Notifications' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Pricing' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Labels' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Reporting' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Part Categories' }).click();
|
|
||||||
//wait page.locator('#mantine-9hqbwrml8-tab-parts').click();
|
|
||||||
//await page.locator('#mantine-9hqbwrml8-tab-stock').click();
|
|
||||||
await page.getByRole('tab', { name: 'Stocktake' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Build Orders' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Purchase Orders' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Sales Orders' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Return Orders' }).click();
|
|
||||||
|
|
||||||
// Admin Center
|
|
||||||
await page.getByRole('button', { name: 'admin' }).click();
|
|
||||||
await page.getByRole('menuitem', { name: 'Admin Center' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Background Tasks' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Error Reports' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Currencies' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Project Codes' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Custom Units' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Part Parameters' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Category Parameters' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Label Templates' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Report Templates' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Plugins' }).click();
|
|
||||||
await page.getByRole('tab', { name: 'Machines' }).click();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PUI - Language / Color', async ({ page }) => {
|
test('PUI - Language / Color', async ({ page }) => {
|
||||||
await doQuickLogin(page);
|
await doQuickLogin(page);
|
||||||
|
|
||||||
|
83
src/frontend/tests/pui_settings.spec.ts
Normal file
83
src/frontend/tests/pui_settings.spec.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { test } from './baseFixtures.js';
|
||||||
|
import { baseUrl } from './defaults.js';
|
||||||
|
import { doLogout, doQuickLogin } from './login.js';
|
||||||
|
|
||||||
|
test('PUI - Admin', async ({ page }) => {
|
||||||
|
// Note here we login with admin access
|
||||||
|
await doQuickLogin(page, 'admin', 'inventree');
|
||||||
|
|
||||||
|
// User settings
|
||||||
|
await page.getByRole('button', { name: 'admin' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Account settings' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Security' }).click();
|
||||||
|
|
||||||
|
await page.getByRole('tab', { name: 'Display Options' }).click();
|
||||||
|
await page.getByText('Date Format').waitFor();
|
||||||
|
await page.getByRole('tab', { name: 'Search' }).click();
|
||||||
|
await page.getByText('Regex Search').waitFor();
|
||||||
|
await page.getByRole('tab', { name: 'Notifications' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Reporting' }).click();
|
||||||
|
await page.getByText('Inline report display').waitFor();
|
||||||
|
|
||||||
|
// System Settings
|
||||||
|
await page.getByRole('link', { name: 'Switch to System Setting' }).click();
|
||||||
|
await page.getByText('Base URL', { exact: true }).waitFor();
|
||||||
|
await page.getByRole('tab', { name: 'Login' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Barcodes' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Notifications' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Pricing' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Labels' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Reporting' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Part Categories' }).click();
|
||||||
|
|
||||||
|
await page.getByRole('tab', { name: 'Stocktake' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Build Orders' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Purchase Orders' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Sales Orders' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Return Orders' }).click();
|
||||||
|
|
||||||
|
// Admin Center
|
||||||
|
await page.getByRole('button', { name: 'admin' }).click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Admin Center' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Background Tasks' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Error Reports' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Currencies' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Project Codes' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Custom Units' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Part Parameters' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Category Parameters' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Label Templates' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Report Templates' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Plugins' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Machines' }).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('PUI - Admin - Unauthorized', async ({ page }) => {
|
||||||
|
// Try to access "admin" page with a non-staff user
|
||||||
|
await doQuickLogin(page, 'allaccess', 'nolimits');
|
||||||
|
|
||||||
|
await page.goto(`${baseUrl}/settings/admin/`);
|
||||||
|
await page.waitForURL('**/settings/admin/**');
|
||||||
|
|
||||||
|
// Should get a permission denied message
|
||||||
|
await page.getByText('Permission Denied').waitFor();
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Return to the index page' })
|
||||||
|
.waitFor();
|
||||||
|
|
||||||
|
// Try to access user settings page (should be accessible)
|
||||||
|
await page.goto(`${baseUrl}/settings/user/`);
|
||||||
|
await page.waitForURL('**/settings/user/**');
|
||||||
|
|
||||||
|
await page.getByRole('tab', { name: 'Display Options' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Account' }).click();
|
||||||
|
|
||||||
|
// Try to access global settings page
|
||||||
|
await page.goto(`${baseUrl}/settings/system/`);
|
||||||
|
await page.waitForURL('**/settings/system/**');
|
||||||
|
|
||||||
|
await page.getByText('Permission Denied').waitFor();
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Return to the index page' })
|
||||||
|
.waitFor();
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user