From 7b0ff42350373c9d20744338e27c8ba21a3e40db Mon Sep 17 00:00:00 2001 From: amjed-ali-k Date: Wed, 25 Oct 2023 01:27:24 +0530 Subject: [PATCH] feat: added student batch add form --- public/student-sample.csv | 1 + .../exam-seating/student-batches/route.ts | 57 +-- .../result-formatter/_components/form.tsx | 14 +- .../student-batches/new/_components.tsx | 331 +++++++++++++++--- 4 files changed, 308 insertions(+), 95 deletions(-) create mode 100644 public/student-sample.csv diff --git a/public/student-sample.csv b/public/student-sample.csv new file mode 100644 index 0000000..4aed194 --- /dev/null +++ b/public/student-sample.csv @@ -0,0 +1 @@ +name, rollNumber, regNumber, admnNumber diff --git a/src/app/api/secure/exam-seating/student-batches/route.ts b/src/app/api/secure/exam-seating/student-batches/route.ts index 020098d..514da98 100644 --- a/src/app/api/secure/exam-seating/student-batches/route.ts +++ b/src/app/api/secure/exam-seating/student-batches/route.ts @@ -3,26 +3,17 @@ import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/server/db/prisma"; import { getUserId } from "@/components/auth/server"; -enum SeatType { - THEORY, - DRAWING, - COMMON, - BLANK, -} - const schema = z.object({ - name: z.string(), - structure: z + name: z.string().min(3, "Minimum 3 characters required"), + students: z .array( - z - .array( - z.object({ - name: z.string(), - seatCount: z.number().positive(), - structure: z.array(z.number().nonnegative()), - }) - ) - .min(1) + z.object({ + name: z.string().optional(), + primaryNumber: z.string(), + rollNumber: z.string().optional(), + regNumber: z.string().optional(), + admnNumber: z.string().optional(), + }) ) .min(1), }); @@ -41,42 +32,18 @@ export async function PUT(request: NextRequest) { ); } - const results = await prisma.examHall.create({ + const results = await prisma.studentBatchForExam.create({ data: { name: body.data.name, - structure: body.data.structure, + students: body.data.students, createdById: userId, - theoryOnlySeats: getCount(body.data.structure, SeatType.THEORY), - drawingOnlySeats: getCount(body.data.structure, SeatType.DRAWING), - commonSeats: getCount(body.data.structure, SeatType.COMMON), + studentsCount: body.data.students.length, }, }); return NextResponse.json(results); } -export type SeatObjectType = { - name: string; - seatCount: number; - structure: SeatType[]; -}; - -function getCount(structure: SeatObjectType[][], type: SeatType) { - return structure.reduce((acc, curr) => { - return ( - acc + - curr.reduce((acc, curr) => { - return ( - acc + - curr.structure.reduce((acc, curr) => { - return curr === type ? (acc += 1) : acc; - }, 0) - ); - }, 0) - ); - }, 0); -} - const deleteSchema = z.object({ id: z.string(), }); diff --git a/src/app/dashboard/result-formatter/_components/form.tsx b/src/app/dashboard/result-formatter/_components/form.tsx index 4144a9f..cc5eceb 100644 --- a/src/app/dashboard/result-formatter/_components/form.tsx +++ b/src/app/dashboard/result-formatter/_components/form.tsx @@ -37,16 +37,10 @@ import axios from "axios"; const allowedMonths = ["April", "November"]; const fomrSchema = z.object({ - month: z - .string() - .nonempty() - .refine((val) => { - return allowedMonths.includes(val); - }), - year: z - .string() - .nonempty() - .regex(/^[0-9]{4}$/), + month: z.string().refine((val) => { + return allowedMonths.includes(val); + }), + year: z.string().regex(/^[0-9]{4}$/), upload: z.boolean(), }); diff --git a/src/app/dashboard/tools/exam-seating/student-batches/new/_components.tsx b/src/app/dashboard/tools/exam-seating/student-batches/new/_components.tsx index 66fbed2..dd605cf 100644 --- a/src/app/dashboard/tools/exam-seating/student-batches/new/_components.tsx +++ b/src/app/dashboard/tools/exam-seating/student-batches/new/_components.tsx @@ -14,7 +14,7 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; -import { FileSpreadsheet, Hash, User } from "lucide-react"; +import { Ban, FileSpreadsheet, Hash, UploadCloud, User } from "lucide-react"; import { DropdownMenu, DropdownMenuContent, @@ -39,6 +39,14 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import Link from "next/link"; +import Papa from "papaparse"; +import { Label } from "@/components/ui/label"; +import Dropzone from "react-dropzone"; +import axios from "axios"; +import { StudentBatchForExam } from "@prisma/client"; +import { useToast } from "@/components/ui/use-toast"; +import { useRouter } from "next/navigation"; const formSchema: z.ZodType<{ name: string; @@ -52,14 +60,15 @@ const formSchema: z.ZodType<{ }> = z.object({ name: z.string().min(3, "Minimum 3 characters required"), students: z - .array({ - //@ts-ignore - name: z.string().optional(), - primaryNumber: z.string(), - rollNumber: z.string().optional(), - regNumber: z.string().optional(), - admnNumber: z.string().optional(), - }) + .array( + z.object({ + name: z.string().optional(), + primaryNumber: z.string(), + rollNumber: z.string().optional(), + regNumber: z.string().optional(), + admnNumber: z.string().optional(), + }) + ) .min(1), }); @@ -68,8 +77,29 @@ function NewStudentBatchComponent() { resolver: zodResolver(formSchema), mode: "onBlur", }); + const { toast } = useToast(); + const router = useRouter(); - const onSubmit = (data: z.infer) => {}; + const onSubmit = (data: z.infer) => { + axios + .put( + "/api/secure/exam-seating/student-batches", + data + ) + .then((res) => { + toast({ + title: "Well done!", + description: `Class ${res.data.name} added successfully`, + }); + router.push("/dashboard/tools/exam-seating/student-batches"); + }) + .catch((e) => { + toast({ + title: "Error!", + description: `Something went wrong`, + }); + }); + }; const [isOpen, setIsOpen] = useState(false); const modalType = useRef<"add-student" | "add-roll" | "add-csv">( @@ -195,7 +225,22 @@ function NewStudentBatchComponent() { }} /> )} - {modalType.current === "add-csv" && } + {modalType.current === "add-csv" && ( + { + form.setValue("students", [...students, ...e]); + setIsOpen(false); + }} + /> + )} + {modalType.current === "add-roll" && ( + { + form.setValue("students", [...students, ...e]); + setIsOpen(false); + }} + /> + )} ); @@ -205,7 +250,7 @@ export default NewStudentBatchComponent; const studentFormSchema = z .object({ - name: z.string().optional(), + name: z.string(), rollNumber: z.string().optional(), regNumber: z.string().optional(), admnNumber: z.string().optional(), @@ -215,11 +260,9 @@ const studentFormSchema = z path: ["rollNumber", "regNumber", "admnNumber"], }); -function SingleModal({ - onAdd, -}: { - onAdd?: (data: z.infer["students"][0]) => void; -}) { +type StudentType = z.infer["students"][0]; + +function SingleModal({ onAdd }: { onAdd?: (data: StudentType) => void }) { const form = useForm>({ resolver: zodResolver(studentFormSchema), }); @@ -238,7 +281,7 @@ function SingleModal({ className="space-y-8 @container" > - Add students + Add student Enter student details here
@@ -317,22 +360,194 @@ function SingleModal({ ); } -const csvFormSchema = z.object({ - csvFile: z.string().refine((data) => data.endsWith(".csv"), { - message: "Only CSV file is allowed.", - }), -}); +type studentSchemaType = z.infer; + +const parseCsv = ( + inputFile: File, + onSuccess: (e: studentSchemaType[]) => void = (e) => console.log(e), + onError: (e: string) => void = (e) => console.log(e) +) => { + const reader = new FileReader(); + + reader.onload = (event) => { + if (!event.target) return; + const file = event.target.result; + if (typeof file !== "string") return; + let allLines = file.split(/\r\n|\n/); + // Reading line by line + if (allLines.length < 2) { + onError("File is empty"); + return; + } + Papa.parse(file, { + header: true, + skipEmptyLines: true, + complete: function (results) { + onSuccess(results.data); + }, + }); + }; +}; function CsvModal({ onAdd, }: { onAdd?: (data: z.infer["students"]) => void; }) { - const form = useForm>({ - resolver: zodResolver(csvFormSchema), + const [error, seterror] = useState(""); + const verifyAndFormatFile = async (e: File) => { + parseCsv( + e, + (k) => { + onAdd?.( + k.map((l) => ({ + ...l, + primaryNumber: l.regNumber || l.rollNumber || l.admnNumber, + })) + ); + }, + seterror + ); + }; + return ( + <> + + Add students from CSV + + Upload your csv file contianing student data here + + +
+

+ You can download a sample .csv file{" "} + + from here + + . Modifying and re-uploading it is recommeneded. You can also try to + create your own csv file. We only look for headers{" "} + + name, rollNumber, regNumber, admnNumber + + . +

+ { + verifyAndFormatFile(acceptedFiles[0]); + }} + > + {({ getRootProps, getInputProps, acceptedFiles, isDragReject }) => ( +
+ {acceptedFiles.length > 0 ? ( +
+ +

+ {acceptedFiles[0].name} +

+

+ Click to upload another file or + drag and drop +

+
+ ) : ( +
+ {isDragReject ? ( + <> + +

+ + File format not supported + +

+ + ) : ( + <> + +

+ Click to upload{" "} + or drag and drop +

+ + )} +

+ CSV files only. +
Results won't be accurate if you upload modified + files. +

+
+ )} + +
+ )} +
+
+ {error !== "" &&

{error}

} + + + ); +} + +const rollFormSchema = z.object({ + start: z.string().optional(), + end: z.string().optional(), + exclude: z + .string() + .optional() + .refine( + (e) => { + if (!e) return true; + return e.split(",").every((e) => !isNaN(parseInt(e))); + }, + { + message: "Please enter a valid numbers seperated by comma", + } + ), + prefix: z.string().optional(), +}); + +function RollModal({ + onAdd, +}: { + onAdd?: (data: z.infer["students"]) => void; +}) { + const form = useForm>({ + resolver: zodResolver(rollFormSchema), + defaultValues: { + start: "", + end: "", + prefix: "", + exclude: "", + }, }); - const onSubmit = (data: z.infer) => {}; + const onSubmit = (data: z.infer) => { + const students: StudentType[] = []; + if (data.start && data.end) { + const start = parseInt(data.start); + const end = parseInt(data.end); + const exclude = data.exclude?.split(",").map((e) => parseInt(e)); + for (let i = start; i <= end; i++) { + if (exclude?.includes(i)) continue; + students.push({ + name: `${data.prefix || ""}${i}`, + rollNumber: i.toString(), + primaryNumber: i.toString(), + }); + } + } + onAdd?.(students); + }; return (
@@ -341,29 +556,65 @@ function CsvModal({ className="space-y-8 @container" > - Add students from CSV - - Upload your csv file contianing student data here - + Add student + Enter student details here
-

- You can download a sample .csv file from here. Modifying and - re-uploading it is recommeneded. You can also try to create your own - csv file. We only look for headers{" "} - name, rollNumber, regNumber, admnNumber. -

( + + Starting roll no + + + + Enter starting roll number. + + + )} + /> + ( + + Ending roll no + + + + Enter ending roll number. + + + )} + /> + ( + + Roll no.s to exclude + + + + + Enter numbers seperated by comma + + + + )} + /> + ( - Upload csv file + Prefix - + - Choose a .csv file in given format + Enter prefix to add with roll no as name