import { useQuery, useSuspenseQuery } from '@tanstack/react-query' import { createFileRoute, Link } from '@tanstack/react-router' import { Check, Filter, FilterX, LayoutGrid, List, Loader2, Search, SmilePlus, X } from 'lucide-react' import { useEffect, useState } from 'react' import { Helmet } from 'react-helmet-async' import { Pagination } from '~/components/pagination' import { Alert, AlertDescription, AlertTitle } from '~/components/ui/alert' import { Button } from '~/components/ui/button' import { Card, CardContent } from '~/components/ui/card' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '~/components/ui/dropdown-menu' import { Input } from '~/components/ui/input' import { Skeleton } from '~/components/ui/skeleton' import { useDebouncedCallback } from '~/hooks/use-debounced-callback' import { useLocalStorage } from '~/hooks/use-local-storage' import { assetUrl } from '~/lib/directus' import { categoriesQuery, vendorsListQuery } from '~/lib/queries' import { cn } from '~/lib/utils' type Search = { page?: number q?: string category?: string } export const Route = createFileRoute('/vendors/')({ validateSearch: (search: Record): Search => ({ page: search.page ? Number(search.page) : undefined, q: typeof search.q === 'string' ? search.q : undefined, category: typeof search.category === 'string' ? search.category : undefined, }), loaderDeps: ({ search }) => search, loader: async ({ context: { queryClient } }) => { await queryClient.ensureQueryData(categoriesQuery) }, component: VendorsPage, }) type Layout = 'GRID' | 'LIST' function truncateDescription(desc: string | null | undefined): string { if (!desc) return '' const head = desc.substring(0, 80) const end = head.lastIndexOf(' ') return desc.substring(0, end > 0 ? end : head.length) + ' …' } function VendorsPage() { const { page = 1, q = '', category = '' } = Route.useSearch() const navigate = Route.useNavigate() const { data: categories } = useSuspenseQuery(categoriesQuery) const [perPage, setPerPage] = useLocalStorage('perPage', 12) const [layout, setLayout] = useLocalStorage('layout', 'GRID') const [searchInput, setSearchInput] = useState(q) useEffect(() => { setSearchInput(q) }, [q]) const pushSearch = useDebouncedCallback((value: string) => { navigate({ search: (s) => ({ ...s, q: value || undefined, page: undefined }) }) }, 500) const { data: response, error, isFetching, } = useQuery(vendorsListQuery({ page, perPage, search: q, category })) const vendors = response?.data ?? [] const filterCount = response?.meta?.filter_count ?? 0 const lastPage = Math.max(1, Math.ceil(filterCount / perPage)) useEffect(() => { if (response && page > lastPage) { navigate({ search: (s) => ({ ...s, page: lastPage }), replace: true }) } }, [response, page, lastPage, navigate]) const isEmpty = !isFetching && filterCount === 0 return (
Vendors

Vendors

{isFetching && }
{ const v = e.target.value.toLowerCase() setSearchInput(v) pushSearch(v) }} /> {searchInput && ( )}
{categories.map((cat) => ( navigate({ search: (s) => ({ ...s, category: cat.slug, page: undefined }) }) } > {category === cat.slug && } {cat.name} ))} navigate({ search: (s) => ({ ...s, category: undefined, page: undefined }) })} > Clear
{error && ( There was an error processing your request )} {layout === 'GRID' ? (
{isFetching && !response ? Array.from({ length: perPage }).map((_, k) => ( )) : vendors.map((v) => ( {v.name} {v.name}

{truncateDescription(v.description)}

))}
) : (
{isFetching && !response ? Array.from({ length: perPage }).map((_, k) => ( )) : vendors.map((v) => ( ))}
{v.name} {v.name} {truncateDescription(v.description)}
)} {isEmpty && (
No results found. Try refining your search term and filters …
)} {filterCount > perPage && ( <> ({ q: q || undefined, category: category || undefined, page: p, })} />
Results per page:
{[12, 24, 48].map((n, i) => ( ))}
)}
) }