147 lines
3.8 KiB
TypeScript
147 lines
3.8 KiB
TypeScript
import {
|
|
Container,
|
|
EmptyState,
|
|
Flex,
|
|
Heading,
|
|
Table,
|
|
VStack,
|
|
} from "@chakra-ui/react"
|
|
import { useQuery } from "@tanstack/react-query"
|
|
import { createFileRoute, useNavigate } from "@tanstack/react-router"
|
|
import { FiSearch } from "react-icons/fi"
|
|
import { z } from "zod"
|
|
|
|
import { ItemsService } from "@/client"
|
|
import { ItemActionsMenu } from "@/components/Common/ItemActionsMenu"
|
|
import AddItem from "@/components/Items/AddItem"
|
|
import PendingItems from "@/components/Pending/PendingItems"
|
|
import {
|
|
PaginationItems,
|
|
PaginationNextTrigger,
|
|
PaginationPrevTrigger,
|
|
PaginationRoot,
|
|
} from "@/components/ui/pagination.tsx"
|
|
|
|
const itemsSearchSchema = z.object({
|
|
page: z.number().catch(1),
|
|
})
|
|
|
|
const PER_PAGE = 5
|
|
|
|
function getItemsQueryOptions({ page }: { page: number }) {
|
|
return {
|
|
queryFn: () =>
|
|
ItemsService.readItems({ skip: (page - 1) * PER_PAGE, limit: PER_PAGE }),
|
|
queryKey: ["items", { page }],
|
|
}
|
|
}
|
|
|
|
export const Route = createFileRoute("/_layout/items")({
|
|
component: Items,
|
|
validateSearch: (search) => itemsSearchSchema.parse(search),
|
|
})
|
|
|
|
function ItemsTable() {
|
|
const navigate = useNavigate({ from: Route.fullPath })
|
|
const { page } = Route.useSearch()
|
|
|
|
const { data, isLoading, isPlaceholderData } = useQuery({
|
|
...getItemsQueryOptions({ page }),
|
|
placeholderData: (prevData) => prevData,
|
|
})
|
|
|
|
const setPage = (page: number) => {
|
|
navigate({
|
|
to: "/items",
|
|
search: (prev) => ({ ...prev, page }),
|
|
})
|
|
}
|
|
|
|
const items = data?.data.slice(0, PER_PAGE) ?? []
|
|
const count = data?.count ?? 0
|
|
|
|
if (isLoading) {
|
|
return <PendingItems />
|
|
}
|
|
|
|
if (items.length === 0) {
|
|
return (
|
|
<EmptyState.Root>
|
|
<EmptyState.Content>
|
|
<EmptyState.Indicator>
|
|
<FiSearch />
|
|
</EmptyState.Indicator>
|
|
<VStack textAlign="center">
|
|
<EmptyState.Title>You don't have any items yet</EmptyState.Title>
|
|
<EmptyState.Description>
|
|
Add a new item to get started
|
|
</EmptyState.Description>
|
|
</VStack>
|
|
</EmptyState.Content>
|
|
</EmptyState.Root>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Table.Root size={{ base: "sm", md: "md" }}>
|
|
<Table.Header>
|
|
<Table.Row>
|
|
<Table.ColumnHeader w="sm">ID</Table.ColumnHeader>
|
|
<Table.ColumnHeader w="sm">Title</Table.ColumnHeader>
|
|
<Table.ColumnHeader w="sm">Description</Table.ColumnHeader>
|
|
<Table.ColumnHeader w="sm">Actions</Table.ColumnHeader>
|
|
</Table.Row>
|
|
</Table.Header>
|
|
<Table.Body>
|
|
{items?.map((item) => (
|
|
<Table.Row key={item.id} opacity={isPlaceholderData ? 0.5 : 1}>
|
|
<Table.Cell truncate maxW="sm">
|
|
{item.id}
|
|
</Table.Cell>
|
|
<Table.Cell truncate maxW="sm">
|
|
{item.title}
|
|
</Table.Cell>
|
|
<Table.Cell
|
|
color={!item.description ? "gray" : "inherit"}
|
|
truncate
|
|
maxW="30%"
|
|
>
|
|
{item.description || "N/A"}
|
|
</Table.Cell>
|
|
<Table.Cell>
|
|
<ItemActionsMenu item={item} />
|
|
</Table.Cell>
|
|
</Table.Row>
|
|
))}
|
|
</Table.Body>
|
|
</Table.Root>
|
|
<Flex justifyContent="flex-end" mt={4}>
|
|
<PaginationRoot
|
|
count={count}
|
|
pageSize={PER_PAGE}
|
|
onPageChange={({ page }) => setPage(page)}
|
|
>
|
|
<Flex>
|
|
<PaginationPrevTrigger />
|
|
<PaginationItems />
|
|
<PaginationNextTrigger />
|
|
</Flex>
|
|
</PaginationRoot>
|
|
</Flex>
|
|
</>
|
|
)
|
|
}
|
|
|
|
function Items() {
|
|
return (
|
|
<Container maxW="full">
|
|
<Heading size="lg" pt={12}>
|
|
Items Management
|
|
</Heading>
|
|
<AddItem />
|
|
<ItemsTable />
|
|
</Container>
|
|
)
|
|
}
|