11import {
22 Badge ,
33 Box ,
4+ Button ,
45 Container ,
56 Flex ,
67 Heading ,
@@ -13,90 +14,65 @@ import {
1314 Thead ,
1415 Tr ,
1516} from "@chakra-ui/react"
16- import { useQueryClient , useSuspenseQuery } from "@tanstack/react-query"
17- import { createFileRoute } from "@tanstack/react-router"
17+ import { useQuery , useQueryClient } from "@tanstack/react-query"
18+ import { createFileRoute , useNavigate } from "@tanstack/react-router"
19+ import { useEffect } from "react"
20+ import { z } from "zod"
1821
19- import { Suspense } from "react"
2022import { type UserPublic , UsersService } from "../../client"
2123import AddUser from "../../components/Admin/AddUser"
2224import ActionsMenu from "../../components/Common/ActionsMenu"
2325import Navbar from "../../components/Common/Navbar"
2426
27+ const usersSearchSchema = z . object ( {
28+ page : z . number ( ) . catch ( 1 ) ,
29+ } )
30+
2531export const Route = createFileRoute ( "/_layout/admin" ) ( {
2632 component : Admin ,
33+ validateSearch : ( search ) => usersSearchSchema . parse ( search ) ,
2734} )
2835
29- const MembersTableBody = ( ) => {
36+ const PER_PAGE = 5
37+
38+ function getUsersQueryOptions ( { page } : { page : number } ) {
39+ return {
40+ queryFn : ( ) =>
41+ UsersService . readUsers ( { skip : ( page - 1 ) * PER_PAGE , limit : PER_PAGE } ) ,
42+ queryKey : [ "users" , { page } ] ,
43+ }
44+ }
45+
46+ function UsersTable ( ) {
3047 const queryClient = useQueryClient ( )
3148 const currentUser = queryClient . getQueryData < UserPublic > ( [ "currentUser" ] )
49+ const { page } = Route . useSearch ( )
50+ const navigate = useNavigate ( { from : Route . fullPath } )
51+ const setPage = ( page : number ) =>
52+ navigate ( { search : ( prev ) => ( { ...prev , page } ) } )
3253
33- const { data : users } = useSuspenseQuery ( {
34- queryKey : [ "users" ] ,
35- queryFn : ( ) => UsersService . readUsers ( { } ) ,
54+ const {
55+ data : users ,
56+ isPending,
57+ isPlaceholderData,
58+ } = useQuery ( {
59+ ...getUsersQueryOptions ( { page } ) ,
60+ placeholderData : ( prevData ) => prevData ,
3661 } )
3762
38- return (
39- < Tbody >
40- { users . data . map ( ( user ) => (
41- < Tr key = { user . id } >
42- < Td color = { ! user . full_name ? "ui.dim" : "inherit" } >
43- { user . full_name || "N/A" }
44- { currentUser ?. id === user . id && (
45- < Badge ml = "1" colorScheme = "teal" >
46- You
47- </ Badge >
48- ) }
49- </ Td >
50- < Td > { user . email } </ Td >
51- < Td > { user . is_superuser ? "Superuser" : "User" } </ Td >
52- < Td >
53- < Flex gap = { 2 } >
54- < Box
55- w = "2"
56- h = "2"
57- borderRadius = "50%"
58- bg = { user . is_active ? "ui.success" : "ui.danger" }
59- alignSelf = "center"
60- />
61- { user . is_active ? "Active" : "Inactive" }
62- </ Flex >
63- </ Td >
64- < Td >
65- < ActionsMenu
66- type = "User"
67- value = { user }
68- disabled = { currentUser ?. id === user . id ? true : false }
69- />
70- </ Td >
71- </ Tr >
72- ) ) }
73- </ Tbody >
74- )
75- }
63+ const hasNextPage = ! isPlaceholderData && users ?. data . length === PER_PAGE
64+ const hasPreviousPage = page > 1
7665
77- const MembersBodySkeleton = ( ) => {
78- return (
79- < Tbody >
80- < Tr >
81- { new Array ( 5 ) . fill ( null ) . map ( ( _ , index ) => (
82- < Td key = { index } >
83- < SkeletonText noOfLines = { 1 } paddingBlock = "16px" />
84- </ Td >
85- ) ) }
86- </ Tr >
87- </ Tbody >
88- )
89- }
66+ useEffect ( ( ) => {
67+ if ( hasNextPage ) {
68+ queryClient . prefetchQuery ( getUsersQueryOptions ( { page : page + 1 } ) )
69+ }
70+ } , [ page , queryClient , hasNextPage ] )
9071
91- function Admin ( ) {
9272 return (
93- < Container maxW = "full" >
94- < Heading size = "lg" textAlign = { { base : "center" , md : "left" } } pt = { 12 } >
95- User Management
96- </ Heading >
97- < Navbar type = { "User" } addModalAs = { AddUser } />
73+ < >
9874 < TableContainer >
99- < Table fontSize = "md" size = { { base : "sm" , md : "md" } } >
75+ < Table size = { { base : "sm" , md : "md" } } >
10076 < Thead >
10177 < Tr >
10278 < Th width = "20%" > Full name</ Th >
@@ -106,11 +82,89 @@ function Admin() {
10682 < Th width = "10%" > Actions</ Th >
10783 </ Tr >
10884 </ Thead >
109- < Suspense fallback = { < MembersBodySkeleton /> } >
110- < MembersTableBody />
111- </ Suspense >
85+ { isPending ? (
86+ < Tbody >
87+ < Tr >
88+ { new Array ( 4 ) . fill ( null ) . map ( ( _ , index ) => (
89+ < Td key = { index } >
90+ < SkeletonText noOfLines = { 1 } paddingBlock = "16px" />
91+ </ Td >
92+ ) ) }
93+ </ Tr >
94+ </ Tbody >
95+ ) : (
96+ < Tbody >
97+ { users ?. data . map ( ( user ) => (
98+ < Tr key = { user . id } >
99+ < Td
100+ color = { ! user . full_name ? "ui.dim" : "inherit" }
101+ isTruncated
102+ maxWidth = "150px"
103+ >
104+ { user . full_name || "N/A" }
105+ { currentUser ?. id === user . id && (
106+ < Badge ml = "1" colorScheme = "teal" >
107+ You
108+ </ Badge >
109+ ) }
110+ </ Td >
111+ < Td isTruncated maxWidth = "150px" >
112+ { user . email }
113+ </ Td >
114+ < Td > { user . is_superuser ? "Superuser" : "User" } </ Td >
115+ < Td >
116+ < Flex gap = { 2 } >
117+ < Box
118+ w = "2"
119+ h = "2"
120+ borderRadius = "50%"
121+ bg = { user . is_active ? "ui.success" : "ui.danger" }
122+ alignSelf = "center"
123+ />
124+ { user . is_active ? "Active" : "Inactive" }
125+ </ Flex >
126+ </ Td >
127+ < Td >
128+ < ActionsMenu
129+ type = "User"
130+ value = { user }
131+ disabled = { currentUser ?. id === user . id ? true : false }
132+ />
133+ </ Td >
134+ </ Tr >
135+ ) ) }
136+ </ Tbody >
137+ ) }
112138 </ Table >
113139 </ TableContainer >
140+ < Flex
141+ gap = { 4 }
142+ alignItems = "center"
143+ mt = { 4 }
144+ direction = "row"
145+ justifyContent = "flex-end"
146+ >
147+ < Button onClick = { ( ) => setPage ( page - 1 ) } isDisabled = { ! hasPreviousPage } >
148+ Previous
149+ </ Button >
150+ < span > Page { page } </ span >
151+ < Button isDisabled = { ! hasNextPage } onClick = { ( ) => setPage ( page + 1 ) } >
152+ Next
153+ </ Button >
154+ </ Flex >
155+ </ >
156+ )
157+ }
158+
159+ function Admin ( ) {
160+ return (
161+ < Container maxW = "full" >
162+ < Heading size = "lg" textAlign = { { base : "center" , md : "left" } } pt = { 12 } >
163+ Users Management
164+ </ Heading >
165+
166+ < Navbar type = { "User" } addModalAs = { AddUser } />
167+ < UsersTable />
114168 </ Container >
115169 )
116170}
0 commit comments