InvenTree/src/frontend/src/components/tables/settings/UserTable.tsx

271 lines
6.9 KiB
TypeScript

import { Trans, t } from '@lingui/macro';
import { Alert, List, LoadingOverlay, Stack, Text, Title } from '@mantine/core';
import { IconInfoCircle } from '@tabler/icons-react';
import { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { ApiEndpoints } from '../../../enums/ApiEndpoints';
import { openCreateApiForm, openDeleteApiForm } from '../../../functions/forms';
import { useInstance } from '../../../hooks/UseInstance';
import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState';
import { useUserState } from '../../../states/UserState';
import { AddItemButton } from '../../buttons/AddItemButton';
import { EditApiForm } from '../../forms/ApiForm';
import { DetailDrawer } from '../../nav/DetailDrawer';
import { TableColumn } from '../Column';
import { BooleanColumn } from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
import { GroupDetailI } from './GroupTable';
export interface UserDetailI {
pk: number;
username: string;
email: string;
first_name: string;
last_name: string;
groups: GroupDetailI[];
is_active: boolean;
is_staff: boolean;
is_superuser: boolean;
}
export function UserDrawer({
id,
refreshTable
}: {
id: string;
refreshTable: () => void;
}) {
const {
instance: userDetail,
refreshInstance,
instanceQuery: { isFetching, error }
} = useInstance<UserDetailI>({
endpoint: ApiEndpoints.user_list,
pk: id,
throwError: true
});
const currentUserPk = useUserState((s) => s.user?.pk);
const isCurrentUser = useMemo(
() => currentUserPk === parseInt(id, 10),
[currentUserPk, id]
);
if (isFetching) {
return <LoadingOverlay visible={true} />;
}
if (error) {
return (
<Text>
{(error as any)?.response?.status === 404 ? (
<Trans>User with id {id} not found</Trans>
) : (
<Trans>An error occurred while fetching user details</Trans>
)}
</Text>
);
}
return (
<Stack>
<EditApiForm
props={{
url: ApiEndpoints.user_list,
pk: id,
fields: {
username: {},
first_name: {},
last_name: {},
email: {},
is_active: {
label: t`Is Active`,
description: t`Designates whether this user should be treated as active. Unselect this instead of deleting accounts.`,
disabled: isCurrentUser
},
is_staff: {
label: t`Is Staff`,
description: t`Designates whether the user can log into the django admin site.`,
disabled: isCurrentUser
},
is_superuser: {
label: t`Is Superuser`,
description: t`Designates that this user has all permissions without explicitly assigning them.`,
disabled: isCurrentUser
}
},
postFormContent: isCurrentUser ? (
<Alert
title={<Trans>Info</Trans>}
color="blue"
icon={<IconInfoCircle />}
>
<Trans>
You cannot edit the rights for the currently logged-in user.
</Trans>
</Alert>
) : undefined,
onFormSuccess: () => {
refreshTable();
refreshInstance();
}
}}
id={`user-detail-drawer-${id}`}
/>
<Title order={5}>
<Trans>Groups</Trans>
</Title>
<Text ml={'md'}>
{userDetail?.groups && userDetail?.groups?.length > 0 ? (
<List>
{userDetail?.groups?.map((group) => (
<List.Item key={group.pk}>
<Link to={`../group-${group.pk}`}>{group.name}</Link>
</List.Item>
))}
</List>
) : (
<Trans>No groups</Trans>
)}
</Text>
</Stack>
);
}
/**
* Table for displaying list of users
*/
export function UserTable() {
const table = useTable('users');
const navigate = useNavigate();
const openDetailDrawer = useCallback(
(pk: number) => navigate(`user-${pk}/`),
[]
);
const columns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'email',
sortable: true,
title: t`Email`
},
{
accessor: 'username',
sortable: true,
switchable: false,
title: t`Username`
},
{
accessor: 'first_name',
sortable: true,
title: t`First Name`
},
{
accessor: 'last_name',
sortable: true,
title: t`Last Name`
},
{
accessor: 'groups',
sortable: true,
switchable: true,
title: t`Groups`,
render: (record: any) => {
return record.groups.length;
}
},
BooleanColumn({
accessor: 'is_staff',
title: t`Staff`
}),
BooleanColumn({
accessor: 'is_superuser',
title: t`Superuser`
}),
BooleanColumn({
accessor: 'is_active',
title: t`Active`
})
];
}, []);
const rowActions = useCallback((record: UserDetailI): RowAction[] => {
return [
RowEditAction({
onClick: () => openDetailDrawer(record.pk)
}),
RowDeleteAction({
onClick: () => {
openDeleteApiForm({
url: ApiEndpoints.user_list,
pk: record.pk,
title: t`Delete user`,
successMessage: t`User deleted`,
onFormSuccess: table.refreshTable,
preFormWarning: t`Are you sure you want to delete this user?`
});
}
})
];
}, []);
const addUser = useCallback(() => {
openCreateApiForm({
url: ApiEndpoints.user_list,
title: t`Add user`,
fields: {
username: {},
email: {},
first_name: {},
last_name: {}
},
onFormSuccess: table.refreshTable,
successMessage: t`Added user`
});
}, []);
const tableActions = useMemo(() => {
let actions = [];
actions.push(
<AddItemButton key="add-user" onClick={addUser} tooltip={t`Add user`} />
);
return actions;
}, []);
return (
<>
<DetailDrawer
title={t`Edit user`}
renderContent={(id) => {
if (!id || !id.startsWith('user-')) return false;
return (
<UserDrawer
id={id.replace('user-', '')}
refreshTable={table.refreshTable}
/>
);
}}
/>
<InvenTreeTable
url={apiUrl(ApiEndpoints.user_list)}
tableState={table}
columns={columns}
props={{
rowActions: rowActions,
tableActions: tableActions,
onRowClick: (record) => openDetailDrawer(record.pk)
}}
/>
</>
);
}