mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +00:00
[PUI] notifications fix (#8392)
* Include actual model name in notification repot * Include model_id in API * Refactoring for NotificationDrawer - Add error boundary - Separate rendering func for individual entry - Allow router navigation - Better link introspection - Add tooltip for notification description * Add URL support for errors * Allow navigation to an individual error in the admin center * Updates * Add rendering method for 'error' model type
This commit is contained in:
parent
40f456fbc9
commit
8a7a2da07b
@ -1,13 +1,16 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 273
|
INVENTREE_API_VERSION = 274
|
||||||
|
|
||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
INVENTREE_API_TEXT = """
|
||||||
|
|
||||||
|
v274 - 2024-10-29 : https://github.com/inventree/InvenTree/pull/8392
|
||||||
|
- Add more detailed information to NotificationEntry API serializer
|
||||||
|
|
||||||
v273 - 2024-10-28 : https://github.com/inventree/InvenTree/pull/8376
|
v273 - 2024-10-28 : https://github.com/inventree/InvenTree/pull/8376
|
||||||
- Fixes for the BuildLine API endpoint
|
- Fixes for the BuildLine API endpoint
|
||||||
|
|
||||||
|
@ -1022,7 +1022,14 @@ def get_objectreference(
|
|||||||
ret = {}
|
ret = {}
|
||||||
if url_fnc:
|
if url_fnc:
|
||||||
ret['link'] = url_fnc()
|
ret['link'] = url_fnc()
|
||||||
return {'name': str(item), 'model': str(model_cls._meta.verbose_name), **ret}
|
|
||||||
|
return {
|
||||||
|
'name': str(item),
|
||||||
|
'model_name': str(model_cls._meta.verbose_name),
|
||||||
|
'model_type': str(model_cls._meta.model_name),
|
||||||
|
'model_id': getattr(item, 'pk', None),
|
||||||
|
**ret,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Inheritors_T = TypeVar('Inheritors_T')
|
Inheritors_T = TypeVar('Inheritors_T')
|
||||||
|
@ -2,12 +2,13 @@ import { t } from '@lingui/macro';
|
|||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
Alert,
|
Alert,
|
||||||
|
Anchor,
|
||||||
Center,
|
Center,
|
||||||
Divider,
|
Divider,
|
||||||
Drawer,
|
Drawer,
|
||||||
Group,
|
Group,
|
||||||
Loader,
|
Loader,
|
||||||
Space,
|
Paper,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
Tooltip
|
Tooltip
|
||||||
@ -15,14 +16,84 @@ import {
|
|||||||
import { IconArrowRight, IconBellCheck } from '@tabler/icons-react';
|
import { IconArrowRight, IconBellCheck } from '@tabler/icons-react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { api } from '../../App';
|
import { api } from '../../App';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { navigateToLink } from '../../functions/navigation';
|
import { navigateToLink } from '../../functions/navigation';
|
||||||
|
import { getDetailUrl } from '../../functions/urls';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
|
import { Boundary } from '../Boundary';
|
||||||
import { StylishText } from '../items/StylishText';
|
import { StylishText } from '../items/StylishText';
|
||||||
|
import { ModelInformationDict } from '../render/ModelType';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a single notification entry in the drawer
|
||||||
|
*/
|
||||||
|
function NotificationEntry({
|
||||||
|
notification,
|
||||||
|
onRead
|
||||||
|
}: {
|
||||||
|
notification: any;
|
||||||
|
onRead: () => void;
|
||||||
|
}) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
let link = notification.target?.link;
|
||||||
|
|
||||||
|
let model_type = notification.target?.model_type;
|
||||||
|
let model_id = notification.target?.model_id;
|
||||||
|
|
||||||
|
// If a valid model type is provided, that overrides the specified link
|
||||||
|
if (model_type as ModelType) {
|
||||||
|
let model_info = ModelInformationDict[model_type as ModelType];
|
||||||
|
if (model_info?.url_detail && model_id) {
|
||||||
|
link = getDetailUrl(model_type as ModelType, model_id);
|
||||||
|
} else if (model_info?.url_overview) {
|
||||||
|
link = model_info.url_overview;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper p="xs" shadow="xs">
|
||||||
|
<Group justify="space-between" wrap="nowrap">
|
||||||
|
<Tooltip
|
||||||
|
label={notification.message}
|
||||||
|
position="bottom-end"
|
||||||
|
hidden={!notification.message}
|
||||||
|
>
|
||||||
|
<Stack gap={2}>
|
||||||
|
<Anchor
|
||||||
|
href={link}
|
||||||
|
underline="hover"
|
||||||
|
target="_blank"
|
||||||
|
onClick={(event: any) => {
|
||||||
|
if (link) {
|
||||||
|
// Mark the notification as read
|
||||||
|
onRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (link.startsWith('/')) {
|
||||||
|
navigateToLink(link, navigate, event);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text size="sm">{notification.name}</Text>
|
||||||
|
</Anchor>
|
||||||
|
<Text size="xs">{notification.age_human}</Text>
|
||||||
|
</Stack>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label={t`Mark as read`} position="bottom-end">
|
||||||
|
<ActionIcon variant="transparent" onClick={onRead}>
|
||||||
|
<IconBellCheck />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a notification drawer.
|
* Construct a notification drawer.
|
||||||
@ -46,7 +117,8 @@ export function NotificationDrawer({
|
|||||||
.get(apiUrl(ApiEndpoints.notifications_list), {
|
.get(apiUrl(ApiEndpoints.notifications_list), {
|
||||||
params: {
|
params: {
|
||||||
read: false,
|
read: false,
|
||||||
limit: 10
|
limit: 10,
|
||||||
|
ordering: '-creation'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((response) => response.data)
|
.then((response) => response.data)
|
||||||
@ -73,6 +145,19 @@ export function NotificationDrawer({
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const markAsRead = useCallback((notification: any) => {
|
||||||
|
api
|
||||||
|
.patch(apiUrl(ApiEndpoints.notifications_list, notification.pk), {
|
||||||
|
read: true
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
notificationQuery.refetch();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
notificationQuery.refetch();
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
opened={opened}
|
opened={opened}
|
||||||
@ -117,6 +202,7 @@ export function NotificationDrawer({
|
|||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<Boundary label="NotificationDrawer">
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Divider />
|
<Divider />
|
||||||
{!hasNotifications && (
|
{!hasNotifications && (
|
||||||
@ -126,51 +212,11 @@ export function NotificationDrawer({
|
|||||||
)}
|
)}
|
||||||
{hasNotifications &&
|
{hasNotifications &&
|
||||||
notificationQuery.data?.results?.map((notification: any) => (
|
notificationQuery.data?.results?.map((notification: any) => (
|
||||||
<Group justify="space-between" key={notification.pk}>
|
<NotificationEntry
|
||||||
<Stack gap="3">
|
key={`notification-${notification.pk}`}
|
||||||
{notification?.target?.link ? (
|
notification={notification}
|
||||||
<Text
|
onRead={() => markAsRead(notification)}
|
||||||
size="sm"
|
/>
|
||||||
component={Link}
|
|
||||||
to={notification?.target?.link}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{notification.target?.name ??
|
|
||||||
notification.name ??
|
|
||||||
t`Notification`}
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<Text size="sm">
|
|
||||||
{notification.target?.name ??
|
|
||||||
notification.name ??
|
|
||||||
t`Notification`}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
<Text size="xs">{notification.age_human ?? ''}</Text>
|
|
||||||
</Stack>
|
|
||||||
<Space />
|
|
||||||
<ActionIcon
|
|
||||||
color="gray"
|
|
||||||
variant="hover"
|
|
||||||
onClick={() => {
|
|
||||||
let url = apiUrl(
|
|
||||||
ApiEndpoints.notifications_list,
|
|
||||||
notification.pk
|
|
||||||
);
|
|
||||||
api
|
|
||||||
.patch(url, {
|
|
||||||
read: true
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
notificationQuery.refetch();
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Tooltip label={t`Mark as read`}>
|
|
||||||
<IconBellCheck />
|
|
||||||
</Tooltip>
|
|
||||||
</ActionIcon>
|
|
||||||
</Group>
|
|
||||||
))}
|
))}
|
||||||
{notificationQuery.isFetching && (
|
{notificationQuery.isFetching && (
|
||||||
<Center>
|
<Center>
|
||||||
@ -178,6 +224,7 @@ export function NotificationDrawer({
|
|||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</Boundary>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { t } from '@lingui/macro';
|
|||||||
import { Accordion, Alert, Card, Stack, Text } from '@mantine/core';
|
import { Accordion, Alert, Card, Stack, Text } from '@mantine/core';
|
||||||
import { IconExclamationCircle } from '@tabler/icons-react';
|
import { IconExclamationCircle } from '@tabler/icons-react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
import { useInstance } from '../../hooks/UseInstance';
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
@ -18,12 +19,18 @@ export default function PluginDrawer({
|
|||||||
pluginKey,
|
pluginKey,
|
||||||
pluginInstance
|
pluginInstance
|
||||||
}: {
|
}: {
|
||||||
pluginKey: string;
|
pluginKey?: string;
|
||||||
pluginInstance: PluginInterface;
|
pluginInstance: PluginInterface;
|
||||||
}) {
|
}) {
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const pluginPrimaryKey: string = useMemo(() => {
|
||||||
|
return pluginKey || id || '';
|
||||||
|
}, [pluginKey, id]);
|
||||||
|
|
||||||
const { instance: pluginAdmin } = useInstance({
|
const { instance: pluginAdmin } = useInstance({
|
||||||
endpoint: ApiEndpoints.plugin_admin,
|
endpoint: ApiEndpoints.plugin_admin,
|
||||||
pathParams: { key: pluginKey },
|
pathParams: { key: pluginPrimaryKey },
|
||||||
defaultValue: {},
|
defaultValue: {},
|
||||||
hasPrimaryKey: false,
|
hasPrimaryKey: false,
|
||||||
refetchOnMount: true
|
refetchOnMount: true
|
||||||
@ -128,7 +135,7 @@ export default function PluginDrawer({
|
|||||||
</Accordion.Control>
|
</Accordion.Control>
|
||||||
<Accordion.Panel>
|
<Accordion.Panel>
|
||||||
<Card withBorder>
|
<Card withBorder>
|
||||||
<PluginSettingList pluginKey={pluginKey} />
|
<PluginSettingList pluginKey={pluginPrimaryKey} />
|
||||||
</Card>
|
</Card>
|
||||||
</Accordion.Panel>
|
</Accordion.Panel>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
|
@ -21,6 +21,12 @@ export function RenderContentType({
|
|||||||
return instance && <RenderInlineModel primary={instance.app_labeled_name} />;
|
return instance && <RenderInlineModel primary={instance.app_labeled_name} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function RenderError({
|
||||||
|
instance
|
||||||
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
|
return instance && <RenderInlineModel primary={instance.name} />;
|
||||||
|
}
|
||||||
|
|
||||||
export function RenderImportSession({
|
export function RenderImportSession({
|
||||||
instance
|
instance
|
||||||
}: {
|
}: {
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
} from './Company';
|
} from './Company';
|
||||||
import {
|
import {
|
||||||
RenderContentType,
|
RenderContentType,
|
||||||
|
RenderError,
|
||||||
RenderImportSession,
|
RenderImportSession,
|
||||||
RenderProjectCode
|
RenderProjectCode
|
||||||
} from './Generic';
|
} from './Generic';
|
||||||
@ -92,7 +93,8 @@ const RendererLookup: EnumDictionary<
|
|||||||
[ModelType.reporttemplate]: RenderReportTemplate,
|
[ModelType.reporttemplate]: RenderReportTemplate,
|
||||||
[ModelType.labeltemplate]: RenderLabelTemplate,
|
[ModelType.labeltemplate]: RenderLabelTemplate,
|
||||||
[ModelType.pluginconfig]: RenderPlugin,
|
[ModelType.pluginconfig]: RenderPlugin,
|
||||||
[ModelType.contenttype]: RenderContentType
|
[ModelType.contenttype]: RenderContentType,
|
||||||
|
[ModelType.error]: RenderError
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RenderInstanceProps = {
|
export type RenderInstanceProps = {
|
||||||
|
@ -252,6 +252,13 @@ export const ModelInformationDict: ModelDict = {
|
|||||||
label: () => t`Content Type`,
|
label: () => t`Content Type`,
|
||||||
label_multiple: () => t`Content Types`,
|
label_multiple: () => t`Content Types`,
|
||||||
api_endpoint: ApiEndpoints.content_type_list
|
api_endpoint: ApiEndpoints.content_type_list
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
label: () => t`Error`,
|
||||||
|
label_multiple: () => t`Errors`,
|
||||||
|
api_endpoint: ApiEndpoints.error_report_list,
|
||||||
|
url_overview: '/settings/admin/errors',
|
||||||
|
url_detail: '/settings/admin/errors/:pk/'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -32,5 +32,6 @@ export enum ModelType {
|
|||||||
reporttemplate = 'reporttemplate',
|
reporttemplate = 'reporttemplate',
|
||||||
labeltemplate = 'labeltemplate',
|
labeltemplate = 'labeltemplate',
|
||||||
pluginconfig = 'pluginconfig',
|
pluginconfig = 'pluginconfig',
|
||||||
contenttype = 'contenttype'
|
contenttype = 'contenttype',
|
||||||
|
error = 'error'
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
IconBellCheck,
|
IconBellCheck,
|
||||||
IconBellExclamation,
|
IconBellExclamation,
|
||||||
IconCircleCheck,
|
IconCircleCheck,
|
||||||
IconCircleX,
|
IconMail,
|
||||||
IconMailOpened,
|
IconMailOpened,
|
||||||
IconTrash
|
IconTrash
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
@ -106,7 +106,7 @@ export default function NotificationsPage() {
|
|||||||
actions={(record) => [
|
actions={(record) => [
|
||||||
{
|
{
|
||||||
title: t`Mark as unread`,
|
title: t`Mark as unread`,
|
||||||
icon: <IconCircleX />,
|
icon: <IconMail />,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
let url = apiUrl(ApiEndpoints.notifications_list, record.pk);
|
let url = apiUrl(ApiEndpoints.notifications_list, record.pk);
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Drawer, Group, Stack, Table, Text } from '@mantine/core';
|
import { Group, Loader, Stack, Table, Text } from '@mantine/core';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { CopyButton } from '../../components/buttons/CopyButton';
|
import { CopyButton } from '../../components/buttons/CopyButton';
|
||||||
import { StylishText } from '../../components/items/StylishText';
|
import { DetailDrawer } from '../../components/nav/DetailDrawer';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
import { useDeleteApiFormModal } from '../../hooks/UseForm';
|
import { useDeleteApiFormModal } from '../../hooks/UseForm';
|
||||||
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
@ -14,7 +15,33 @@ import { TableColumn } from '../Column';
|
|||||||
import { InvenTreeTable } from '../InvenTreeTable';
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
import { RowAction, RowDeleteAction } from '../RowActions';
|
import { RowAction, RowDeleteAction } from '../RowActions';
|
||||||
|
|
||||||
function ErrorDetail({ error }: { error: any }) {
|
function ErrorDetail({ errorId }: { errorId?: number }) {
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const errorPrimaryKey = useMemo(() => {
|
||||||
|
return errorId ?? id;
|
||||||
|
}, [errorId, id]);
|
||||||
|
|
||||||
|
const errorInstance = useInstance({
|
||||||
|
endpoint: ApiEndpoints.error_report_list,
|
||||||
|
pk: errorPrimaryKey,
|
||||||
|
defaultValue: {},
|
||||||
|
hasPrimaryKey: true,
|
||||||
|
refetchOnMount: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const error = useMemo(
|
||||||
|
() => errorInstance.instance || {},
|
||||||
|
[errorInstance.instance]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
errorInstance.instanceQuery.isFetching ||
|
||||||
|
errorInstance.instanceQuery.isLoading
|
||||||
|
) {
|
||||||
|
return <Loader />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Table>
|
<Table>
|
||||||
@ -47,7 +74,7 @@ function ErrorDetail({ error }: { error: any }) {
|
|||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Td colSpan={2}>
|
<Table.Td colSpan={2}>
|
||||||
<Stack gap={3}>
|
<Stack gap={3}>
|
||||||
{error.data.split('\n').map((line: string, index: number) => (
|
{error.data?.split('\n').map((line: string, index: number) => (
|
||||||
<Text size="xs" key={`error-line-${index}`}>
|
<Text size="xs" key={`error-line-${index}`}>
|
||||||
{line}
|
{line}
|
||||||
</Text>
|
</Text>
|
||||||
@ -67,8 +94,7 @@ function ErrorDetail({ error }: { error: any }) {
|
|||||||
export default function ErrorReportTable() {
|
export default function ErrorReportTable() {
|
||||||
const table = useTable('error-report');
|
const table = useTable('error-report');
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
const navigate = useNavigate();
|
||||||
const [opened, { open, close }] = useDisclosure(false);
|
|
||||||
|
|
||||||
const columns: TableColumn[] = useMemo(() => {
|
const columns: TableColumn[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
@ -116,15 +142,15 @@ export default function ErrorReportTable() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{deleteErrorModal.modal}
|
{deleteErrorModal.modal}
|
||||||
<Drawer
|
<DetailDrawer
|
||||||
opened={opened}
|
title={t`Error Details`}
|
||||||
size="xl"
|
size={'xl'}
|
||||||
position="right"
|
renderContent={(pk) => {
|
||||||
title={<StylishText>{t`Error Details`}</StylishText>}
|
if (!pk) return;
|
||||||
onClose={close}
|
|
||||||
>
|
return <ErrorDetail errorId={selectedError.pk} />;
|
||||||
<ErrorDetail error={selectedError} />
|
}}
|
||||||
</Drawer>
|
/>
|
||||||
<InvenTreeTable
|
<InvenTreeTable
|
||||||
url={apiUrl(ApiEndpoints.error_report_list)}
|
url={apiUrl(ApiEndpoints.error_report_list)}
|
||||||
tableState={table}
|
tableState={table}
|
||||||
@ -135,7 +161,7 @@ export default function ErrorReportTable() {
|
|||||||
rowActions: rowActions,
|
rowActions: rowActions,
|
||||||
onRowClick: (row) => {
|
onRowClick: (row) => {
|
||||||
setSelectedError(row);
|
setSelectedError(row);
|
||||||
open();
|
navigate(`${row.pk}/`);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user