Skip to content

Commit 534c267

Browse files
committed
feat: query by academic year
1 parent 0ad967e commit 534c267

6 files changed

Lines changed: 98 additions & 34 deletions

File tree

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@
2828
"editor.tabSize": 4,
2929
"editor.insertSpaces": true,
3030
"editor.detectIndentation": false,
31-
"editor.formatOnSave": true
31+
"editor.formatOnSave": true,
32+
"sqltools.formatLanguages": []
3233
}

collection/app/(app)/UserSearch.tsx

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,6 @@ export const UserSearch: React.FC<UserSearchProps> = ({
2929
const router = useRouter();
3030
const shortcodeURLParam = useMemo(() => searchParams.get("shortcode") || "", [searchParams]);
3131

32-
const fetchPurchases = useCallback(async (shortcode: string) => {
33-
const res = await fetch(`/api/purchases/${shortcode}`, {
34-
next: { tags: [`purchases:${shortcode}`] },
35-
});
36-
if (!res.ok) {
37-
setError("An error occurred");
38-
return;
39-
}
40-
41-
const purchases = await res.json();
42-
if (purchases.status === "error") {
43-
setError(purchases.message);
44-
setPurchases([]);
45-
} else {
46-
setPurchases(purchases.orders);
47-
}
48-
}, []);
49-
5032
// Form
5133
// 2 fields: shortcode & academic year purcahses.
5234
// Default value of shortcode is from the URL if possible
@@ -60,26 +42,67 @@ export const UserSearch: React.FC<UserSearchProps> = ({
6042
},
6143
},
6244
});
45+
46+
const fetchPurchases = useCallback(
47+
async (shortcode: string) => {
48+
const res = await fetch(
49+
`/api/purchases/${shortcode}?` +
50+
new URLSearchParams({
51+
academicYears: form.getValues().academicYears.join(","),
52+
}),
53+
{
54+
next: { tags: [`purchases:${shortcode}`] },
55+
},
56+
);
57+
if (!res.ok) {
58+
setError("An error occurred");
59+
return;
60+
}
61+
62+
const purchases = await res.json();
63+
if (purchases.status === "error") {
64+
setError(purchases.message);
65+
setPurchases([]);
66+
} else {
67+
setPurchases(purchases.orders);
68+
}
69+
},
70+
[form],
71+
);
72+
73+
// This looks a bit weird, but effectively prevent an infinite loop of fetching purchases
74+
const [prevURLParam, setPrevURLParam] = useState(shortcodeURLParam);
6375
useEffect(() => {
64-
if (shortcodeURLParam) {
65-
fetchPurchases(shortcodeURLParam);
66-
} else {
67-
setPurchases([]);
68-
form.setFieldValue("shortcode", "");
76+
if (!shortcodeURLParam) {
77+
// only allow one rerender when the shortcode is clear
78+
// by tracking the previous value
79+
if (prevURLParam) {
80+
setPurchases([]);
81+
form.setFieldValue("shortcode", "");
82+
setPrevURLParam("");
83+
}
84+
}
85+
}, [shortcodeURLParam, prevURLParam, form]);
86+
87+
// Fetch purchases if academic year changes
88+
form.watch("academicYears", (academicYears) => {
89+
if (form.getValues().shortcode) {
90+
startTransition(async () => {
91+
setError(null);
92+
await fetchPurchases(form.getValues().shortcode);
93+
});
6994
}
70-
// can't add form as would cause infinite loop
71-
// eslint-disable-next-line react-hooks/exhaustive-deps
72-
}, [shortcodeURLParam, fetchPurchases]);
95+
});
7396

