Skip to content

Commit fcfed65

Browse files
authored
Merge pull request #499 from STAPLE-verse/498-only-100-notifications-load-in-the-tables
498 only 100 notifications load in the tables
2 parents c3024d4 + b0cbe02 commit fcfed65

45 files changed

Lines changed: 1063 additions & 222 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/comments/queries/getComments.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,12 @@ interface GetCommentsInput
88

99
export default resolver.pipe(
1010
resolver.authorize(),
11-
async ({
12-
where,
13-
orderBy,
14-
skip = 0,
15-
take = 100,
16-
}: GetCommentsInput): Promise<CommentWithAuthor[]> => {
11+
async ({ where, orderBy, skip = 0, take }: GetCommentsInput): Promise<CommentWithAuthor[]> => {
1712
const comments = await db.comment.findMany({
1813
where,
1914
orderBy: orderBy || { createdAt: "asc" }, // Default ordering by creation date
2015
skip,
21-
take,
16+
...(typeof take === "number" ? { take } : {}),
2217
include: {
2318
author: {
2419
include: {

src/contributors/components/ContributorInformation.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,15 @@ const ContributorInformation = ({
4747
})
4848

4949
// get taskLogs for those tasks
50-
const [fetchedTaskLogs, { refetch: refetchTaskLogs }] = useQuery(getTaskLogs, {
50+
const [{ taskLogs: fetchedTaskLogs = [] }, { refetch: refetchTaskLogs }] = useQuery(getTaskLogs, {
5151
where: {
5252
taskId: { in: tasks.map((task) => task.id) },
5353
assignedToId: contributorId,
5454
},
5555
include: {
5656
task: true,
5757
},
58-
}) as unknown as [TaskLogWithTask[], { refetch: () => Promise<any> }]
58+
})
5959

6060
useEffect(() => {
6161
const handleUpdate = () => {
@@ -67,7 +67,7 @@ const ContributorInformation = ({
6767
}, [refetchTasks, refetchTaskLogs])
6868

6969
// Cast and handle the possibility of `undefined`
70-
const taskLogs: TaskLogWithTask[] = (fetchedTaskLogs ?? []) as TaskLogWithTask[]
70+
const taskLogs: TaskLogWithTask[] = fetchedTaskLogs as TaskLogWithTask[]
7171

7272
// only the latest task log
7373
const allTaskLogs = getLatestTaskLogs<TaskLogWithTask>(taskLogs)
Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useQuery } from "@blitzjs/rpc"
1+
import { usePaginatedQuery } from "@blitzjs/rpc"
22
import getContributors from "src/contributors/queries/getContributors"
33
import {
44
processContributor,
@@ -7,27 +7,53 @@ import {
77
import { MemberPrivileges } from "@prisma/client"
88
import { useMemo } from "react"
99
import { CurrentUser } from "src/users/queries/getCurrentUser"
10-
import { ProjectMemberWithUsers } from "src/core/types"
10+
import { PaginationState } from "@tanstack/react-table"
11+
12+
type UseContributorsDataResult = {
13+
data: ContributorTableData[]
14+
count: number
15+
refetch: () => Promise<any>
16+
}
1117

1218
export function useContributorsData(
1319
privilege: MemberPrivileges,
1420
currentUser: CurrentUser,
15-
projectId: number
16-
): ContributorTableData[] {
17-
// Fetch
18-
const [contributors] = useQuery(getContributors, { projectId: projectId, deleted: false })
21+
projectId: number,
22+
pagination?: PaginationState
23+
): UseContributorsDataResult {
24+
const shouldPaginate = privilege !== MemberPrivileges.CONTRIBUTOR && Boolean(pagination)
25+
26+
const baseArgs = {
27+
projectId,
28+
deleted: false,
29+
orderBy: { id: "asc" as const },
30+
}
31+
32+
const skip = pagination ? pagination.pageIndex * pagination.pageSize : 0
33+
const take = pagination ? pagination.pageSize : undefined
34+
35+
const [{ contributors, count }, { refetch }] = usePaginatedQuery(getContributors, {
36+
...baseArgs,
37+
skip,
38+
take,
39+
})
1940

20-
// Filter based on privilege
2141
const filteredContributors = useMemo(() => {
2242
if (privilege === MemberPrivileges.CONTRIBUTOR) {
2343
return contributors.filter(
24-
(contributor: ProjectMemberWithUsers) =>
44+
(contributor) =>
2545
contributor.users.length === 1 && contributor.users[0]?.id === currentUser.id
2646
)
2747
}
2848
return contributors
2949
}, [contributors, privilege, currentUser.id])
3050

31-
// Process the data for table rendering
32-
return processContributor(filteredContributors, projectId)
51+
const resultCount =
52+
privilege === MemberPrivileges.CONTRIBUTOR ? filteredContributors.length : count
53+
54+
return {
55+
data: processContributor(filteredContributors, projectId),
56+
count: resultCount,
57+
refetch,
58+
}
3359
}
Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,76 @@
11
import { resolver } from "@blitzjs/rpc"
2-
import db from "db"
2+
import db, { Prisma } from "db"
33
import { ProjectMemberWithUsers } from "src/core/types"
44
import { anonymizeNestedUsers } from "src/core/utils/anonymizeNestedUsers"
5+
import { paginate } from "blitz"
56

6-
interface GetContributorsInput {
7+
interface GetContributorsInput
8+
extends Pick<Prisma.ProjectMemberFindManyArgs, "skip" | "take" | "orderBy"> {
79
projectId: number
810
deleted?: boolean
911
}
1012

13+
const validateContributors = (contributors: ProjectMemberWithUsers[]) => {
14+
contributors.forEach((contributor) => {
15+
if (contributor.users.length !== 1) {
16+
throw new Error(
17+
`Contributor with ID ${contributor.id} has ${contributor.users.length} users! Expected exactly 1.`
18+
)
19+
}
20+
})
21+
}
22+
1123
export default resolver.pipe(
1224
resolver.authorize(),
13-
async ({ projectId, deleted }: GetContributorsInput): Promise<ProjectMemberWithUsers[]> => {
14-
// Directly query the database for contributors
15-
const contributors = await db.projectMember.findMany({
16-
where: {
17-
projectId: projectId,
18-
deleted: deleted,
19-
name: null, // Ensures we're only getting contributors (where name is null)
20-
},
21-
orderBy: { id: "asc" },
22-
include: {
23-
users: true, // Include the related user
24-
},
25-
})
25+
async ({ projectId, deleted, skip = 0, take, orderBy = { id: "asc" } }: GetContributorsInput) => {
26+
const baseWhere: Prisma.ProjectMemberWhereInput = {
27+
projectId,
28+
deleted,
29+
name: null,
30+
}
31+
32+
if (typeof take !== "number") {
33+
const [contributors, count] = await Promise.all([
34+
db.projectMember.findMany({
35+
where: baseWhere,
36+
orderBy,
37+
include: {
38+
users: true,
39+
},
40+
skip,
41+
}),
42+
db.projectMember.count({ where: baseWhere }),
43+
])
2644

27-
// Check if any contributor has more than one user and throw an error
28-
contributors.forEach((contributor) => {
29-
if (contributor.users.length !== 1) {
30-
throw new Error(
31-
`Contributor with ID ${contributor.id} has ${contributor.users.length} users! Expected exactly 1.`
32-
)
33-
}
45+
const processed = anonymizeNestedUsers(contributors) as ProjectMemberWithUsers[]
46+
validateContributors(processed)
47+
48+
return { contributors: processed, nextPage: null, hasMore: false, count }
49+
}
50+
51+
const {
52+
items: contributors,
53+
hasMore,
54+
nextPage,
55+
count,
56+
} = await paginate({
57+
skip,
58+
take,
59+
count: () => db.projectMember.count({ where: baseWhere }),
60+
query: (paginateArgs) =>
61+
db.projectMember.findMany({
62+
...paginateArgs,
63+
where: baseWhere,
64+
orderBy,
65+
include: {
66+
users: true,
67+
},
68+
}),
3469
})
3570

36-
return anonymizeNestedUsers(contributors) // This is automatically typed as ProjectMemberWithUsers[]
71+
const processed = anonymizeNestedUsers(contributors) as ProjectMemberWithUsers[]
72+
validateContributors(processed)
73+
74+
return { contributors: processed, hasMore, nextPage, count }
3775
}
3876
)

src/core/components/Table.tsx

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
useReactTable,
1010
getPaginationRowModel,
1111
getFacetedMinMaxValues,
12+
PaginationState,
13+
OnChangeFn,
1214
} from "@tanstack/react-table"
1315
import React from "react"
1416

@@ -131,6 +133,11 @@ type TableProps<TData> = {
131133
enableGlobalSearch?: boolean
132134
globalSearchPlaceholder?: string
133135
addPagination?: boolean
136+
manualPagination?: boolean
137+
paginationState?: PaginationState
138+
onPaginationChange?: OnChangeFn<PaginationState>
139+
pageCount?: number
140+
pageSizeOptions?: number[]
134141
classNames?: {
135142
table?: string
136143
thead?: string
@@ -179,9 +186,26 @@ const Table = <TData,>({
179186
enableGlobalSearch = true,
180187
globalSearchPlaceholder = "Search...",
181188
addPagination = false,
189+
manualPagination = false,
190+
paginationState,
191+
onPaginationChange,
192+
pageCount: controlledPageCount,
193+
pageSizeOptions = [5, 10, 20, 30, 40, 50],
182194
}: TableProps<TData>) => {
183195
const [sorting, setSorting] = React.useState([])
184196
const [globalFilter, setGlobalFilter] = React.useState("")
197+
const [internalPagination, setInternalPagination] = React.useState<PaginationState>({
198+
pageIndex: 0,
199+
pageSize: 5,
200+
})
201+
202+
const resolvedPaginationState = manualPagination
203+
? paginationState ?? { pageIndex: 0, pageSize: 5 }
204+
: internalPagination
205+
206+
const handlePaginationChange: OnChangeFn<PaginationState> = manualPagination
207+
? onPaginationChange ?? (() => {})
208+
: setInternalPagination
185209

186210
const table = useReactTable({
187211
data,
@@ -192,19 +216,18 @@ const Table = <TData,>({
192216
getSortedRowModel: getSortedRowModel(),
193217
getFilteredRowModel: getFilteredRowModel(),
194218
getFacetedUniqueValues: getFacetedUniqueValues(),
195-
getPaginationRowModel: getPaginationRowModel(),
219+
...(manualPagination ? {} : { getPaginationRowModel: getPaginationRowModel() }),
196220
getFacetedMinMaxValues: getFacetedMinMaxValues(),
221+
manualPagination,
222+
pageCount: manualPagination ? controlledPageCount : undefined,
197223
state: {
198224
sorting: sorting,
199225
globalFilter: globalFilter,
200-
},
201-
initialState: {
202-
pagination: {
203-
pageSize: 5,
204-
},
226+
pagination: resolvedPaginationState,
205227
},
206228
onSortingChange: setSorting,
207229
onGlobalFilterChange: setGlobalFilter,
230+
onPaginationChange: handlePaginationChange,
208231
globalFilterFn: defaultGlobalFilterFn,
209232
autoResetPageIndex: false,
210233
})
@@ -220,10 +243,10 @@ const Table = <TData,>({
220243
return
221244
}
222245

223-
if (pageCount > 0 && pageIndex >= pageCount) {
246+
if (!manualPagination && pageCount > 0 && pageIndex >= pageCount) {
224247
table.setPageIndex(0)
225248
}
226-
}, [addPagination, pageCount, pageIndex, table])
249+
}, [addPagination, pageCount, pageIndex, table, manualPagination])
227250

228251
return (
229252
<>
@@ -386,7 +409,7 @@ const Table = <TData,>({
386409
classNames?.pageSizeSelect || ""
387410
}`}
388411
>
389-
{[5, 10, 20, 30, 40, 50].map((pageSize) => (
412+
{pageSizeOptions.map((pageSize) => (
390413
<option key={pageSize} value={pageSize}>
391414
Show {pageSize}
392415
</option>

src/core/components/fields/MultiSelectCheckbox.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ type MultiSelectCheckboxProps = {
66
}
77

88
export const MultiSelectCheckbox = React.memo(({ id }: MultiSelectCheckboxProps) => {
9-
const { selectedIds, toggleSelection } = useMultiSelect()
9+
const { selectedIds, toggleSelection, isGlobalSelection } = useMultiSelect()
10+
const isChecked = isGlobalSelection || selectedIds.includes(id)
1011

1112
return (
1213
<div>
@@ -15,7 +16,8 @@ export const MultiSelectCheckbox = React.memo(({ id }: MultiSelectCheckboxProps)
1516
<input
1617
type="checkbox"
1718
className="checkbox checkbox-primary border-2"
18-
checked={selectedIds.includes(id)}
19+
checked={isChecked}
20+
disabled={isGlobalSelection}
1921
onChange={() => toggleSelection(id)}
2022
/>
2123
</label>

src/core/components/fields/MultiSelectContext.tsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ interface MultiSelectContextType {
66
toggleSelection: (selectedId: number) => void
77
resetSelection: () => void
88
handleBulkSelection: (selectedIds: number[], isSelectAll: boolean) => void
9+
isGlobalSelection: boolean
10+
enableGlobalSelection: () => void
11+
disableGlobalSelection: () => void
912
}
1013

1114
// Create the context
@@ -14,26 +17,49 @@ const MultiSelectContext = createContext<MultiSelectContextType | undefined>(und
1417
// Context provider component
1518
export const MultiSelectProvider = ({ children }: { children?: ReactNode }) => {
1619
const [selectedIds, setSelectedIds] = useState<number[]>([])
20+
const [isGlobalSelection, setIsGlobalSelection] = useState(false)
1721

1822
// Toggle individual selection
1923
const toggleSelection = (selectedId: number) => {
24+
setIsGlobalSelection(false)
2025
setSelectedIds((prev) =>
2126
prev.includes(selectedId) ? prev.filter((item) => item !== selectedId) : [...prev, selectedId]
2227
)
2328
}
2429

2530
// Handle bulk selection (select/deselect all)
2631
const handleBulkSelection = (selectedIds: number[], isSelectAll: boolean) => {
32+
setIsGlobalSelection(false)
2733
setSelectedIds((prev) => (isSelectAll ? [...new Set([...prev, ...selectedIds])] : []))
2834
}
2935

3036
// Add resetSelection to clear all selected IDs
31-
const resetSelection = () => setSelectedIds([])
37+
const resetSelection = () => {
38+
setIsGlobalSelection(false)
39+
setSelectedIds([])
40+
}
41+
42+
const enableGlobalSelection = () => {
43+
setIsGlobalSelection(true)
44+
setSelectedIds([])
45+
}
46+
47+
const disableGlobalSelection = () => {
48+
setIsGlobalSelection(false)
49+
}
3250

3351
// Provide the selectedIds and the handler to children components
3452
return (
3553
<MultiSelectContext.Provider
36-
value={{ selectedIds, toggleSelection, resetSelection, handleBulkSelection }}
54+
value={{
55+
selectedIds,
56+
toggleSelection,
57+
resetSelection,
58+
handleBulkSelection,
59+
isGlobalSelection,
60+
enableGlobalSelection,
61+
disableGlobalSelection,
62+
}}
3763
>
3864
{children}
3965
</MultiSelectContext.Provider>

0 commit comments

Comments
 (0)