From c28dc2146c41fb692e9375b7bfbfffcb4cfe1ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Markovi=C4=87?= Date: Sun, 9 Jul 2023 17:41:13 +0400 Subject: [PATCH] Improve vendors list --- frontend/pages/vendors/index.js | 164 +++++++++++++++++++++++++++----- 1 file changed, 140 insertions(+), 24 deletions(-) diff --git a/frontend/pages/vendors/index.js b/frontend/pages/vendors/index.js index f195ca6..1510026 100644 --- a/frontend/pages/vendors/index.js +++ b/frontend/pages/vendors/index.js @@ -1,7 +1,9 @@ import { Link } from '@chakra-ui/next-js' import { Alert, + AlertDescription, AlertIcon, + AlertTitle, Box, Button, ButtonGroup, @@ -12,8 +14,17 @@ import { Heading, IconButton, Image, + Input, + InputGroup, + InputLeftElement, + InputRightElement, LinkBox, LinkOverlay, + Menu, + MenuButton, + MenuDivider, + MenuItem, + MenuList, SimpleGrid, Skeleton, Spinner, @@ -23,18 +34,42 @@ import { Text, Tr, } from '@chakra-ui/react' -import { useLocalStorageValue } from '@react-hookz/web' +import { useDebouncedCallback, useLocalStorageValue } from '@react-hookz/web' 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 Pagination from '~/components/pagination' 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 { - query: { page = 1 }, + query: { page = 1, category = '', q: search = '' }, } = router const { value: perPage, set: setPerPage } = useLocalStorageValue('perPage', { @@ -47,14 +82,43 @@ export default function VendorsPage() { initializeWithValue: false, }) - const { data, error, isLoading, isValidating } = useSWR(['vendors', page, perPage], async () => { - // await new Promise((r) => setTimeout(r, 2000)) + const setSearch = useDebouncedCallback( + (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`) url.searchParams.append('fields[]', '*') url.searchParams.append('limit', perPage) url.searchParams.append('page', page) url.searchParams.append('sort', 'name') 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()) if (!res.ok) { throw new Error('Oops') @@ -64,17 +128,54 @@ export default function VendorsPage() { 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 ( -
- + <> + Vendors {isValidating && } - - } onClick={() => setLayout('GRID')} isDisabled={layout === 'GRID'} /> - } onClick={() => setLayout('LIST')} isDisabled={layout === 'LIST'} /> - + + + + + + setSearch(ev.target.value.toLowerCase())} /> + { + ev.currentTarget.parentNode.childNodes[1].value = '' + setSearch('') + }} + > + {search !== '' && } + + + + : } /> + + {categories.map((cat) => ( + + } isDisabled={category === cat.slug}> + {cat.name} + + + ))} + + + Clear + + + + + } onClick={() => setLayout('GRID')} isDisabled={layout === 'GRID'} /> + } onClick={() => setLayout('LIST')} isDisabled={layout === 'LIST'} /> + + {error && ( @@ -176,18 +277,33 @@ export default function VendorsPage() { )} - + {meta.filter_count === 0 && ( + + + + + No results found. + + Try refining your search term and filters … + + + )} - - Results per page: - - {[12, 24, 48].map((n) => ( - - ))} - - -
+ {meta.filter_count > perPage && ( + <> + + + Results per page: + + {[12, 24, 48].map((n) => ( + + ))} + + + + )} + ) }