7497
const submitAction = useCallback(
7598
({ shortcode }: { shortcode: string }) => {
76-
startTransition(() => {
99+
startTransition(async () => {
77100
setError(null);
78-
101+
await fetchPurchases(shortcode);
79102
router.push("/?shortcode=" + shortcode);
80103
});
81104
},
82-
[router],
105+
[router, fetchPurchases],
83106
);
84107

85108
return (
@@ -96,6 +119,7 @@ export const UserSearch: React.FC<UserSearchProps> = ({
96119
alignItems: "center",
97120
}}
98121
onSubmit={form.onSubmit(submitAction)}
122+
// eslint-disable-next-line @typescript-eslint/no-empty-function
99123
action={() => {}}
100124
>
101125
<Group w="100%">
@@ -115,6 +139,7 @@ export const UserSearch: React.FC<UserSearchProps> = ({
115139
<Button
116140
color="pink"
117141
onClick={() => {
142+
setPurchases([]);
118143
router.push("/");
119144
}}
120145
>
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
import { getAcademicYear } from "@/lib/config";
12
import { getPurchasesByShortcode } from "@/lib/crud/purchase";
2-
import { NextResponse } from "next/server";
3+
import { NextRequest, NextResponse } from "next/server";
34

4-
export async function GET(request: Request, { params }: { params: { shortcode: string } }) {
5-
return NextResponse.json(await getPurchasesByShortcode(params.shortcode));
5+
export async function GET(request: NextRequest, { params }: { params: { shortcode: string } }) {
6+
const searchParams = request.nextUrl.searchParams;
7+
const academicYears =
8+
searchParams.get("academicYears")?.split(",") ?? (await getAcademicYear());
9+
return NextResponse.json(await getPurchasesByShortcode(params.shortcode, academicYears));
610
}

collection/lib/crud/purchase.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ export type GetPurchasesReturn =
2424
orders: OrderResponse[];
2525
}
2626
| { status: "error"; message: string };
27-
export const getPurchasesByShortcode = async (shortcode: string): Promise<GetPurchasesReturn> => {
27+
export const getPurchasesByShortcode = async (
28+
shortcode: string,
29+
academicYears: string[],
30+
): Promise<GetPurchasesReturn> => {
2831
const student = await prisma.imperialStudent.findFirst({
2932
where: {
3033
shortcode: shortcode,
@@ -42,6 +45,11 @@ export const getPurchasesByShortcode = async (shortcode: string): Promise<GetPur
4245
},
4346
},
4447
},
48+
where: {
49+
academicYear: {
50+
in: academicYears,
51+
},
52+
},
4553
},
4654
},
4755
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
Warnings:
3+
4+
- Added the required column `academicYear` to the `Order` table without a default value. This is not possible if the table is not empty.
5+
6+
*/
7+
-- AlterTable
8+
ALTER TABLE "Order" ADD COLUMN "academicYear" TEXT;
9+
10+
-- AddForeignKey
11+
ALTER TABLE "Order" ADD CONSTRAINT "Order_academicYear_fkey" FOREIGN KEY ("academicYear") REFERENCES "AcademicYear"("year") ON DELETE RESTRICT ON UPDATE CASCADE;
12+
13+
-- Set default value to 23-24
14+
-- create 23-24 if it doesn't exist
15+
INSERT INTO "AcademicYear" ("year") VALUES ('23-24') ON CONFLICT DO NOTHING;
16+
UPDATE "Order" SET "academicYear" = '23-24';
17+
18+
-- Make it non-NULL
19+
ALTER TABLE "Order" ALTER COLUMN "academicYear" SET NOT NULL;
20+

collection/prisma/schema.prisma

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ model AcademicYear {
2525
2626
createdAt DateTime @default(now())
2727
updatedAt DateTime @default(now()) @updatedAt
28+
29+
Order Order[]
2830
}
2931

3032
// ==================
@@ -78,6 +80,10 @@ model Order {
7880
orderNo Int @unique
7981
ImperialStudent ImperialStudent @relation(fields: [studentId], references: [id])
8082
OrderItem OrderItem[]
83+
84+
// An order is placed in some academic year
85+
academicYear String
86+
academicYearReference AcademicYear @relation(fields: [academicYear], references: [year])
8187
}
8288

8389
// OrderItems are the items that make up an order

0 commit comments

Comments
 (0)