Implement paging in manage users, implement search

This commit is contained in:
Manuel
2023-07-31 22:57:22 +02:00
parent 35d505e9b1
commit d2408ebe4b
3 changed files with 80 additions and 40 deletions

View File

@@ -19,6 +19,7 @@ import {
IconMail, IconMail,
IconMailCheck, IconMailCheck,
IconUser, IconUser,
IconUserPlus,
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import Head from 'next/head'; import Head from 'next/head';
import Link from 'next/link'; import Link from 'next/link';
@@ -59,7 +60,7 @@ const CreateNewUserPage = () => {
}); });
const context = api.useContext(); const context = api.useContext();
const { mutateAsync, isSuccess, isLoading } = api.user.createUser.useMutation({ const { mutateAsync, isLoading } = api.user.createUser.useMutation({
onSettled: () => { onSettled: () => {
void context.user.getAll.invalidate(); void context.user.getAll.invalidate();
}, },
@@ -173,10 +174,6 @@ const CreateNewUserPage = () => {
<Flex justify="end" wrap="nowrap"> <Flex justify="end" wrap="nowrap">
<Button <Button
onClick={async () => { onClick={async () => {
if (isSuccess) {
return;
}
await mutateAsync({ await mutateAsync({
username: form.values.account.username, username: form.values.account.username,
password: form.values.security.password, password: form.values.security.password,
@@ -198,6 +195,17 @@ const CreateNewUserPage = () => {
User has been created in the database. They can now log in. User has been created in the database. They can now log in.
</Alert> </Alert>
<Group>
<Button
onClick={() => {
form.reset();
setActive(0);
}}
leftIcon={<IconUserPlus size="1rem" />}
variant="default"
>
Create another
</Button>
<Button <Button
component={Link} component={Link}
leftIcon={<IconArrowLeft size="1rem" />} leftIcon={<IconArrowLeft size="1rem" />}
@@ -206,6 +214,7 @@ const CreateNewUserPage = () => {
> >
Go back to users Go back to users
</Button> </Button>
</Group>
</Stepper.Completed> </Stepper.Completed>
</Stepper> </Stepper>
</MainLayout> </MainLayout>

View File

@@ -2,6 +2,7 @@ import {
ActionIcon, ActionIcon,
Autocomplete, Autocomplete,
Avatar, Avatar,
Box,
Button, Button,
Flex, Flex,
Group, Group,
@@ -11,6 +12,7 @@ import {
Text, Text,
Title, Title,
} from '@mantine/core'; } from '@mantine/core';
import { useDebouncedValue } from '@mantine/hooks';
import { openContextModal } from '@mantine/modals'; import { openContextModal } from '@mantine/modals';
import { IconPlus, IconTrash } from '@tabler/icons-react'; import { IconPlus, IconTrash } from '@tabler/icons-react';
import Head from 'next/head'; import Head from 'next/head';
@@ -20,16 +22,13 @@ import { MainLayout } from '~/components/layout/admin/main-admin.layout';
import { api } from '~/utils/api'; import { api } from '~/utils/api';
const ManageUsersPage = () => { const ManageUsersPage = () => {
const { data, fetchNextPage, fetchPreviousPage } = api.user.getAll.useInfiniteQuery( const [activePage, setActivePage] = useState(0);
{ const [nonDebouncedSearch, setNonDebouncedSearch] = useState<string | undefined>('');
limit: 10, const [debouncedSearch] = useDebouncedValue<string | undefined>(nonDebouncedSearch, 200);
}, const { data } = api.user.getAll.useQuery({
{ page: activePage,
getNextPageParam: (lastPage) => lastPage.nextCursor, search: debouncedSearch,
} });
);
const [activePage, _] = useState(0);
return ( return (
<MainLayout> <MainLayout>
@@ -46,8 +45,13 @@ const ManageUsersPage = () => {
<Flex columnGap={10} justify="end" mb="md"> <Flex columnGap={10} justify="end" mb="md">
<Autocomplete <Autocomplete
placeholder="Filter" placeholder="Filter"
data={['React', 'Angular', 'Svelte', 'Vue']} data={
(data?.users.map((user) => user.name).filter((name) => name !== null) as string[]) ?? []
}
variant="filled" variant="filled"
onChange={(value) => {
setNonDebouncedSearch(value);
}}
/> />
<Button <Button
component={Link} component={Link}
@@ -68,7 +72,7 @@ const ManageUsersPage = () => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{data.pages[activePage].users.map((user, index) => ( {data.users.map((user, index) => (
<tr key={index}> <tr key={index}>
<td> <td>
<Group position="apart"> <Group position="apart">
@@ -98,13 +102,30 @@ const ManageUsersPage = () => {
</td> </td>
</tr> </tr>
))} ))}
{debouncedSearch && debouncedSearch.length > 0 && (
<tr>
<td colSpan={1}>
<Box p={15}>
<Text>Your search does not match any entries. Please adjust your filter.</Text>
</Box>
</td>
</tr>
)}
</tbody> </tbody>
</Table> </Table>
<Pagination <Pagination
total={data.pages.length} total={data.countPages}
value={activePage + 1} value={activePage + 1}
onNextPage={fetchNextPage} onNextPage={() => {
onPreviousPage={fetchPreviousPage} setActivePage((prev) => prev + 1);
}}
onPreviousPage={() => {
setActivePage((prev) => prev - 1);
}}
onChange={(targetPage) => {
setActivePage(targetPage - 1);
}}
/> />
</> </>
)} )}

View File

@@ -161,23 +161,33 @@ export const userRouter = createTRPCRouter({
getAll: publicProcedure getAll: publicProcedure
.input( .input(
z.object({ z.object({
limit: z.number().min(1).max(100).nullish(), limit: z.number().min(1).max(100).default(10),
cursor: z.string().nullish(), page: z.number().min(0),
search: z
.string()
.optional()
.transform((value) => (value === '' ? undefined : value)),
}) })
) )
.query(async ({ ctx, input }) => { .query(async ({ ctx, input }) => {
const limit = input.limit ?? 50; const limit = input.limit;
const cursor = input.cursor;
const users = await ctx.prisma.user.findMany({ const users = await ctx.prisma.user.findMany({
take: limit + 1, // get an extra item at the end which we'll use as next cursor take: limit + 1,
cursor: cursor ? { id: cursor } : undefined, skip: limit * input.page,
where: {
name: {
contains: input.search,
},
},
}); });
let nextCursor: typeof cursor | undefined = undefined; const countUsers = await ctx.prisma.user.count({
if (users.length > limit) { where: {
const nextItem = users.pop(); name: {
nextCursor = nextItem!.id; contains: input.search,
} },
},
});
return { return {
users: users.map((user) => ({ users: users.map((user) => ({
@@ -186,7 +196,7 @@ export const userRouter = createTRPCRouter({
email: user.email, email: user.email,
emailVerified: user.emailVerified, emailVerified: user.emailVerified,
})), })),
nextCursor, countPages: Math.ceil(countUsers / limit),
}; };
}), }),
createUser: publicProcedure.input(createNewUserSchema).mutation(async ({ ctx, input }) => { createUser: publicProcedure.input(createNewUserSchema).mutation(async ({ ctx, input }) => {