From 4fab07cb077564c62125a6fbde9d4168cf9c65a8 Mon Sep 17 00:00:00 2001 From: Abhimanyu-dev Date: Thu, 4 Jul 2024 02:49:40 +0530 Subject: [PATCH 1/5] Added Document Upload --- callbacks/admin/student/documents.ts | 95 ++++++++ callbacks/student/rc/documents.ts | 107 +++++++++ components/Layouts/LayoutWrapper.tsx | 13 ++ pages/admin/documents/index.tsx | 264 +++++++++++++++++++++ pages/student/documents.tsx | 333 +++++++++++++++++++++++++++ 5 files changed, 812 insertions(+) create mode 100644 callbacks/admin/student/documents.ts create mode 100644 callbacks/student/rc/documents.ts create mode 100644 pages/admin/documents/index.tsx create mode 100644 pages/student/documents.tsx diff --git a/callbacks/admin/student/documents.ts b/callbacks/admin/student/documents.ts new file mode 100644 index 00000000..4a80c4ed --- /dev/null +++ b/callbacks/admin/student/documents.ts @@ -0,0 +1,95 @@ +import axios, { AxiosResponse } from "axios"; + +import { errorNotification, successNotification } from "@callbacks/notifcation"; +import { + ADMIN_STUDENT_URL, + ErrorType, + SERVER_ERROR, + setConfig, +} from "@callbacks/constants"; + +const instance = axios.create({ + baseURL: ADMIN_STUDENT_URL, + timeout: 15000, + timeoutErrorMessage: SERVER_ERROR, +}); + +// eslint-disable-next-line no-unused-vars +interface nullBool { + Bool: boolean; + Valid: boolean; +} + +export interface AllStudentDocumentsResponse { + name: string; + email: string; + sid: number; + rsid: number; + path: string; + verified: boolean; + action_taken_by: string; +} + +export interface StudentDocumentsResponse { + ID: number; // rsid + CreatedAt: string; + UpdatedAt: string; + DeletedAt: string; + recruitment_cycle_id: number; + student_recruitment_cycle_id: number; + path: string; + verified: boolean; + action_taken_by: string; +} +export interface VerifyBody { + verified: boolean; +} + +export interface SuccessResponse { + sucess: boolean; +} + +const responseBody = (response: AxiosResponse) => response.data; + +const adminDocumentsRequest = { + getAll: (token: string) => + instance + .get(`/documents`, setConfig(token)) + .then(responseBody) + .catch((err: ErrorType) => { + errorNotification( + "Error in fetching data", + err.response?.data?.error || err.message + ); + return [] as AllStudentDocumentsResponse[]; + }), + get: (token: string, sid: string) => + instance + .get(`/${sid}/documents`, setConfig(token)) + .then(responseBody) + .catch((err: ErrorType) => { + errorNotification( + "Error in fetching data", + err.response?.data?.error || err.message + ); + return [] as StudentDocumentsResponse[]; + }), + putVerify: (token: string, docid: string, body: VerifyBody) => + instance + .put>( + `/documents/${docid}/verify`, + body, + setConfig(token) + ) + .then(() => { + let stat = body.verified ? "accepted" : "rejected"; + successNotification("Success", `Documents ${stat}`); + return true; + }) + .catch((err: ErrorType) => { + errorNotification("Error", err.response?.data?.error || err.message); + return false; + }), +}; + +export default adminDocumentsRequest; diff --git a/callbacks/student/rc/documents.ts b/callbacks/student/rc/documents.ts new file mode 100644 index 00000000..42653faa --- /dev/null +++ b/callbacks/student/rc/documents.ts @@ -0,0 +1,107 @@ +import axios, { AxiosResponse } from "axios"; + +import { errorNotification, successNotification } from "@callbacks/notifcation"; + +import { + CDN_URL, + ErrorType, + SERVER_ERROR, + STUDENT_URL, + StatusResponse, + responseBody, + setConfig, +} from "../../constants"; + +const cdn_instance = axios.create({ + baseURL: CDN_URL, + timeout: 15000, + timeoutErrorMessage: SERVER_ERROR, +}); + +const instance = axios.create({ + baseURL: STUDENT_URL, + timeout: 15000, + timeoutErrorMessage: SERVER_ERROR, +}); +export interface DocumentResponse { + message: string; + filename: string; +} + +export interface DocumentsBackendParams { + path: string; +} + +// eslint-disable-next-line no-unused-vars +interface nullBool { + Bool: boolean; + Valid: boolean; +} + +export interface AllStudentDocumentsResponse { + ID: number; + CreatedAt: string; + UpdatedAt: string; + DeletedAt: string | null; + student_recruitment_cycle_id: number; + recruitment_cycle_id: number; + resume: string; + verified: boolean; + action_taken_by: string; +} + +const documentRequest = { + post: (body: FormData, token: string) => + cdn_instance + .post< + DocumentResponse, + AxiosResponse, + FormData + >("/upload", body, { + headers: { + token, + }, + }) + .then( + // second api call to backend instance + (response: AxiosResponse) => + instance + .post< + StatusResponse, + AxiosResponse, + DocumentsBackendParams + >( + `/document`, + { + path: response.data.filename, + }, + setConfig(token) + ) + .then(() => { + successNotification("Success", "Documents uploaded"); + return responseBody; + }) + .catch((err: ErrorType) => { + errorNotification( + "Error", + err.response?.data?.error || err.message + ); + return { status: "error" } as StatusResponse; + }) + ) + .catch((err: ErrorType) => { + errorNotification("Upload Failed", err.response?.data?.error); + return { message: "", filename: "" } as DocumentResponse; + }), + + get: (token: string) => + instance + .get(`/documents`, setConfig(token)) + .then(responseBody) + .catch((err: ErrorType) => { + errorNotification("Error", err.response?.data?.error || err.message); + return [] as AllStudentDocumentsResponse[]; + }), +}; + +export default documentRequest; diff --git a/components/Layouts/LayoutWrapper.tsx b/components/Layouts/LayoutWrapper.tsx index 4f4af14f..f9d50eb7 100644 --- a/components/Layouts/LayoutWrapper.tsx +++ b/components/Layouts/LayoutWrapper.tsx @@ -2,6 +2,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import AccountCircleIcon from "@mui/icons-material/AccountCircle"; import ArticleIcon from "@mui/icons-material/Article"; +import FileCopyIcon from "@mui/icons-material/FileCopy"; import AssignmentIcon from "@mui/icons-material/Assignment"; import BarChartIcon from "@mui/icons-material/BarChart"; import CalendarMonthIcon from "@mui/icons-material/CalendarMonth"; @@ -217,6 +218,11 @@ function LayoutWrapper({ children }: { children: JSX.Element }) { name: "Profile", id: "/profile", }, + { + avatar: , + name: "Documents", + id: "/documents", + }, ], extra: [ { @@ -424,6 +430,13 @@ function LayoutWrapper({ children }: { children: JSX.Element }) { id: "/company", } : { avatar: <>, name: "", id: "", hidden: true }, + role === 100 || role === 101 + ? { + avatar: , + name: "Master Database (Documents)", + id: "/documents", + } + : { avatar: <>, name: "", id: "", hidden: true }, role === 100 || role === 101 ? { avatar: , diff --git a/pages/admin/documents/index.tsx b/pages/admin/documents/index.tsx new file mode 100644 index 00000000..21d10568 --- /dev/null +++ b/pages/admin/documents/index.tsx @@ -0,0 +1,264 @@ +import { Button, Container, Modal, Stack } from "@mui/material"; +import Grid from "@mui/material/Grid"; +// import { GridColDef, GridValueGetterParams } from "@mui/x-data-grid"; +import { GridColDef } from "@mui/x-data-grid"; +import * as React from "react"; +import { useEffect, useState } from "react"; + +import adminDocumentsRequest, { + AllStudentDocumentsResponse, +} from "@callbacks/admin/student/documents"; +import DataGrid from "@components/DataGrid"; +import Clarification from "@components/Modals/clarification"; +import Meta from "@components/Meta"; +import useStore from "@store/store"; +import { CDN_URL } from "@callbacks/constants"; + +const transformName = (name: string) => { + const nname = name.replace(`${CDN_URL}/view/`, ""); + const nameArray = nname.split("."); + const newName = nameArray[0].slice(14, -33); + const newNameWithExtension = `${newName}.${nameArray[1]}`; + return newNameWithExtension; +}; + +const getURL = (url: string) => `${CDN_URL}/view/${url}`; + +function AcceptDocumentButton(props: { + docid: string; + updateCallback: () => Promise; +}) { + const { token } = useStore(); + const { docid, updateCallback } = props; + return ( + + ); +} + +function RejectResumeButton(props: { + docid: string; + updateCallback: () => Promise; +}) { + const { token } = useStore(); + const { docid, updateCallback } = props; + return ( + + ); +} + +function AskClarification(props: { + role: number; + sid: string; + row: AllStudentDocumentsResponse; +}) { + const { role, sid, row } = props; + const [openNew, setOpenNew] = useState(false); + const handleOpenNew = () => { + setOpenNew(true); + }; + const handleCloseNew = () => { + setOpenNew(false); + }; + // if (!params.row.verified?.Valid || role === 100 || role === 101) { + return !row.verified || role === 100 || role === 101 ? ( +
+ + + + +
+ ) : ( +
+ ); +} +function Index() { + const [allDocuments, setAllDocuments] = useState< + AllStudentDocumentsResponse[] + >([]); + const { token, role } = useStore(); + useEffect(() => { + const fetchData = async () => { + const res = await adminDocumentsRequest.getAll(token); + if (res !== null && res?.length > 0) setAllDocuments(res); + else setAllDocuments([]); + }; + fetchData(); + }, [token]); + + const updateTable = React.useCallback(async () => { + const res = await adminDocumentsRequest.getAll(token); + if (res !== null && res?.length > 0) setAllDocuments(res); + else setAllDocuments([]); + }, [token]); + + const columns: GridColDef[] = [ + { + field: "ID", + headerName: "Document ID", + }, + { + field: "CreatedAt", + headerName: "Created At", + hide: true, + }, + { + field: "UpdatedAt", + headerName: "Updated At", + hide: true, + }, + // { + // field: "name", + // headerName: "Student Name", + // renderCell: (params) => ( + // + //
{params.value}
+ //
+ // ), + // }, + // { + // field: "email", + // headerName: "Student Email", + // renderCell: (params) => ( + // + //
{params.value}
+ //
+ // ), + // }, + // { + // field: "roll_no", + // headerName: "Student Roll No", + // }, + { + field: "path", + headerName: "Documents Link", + sortable: false, + align: "center", + width: 400, + headerAlign: "center", + valueGetter: (params) => getURL(params?.value), + renderCell: (params) => ( + + ), + }, + { + field: "verified", + headerName: "Verification Status", + align: "center", + headerAlign: "center", + valueGetter: ({ value }) => { + if (value) { + if (value) return "Accepted"; + return "Rejected"; + } + if (!value?.Valid) return "Pending Verification"; + return "Unkown"; + }, + }, + { + field: "action_taken_by", + headerName: "Action Taken By", + align: "center", + headerAlign: "center", + hide: true, + }, + + { + field: "AskClarification", + headerName: "Ask Clarification", + align: "center", + headerAlign: "center", + // eslint-disable-next-line consistent-return + renderCell: (params) => ( + + ), + // eslint-disable-next-line react-hooks/rules-of-hooks + }, + { + field: "options", + headerName: "", + align: "center", + // eslint-disable-next-line consistent-return + renderCell: (cellValues) => { + if (!cellValues.row.verified || role === 100 || role === 101) { + return ( + + + + + ); + } + }, + }, + ]; + + return ( +
+ + + + +

Documents

+
+
+ + row.ID} + columns={columns} + /> +
+
+ ); +} + +Index.layout = "adminDashBoard"; +export default Index; diff --git a/pages/student/documents.tsx b/pages/student/documents.tsx new file mode 100644 index 00000000..a04e4674 --- /dev/null +++ b/pages/student/documents.tsx @@ -0,0 +1,333 @@ +/* eslint-disable no-nested-ternary */ +import React, { useEffect, useState } from "react"; +import { GridColDef } from "@mui/x-data-grid"; +import { + Box, + Button, + CircularProgress, + Fab, + Grid, + IconButton, + Modal, + Stack, + Typography, +} from "@mui/material"; +import AddIcon from "@mui/icons-material/Add"; +import { useRouter } from "next/router"; +import CheckIcon from "@mui/icons-material/Check"; +import CloseIcon from "@mui/icons-material/Close"; +import AvTimerIcon from "@mui/icons-material/AvTimer"; +import { styled } from "@mui/material/styles"; +import { green } from "@mui/material/colors"; +import SaveIcon from "@mui/icons-material/Save"; + +import useStore from "@store/store"; +import DataGrid from "@components/DataGrid"; +import Meta from "@components/Meta"; +import documentRequest, { + AllStudentDocumentsResponse, +} from "@callbacks/student/rc/documents"; +import { CDN_URL } from "@callbacks/constants"; +import { errorNotification } from "@callbacks/notifcation"; + +const boxStyle = { + position: "absolute" as const, + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: 400, + bgcolor: "background.paper", + border: "white solid 2px", + borderRadius: "10px", + boxShadow: 24, + p: 4, +}; + +const gridMain = { + width: "100%", + display: "flex", + alignItems: "right", + justifyContent: "right", +}; + +const transformName = (name: string) => { + const nname = name.replace(`${CDN_URL}/view/`, ""); + const nameArray = nname.split("."); + const newName = nameArray[0].slice(14, -33); + const newNameWithExtension = `${newName}.${nameArray[1]}`; + return newNameWithExtension; +}; + +const getURL = (url: string) => `${CDN_URL}/view/${url}`; + +const columns: GridColDef[] = [ + { + field: "ID", + headerName: "Document ID", + align: "center", + headerAlign: "center", + }, + { + field: "path", + headerName: "Document Link", + sortable: false, + align: "center", + headerAlign: "center", + valueGetter: (params) => getURL(params?.value), + renderCell: (params) => ( + + ), + }, + { + field: "CreatedAt", + valueGetter: ({ value }) => value && `${new Date(value).toLocaleString()}`, + headerName: "Upload Time", + align: "center", + headerAlign: "center", + }, + { + field: "verified", + headerName: "Verification Status", + align: "center", + headerAlign: "center", + renderCell: (params) => + params.row.verified ? ( + params.row.verified ? ( + + ) : ( + + ) + ) : ( + + ), + }, +]; + +const Input = styled("input")({ + display: "none", +}); + +function Resume() { + const router = useRouter(); + const [fileSaved, setFileSaved] = useState(null); + const { token } = useStore(); + const [allDocuments, setAllDocuments] = useState< + AllStudentDocumentsResponse[] + >([]); + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + + const handleOpen = () => setOpen(true); + const handleClose = () => { + setOpen(false); + setSuccess(false); + setFileSaved(null); + setLoading(false); + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const handleChange = (event: { target: { files: any } }) => { + const { files } = event.target; + + if (allDocuments.filter((document) => !document.verified).length >= 5) { + errorNotification("You can only upload 5 documents", "Cannot upload"); + setLoading(false); + return; + } + + if (!(files && files.length > 0)) { + setLoading(false); + return; + } + + const file = files[0]; + + if (file.size > 256000) { + errorNotification("File size too large", "Max file size is about 200KB"); + setLoading(false); + return; + } + + setFileSaved(file); + setSuccess(true); + setLoading(false); + }; + + const fetchData = React.useCallback(async () => { + const data = await documentRequest.get(token); + setAllDocuments(data); + }, [token]); + + const handleSubmit = async (event: { preventDefault: () => void }) => { + event.preventDefault(); + + const formData = new FormData(); + formData.append("file", fileSaved !== null ? fileSaved : new Blob()); + await documentRequest.post(formData, token); + setFileSaved(null); + handleClose(); + fetchData(); + }; + + useEffect(() => { + if (router.isReady) { + fetchData(); + } + + setLoading(false); + setSuccess(false); + }, [fetchData, router.isReady]); + + const handleButtonClick = () => { + if (!loading) { + setSuccess(false); + setLoading(true); + } + }; + + const buttonSx = { + ...(success && { + bgcolor: green[500], + "&:hover": { + bgcolor: green[700], + }, + }), + }; + + return ( + <> +
+ + + +

Manage Documents

+
+ +
+ + + +
+
+
+
+ Instructions for submitting your resume: +
    +
  1. + Documents should be in PDF format only. +
  2. +
  3. + The maximum permissible file size is 200KB.{" "} +
  4. +
  5. + You have to upload the following documents: +
      +
    1. 10th Marksheet
    2. +
    3. 12th Marksheet
    4. +
    5. Pingala Verification
    6. +
    7. JEE Mains Result
    8. +
    9. JEE Advanced Result
    10. +
    +
  6. +
+
+ + row.ID} + columns={columns} + /> + +
+ + +

+ Upload Document +

+
+ + + + {fileSaved?.name} + + + +
+
+
+ + ); +} +Resume.layout = "studentDashboard"; +export default Resume; From 83eb1341f0400c4b2eb5fa3e5c095537a09e519e Mon Sep 17 00:00:00 2001 From: Abhimanyu-dev Date: Fri, 5 Jul 2024 04:47:39 +0530 Subject: [PATCH 2/5] Updated document upload section --- callbacks/admin/student/documents.ts | 28 ++++-- callbacks/student/rc/documents.ts | 16 ++-- pages/admin/documents/index.tsx | 76 ++++++++++++---- pages/student/documents.tsx | 130 ++++++++++++++++++++++++--- 4 files changed, 211 insertions(+), 39 deletions(-) diff --git a/callbacks/admin/student/documents.ts b/callbacks/admin/student/documents.ts index 4a80c4ed..d22ad44f 100644 --- a/callbacks/admin/student/documents.ts +++ b/callbacks/admin/student/documents.ts @@ -21,10 +21,12 @@ interface nullBool { } export interface AllStudentDocumentsResponse { - name: string; - email: string; + ID: number; + CreatedAt: string | null; + UpdatedAt: string | null; + DeletedAt: string | null; sid: number; - rsid: number; + type: string; path: string; verified: boolean; action_taken_by: string; @@ -35,8 +37,6 @@ export interface StudentDocumentsResponse { CreatedAt: string; UpdatedAt: string; DeletedAt: string; - recruitment_cycle_id: number; - student_recruitment_cycle_id: number; path: string; verified: boolean; action_taken_by: string; @@ -74,10 +74,24 @@ const adminDocumentsRequest = { ); return [] as StudentDocumentsResponse[]; }), - putVerify: (token: string, docid: string, body: VerifyBody) => + getByType: (token: string, type: string) => + instance + .get( + `/documents/type/${type}`, + setConfig(token) + ) + .then(responseBody) + .catch((err: ErrorType) => { + errorNotification( + "Error infetching data", + err.response?.data?.error || err.message + ); + return [] as AllStudentDocumentsResponse[]; + }), + putVerify: (token: string, docid: number, body: VerifyBody) => instance .put>( - `/documents/${docid}/verify`, + `/document/${docid}/verify`, body, setConfig(token) ) diff --git a/callbacks/student/rc/documents.ts b/callbacks/student/rc/documents.ts index 42653faa..a3c5d2be 100644 --- a/callbacks/student/rc/documents.ts +++ b/callbacks/student/rc/documents.ts @@ -30,6 +30,8 @@ export interface DocumentResponse { export interface DocumentsBackendParams { path: string; + type: string; + sid: number; } // eslint-disable-next-line no-unused-vars @@ -40,18 +42,18 @@ interface nullBool { export interface AllStudentDocumentsResponse { ID: number; - CreatedAt: string; - UpdatedAt: string; + CreatedAt: string | null; + UpdatedAt: string | null; DeletedAt: string | null; - student_recruitment_cycle_id: number; - recruitment_cycle_id: number; - resume: string; + sid: number; + type: string; + path: string; verified: boolean; action_taken_by: string; } const documentRequest = { - post: (body: FormData, token: string) => + post: (body: FormData, token: string, doc: AllStudentDocumentsResponse) => cdn_instance .post< DocumentResponse, @@ -74,6 +76,8 @@ const documentRequest = { `/document`, { path: response.data.filename, + type: doc.type, + sid: doc.sid, }, setConfig(token) ) diff --git a/pages/admin/documents/index.tsx b/pages/admin/documents/index.tsx index 21d10568..3a706fbe 100644 --- a/pages/admin/documents/index.tsx +++ b/pages/admin/documents/index.tsx @@ -1,4 +1,13 @@ -import { Button, Container, Modal, Stack } from "@mui/material"; +import { + Button, + Container, + MenuItem, + Modal, + Select, + SelectChangeEvent, + Stack, + Typography, +} from "@mui/material"; import Grid from "@mui/material/Grid"; // import { GridColDef, GridValueGetterParams } from "@mui/x-data-grid"; import { GridColDef } from "@mui/x-data-grid"; @@ -24,8 +33,17 @@ const transformName = (name: string) => { const getURL = (url: string) => `${CDN_URL}/view/${url}`; +const documentTypes = [ + "10th Marksheet", + "12th Marksheet", + "Pingala Transcript", + "JEE Mains Result", + "JEE Advanced Result", + "All", +]; + function AcceptDocumentButton(props: { - docid: string; + docid: number; updateCallback: () => Promise; }) { const { token } = useStore(); @@ -50,7 +68,7 @@ function AcceptDocumentButton(props: { } function RejectResumeButton(props: { - docid: string; + docid: number; updateCallback: () => Promise; }) { const { token } = useStore(); @@ -103,24 +121,33 @@ function AskClarification(props: { ); } function Index() { + const [type, setType] = useState(documentTypes[5]); const [allDocuments, setAllDocuments] = useState< AllStudentDocumentsResponse[] >([]); const { token, role } = useStore(); - useEffect(() => { - const fetchData = async () => { + + const updateTable = React.useCallback(async () => { + if (type === "All") { const res = await adminDocumentsRequest.getAll(token); if (res !== null && res?.length > 0) setAllDocuments(res); else setAllDocuments([]); - }; - fetchData(); - }, [token]); + } else { + const res = await adminDocumentsRequest.getByType(token, type); + if (res !== null && res?.length > 0) setAllDocuments(res); + else setAllDocuments([]); + } + }, [token, type]); - const updateTable = React.useCallback(async () => { - const res = await adminDocumentsRequest.getAll(token); - if (res !== null && res?.length > 0) setAllDocuments(res); - else setAllDocuments([]); - }, [token]); + useEffect(() => { + updateTable(); + }, [updateTable]); + + const handleChangeType = (e: SelectChangeEvent) => { + setType(e.target.value); + setAllDocuments([]); + updateTable(); + }; const columns: GridColDef[] = [ { @@ -137,6 +164,11 @@ function Index() { headerName: "Updated At", hide: true, }, + { + field: "type", + headerName: "Document Type", + hide: false, + }, // { // field: "name", // headerName: "Student Name", @@ -222,11 +254,11 @@ function Index() { return ( @@ -239,6 +271,20 @@ function Index() { return (
+ + Select Type + + (""); + const [currentHeading, setCurrentHeading] = useState(""); + const [currentFile, setCurrentFile] = useState(""); const [fileSaved, setFileSaved] = useState(null); + const [studentData, setStudent] = useState({ ID: 0 } as Student); const { token } = useStore(); const [allDocuments, setAllDocuments] = useState< AllStudentDocumentsResponse[] >([]); const [open, setOpen] = useState(false); + const [UploadOpen, setUploadOpen] = useState(false); const [loading, setLoading] = useState(false); const [success, setSuccess] = useState(false); - const handleOpen = () => setOpen(true); + const handleOpen = (event: any) => { + let name = `${fileNames[parseInt(event.target.id, 10)]}_${fileName}.pdf`; + let heading = modalHeaders[parseInt(event.target.id, 10)]; + setCurrentFile(name); + setCurrentHeading(heading); + setOpen(true); + }; const handleClose = () => { setOpen(false); setSuccess(false); setFileSaved(null); setLoading(false); + setCurrentFile(""); + setCurrentHeading(""); + }; + + const handleUploadOpen = () => setUploadOpen(true); + const handleUploadClose = () => setUploadOpen(false); + + const isDisabled = (name: string) => { + let result = false; + allDocuments.forEach((doc) => { + if (doc.type === name) { + result = true; + } + }); + return result; }; // eslint-disable-next-line @typescript-eslint/no-explicit-any const handleChange = (event: { target: { files: any } }) => { const { files } = event.target; - if (allDocuments.filter((document) => !document.verified).length >= 5) { + if (allDocuments.filter((doc) => !doc.verified).length >= 5) { errorNotification("You can only upload 5 documents", "Cannot upload"); setLoading(false); return; @@ -177,6 +221,15 @@ function Resume() { return; } + if (file.name !== currentFile) { + errorNotification( + "File must follow the name constraint", + `Expected File Name: ${currentFile}` + ); + setLoading(false); + return; + } + setFileSaved(file); setSuccess(true); setLoading(false); @@ -189,23 +242,44 @@ function Resume() { const handleSubmit = async (event: { preventDefault: () => void }) => { event.preventDefault(); - + let doc = { ID: 0 } as AllStudentDocumentsResponse; + doc.sid = studentData.ID; + doc.type = currentHeading; const formData = new FormData(); + formData.append("type", currentFile); formData.append("file", fileSaved !== null ? fileSaved : new Blob()); - await documentRequest.post(formData, token); + + await documentRequest.post(formData, token, doc); setFileSaved(null); handleClose(); fetchData(); }; useEffect(() => { + const fetchStudent = async () => { + const student = await studentRequest + .get(token) + .catch(() => ({ ID: 0 } as Student)); + if (student.ID) { + const progdept = getDeptProgram(student.program_department_id); + let name = `${student.roll_no.split("@")[0]} ${ + student.name + } ${progdept}`; + name = name.replace(/[^\w]/gi, "_"); + name = name.toLowerCase(); + setFileName(name); + setStudent(student); + } + }; + if (router.isReady) { fetchData(); + fetchStudent(); } setLoading(false); setSuccess(false); - }, [fetchData, router.isReady]); + }, [fetchData, token, router.isReady]); const handleButtonClick = () => { if (!loading) { @@ -233,7 +307,7 @@ function Resume() {
- +
@@ -270,9 +344,16 @@ function Resume() {
-

- Upload Document +

+ Upload {currentHeading}

+

File Name: {currentFile}

+ + +

Documents to upload:

+ + {modalHeaders.map((name, index) => ( + + ))} + +
+
); } -Resume.layout = "studentDashboard"; -export default Resume; +Documents.layout = "studentDashboard"; +export default Documents; From eb56ba2cb497f984629221614e98dec902bca050 Mon Sep 17 00:00:00 2001 From: Abhimanyu-dev Date: Sat, 6 Jul 2024 03:28:01 +0530 Subject: [PATCH 3/5] Added other doc types and updated layout --- callbacks/admin/student/documents.ts | 15 ++- callbacks/student/rc/documents.ts | 11 +- .../DocumentGrid}/index.tsx | 82 +++---------- components/Layouts/LayoutWrapper.tsx | 7 -- .../rc/[rcid]/student/[studentid]/index.tsx | 5 + pages/admin/student/[studentId]/index.tsx | 2 + pages/student/documents.tsx | 109 +++++++++++++++--- 7 files changed, 142 insertions(+), 89 deletions(-) rename {pages/admin/documents => components/DocumentGrid}/index.tsx (79%) diff --git a/callbacks/admin/student/documents.ts b/callbacks/admin/student/documents.ts index d22ad44f..1b7e1b72 100644 --- a/callbacks/admin/student/documents.ts +++ b/callbacks/admin/student/documents.ts @@ -38,7 +38,7 @@ export interface StudentDocumentsResponse { UpdatedAt: string; DeletedAt: string; path: string; - verified: boolean; + verified: nullBool; action_taken_by: string; } export interface VerifyBody { @@ -83,7 +83,18 @@ const adminDocumentsRequest = { .then(responseBody) .catch((err: ErrorType) => { errorNotification( - "Error infetching data", + "Error in fetching data", + err.response?.data?.error || err.message + ); + return [] as AllStudentDocumentsResponse[]; + }), + getBySid: (token: string, sid: string | string[]) => + instance + .get(`/${sid}/documents`, setConfig(token)) + .then(responseBody) + .catch((err: ErrorType) => { + errorNotification( + "Error in fetching data", err.response?.data?.error || err.message ); return [] as AllStudentDocumentsResponse[]; diff --git a/callbacks/student/rc/documents.ts b/callbacks/student/rc/documents.ts index a3c5d2be..0dce8687 100644 --- a/callbacks/student/rc/documents.ts +++ b/callbacks/student/rc/documents.ts @@ -48,12 +48,17 @@ export interface AllStudentDocumentsResponse { sid: number; type: string; path: string; - verified: boolean; + verified: nullBool; action_taken_by: string; } const documentRequest = { - post: (body: FormData, token: string, doc: AllStudentDocumentsResponse) => + post: ( + body: FormData, + token: string, + doc: AllStudentDocumentsResponse, + sid: number + ) => cdn_instance .post< DocumentResponse, @@ -77,7 +82,7 @@ const documentRequest = { { path: response.data.filename, type: doc.type, - sid: doc.sid, + sid, }, setConfig(token) ) diff --git a/pages/admin/documents/index.tsx b/components/DocumentGrid/index.tsx similarity index 79% rename from pages/admin/documents/index.tsx rename to components/DocumentGrid/index.tsx index 3a706fbe..2fb6cf4d 100644 --- a/pages/admin/documents/index.tsx +++ b/components/DocumentGrid/index.tsx @@ -1,18 +1,10 @@ -import { - Button, - Container, - MenuItem, - Modal, - Select, - SelectChangeEvent, - Stack, - Typography, -} from "@mui/material"; +import { Button, Container, Modal } from "@mui/material"; import Grid from "@mui/material/Grid"; // import { GridColDef, GridValueGetterParams } from "@mui/x-data-grid"; import { GridColDef } from "@mui/x-data-grid"; import * as React from "react"; import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; import adminDocumentsRequest, { AllStudentDocumentsResponse, @@ -33,15 +25,6 @@ const transformName = (name: string) => { const getURL = (url: string) => `${CDN_URL}/view/${url}`; -const documentTypes = [ - "10th Marksheet", - "12th Marksheet", - "Pingala Transcript", - "JEE Mains Result", - "JEE Advanced Result", - "All", -]; - function AcceptDocumentButton(props: { docid: number; updateCallback: () => Promise; @@ -120,35 +103,33 @@ function AskClarification(props: {
); } -function Index() { - const [type, setType] = useState(documentTypes[5]); +function DocumentGrid() { const [allDocuments, setAllDocuments] = useState< AllStudentDocumentsResponse[] >([]); + const router = useRouter(); const { token, role } = useStore(); + const { query } = router; + let { studentId } = query; + if (!studentId) { + studentId = query.studentid; + } const updateTable = React.useCallback(async () => { - if (type === "All") { - const res = await adminDocumentsRequest.getAll(token); - if (res !== null && res?.length > 0) setAllDocuments(res); - else setAllDocuments([]); - } else { - const res = await adminDocumentsRequest.getByType(token, type); + if (router.isReady) { + const res = await adminDocumentsRequest.getBySid( + token, + studentId as string + ); if (res !== null && res?.length > 0) setAllDocuments(res); else setAllDocuments([]); } - }, [token, type]); + }, [token, studentId, router.isReady]); useEffect(() => { updateTable(); }, [updateTable]); - const handleChangeType = (e: SelectChangeEvent) => { - setType(e.target.value); - setAllDocuments([]); - updateTable(); - }; - const columns: GridColDef[] = [ { field: "ID", @@ -217,8 +198,8 @@ function Index() { align: "center", headerAlign: "center", valueGetter: ({ value }) => { - if (value) { - if (value) return "Accepted"; + if (value?.Valid) { + if (value?.Bool) return "Accepted"; return "Rejected"; } if (!value?.Valid) return "Pending Verification"; @@ -246,7 +227,7 @@ function Index() { }, { field: "options", - headerName: "", + headerName: "Accept/Reject Document", align: "center", // eslint-disable-next-line consistent-return renderCell: (cellValues) => { @@ -271,31 +252,7 @@ function Index() { return (
- - Select Type - - - - -

Documents

-
-
- row.ID} @@ -306,5 +263,4 @@ function Index() { ); } -Index.layout = "adminDashBoard"; -export default Index; +export default DocumentGrid; diff --git a/components/Layouts/LayoutWrapper.tsx b/components/Layouts/LayoutWrapper.tsx index f9d50eb7..b9ff0813 100644 --- a/components/Layouts/LayoutWrapper.tsx +++ b/components/Layouts/LayoutWrapper.tsx @@ -430,13 +430,6 @@ function LayoutWrapper({ children }: { children: JSX.Element }) { id: "/company", } : { avatar: <>, name: "", id: "", hidden: true }, - role === 100 || role === 101 - ? { - avatar: , - name: "Master Database (Documents)", - id: "/documents", - } - : { avatar: <>, name: "", id: "", hidden: true }, role === 100 || role === 101 ? { avatar: , diff --git a/pages/admin/rc/[rcid]/student/[studentid]/index.tsx b/pages/admin/rc/[rcid]/student/[studentid]/index.tsx index 054b8b84..95c24d01 100644 --- a/pages/admin/rc/[rcid]/student/[studentid]/index.tsx +++ b/pages/admin/rc/[rcid]/student/[studentid]/index.tsx @@ -31,6 +31,7 @@ import getStudentApplication, { } from "@callbacks/admin/rc/student/getApplications"; import { CDN_URL } from "@callbacks/constants"; import Clarification from "@components/Modals/clarification"; +import DocumentGrid from "@components/DocumentGrid"; const transformName = (name: string) => { const nname = name.replace(`${CDN_URL}/view/`, ""); @@ -579,6 +580,10 @@ function Index() { getRowId={(row) => row?.ID || 0} />
+
+

Document Status

+ +

Application Status

diff --git a/pages/admin/student/[studentId]/index.tsx b/pages/admin/student/[studentId]/index.tsx index 41cf905b..470c5385 100644 --- a/pages/admin/student/[studentId]/index.tsx +++ b/pages/admin/student/[studentId]/index.tsx @@ -9,6 +9,7 @@ import AdminStudentRequest, { } from "@callbacks/admin/student/adminStudent"; import useStore from "@store/store"; import { getDepartment, getProgram } from "@components/Parser/parser"; +import DocumentGrid from "@components/DocumentGrid"; const info: { field: string; value: string; disabled: boolean; api_id: any }[] = [ @@ -363,6 +364,7 @@ function Details() { +
); } diff --git a/pages/student/documents.tsx b/pages/student/documents.tsx index 0b0944cf..6e06c1dc 100644 --- a/pages/student/documents.tsx +++ b/pages/student/documents.tsx @@ -8,6 +8,7 @@ import { Fab, Grid, IconButton, + Input, Modal, Stack, Typography, @@ -107,8 +108,8 @@ const columns: GridColDef[] = [ align: "center", headerAlign: "center", renderCell: (params) => - params.row.verified ? ( - params.row.verified ? ( + params.row.verified?.Valid ? ( + params.row.verified?.Bool ? ( + // ), + // }, ]; -const Input = styled("input")({ +const FileInput = styled("input")({ display: "none", }); -const fileNames = ["10th_marks", "12th_marks", "pingala", "mains", "advanced"]; +const fileNames = [ + "10th_marks", + "12th_marks", + "pingala", + "mains", + "advanced", + "others", +]; const modalHeaders = [ "10th Marksheet", @@ -151,6 +170,7 @@ const modalHeaders = [ "Pingala Transcript", "JEE Mains Result", "JEE Advanced Result", + "Others", ]; function Documents() { @@ -166,10 +186,30 @@ function Documents() { >([]); const [open, setOpen] = useState(false); const [UploadOpen, setUploadOpen] = useState(false); + const [typeModal, setTypeModal] = useState(false); const [loading, setLoading] = useState(false); const [success, setSuccess] = useState(false); const handleOpen = (event: any) => { + if (typeModal) { + let error = false; + allDocuments.forEach((doc) => { + if (doc.type === currentHeading) error = true; + }); + if (error) { + errorNotification( + "Duplicate Document", + "You have already uploaded this document" + ); + return; + } + setOpen(true); + return; + } + if (event.target.id === "5") { + setTypeModal(true); + return; + } let name = `${fileNames[parseInt(event.target.id, 10)]}_${fileName}.pdf`; let heading = modalHeaders[parseInt(event.target.id, 10)]; setCurrentFile(name); @@ -185,13 +225,28 @@ function Documents() { setCurrentHeading(""); }; + const handleTypeChange = (event: { target: { value: string } }) => { + let name = event.target.value; + name = name.toLowerCase().split(" ").join("_"); + if (name.length !== 0) setCurrentFile(`${name}_${fileName}.pdf`); + else setCurrentFile(""); + setCurrentHeading(event.target.value); + }; + + const handleTypeClose = () => { + setTypeModal(false); + }; + const handleUploadOpen = () => setUploadOpen(true); const handleUploadClose = () => setUploadOpen(false); const isDisabled = (name: string) => { + if (name === "Others") { + return false; + } let result = false; allDocuments.forEach((doc) => { - if (doc.type === name) { + if (doc.type === name && doc.verified.Bool) { result = true; } }); @@ -202,12 +257,6 @@ function Documents() { const handleChange = (event: { target: { files: any } }) => { const { files } = event.target; - if (allDocuments.filter((doc) => !doc.verified).length >= 5) { - errorNotification("You can only upload 5 documents", "Cannot upload"); - setLoading(false); - return; - } - if (!(files && files.length > 0)) { setLoading(false); return; @@ -246,10 +295,9 @@ function Documents() { doc.sid = studentData.ID; doc.type = currentHeading; const formData = new FormData(); - formData.append("type", currentFile); formData.append("file", fileSaved !== null ? fileSaved : new Blob()); - await documentRequest.post(formData, token, doc); + await documentRequest.post(formData, token, doc, studentData.ID); setFileSaved(null); handleClose(); fetchData(); @@ -322,6 +370,10 @@ function Documents() {
  • The maximum permissible file size is 200KB.{" "}
  • +
  • + Be careful while uploading documents because you can only upload a + document once.{" "} +
  • You have to upload the following documents:
      @@ -330,6 +382,7 @@ function Documents() {
    1. Pingala Verification
    2. JEE Mains Result
    3. JEE Advanced Result
    4. +
    5. Any other necessary document
  • @@ -365,7 +418,7 @@ function Documents() { > - +
    + +
    ); } From da7fca8e30207d39c3e2e1f45272d772052d0f17 Mon Sep 17 00:00:00 2001 From: Abhimanyu-dev Date: Sun, 7 Jul 2024 12:56:59 +0530 Subject: [PATCH 5/5] Minor Changes --- pages/admin/student/[studentId]/index.tsx | 1 + pages/student/documents.tsx | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pages/admin/student/[studentId]/index.tsx b/pages/admin/student/[studentId]/index.tsx index 64a04c07..59a2f7a5 100644 --- a/pages/admin/student/[studentId]/index.tsx +++ b/pages/admin/student/[studentId]/index.tsx @@ -365,6 +365,7 @@ function Details() {
    +

    Documents

    diff --git a/pages/student/documents.tsx b/pages/student/documents.tsx index 6e06c1dc..64faf448 100644 --- a/pages/student/documents.tsx +++ b/pages/student/documents.tsx @@ -246,8 +246,14 @@ function Documents() { } let result = false; allDocuments.forEach((doc) => { - if (doc.type === name && doc.verified.Bool) { - result = true; + if (doc.type === name) { + if (doc.verified.Valid) { + if (doc.verified.Bool) { + result = true; + } + } else { + result = true; + } } }); return result; @@ -372,7 +378,7 @@ function Documents() {
  • Be careful while uploading documents because you can only upload a - document once.{" "} + document once before being verified.{" "}
  • You have to upload the following documents: