WIP: Pagination
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
"start": "directus start"
|
"start": "directus start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"directus": "10.3.0",
|
"directus": "10.4.2",
|
||||||
"sqlite3": "5.1.6"
|
"sqlite3": "5.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
545
backend/pnpm-lock.yaml
generated
545
backend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
# The URL where your API can be reached on the web.
|
# The URL where your API can be reached on the web.
|
||||||
DIRECTUS_API_URL="http://0.0.0.0:8055"
|
NEXT_PUBLIC_DIRECTUS_API_URL="http://0.0.0.0:8055"
|
||||||
GLOBALS_ID="4f8d9e66-ec95-4bdd-a5e7-34df8ea68a45"
|
NEXT_PUBLIC_GLOBALS_ID="4f8d9e66-ec95-4bdd-a5e7-34df8ea68a45"
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { MoonIcon, SunIcon } from '@chakra-ui/icons'
|
import { MoonIcon, SunIcon } from '@chakra-ui/icons'
|
||||||
import { Box, Button, Flex, Heading, Spacer, Stack, useColorMode, useColorModeValue } from '@chakra-ui/react'
|
import { Box, Button, Flex, Heading, Spacer, Stack, useColorMode, useColorModeValue } from '@chakra-ui/react'
|
||||||
import { Link } from '@chakra-ui/next-js'
|
import { Link } from '@chakra-ui/next-js'
|
||||||
|
|||||||
78
frontend/components/pagination.js
Normal file
78
frontend/components/pagination.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { Flex, Link, Select, Text } from '@chakra-ui/react'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import NextLink from 'next/link'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { Fragment } from 'react'
|
||||||
|
|
||||||
|
export default function Pagination({ page = 1, itemsPerPage = 12, totalItems = 0, limit = 4 }) {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(totalItems / itemsPerPage)
|
||||||
|
|
||||||
|
if (totalPages === 1) return <></>
|
||||||
|
|
||||||
|
const links = new Array(totalPages)
|
||||||
|
.fill(true)
|
||||||
|
.map((_v, i) => (i < limit || i >= totalPages - limit || (i >= page - limit && i < page + limit - 1)) && i + 1)
|
||||||
|
.filter((i) => i !== false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex justifyContent="space-between" direction={['column', 'row']} mt={6} mb={6} gap={3}>
|
||||||
|
<Link
|
||||||
|
//
|
||||||
|
as={NextLink}
|
||||||
|
href={{ query: { ...router.query, page: Math.max(1, page - 1) } }}
|
||||||
|
variant="pagination"
|
||||||
|
>
|
||||||
|
← Previous
|
||||||
|
</Link>
|
||||||
|
<Flex gap={1} display={['none', 'none', 'flex']}>
|
||||||
|
{links.map((v, i) => (
|
||||||
|
<Fragment key={i}>
|
||||||
|
{v > 1 && v - 1 !== links[i - 1] && (
|
||||||
|
<Text marginX={3} lineHeight={10}>
|
||||||
|
…
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<Link
|
||||||
|
//
|
||||||
|
key={v}
|
||||||
|
as={NextLink}
|
||||||
|
prefetch={false}
|
||||||
|
href={{ query: { ...router.query, page: v } }}
|
||||||
|
className={clsx({
|
||||||
|
current: v === page,
|
||||||
|
})}
|
||||||
|
variant="pagination"
|
||||||
|
>
|
||||||
|
{v}
|
||||||
|
</Link>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
<Select
|
||||||
|
value={page}
|
||||||
|
onChange={(v) => router.push({ query: { ...router.query, page: v.target.value } })}
|
||||||
|
display={['flex', 'flex', 'none']}
|
||||||
|
textAlign="center"
|
||||||
|
>
|
||||||
|
{new Array(totalPages).fill(true).map((v, i) => (
|
||||||
|
<option value={i + 1} key={i}>
|
||||||
|
{i + 1}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Link
|
||||||
|
//
|
||||||
|
as={NextLink}
|
||||||
|
href={{ query: { ...router.query, page: Math.min(totalPages, page + 1) } }}
|
||||||
|
variant="pagination"
|
||||||
|
textAlign="right"
|
||||||
|
>
|
||||||
|
Next →
|
||||||
|
</Link>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { Directus } from '@directus/sdk'
|
|
||||||
|
|
||||||
const directus = new Directus(process.env.DIRECTUS_API_URL)
|
|
||||||
|
|
||||||
export default directus
|
|
||||||
@@ -16,7 +16,9 @@
|
|||||||
"@directus/sdk": "10.3.3",
|
"@directus/sdk": "10.3.3",
|
||||||
"@emotion/react": "11.11.1",
|
"@emotion/react": "11.11.1",
|
||||||
"@emotion/styled": "11.11.0",
|
"@emotion/styled": "11.11.0",
|
||||||
"next": "13.4.7",
|
"@tanstack/react-query": "4.29.19",
|
||||||
|
"clsx": "1.2.1",
|
||||||
|
"next": "13.4.8",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0"
|
"react-dom": "18.2.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import { ChakraProvider } from '@chakra-ui/react'
|
import { ChakraProvider } from '@chakra-ui/react'
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
import Layout from '~/components/layout'
|
import Layout from '~/components/layout'
|
||||||
|
import theme from './theme'
|
||||||
|
|
||||||
|
const queryClient = new QueryClient()
|
||||||
|
|
||||||
export default function MyApp({ Component, pageProps }) {
|
export default function MyApp({ Component, pageProps }) {
|
||||||
return (
|
return (
|
||||||
<ChakraProvider>
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<ChakraProvider theme={theme}>
|
||||||
<Layout>
|
<Layout>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</Layout>
|
</Layout>
|
||||||
</ChakraProvider>
|
</ChakraProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
40
frontend/pages/theme.js
Normal file
40
frontend/pages/theme.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { extendTheme } from '@chakra-ui/react'
|
||||||
|
import { defineStyle, defineStyleConfig } from '@chakra-ui/react'
|
||||||
|
|
||||||
|
const pagination = defineStyle({
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
bg: 'transparent',
|
||||||
|
gap: 3,
|
||||||
|
paddingInlineStart: 3,
|
||||||
|
paddingInlineEnd: 3,
|
||||||
|
lineHeight: 10,
|
||||||
|
borderRadius: 'md',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'inherit',
|
||||||
|
width: '100%',
|
||||||
|
_hover: {
|
||||||
|
textDecor: 'none',
|
||||||
|
},
|
||||||
|
'&.current': {
|
||||||
|
bg: 'var(--chakra-colors-gray-300)',
|
||||||
|
border: '1px solid transparent',
|
||||||
|
},
|
||||||
|
_dark: {
|
||||||
|
bg: 'var(--chakra-colors-gray-900)',
|
||||||
|
'&.current': {
|
||||||
|
bg: 'var(--chakra-colors-gray-700)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const linkTheme = defineStyleConfig({
|
||||||
|
variants: { pagination },
|
||||||
|
})
|
||||||
|
|
||||||
|
const theme = extendTheme({
|
||||||
|
components: {
|
||||||
|
Link: linkTheme,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default theme
|
||||||
113
frontend/pages/vendors/index.js
vendored
113
frontend/pages/vendors/index.js
vendored
@@ -1,70 +1,62 @@
|
|||||||
import {
|
|
||||||
Pagination,
|
|
||||||
PaginationContainer,
|
|
||||||
PaginationNext,
|
|
||||||
PaginationPage,
|
|
||||||
PaginationPageGroup,
|
|
||||||
PaginationPrevious,
|
|
||||||
usePagination,
|
|
||||||
} from '@ajna/pagination'
|
|
||||||
import { Link } from '@chakra-ui/next-js'
|
import { Link } from '@chakra-ui/next-js'
|
||||||
import { Card, CardBody, Heading, Image, LinkBox, LinkOverlay, SimpleGrid, Text } from '@chakra-ui/react'
|
import { Card, CardBody, Heading, Image, LinkBox, LinkOverlay, SimpleGrid, Text, Skeleton } from '@chakra-ui/react'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import directus from '~/lib/directus'
|
import { useRouter } from 'next/router'
|
||||||
|
import Pagination from '~/components/pagination'
|
||||||
|
|
||||||
const PER_PAGE = 12
|
const PER_PAGE = 12
|
||||||
|
|
||||||
export const getServerSideProps = async ({ query }) => {
|
export default function VendorsPage() {
|
||||||
const page = query?.page || 1
|
|
||||||
const sort = query?.sort || 'name'
|
|
||||||
|
|
||||||
const { data: vendors, meta } = await directus.items('vendors').readByQuery({
|
|
||||||
fields: [
|
|
||||||
//
|
|
||||||
'*',
|
|
||||||
],
|
|
||||||
limit: PER_PAGE,
|
|
||||||
page,
|
|
||||||
meta: ['filter_count'],
|
|
||||||
sort,
|
|
||||||
})
|
|
||||||
return { props: { vendors, meta, page } }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function VendorsPage({ vendors, meta, page }) {
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const {
|
||||||
|
query: { page = 1 },
|
||||||
|
} = router
|
||||||
|
|
||||||
const { pages, pagesCount, currentPage, setCurrentPage, isDisabled } = usePagination({
|
const { data, isFetching } = useQuery({
|
||||||
total: meta.filter_count,
|
queryKey: ['vendors', page],
|
||||||
limits: {
|
queryFn: async (...props2) => {
|
||||||
outer: 1,
|
await new Promise((r) => setTimeout(r, 2000))
|
||||||
inner: 1,
|
|
||||||
},
|
const res = await fetch(
|
||||||
initialState: {
|
`${process.env.NEXT_PUBLIC_DIRECTUS_API_URL}/items/vendors?fields[]=*&limit=12&page=${page}&meta[]=filter_count&sort=name`
|
||||||
pageSize: PER_PAGE,
|
)
|
||||||
isDisabled: false,
|
if (!res.ok) {
|
||||||
currentPage: page,
|
throw new Error('wrong')
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json()
|
||||||
},
|
},
|
||||||
|
keepPreviousData: true,
|
||||||
|
cacheTime: 1000 * 60 * 30,
|
||||||
|
staleTime: 1000 * 60 * 30,
|
||||||
})
|
})
|
||||||
|
|
||||||
const handlePageChange = (nextPage) => {
|
const { meta, data: vendors } = data || { meta: {}, data: [] }
|
||||||
setCurrentPage(nextPage)
|
|
||||||
router.replace(`?page=${nextPage}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Heading size="lg" my={8}>
|
<Heading size="lg" my={8}>
|
||||||
Vendors
|
Vendors
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
<SimpleGrid columns={[1, 2, 3, 4]} spacing={3}>
|
<SimpleGrid columns={[1, 2, 3, 4]} spacing={3}>
|
||||||
{vendors.map((v) => (
|
{isFetching &&
|
||||||
|
new Array(PER_PAGE).fill(true).map((v, k) => (
|
||||||
|
<Card key={k}>
|
||||||
|
<Skeleton h="40" />
|
||||||
|
<CardBody>
|
||||||
|
<Skeleton h="4" />
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
{!isFetching &&
|
||||||
|
vendors.map((v) => (
|
||||||
<LinkBox as={Card} rounded={4} key={v.id}>
|
<LinkBox as={Card} rounded={4} key={v.id}>
|
||||||
<Image
|
<Image
|
||||||
roundedTop={4}
|
roundedTop={4}
|
||||||
objectFit="cover"
|
objectFit="cover"
|
||||||
src="https://images.unsplash.com/photo-1531403009284-440f080d1e12?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=250&q=80"
|
src="https://images.unsplash.com/photo-1531403009284-440f080d1e12?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=250&q=80"
|
||||||
alt="Chakra UI"
|
alt={v.name}
|
||||||
/>
|
/>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<LinkOverlay as={Link} href={`/vendors/${v.slug}`} prefetch={false}>
|
<LinkOverlay as={Link} href={`/vendors/${v.slug}`} prefetch={false}>
|
||||||
@@ -75,34 +67,7 @@ export default function VendorsPage({ vendors, meta, page }) {
|
|||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
|
|
||||||
<Pagination
|
<Pagination page={Number(page)} itemsPerPage={PER_PAGE} totalItems={meta.filter_count} />
|
||||||
pagesCount={pagesCount}
|
|
||||||
currentPage={currentPage}
|
|
||||||
isDisabled={isDisabled}
|
|
||||||
onPageChange={handlePageChange}
|
|
||||||
>
|
|
||||||
<PaginationContainer align="center" my={4} w={'full'} justifyContent={'space-between'}>
|
|
||||||
<PaginationPrevious>
|
|
||||||
<Text>«</Text>
|
|
||||||
</PaginationPrevious>
|
|
||||||
<PaginationPageGroup align="center" mx={4}>
|
|
||||||
{pages.map((page) => (
|
|
||||||
<PaginationPage
|
|
||||||
w={10}
|
|
||||||
key={`pagination_page_${page}`}
|
|
||||||
page={page}
|
|
||||||
fontSize="sm"
|
|
||||||
_current={{
|
|
||||||
bg: 'gray.400',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</PaginationPageGroup>
|
|
||||||
<PaginationNext>
|
|
||||||
<Text>»</Text>
|
|
||||||
</PaginationNext>
|
|
||||||
</PaginationContainer>
|
|
||||||
</Pagination>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
1681
frontend/pnpm-lock.yaml
generated
1681
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user