Files
full-stack-fastapi-template/frontend/src/routes/_layout/items.tsx
2024-06-27 14:49:54 -05:00

143 lines
3.6 KiB
TypeScript

import { z } from "zod"
import {
Button,
Container,
Flex,
Heading,
Skeleton,
Table,
TableContainer,
Tbody,
Td,
Th,
Thead,
Tr,
} from "@chakra-ui/react"
import { useQuery, useQueryClient } from "@tanstack/react-query"
import { createFileRoute, useNavigate } from "@tanstack/react-router"
import { useEffect } from "react"
import { ItemsService } from "../../client"
import ActionsMenu from "../../components/Common/ActionsMenu"
import Navbar from "../../components/Common/Navbar"
const itemsSearchSchema = z.object({
page: z.number().catch(1),
})
export const Route = createFileRoute("/_layout/items")({
component: Items,
validateSearch: (search) => itemsSearchSchema.parse(search),
})
const PER_PAGE = 5
function getItemsQueryOptions({ page }: { page: number }) {
return {
queryFn: () =>
ItemsService.readItems({ skip: (page - 1) * PER_PAGE, limit: PER_PAGE }),
queryKey: ["items", { page }],
}
}
function ItemsTable() {
const queryClient = useQueryClient()
const { page } = Route.useSearch()
const navigate = useNavigate({ from: Route.fullPath })
const setPage = (page: number) =>
navigate({ search: (prev) => ({ ...prev, page }) })
const {
data: items,
isPending,
isPlaceholderData,
} = useQuery({
...getItemsQueryOptions({ page }),
placeholderData: (prevData) => prevData,
})
const hasNextPage = !isPlaceholderData && items?.data.length === PER_PAGE
const hasPreviousPage = page > 1
useEffect(() => {
if (hasNextPage) {
queryClient.prefetchQuery(getItemsQueryOptions({ page: page + 1 }))
}
}, [page, queryClient])
return (
<>
<TableContainer>
<Table size={{ base: "sm", md: "md" }}>
<Thead>
<Tr>
<Th>ID</Th>
<Th>Title</Th>
<Th>Description</Th>
<Th>Actions</Th>
</Tr>
</Thead>
{isPending ? (
<Tbody>
{new Array(5).fill(null).map((_, index) => (
<Tr key={index}>
{new Array(4).fill(null).map((_, index) => (
<Td key={index}>
<Flex>
<Skeleton height="20px" width="20px" />
</Flex>
</Td>
))}
</Tr>
))}
</Tbody>
) : (
<Tbody>
{items?.data.map((item) => (
<Tr key={item.id} opacity={isPlaceholderData ? 0.5 : 1}>
<Td>{item.id}</Td>
<Td>{item.title}</Td>
<Td color={!item.description ? "ui.dim" : "inherit"}>
{item.description || "N/A"}
</Td>
<Td>
<ActionsMenu type={"Item"} value={item} />
</Td>
</Tr>
))}
</Tbody>
)}
</Table>
</TableContainer>
<Flex
gap={4}
alignItems="center"
mt={4}
direction="row"
justifyContent="flex-end"
>
<Button onClick={() => setPage(page - 1)} isDisabled={!hasPreviousPage}>
Previous
</Button>
<span>Page {page}</span>
<Button isDisabled={!hasNextPage} onClick={() => setPage(page + 1)}>
Next
</Button>
</Flex>
</>
)
}
function Items() {
return (
<Container maxW="full">
<Heading size="lg" textAlign={{ base: "center", md: "left" }} pt={12}>
Items Management
</Heading>
<Navbar type={"Item"} />
<ItemsTable />
</Container>
)
}