Improve vendors list

This commit is contained in:
2023-07-09 17:41:13 +04:00
parent 5e2c13d757
commit c28dc2146c

View File

@@ -1,7 +1,9 @@
import { Link } from '@chakra-ui/next-js' import { Link } from '@chakra-ui/next-js'
import { import {
Alert, Alert,
AlertDescription,
AlertIcon, AlertIcon,
AlertTitle,
Box, Box,
Button, Button,
ButtonGroup, ButtonGroup,
@@ -12,8 +14,17 @@ import {
Heading, Heading,
IconButton, IconButton,
Image, Image,
Input,
InputGroup,
InputLeftElement,
InputRightElement,
LinkBox, LinkBox,
LinkOverlay, LinkOverlay,
Menu,
MenuButton,
MenuDivider,
MenuItem,
MenuList,
SimpleGrid, SimpleGrid,
Skeleton, Skeleton,
Spinner, Spinner,
@@ -23,18 +34,42 @@ import {
Text, Text,
Tr, Tr,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import { useLocalStorageValue } from '@react-hookz/web' import { useDebouncedCallback, useLocalStorageValue } from '@react-hookz/web'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { TbLayoutGrid, TbLayoutList } from 'react-icons/tb' import { TbCheck, TbFilter, TbFilterEdit, TbLayoutGrid, TbLayoutList, TbMoodSad, TbSearch, TbX } from 'react-icons/tb'
import useSWR from 'swr' import useSWR from 'swr'
import Pagination from '~/components/pagination' import Pagination from '~/components/pagination'
const PER_PAGE = 12 const PER_PAGE = 12
export default function VendorsPage() { export const getStaticProps = async () => {
const url = new URL(`${process.env.NEXT_PUBLIC_DIRECTUS_API_URL}/items/categories`)
url.searchParams.append('fields[]', 'slug')
url.searchParams.append('fields[]', 'name')
url.searchParams.append('fields[]', 'subcategories.slug')
url.searchParams.append('fields[]', 'subcategories.name')
url.searchParams.append('sort', 'name')
url.searchParams.append('limit', -1)
url.searchParams.append(
'filter',
JSON.stringify({
parent_id: {
_null: true,
},
})
)
const res = await fetch(url.toString())
const { data: categories } = await res.json()
return { props: { categories } }
}
export default function VendorsPage({ categories }) {
const router = useRouter() const router = useRouter()
const { const {
query: { page = 1 }, query: { page = 1, category = '', q: search = '' },
} = router } = router
const { value: perPage, set: setPerPage } = useLocalStorageValue('perPage', { const { value: perPage, set: setPerPage } = useLocalStorageValue('perPage', {
@@ -47,14 +82,43 @@ export default function VendorsPage() {
initializeWithValue: false, initializeWithValue: false,
}) })
const { data, error, isLoading, isValidating } = useSWR(['vendors', page, perPage], async () => { const setSearch = useDebouncedCallback(
// await new Promise((r) => setTimeout(r, 2000)) (q) => {
router.replace({ query: { ...router.query, q } })
},
[router],
500
)
const { data, error, isLoading, isValidating } = useSWR(['vendors', page, perPage, search, category], async () => {
if (!perPage) return
await new Promise((r) => setTimeout(r, 2000))
const url = new URL(`${process.env.NEXT_PUBLIC_DIRECTUS_API_URL}/items/vendors`) const url = new URL(`${process.env.NEXT_PUBLIC_DIRECTUS_API_URL}/items/vendors`)
url.searchParams.append('fields[]', '*') url.searchParams.append('fields[]', '*')
url.searchParams.append('limit', perPage) url.searchParams.append('limit', perPage)
url.searchParams.append('page', page) url.searchParams.append('page', page)
url.searchParams.append('sort', 'name') url.searchParams.append('sort', 'name')
url.searchParams.append('meta[]', 'filter_count') url.searchParams.append('meta[]', 'filter_count')
if (search !== '' || category !== '') {
url.searchParams.append(
'filter',
JSON.stringify({
_and: [
category !== '' ? { categories: { categories_id: { slug: { _eq: category } } } } : {},
search !== ''
? {
_or: ['name', 'description', 'long_description', 'city'].map((s) => ({
[s]: { _contains: search },
})),
}
: {},
],
})
)
}
const res = await fetch(url.toString()) const res = await fetch(url.toString())
if (!res.ok) { if (!res.ok) {
throw new Error('Oops') throw new Error('Oops')
@@ -64,17 +128,54 @@ export default function VendorsPage() {
const { meta, data: vendors } = data || { meta: {}, data: [] } const { meta, data: vendors } = data || { meta: {}, data: [] }
const lastPage = Math.max(1, Math.ceil(meta.filter_count / perPage))
if (!isNaN(lastPage) && page > lastPage) {
router.replace({ query: { ...router.query, page: lastPage } })
}
return ( return (
<div> <>
<Flex alignItems="center" justifyContent="space-between"> <Flex alignItems="center" justifyContent="space-between" direction={['column', 'row']} mb="5" gap="5">
<Heading size="lg" my={8}> <Heading size="lg" my={8}>
Vendors Vendors
</Heading> </Heading>
<Box>{isValidating && <Spinner />}</Box> <Box>{isValidating && <Spinner />}</Box>
<ButtonGroup isAttached> <HStack gap="5">
<IconButton icon={<TbLayoutGrid />} onClick={() => setLayout('GRID')} isDisabled={layout === 'GRID'} /> <InputGroup>
<IconButton icon={<TbLayoutList />} onClick={() => setLayout('LIST')} isDisabled={layout === 'LIST'} /> <InputLeftElement>
</ButtonGroup> <TbSearch />
</InputLeftElement>
<Input onChange={(ev) => setSearch(ev.target.value.toLowerCase())} />
<InputRightElement
onClick={(ev) => {
ev.currentTarget.parentNode.childNodes[1].value = ''
setSearch('')
}}
>
{search !== '' && <TbX />}
</InputRightElement>
</InputGroup>
<Menu>
<MenuButton as={IconButton} icon={category === '' ? <TbFilter /> : <TbFilterEdit />} />
<MenuList>
{categories.map((cat) => (
<Link key={cat.slug} href={{ query: { ...router.query, category: cat.slug } }}>
<MenuItem icon={category === cat.slug && <TbCheck />} isDisabled={category === cat.slug}>
{cat.name}
</MenuItem>
</Link>
))}
<MenuDivider />
<Link href={{ query: { ...router.query, category: '' } }}>
<MenuItem>Clear</MenuItem>
</Link>
</MenuList>
</Menu>
<ButtonGroup isAttached>
<IconButton icon={<TbLayoutGrid />} onClick={() => setLayout('GRID')} isDisabled={layout === 'GRID'} />
<IconButton icon={<TbLayoutList />} onClick={() => setLayout('LIST')} isDisabled={layout === 'LIST'} />
</ButtonGroup>
</HStack>
</Flex> </Flex>
{error && ( {error && (
@@ -176,18 +277,33 @@ export default function VendorsPage() {
</> </>
)} )}
<Pagination page={Number(page)} itemsPerPage={perPage} totalItems={meta.filter_count} /> {meta.filter_count === 0 && (
<Box>
<Alert status="info" flexDirection="column" justifyContent="center" rounded="md" paddingY="10">
<TbMoodSad style={{ width: '2rem', height: '2rem' }} />
<AlertTitle mt={4} mb={1} fontSize="lg">
No results found.
</AlertTitle>
<AlertDescription maxWidth="sm">Try refining your search term and filters &hellip;</AlertDescription>
</Alert>
</Box>
)}
<HStack> {meta.filter_count > perPage && (
<Text>Results per page:</Text> <>
<ButtonGroup isAttached> <Pagination page={Number(page)} itemsPerPage={perPage} totalItems={meta.filter_count} />
{[12, 24, 48].map((n) => ( <HStack mt="5" justifyContent="center">
<Button key={n} onClick={() => setPerPage(n)} isDisabled={perPage === n}> <Text>Results per page:</Text>
{n} <ButtonGroup isAttached>
</Button> {[12, 24, 48].map((n) => (
))} <Button key={n} onClick={() => setPerPage(n)} isDisabled={perPage === n}>
</ButtonGroup> {n}
</HStack> </Button>
</div> ))}
</ButtonGroup>
</HStack>
</>
)}
</>
) )
} }