From 36ea1265770192bc473407eb86d53dbd61affb8f Mon Sep 17 00:00:00 2001 From: Arthur Lian <24046428@student.uwa.edu.au> Date: Wed, 12 Feb 2025 06:25:08 +0000 Subject: [PATCH 1/8] Create page fields fit the api fields --- .../components/ui/Users/data-table-form.tsx | 137 +++++++++++++----- client/src/types/user.ts | 20 +-- 2 files changed, 110 insertions(+), 47 deletions(-) diff --git a/client/src/components/ui/Users/data-table-form.tsx b/client/src/components/ui/Users/data-table-form.tsx index c410c730..c7f848ce 100644 --- a/client/src/components/ui/Users/data-table-form.tsx +++ b/client/src/components/ui/Users/data-table-form.tsx @@ -49,12 +49,21 @@ type User = z.infer; * Additional Reference: * - {@link https://react-hook-form.com/docs/usefieldarray React Hook Form: useFieldArray} */ + export function DataTableForm() { - const defaultUser = { - username: "", + // calculate the default year for the attendent_year field + const currentYear = new Date().getFullYear(); + const defaultAttendentYear = Math.max(2024, Math.min(currentYear, 2050)); + + const defaultUser: User = { + first_name: "", + last_name: "", password: "", - email: "", - } as User; + year_level: "7", // default year_level is "7" + school_id: 1, // default school_id is 1 + attendent_year: defaultAttendentYear, + // extenstion_time is optional, so it can be omitted + }; const createUserForm = useForm<{ users: User[]; @@ -83,9 +92,11 @@ export function DataTableForm() { }); const newRecords = data.users.map((user) => ({ - username: user.username, + firstname: user.first_name, + lastrname: user.last_name, + userRole: user.year_level, password: user.password, - role: user.userRole, + createdAt: perthTime, })); @@ -164,50 +175,55 @@ export function DataTableForm() { No. - Username* + First Name* - + + Last Name* + + Password* Minimum 8 characters with letters, numbers, and symbols - Email - User Role* + Year Level* School* + + Attendent Year* + + + Extenstion Time + + {fields.map((field, index) => ( {/* No. Field */} {index + 1} - {/* Username Field */} + {/* First Name */} ( - + @@ -215,22 +231,23 @@ export function DataTableForm() { /> - {/* Password Field */} - {/* + {/* Last Name */} + ( - + )} /> - */} + + {/* Password Field */}
- {/* input*/} { const randomPwd = createRandomPwd(); field.onChange(randomPwd); @@ -264,15 +279,23 @@ export function DataTableForm() { /> - {/* Email Field */} + {/* Year Level Field (7/8/9) */} ( - + - + @@ -280,16 +303,16 @@ export function DataTableForm() { /> - {/* User Role Field */} + {/* School Field */} ( - @@ -299,17 +322,54 @@ export function DataTableForm() { /> - {/* School Field */} + {/* Attendent Year Field (2024-2050) */} ( + + + + + + + )} + /> + + + {/* Extenstion Time (optional) */} + + ( - + field.onChange(Number(e.target.value)) + } /> @@ -332,6 +392,7 @@ export function DataTableForm() { ))} +
+
+ + +
+ )} + /> +
+ + {/* Year Level Field (7/8/9) */} + + ( + + + + + + + )} + /> + + + {/* School Field */} + + ( + + + + + + + )} + /> + + + {/* Attendent Year Field (2024-2050) */} + + ( + + + + + + + )} + /> + + + {/* Extenstion Time (optional) */} + + ( + + + + field.onChange(Number(e.target.value)) + } + /> + + + + )} + /> + + + {/* Delete Button */} + + + + + ))} + + +
+
- + +
diff --git a/client/src/types/user.ts b/client/src/types/user.ts index a95740e0..e3b2143e 100644 --- a/client/src/types/user.ts +++ b/client/src/types/user.ts @@ -127,7 +127,7 @@ export const createUserSchema = z.object({ password: z.string().min(1, "Password is required"), year_level: z.enum(["7", "8", "9"]), //School ID now is not compuslory, need to modify later - school_id: z.number().int().positive(), + school_id: z.number().int().positive("Required"), attendent_year: z.number().int().min(2024).max(2050), extenstion_time: z.number().int().min(0).optional(), }); From 3ad7a09a18756587359afb51cc95799bcda9b77e Mon Sep 17 00:00:00 2001 From: Arthur Lian <24046428@student.uwa.edu.au> Date: Thu, 13 Feb 2025 07:00:41 +0000 Subject: [PATCH 4/8] Add function of Delete local storage and also alert (still need to check the response and add Password in csv, waiting for the change in the Backend response) --- .../components/ui/Users/data-table-form.tsx | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/client/src/components/ui/Users/data-table-form.tsx b/client/src/components/ui/Users/data-table-form.tsx index 7d75e8ad..752628bf 100644 --- a/client/src/components/ui/Users/data-table-form.tsx +++ b/client/src/components/ui/Users/data-table-form.tsx @@ -35,7 +35,6 @@ type StoredRecord = { schoolId: number; schoolName: string; attendentYear: number; - createdAt: string; extensionTime: number; }; type User = z.infer; @@ -90,6 +89,16 @@ export function DataTableForm() { name: "users", }); + const clearHistory = () => { + const userConfirmed = window.confirm( + "Are you sure you want to clear all creation history in this browser? This cannot be undone. We recommend exporting the data first.", + ); + if (userConfirmed) { + localStorage.removeItem("studentRecords"); + toast.success("Creation history cleared from this browser."); + } + }; + const { mutate: createUser, isPending } = usePostMutation({ mutationKey: ["students"], endpoint: "/users/students/", @@ -107,7 +116,6 @@ export function DataTableForm() { schoolId: std.school.id, schoolName: std.school.name, attendentYear: std.attendent_year, - createdAt: std.created_at.toString(), extensionTime: std.extenstion_time || 0, })); @@ -118,7 +126,9 @@ export function DataTableForm() { const updatedData = [...previousData, ...newRecords]; localStorage.setItem("studentRecords", JSON.stringify(updatedData)); - toast.success("Please Click Export CSV button to download data."); + toast.success( + "You can click Export CSV button to download the historical user creation data.", + ); } catch (error) { toast.error(`Error when update data for Export CSV. ${error}`); } @@ -154,7 +164,6 @@ export function DataTableForm() { "School ID", "School Name", "Attendent Year", - "Created At", "Extension Time", ]; @@ -167,7 +176,6 @@ export function DataTableForm() { record.schoolId, record.schoolName, record.attendentYear, - record.createdAt, record.extensionTime, ]); @@ -182,7 +190,7 @@ export function DataTableForm() { const link = document.createElement("a"); link.href = url; - link.setAttribute("download", "student_data.csv"); + link.setAttribute("download", "Student_Create_Data.csv"); document.body.appendChild(link); link.click(); document.body.removeChild(link); @@ -451,6 +459,16 @@ export function DataTableForm() { Add Row
+ {/* 新增的按钮 */} + +
- {/* 新增的按钮 */} - + + + + + +
+ + + ); +} From 376314138c9cc7ad2d2eab76677ed89b017860bf Mon Sep 17 00:00:00 2001 From: Lok Yx Date: Thu, 13 Feb 2025 09:40:56 +0000 Subject: [PATCH 6/8] fix(fetch): SelectSchool UseDataTable hook and improve loading/error handling --- .../src/components/ui/Users/select-school.tsx | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/client/src/components/ui/Users/select-school.tsx b/client/src/components/ui/Users/select-school.tsx index 5197be1b..b8880059 100644 --- a/client/src/components/ui/Users/select-school.tsx +++ b/client/src/components/ui/Users/select-school.tsx @@ -5,7 +5,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { useFetchData } from "@/hooks/use-fetch-data"; +import { useFetchDataTable } from "@/hooks/use-fetch-data"; import { cn } from "@/lib/utils"; import { School, SchoolTypeEnum } from "@/types/user"; @@ -42,13 +42,13 @@ type Props = { * /> */ export function SelectSchool({ selectedId, onChange, className }: Props) { - const { - data: schools, - isPending, - isError, - } = useFetchData({ + const { data, isLoading, error, totalPages } = useFetchDataTable({ queryKey: ["users.schools"], - endpoint: "http://127.0.0.1:8000/api/users/schools/", + endpoint: "/users/schools/", + searchParams: { + nrows: 999999, // to get all with some large number + page: 1, + }, }); const onValueChange = (value: string) => { @@ -58,21 +58,28 @@ export function SelectSchool({ selectedId, onChange, className }: Props) { } }; + // Auto Select when only 1 data + const value = selectedId + ? selectedId.toString() + : data?.length === 1 + ? data[0].id.toString() + : ""; return ( - - {isPending || isError ? ( + {isLoading || error ? ( + + {isLoading ? "Loading..." : "Error"} + + ) : !data || !data.length ? ( - Loading... + No Data Found ) : ( - schools.map((school) => ( + data.map((school) => ( {school.name} From e101907618aaa51440590b499d07a3a4ec36e1a7 Mon Sep 17 00:00:00 2001 From: Yunho Ding Date: Fri, 14 Feb 2025 02:50:59 +0000 Subject: [PATCH 7/8] fix(user): update student creation logic to include plain password --- server/api/users/views.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/api/users/views.py b/server/api/users/views.py index d41680f2..b49b59e6 100644 --- a/server/api/users/views.py +++ b/server/api/users/views.py @@ -55,14 +55,16 @@ def get_queryset(self): def create(self, request, *args, **kwargs): data = request.data.copy() # Create a mutable copy of request.data - if hasattr(self.request.user, "teacher"): + if hasattr(request.user, "teacher"): # teacher can only create students for their school for student in data: - student["school_id"] = self.request.user.teacher.school.id + student["school_id"] = request.user.teacher.school.id serializer = self.get_serializer(data=data, many=True) serializer.is_valid(raise_exception=True) self.perform_create(serializer) + for i, student in enumerate(serializer.data): + student["password"] = request.data[i]["password"] return Response(serializer.data, status=status.HTTP_201_CREATED) elif self.request.user.is_staff: # allow bulk creation of students by admin From 720d95527f38e70ea3a152f878afc07f588717d0 Mon Sep 17 00:00:00 2001 From: Yunho Ding Date: Sat, 15 Feb 2025 03:37:11 +0000 Subject: [PATCH 8/8] fix(user): include password in student creation response --- server/api/users/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/api/users/views.py b/server/api/users/views.py index b49b59e6..54a7a40c 100644 --- a/server/api/users/views.py +++ b/server/api/users/views.py @@ -71,6 +71,9 @@ def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=data, many=True) serializer.is_valid(raise_exception=True) self.perform_create(serializer) + for i, student in enumerate(serializer.data): + student["password"] = request.data[i]["password"] + return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.data, status=status.HTTP_201_CREATED) else: return Response(