Skip to content

Commit 6a37c3c

Browse files
committed
Students list pagination
1 parent 6377244 commit 6a37c3c

File tree

5 files changed

+81
-31
lines changed

5 files changed

+81
-31
lines changed

src/backend/routers/case_manager.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ test("getMyStudentsAndIepInfo - student does not have IEP", async (t) => {
6161
.executeTakeFirstOrThrow();
6262

6363
const myStudents = await trpc.case_manager.getMyStudentsAndIepInfo.query();
64-
t.is(myStudents.length, 1);
65-
t.is(myStudents[0].student_id, student.student_id);
66-
t.is(myStudents[0].iep_id, null);
67-
t.is(myStudents[0].end_date, null);
64+
t.is(myStudents.records.length, 1);
65+
t.is(myStudents.records[0].student_id, student.student_id);
66+
t.is(myStudents.records[0].iep_id, null);
67+
t.is(myStudents.records[0].end_date, null);
6868
});
6969

7070
test("getMyStudentsAndIepInfo - student has IEP", async (t) => {
@@ -81,8 +81,8 @@ test("getMyStudentsAndIepInfo - student has IEP", async (t) => {
8181

8282
const myStudentsBefore =
8383
await trpc.case_manager.getMyStudentsAndIepInfo.query();
84-
t.is(myStudentsBefore[0].iep_id, null);
85-
t.is(myStudentsBefore[0].end_date, null);
84+
t.is(myStudentsBefore.records[0].iep_id, null);
85+
t.is(myStudentsBefore.records[0].end_date, null);
8686

8787
const iep = await trpc.student.addIep.mutate({
8888
student_id: seed.student.student_id,
@@ -92,8 +92,8 @@ test("getMyStudentsAndIepInfo - student has IEP", async (t) => {
9292

9393
const myStudentsAfter =
9494
await trpc.case_manager.getMyStudentsAndIepInfo.query();
95-
t.is(myStudentsAfter[0].iep_id, iep.iep_id);
96-
t.deepEqual(myStudentsAfter[0].end_date, iep.end_date);
95+
t.is(myStudentsAfter.records[0].iep_id, iep.iep_id);
96+
t.deepEqual(myStudentsAfter.records[0].end_date, iep.end_date);
9797
});
9898

9999
test("getMyStudentsAndIepInfo - paras do not have access", async (t) => {

src/backend/routers/case_manager.ts

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export const case_manager = router({
2828
.input(
2929
z
3030
.object({
31+
page: z.number().min(1).optional(),
32+
pageSize: z.number().min(1).optional(),
3133
search: z.string().optional(),
3234
sort: z.string().optional(),
3335
sortAsc: z.coerce.boolean().optional(),
@@ -36,15 +38,28 @@ export const case_manager = router({
3638
)
3739
.query(async (req) => {
3840
const { userId } = req.ctx.auth;
39-
const { search, sort, sortAsc = true } = req.input ?? {};
41+
const {
42+
search,
43+
sort,
44+
sortAsc = true,
45+
page = 1,
46+
pageSize = 25,
47+
} = req.input ?? {};
4048

4149
let query = req.ctx.db
42-
.selectFrom("iep")
43-
.fullJoin("student", (join) =>
50+
.selectFrom("student")
51+
.leftJoin("iep", (join) =>
4452
join.onRef("student.student_id", "=", "iep.student_id")
4553
)
54+
.select([
55+
"student.student_id as student_id",
56+
"first_name",
57+
"last_name",
58+
"student.grade as grade",
59+
"iep.iep_id as iep_id",
60+
"iep.end_date as end_date",
61+
])
4662
.where("assigned_case_manager_id", "=", userId);
47-
4863
if (search) {
4964
query = query.where((eb) =>
5065
eb.or([
@@ -53,16 +68,9 @@ export const case_manager = router({
5368
])
5469
);
5570
}
56-
57-
const result = await query
58-
.select([
59-
"student.student_id as student_id",
60-
"first_name",
61-
"last_name",
62-
"student.grade as grade",
63-
"iep.iep_id as iep_id",
64-
"iep.end_date as end_date",
65-
])
71+
query = query
72+
.limit(pageSize)
73+
.offset((page - 1) * pageSize)
6674
.orderBy(
6775
(eb) => {
6876
switch (sort) {
@@ -77,10 +85,31 @@ export const case_manager = router({
7785
}
7886
},
7987
sortAsc ? "asc" : "desc"
80-
)
81-
.execute();
88+
);
8289

83-
return result;
90+
let countQuery = req.ctx.db
91+
.selectFrom("student")
92+
.select(req.ctx.db.fn.countAll().as("count"))
93+
.where("assigned_case_manager_id", "=", userId);
94+
if (search) {
95+
countQuery = countQuery.where((eb) =>
96+
eb.or([
97+
eb("student.first_name", "ilike", `%${search}%`),
98+
eb("student.last_name", "ilike", `%${search}%`),
99+
])
100+
);
101+
}
102+
103+
const [records, totalCount] = await Promise.all([
104+
query.execute(),
105+
countQuery.executeTakeFirst(),
106+
]);
107+
108+
return {
109+
records,
110+
totalCount: Number(totalCount?.count ?? 0),
111+
totalPages: Math.ceil(Number(totalCount?.count ?? 0) / pageSize),
112+
};
84113
}),
85114

86115
/**

src/components/design_system/dataTable/DataTable.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
TableCell,
88
TableSortLabel,
99
TableBody,
10+
Typography,
1011
} from "@mui/material";
1112

1213
export interface DataTableColumn {
@@ -19,6 +20,7 @@ export interface DataTableColumn {
1920
interface DataTableProps {
2021
children?: ReactNode;
2122
columns: DataTableColumn[];
23+
countLabel?: ReactNode;
2224
onChangeSort?: (newSort: string, newSortAsc: boolean) => void;
2325
sort?: string;
2426
sortAsc?: boolean;
@@ -27,6 +29,7 @@ interface DataTableProps {
2729
function DataTable({
2830
children,
2931
columns,
32+
countLabel,
3033
onChangeSort,
3134
sort,
3235
sortAsc = true,
@@ -36,7 +39,7 @@ function DataTable({
3639
<Table>
3740
<TableHead>
3841
<TableRow>
39-
{columns.map((column) => (
42+
{columns.map((column, i) => (
4043
<TableCell key={column.id} sx={{ width: column.width ?? "auto" }}>
4144
{column.isSortable && (
4245
<TableSortLabel
@@ -53,6 +56,11 @@ function DataTable({
5356
</TableSortLabel>
5457
)}
5558
{!column.isSortable && column.label}
59+
{i === columns.length - 1 && countLabel && (
60+
<Typography sx={{ textAlign: "right" }}>
61+
{countLabel}
62+
</Typography>
63+
)}
5664
</TableCell>
5765
))}
5866
</TableRow>

src/components/design_system/dataTable/DataTablePage.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ export interface DataTablePageRenderProps<RecordType, NewRecordType> {
1313
title: string;
1414
addLabel?: string;
1515
isLoading: boolean;
16-
records?: RecordType[];
1716
record?: NewRecordType;
17+
records?: RecordType[];
1818
onAddRecord?: () => void;
1919
onSubmit?: () => Promise<void>;
2020
columns: DataTableColumn[];
2121
emptyElement: ReactNode;
22+
renderCount?: () => ReactNode;
2223
renderFormRow: (
2324
record: NewRecordType,
2425
hasError: (path: string[]) => boolean,
@@ -28,6 +29,8 @@ export interface DataTablePageRenderProps<RecordType, NewRecordType> {
2829
}
2930

3031
export interface DataTablePageProps<RecordType, NewRecordType> {
32+
page: number;
33+
pageSize: number;
3134
search?: string;
3235
sort?: string;
3336
sortAsc?: boolean;
@@ -44,6 +47,8 @@ export function withDataTablePage<
4447
function DataTablePage(props: Omit<T, keyof DataTablePageProps<any, any>>) {
4548
const router = useRouter();
4649
const searchParams = useSearchParams();
50+
const page = Number(searchParams.get("page") ?? "1");
51+
const pageSize = Number(searchParams.get("pageSize") ?? "25");
4752
const search = searchParams.get("search") ?? "";
4853
const sort = searchParams.get("sort") ?? "first_name";
4954
const sortAsc = (searchParams.get("sortAsc") ?? "true") === "true";
@@ -101,19 +106,22 @@ export function withDataTablePage<
101106
return (
102107
<WrappedComponent
103108
{...(props as T)}
109+
page={page}
110+
pageSize={pageSize}
104111
search={search}
105112
sort={sort}
106113
sortAsc={sortAsc}
107114
render={({
108115
title,
109116
isLoading,
117+
record,
110118
records,
111119
addLabel,
112120
onAddRecord,
113-
record,
114121
emptyElement,
115122
onSubmit,
116123
columns: COLUMNS,
124+
renderCount,
117125
renderFormRow,
118126
renderRow,
119127
}) => (
@@ -160,6 +168,7 @@ export function withDataTablePage<
160168
>
161169
<DataTable
162170
columns={COLUMNS}
171+
countLabel={renderCount?.()}
163172
sort={sort}
164173
sortAsc={sortAsc}
165174
onChangeSort={onChangeSort}

src/pages/students/index.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,18 @@ interface NewRecordType {
5555

5656
type Unpacked<T> = T extends (infer U)[] ? U : T;
5757
type RecordType = Unpacked<
58-
RouterOutputs["case_manager"]["getMyStudentsAndIepInfo"]
58+
RouterOutputs["case_manager"]["getMyStudentsAndIepInfo"]["records"]
5959
>;
6060

6161
function Students({
62+
page,
63+
pageSize,
6264
search,
6365
sort,
6466
sortAsc,
6567
render,
6668
}: DataTablePageProps<RecordType, NewRecordType>) {
67-
const { data: records, isLoading } =
69+
const { data, isLoading } =
6870
trpc.case_manager.getMyStudentsAndIepInfo.useQuery({
6971
search,
7072
sort,
@@ -104,7 +106,7 @@ function Students({
104106
addLabel: "Add Student",
105107
isLoading,
106108
record,
107-
records,
109+
records: data?.records,
108110
onAddRecord,
109111
onSubmit,
110112
columns: COLUMNS,
@@ -115,6 +117,8 @@ function Students({
115117
<Button onClick={onAddRecord}>Add Student</Button>
116118
</>
117119
),
120+
renderCount: () =>
121+
`${(page - 1) * pageSize + 1}-${Math.min(page * pageSize, data?.totalCount ?? 0)} of ${data?.totalCount ?? 0} students`,
118122
renderFormRow: (record, hasError) => (
119123
<TableRow>
120124
<TableCell>

0 commit comments

Comments
 (0)