Skip to content

Commit

Permalink
♻️ Refactor pagination (#527)
Browse files Browse the repository at this point in the history
  • Loading branch information
alejsdev authored Dec 2, 2024
1 parent cf522c0 commit 18ce54c
Show file tree
Hide file tree
Showing 9 changed files with 360 additions and 190 deletions.
2 changes: 1 addition & 1 deletion frontend/src/components/AppSettings/Deployments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const Deployments = ({
<>
{deployments?.length > 0 ? (
<>
<Table.Root variant="outline" mt="4">
<Table.Root variant="outline" mt="4" interactive>
<Table.Header>
<Table.Row>
{headers.map((header) => (
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Billing/Billing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function BillingTableBody() {
]

return (
<Table.Root key={id}>
<Table.Root key={id} interactive>
{data.map((item, index) => (
<Table.Cell key={index}>{item}</Table.Cell>
))}
Expand Down
5 changes: 1 addition & 4 deletions frontend/src/components/Common/QuickStart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ const QuickStart = () => {
</Text>
<Text>
You can learn more in the{" "}
<Link color="main.dark" fontWeight="bold">
FastAPI CLI documentation
</Link>
.
<Link className="main-link">FastAPI CLI documentation</Link>.
</Text>
</VStack>
</CustomCard>
Expand Down
108 changes: 50 additions & 58 deletions frontend/src/components/Invitations/Invitations.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import { Badge, Box, Center, Container, Flex, Table } from "@chakra-ui/react"
import { useQuery, useQueryClient } from "@tanstack/react-query"
import { useEffect, useState } from "react"
import {
Badge,
Box,
Center,
Container,
Flex,
HStack,
Table,
} from "@chakra-ui/react"
import { useQuery } from "@tanstack/react-query"
import { useState } from "react"
import { ErrorBoundary } from "react-error-boundary"

import { EmailPending } from "@/assets/icons"
import { InvitationsService } from "@/client/services"
import { Button } from "@/components//ui/button"
import {
PaginationItems,
PaginationNextTrigger,
PaginationPrevTrigger,
PaginationRoot,
} from "@/components/ui/pagination"
import { Skeleton } from "@/components/ui/skeleton"
import EmptyState from "../Common/EmptyState"
import CancelInvitation from "./CancelInvitation"
Expand All @@ -27,7 +40,6 @@ const getInvitationsQueryOptions = ({
})

function Invitations({ teamId }: { teamId: string }) {
const queryClient = useQueryClient()
const [page, setPage] = useState(1)
const {
data: invitations,
Expand All @@ -38,26 +50,20 @@ function Invitations({ teamId }: { teamId: string }) {
placeholderData: (previous) => previous,
})

const hasNextPage = invitations?.data.length === PER_PAGE + 1
const invitationsData = invitations?.data.slice(0, PER_PAGE)
const hasPreviousPage = page > 1

// biome-ignore lint/correctness/useExhaustiveDependencies(a): getInvitationsQueryOptions does not need to be included in the dependencies
useEffect(() => {
if (hasNextPage) {
queryClient.prefetchQuery(
getInvitationsQueryOptions({ teamId, page: page + 1 }),
)
}
}, [page, queryClient, hasNextPage, teamId])
const invitationsCount = invitations?.count ?? 0

const headers = ["Email", "Status", "Actions"]

return (
<>
{(invitationsData?.length ?? 0) > 0 ? (
<Container maxW="full" p={0}>
<Table.Root size={{ base: "sm", md: "md" }} variant="outline">
<Table.Root
size={{ base: "sm", md: "md" }}
variant="outline"
interactive
>
<Table.Header>
<Table.Row>
{headers.map((header) => (
Expand All @@ -83,59 +89,45 @@ function Invitations({ teamId }: { teamId: string }) {
)}
>
<Table.Body>
{isLoading ? (
<>
{new Array(3).fill(null).map((_, index) => (
{isLoading
? new Array(3).fill(null).map((_, index) => (
<Table.Row key={index}>
<Table.Cell colSpan={5}>
<Box width="100%">
<Skeleton height="20px" />
</Box>
</Table.Cell>
</Table.Row>
))
: invitationsData?.map(({ id, status, email }) => (
<Table.Row key={id} opacity={isPlaceholderData ? 0.5 : 1}>
<Table.Cell truncate maxWidth="200px">
{email}
</Table.Cell>
<Table.Cell textTransform="capitalize">
<Badge colorScheme="orange">{status}</Badge>
</Table.Cell>
<Table.Cell>
<CancelInvitation id={id} />
</Table.Cell>
</Table.Row>
))}
</>
) : (
invitationsData?.map(({ id, status, email }) => (
<Table.Row key={id} opacity={isPlaceholderData ? 0.5 : 1}>
<Table.Cell truncate maxWidth="200px">
{email}
</Table.Cell>
<Table.Cell textTransform="capitalize">
<Badge colorScheme="orange">{status}</Badge>
</Table.Cell>
<Table.Cell>
<CancelInvitation id={id} />
</Table.Cell>
</Table.Row>
))
)}
</Table.Body>
</ErrorBoundary>
</Table.Root>
{(hasPreviousPage || hasNextPage) && (
<Flex
gap={4}
alignItems="center"
mt={4}
direction="row"
justifyContent="flex-end"
<Flex justifyContent="flex-end" mt={4}>
<PaginationRoot
count={invitationsCount}
pageSize={PER_PAGE}
onPageChange={({ page }) => setPage(page)}
>
<Button
onClick={() => setPage((p) => p - 1)}
disabled={!hasPreviousPage}
>
Previous
</Button>
<span>Page {page}</span>
<Button
disabled={!hasNextPage}
onClick={() => setPage((p) => p + 1)}
>
Next
</Button>
</Flex>
)}
<HStack>
<PaginationPrevTrigger />
<PaginationItems />
<PaginationNextTrigger />
</HStack>
</PaginationRoot>
</Flex>
</Container>
) : (
<Center w="full">
Expand Down
60 changes: 32 additions & 28 deletions frontend/src/components/Teams/Team.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import { Badge, Box, Container, Flex, Skeleton, Table } from "@chakra-ui/react"
import {
Badge,
Box,
Container,
Flex,
HStack,
Skeleton,
Table,
} from "@chakra-ui/react"
import { useSuspenseQuery } from "@tanstack/react-query"
import { Suspense, useState } from "react"
import { ErrorBoundary } from "react-error-boundary"

import { useCurrentUser } from "../../hooks/useAuth"
import { Route } from "../../routes/_layout/$team"
import { fetchTeamBySlug, getCurrentUserRole } from "../../utils"
import {
PaginationItems,
PaginationNextTrigger,
PaginationPrevTrigger,
PaginationRoot,
} from "@/components/ui/pagination"
import { useCurrentUser } from "@/hooks/useAuth"
import { Route } from "@/routes/_layout/$team"
import { fetchTeamBySlug, getCurrentUserRole } from "@/utils"
import ActionsMenu from "../Common/ActionsMenu"
import { Button } from "../ui/button"

const PER_PAGE = 5

Expand All @@ -21,9 +34,7 @@ function Team() {
})

const members = team.user_links.slice((page - 1) * PER_PAGE, page * PER_PAGE)
const hasNextPage = team.user_links.length > page * PER_PAGE
const hasPreviousPage = page > 1

const membersCount = team.user_links.length
const currentUserRole = getCurrentUserRole(team, currentUser)

const headers = ["Email", "Role"]
Expand All @@ -33,7 +44,7 @@ function Team() {

return (
<Container maxW="full" p={0}>
<Table.Root size={{ base: "sm", md: "md" }} variant="outline">
<Table.Root size={{ base: "sm", md: "md" }} variant="outline" interactive>
<Table.Header>
<Table.Row>
{headers.map((header) => (
Expand Down Expand Up @@ -99,26 +110,19 @@ function Team() {
</Suspense>
</ErrorBoundary>
</Table.Root>
{(hasPreviousPage || hasNextPage) && (
<Flex
gap={4}
alignItems="center"
mt={4}
direction="row"
justifyContent="flex-end"
<Flex justifyContent="flex-end" mt={4}>
<PaginationRoot
count={membersCount}
pageSize={PER_PAGE}
onPageChange={({ page }) => setPage(page)}
>
<Button
onClick={() => setPage((p) => p - 1)}
disabled={!hasPreviousPage}
>
Previous
</Button>
<span>Page {page}</span>
<Button onClick={() => setPage((p) => p + 1)} disabled={!hasNextPage}>
Next
</Button>
</Flex>
)}
<HStack>
<PaginationPrevTrigger />
<PaginationItems />
<PaginationNextTrigger />
</HStack>
</PaginationRoot>
</Flex>
</Container>
)
}
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/components/ui/link-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use client"

import type { HTMLChakraProps, RecipeProps } from "@chakra-ui/react"
import { createRecipeContext } from "@chakra-ui/react"

export interface LinkButtonProps
extends HTMLChakraProps<"a", RecipeProps<"button">> {}

const { withContext } = createRecipeContext({ key: "button" })

// Replace "a" with your framework's link component
export const LinkButton = withContext<HTMLAnchorElement, LinkButtonProps>("a")
Loading

0 comments on commit 18ce54c

Please sign in to comment.