From f98d6b95d1658b2a3c07c6482278e979853858c1 Mon Sep 17 00:00:00 2001 From: AryanGKulkarni <95529889+AryanGKulkarni@users.noreply.github.com> Date: Sun, 2 Jun 2024 21:01:01 +0530 Subject: [PATCH] Student view (#53) Co-authored-by: AKSHAT SHARMA --- package-lock.json | 98 +++- package.json | 4 +- src/app/(routes)/student/[studentId]/page.tsx | 81 ---- .../student/interviewExperiences/page.tsx | 87 ++++ .../(routes)/student/jobs/[jobId]/page.tsx | 423 +++++++----------- .../student/jobs/salary/[jobId]/page.tsx | 63 +++ .../student/jobs/salary/[salaryId]/page.tsx | 21 - src/app/(routes)/student/offCampus/page.tsx | 43 ++ src/app/(routes)/student/onCampus/page.tsx | 51 +++ src/app/(routes)/student/profile/page.tsx | 179 ++++++++ src/app/(routes)/student/resumes/page.tsx | 156 +++++++ src/components/HorizontalTimeline.tsx | 103 ++++- src/components/JAF/JafCard.tsx | 2 +- src/components/NavButtonGroup.tsx | 2 +- src/components/SideBar/Roles/admin.tsx | 265 +++++++++++ src/components/SideBar/Roles/recruiter.tsx | 128 ++++++ src/components/SideBar/Roles/student.tsx | 197 ++++++++ src/components/Sidebar.tsx | 360 +-------------- src/components/jobs/JobCard.tsx | 162 +++---- src/components/jobs/OffCampusCard.tsx | 164 +++++++ src/components/jobs/SalaryCard.tsx | 219 +++++++++ src/components/ui/dropdown-menu.tsx | 107 ++--- src/components/ui/hover-card.tsx | 29 ++ src/dummyData/Interviews.tsx | 115 +++++ src/dummyData/salary.js | 172 +++++++ src/helpers/student/api.ts | 154 +++++++ src/helpers/student/types.ts | 314 +++++++++++++ tsconfig.json | 2 +- yarn.lock | 47 ++ 29 files changed, 2868 insertions(+), 880 deletions(-) delete mode 100644 src/app/(routes)/student/[studentId]/page.tsx create mode 100644 src/app/(routes)/student/interviewExperiences/page.tsx create mode 100644 src/app/(routes)/student/jobs/salary/[jobId]/page.tsx delete mode 100644 src/app/(routes)/student/jobs/salary/[salaryId]/page.tsx create mode 100644 src/app/(routes)/student/offCampus/page.tsx create mode 100644 src/app/(routes)/student/onCampus/page.tsx create mode 100644 src/app/(routes)/student/profile/page.tsx create mode 100644 src/app/(routes)/student/resumes/page.tsx create mode 100644 src/components/SideBar/Roles/admin.tsx create mode 100644 src/components/SideBar/Roles/recruiter.tsx create mode 100644 src/components/SideBar/Roles/student.tsx create mode 100644 src/components/jobs/OffCampusCard.tsx create mode 100644 src/components/jobs/SalaryCard.tsx create mode 100644 src/components/ui/hover-card.tsx create mode 100644 src/dummyData/Interviews.tsx create mode 100644 src/dummyData/salary.js create mode 100644 src/helpers/student/api.ts create mode 100644 src/helpers/student/types.ts diff --git a/package-lock.json b/package-lock.json index d2ea8be1..dd81e551 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,11 @@ "version": "0.1.0", "dependencies": { "@radix-ui/colors": "^3.0.0", + "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-hover-card": "^1.0.7", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.7", @@ -21,7 +23,6 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", "@reduxjs/toolkit": "^2.0.1", - "@tailwindcss/forms": "^0.5.7", "@tanstack/react-table": "^8.11.2", "@types/qs": "^6.9.14", "antd": "^5.13.1", @@ -64,6 +65,7 @@ "yup": "^1.3.3" }, "devDependencies": { + "@tailwindcss/forms": "^0.5.7", "@types/js-cookie": "^3.0.6", "@types/node": "latest", "@types/papaparse": "^5.3.10", @@ -652,6 +654,37 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.1.2.tgz", + "integrity": "sha512-fDG7jcoNKVjSK6yfmuAs0EnPDro0WMXIhMtXdTBWqEioVW206ku+4Lw07e+13lUkFkpoEQ2PdeMIAGpdqEAmDg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collapsible": "1.0.3", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz", @@ -705,6 +738,36 @@ } } }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.0.3.tgz", + "integrity": "sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz", @@ -916,6 +979,37 @@ } } }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.0.7.tgz", + "integrity": "sha512-OcUN2FU0YpmajD/qkph3XzMcK/NmSk9hGWnjV68p6QiZMgILugusgQwnLSDs3oFSJYGKf3Y49zgFedhGh04k9A==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-icons": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz", @@ -1631,6 +1725,7 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz", "integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==", + "dev": true, "dependencies": { "mini-svg-data-uri": "^1.2.3" }, @@ -4979,6 +5074,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "dev": true, "bin": { "mini-svg-data-uri": "cli.js" } diff --git a/package.json b/package.json index 744cb581..fa7aa394 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,11 @@ }, "dependencies": { "@radix-ui/colors": "^3.0.0", + "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-hover-card": "^1.0.7", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.7", @@ -22,7 +24,6 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", "@reduxjs/toolkit": "^2.0.1", - "@tailwindcss/forms": "^0.5.7", "@tanstack/react-table": "^8.11.2", "@types/qs": "^6.9.14", "antd": "^5.13.1", @@ -65,6 +66,7 @@ "yup": "^1.3.3" }, "devDependencies": { + "@tailwindcss/forms": "^0.5.7", "@types/js-cookie": "^3.0.6", "@types/node": "latest", "@types/papaparse": "^5.3.10", diff --git a/src/app/(routes)/student/[studentId]/page.tsx b/src/app/(routes)/student/[studentId]/page.tsx deleted file mode 100644 index 800837f2..00000000 --- a/src/app/(routes)/student/[studentId]/page.tsx +++ /dev/null @@ -1,81 +0,0 @@ -"use client"; -import React, { useEffect, useState } from "react"; -import JobCard from "@/components/jobs/JobCard"; -import { Jobs } from "@/dummyData/job"; - -interface Props {} - -interface Job { - id: string; - seasonId: string; - recruiterId: string; - companyId: string; - role: string; - active: boolean; - currentStatus: string; - season: { - id: string; - year: string; - type: string; - }; - company: { - id: string; - name: string; - }; -} - -const salaryData = { - salary: "Rs 40LPA", -}; -const resumes = [ - { - id:"1", - filepath: "Resume 1", - verified: true, - }, - { - id:"2", - filepath: "Resume 2", - verified: false, - }, -]; - -const StudentPage = ({ - params, -}: { - params: { - studentId: String; - }; -}) => { - - const [jobs, setJobs] = useState([]); - - useEffect(() => { - const fetchJobs = async () => { - const response = await fetch("http://localhost:5000/api/v1/jobs"); - if (!response.ok) { - throw new Error("Failed to fetch jobs"); - } - const data = await response.json(); - setJobs(data); - }; - - fetchJobs(); - // setJobs(Jobs); - }, []); - - return ( -
-
-

Apply

-
- {jobs.map((job) => ( -
- -
- ))} -
- ); -}; - -export default StudentPage; diff --git a/src/app/(routes)/student/interviewExperiences/page.tsx b/src/app/(routes)/student/interviewExperiences/page.tsx new file mode 100644 index 00000000..bfe254c4 --- /dev/null +++ b/src/app/(routes)/student/interviewExperiences/page.tsx @@ -0,0 +1,87 @@ +"use client"; +import React, { useState } from "react"; +import { Separator } from "../../../../components/ui/separator"; +import { + Accordion, + AccordionItem, + AccordionTrigger, + AccordionContent, +} from '@radix-ui/react-accordion'; +import { ChevronDownIcon } from '@radix-ui/react-icons'; +import {interviewExpData} from "../../../../dummyData/Interviews"; + +// interface Props { +// interviewExpData: InterviewExperience[]; +// } + + + +interface InterviewExperience { + ques: string; + ans: string; + student_name: string; + difficulty:string, + tags:Array; +} + +const InterviewExperiencesPage = () => { + const [open, setOpen] = useState(false); + return ( +
+
+

Interview Experiences

+
+ +
+ + {interviewExpData.map((e,i)=>( + + + + {e.ques} + + + + + + + + {e.ans} +
+
+
+ By {e.student_name} +
+
+ {e.difficulty} +
+
+ {e.tags[0]} +
+
+ {e.tags[1]} +
+
+
+
+ + ))} +
+
+ +
+ ) +} + +export default InterviewExperiencesPage; \ No newline at end of file diff --git a/src/app/(routes)/student/jobs/[jobId]/page.tsx b/src/app/(routes)/student/jobs/[jobId]/page.tsx index a3e5d2e3..3cdd18da 100644 --- a/src/app/(routes)/student/jobs/[jobId]/page.tsx +++ b/src/app/(routes)/student/jobs/[jobId]/page.tsx @@ -1,7 +1,5 @@ "use client"; import React, { useEffect, useState } from "react"; -import JobCard from "@/components/jobs/JobCard"; -import { Jobs } from "@/dummyData/job"; import { Separator } from "@/components/ui/separator"; import Link from "next/link"; import { @@ -16,130 +14,64 @@ import { import { Button } from "@/components/ui/button"; import { fetchEachJob } from "@/helpers/api"; import HorizontalTimeline from "@/components/HorizontalTimeline"; +import { Job, CustomEvent, EventData, CalenderEvent } from "@/helpers/student/types"; +import { GetJobById } from "@/helpers/student/api"; -const testData = [ - { date: "16/01/2017", status:"older-event" }, - { date: "28/02/2017", status:"older-event" }, - { date: "20/04/2017", status:"older-event" }, - { date: "20/05/2017", status:"older-event" }, - { date: "09/07/2017", status:"selected" }, - { date: "30/08/2017", status:"" }, - // { date: "15/09/2017", status:"" }, - // { date: "01/11/2017", status:"" }, - // { date: "10/12/2017", status:"" }, - // { date: "19/01/2018", status:"" }, - // { date: "03/03/2018", status:"" } -]; +function transformEvents(events: CustomEvent[]): EventData[] { + + // Get the current date + const currentDate = new Date(); + // Sort events by startDateTime + const sortedEvents = events.sort((a, b) => new Date(a.startDateTime).getTime() - new Date(b.startDateTime).getTime()); -interface Props {} + // Transform the sorted events into the desired format + const result: EventData[] = sortedEvents.map(event => { + const eventDate = new Date(event.startDateTime); + let status: string; -interface Job { - id: string; - seasonId: string; - recruiterId: string; - companyId: string; - role: string; - active: boolean; - currentStatus: string; - companyDetailsFilled: { - name: string; - size: number; - address: { - city: string; - line1: string; - line2: string; - state: string; - country: string; - zipCode: string; - }; - domains: string[]; - category: string; - yearOfEstablishment: string; - }; - recruiterDetailsFilled: { - name: string; - email: string; - contact: string; - landline: string | null; - designation: string; - }; - selectionProcedure: { - tests: { - type: string; - duration: number; - }[]; - interviews: { - type: string; - duration: number; - }[]; - requirements: Record; // Update the type as per actual requirements - selectionMode: string; - groupDiscussion: boolean; - shortlistFromResume: boolean; - }; - attachment: string; - skills: string; - location: string; - noOfVacancies: number; - offerLetterReleaseDate: string; - joiningDate: string; - duration: number; - season: { - id: string; - year: string; - type: string; - }; - company: { - id: string; - name: string; - }; - recruiter: { - id: string; - userId: string; - companyId: string; - designation: string; - landline: string | null; - }; - salaries: { - id: string; - jobId: string; - salaryPeriod: string; - others: string | null; - criteria: { - minCPI: number; - genders: string[]; - programs: string[]; - categories: string[]; - tenthMarks: number; - twelthMarks: number; - facultyApprovals: string[]; - }; - baseSalary: number; - totalCTC: number; - takeHomeSalary: number; - grossSalary: number; - otherCompensations: number; - }[]; - jobCoordinators: { - id: string; - tpcMemberId: string; - role: string; - tpcMember: { - id: string; - department: string; - userId: string; - role: string; - user: { - id: string; - email: string; - name: string; - contact: string; - }; + if (eventDate < currentDate) { + status = "older-event"; + } else { + status = "newer-event"; + } + + return { + date: eventDate.toLocaleDateString('en-GB'), // Convert date to DD/MM/YYYY format + status, + title: event.type, }; - }[]; + }); + + // Find the first event after the current date and mark it as 'selected' + const firstFutureEventIndex = result.findIndex(event => new Date(event.date.split('/').reverse().join('-')) >= currentDate); + if (firstFutureEventIndex !== -1) { + result[firstFutureEventIndex].status = "selected"; + } else if (result.length > 0) { + // If all events have passed, mark the last event as 'selected' + result[result.length - 1].status = "selected"; + } + + return result; } +const transformEventsCalender = (jobData: Job): CalenderEvent[] => { + return jobData.events.map(event => ({ + day: new Date(event.startDateTime).setHours(0, 0, 0, 0), // startDate at midnight + description: event.metadata, + id: event.id, + label: "red", // assuming a fixed label as the original data doesn't provide this + timeFrom: event.startDateTime, + timeTo: event.endDateTime, + title: jobData.companyDetailsFilled.name, + })); +}; + +const storeCalenderEvents = (jobData: Job) => { + const transformedEvents = transformEventsCalender(jobData); + localStorage.setItem('savedEvents', JSON.stringify(transformedEvents)); +}; + function formatNumber(num: number): string { if (num >= 1000000000) { return (num / 1000000000).toFixed(1).replace(/\.0$/, '') + 'B'; @@ -164,160 +96,131 @@ const JobPage = ({ params }: { params: { jobId: string } }) => { useEffect(() => { const fetchJobData = async () => { - try { - const response = await fetch(`http://localhost:5000/api/v1/jobs/${params.jobId}`); - if (!response.ok) { - throw new Error('Failed to fetch job data'); - } - const data = await response.json(); - console.log(data); - setJobData(data); - } catch (error) { - console.error('Error fetching job data:', error); - } + const data = await GetJobById(params.jobId); + setJobData(data); + storeCalenderEvents(data); }; fetchJobData(); - // setJobData(fetchEachJob(params.jobId)) - // setJobData(Jobs[0]); }, [params.jobId]); return (
-
{jobData?.companyDetailsFilled.name}
-
- {jobData?.companyDetailsFilled.address.city}, {jobData?.companyDetailsFilled.address.state}, {jobData?.companyDetailsFilled.address.country} -
-
- -
-
-
-
Website
{" "} - Link -
-
-
Domain
{" "} -
- {jobData?.companyDetailsFilled.domains.length === 0 - ? "Not Available" - : jobData?.companyDetailsFilled.domains[0]} + {jobData===null? ( +
No Data
+ ): ( + <> +
{jobData?.companyDetailsFilled.name}
+
+ {jobData?.companyDetailsFilled.address.city}, {jobData?.companyDetailsFilled.address.state}, {jobData?.companyDetailsFilled.address.country}
-
-
-
Category
{" "} -
{jobData?.companyDetailsFilled.category}
-
-
-
Company Size
{" "} -
{formatNumber(jobData?.companyDetailsFilled.size ?? 0)}
-
-
-
Established
{" "} -
{jobData?.companyDetailsFilled.yearOfEstablishment}
-
-
-
- -
-

Recruiter

- - - - Name - Designation - Email - Mobile Number - - - - - {jobData?.recruiterDetailsFilled.name} - {jobData?.recruiterDetailsFilled.designation} - {jobData?.recruiterDetailsFilled.email} - {jobData?.recruiterDetailsFilled.contact} - - - - -
-
- -
-

Job Coordinators

- - - - Name - Role - Department - Email - Mobile Number - - - - {jobData?.jobCoordinators.map((coordinator, index) => ( - - {coordinator.tpcMember.user.name} - {coordinator.role} - {coordinator.tpcMember.department} - {coordinator.tpcMember.user.email} - {coordinator.tpcMember.user.contact} - - ))} - - - -
-
- -
-

Faculty Approval Requests

- - - - Name - Designation - Email - Mobile Number - - - - {faculty.map((faculty, index) => ( - - {faculty.name} - {faculty.designation} - {faculty.email} - {faculty.phoneNumber} - - ))} - - - -
-
- -
- -
- -
-
-
-
- +
+ +
+
+
+
Website
{" "} + Link +
+
+
Domain
{" "} +
+ {jobData?.companyDetailsFilled.domains.length === 0 + ? "Not Available" + : jobData?.companyDetailsFilled.domains[0]} +
+
+
+
Category
{" "} +
{jobData?.companyDetailsFilled.category}
+
+
+
Company Size
{" "} +
{formatNumber(jobData?.companyDetailsFilled.size ?? 0)}
+
+
+
Established
{" "} +
{jobData?.companyDetailsFilled.yearOfEstablishment}
+
+
+
+ +
+

Recruiter

+ + + + Name + Designation + Email + Mobile Number + + + + + {jobData?.recruiterDetailsFilled.name} + {jobData?.recruiterDetailsFilled.designation} + {jobData?.recruiterDetailsFilled.email} + {jobData?.recruiterDetailsFilled.contact} + + + + +
+
+ +
+

Job Coordinators

+ + + + Name + Role + Department + Email + Mobile Number + + + + {jobData?.jobCoordinators.map((coordinator, index) => ( + + {coordinator.tpcMember.user.name} + {coordinator.role} + {coordinator.tpcMember.department} + {coordinator.tpcMember.user.email} + {coordinator.tpcMember.user.contact} + + ))} + + + +
+
+ +
+ + {/* */} +
+
- +
+
+ +
+
+ +
+
-
-
+ + )}
); }; diff --git a/src/app/(routes)/student/jobs/salary/[jobId]/page.tsx b/src/app/(routes)/student/jobs/salary/[jobId]/page.tsx new file mode 100644 index 00000000..f800c79a --- /dev/null +++ b/src/app/(routes)/student/jobs/salary/[jobId]/page.tsx @@ -0,0 +1,63 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, +} from "@/components/ui/table"; +import { SampleJobData } from "@/dummyData/job"; +import { Separator } from "@/components/ui/separator"; +import SalaryCard from "@/components/jobs/SalaryCard"; +import { GetJobById } from "@/helpers/student/api"; + +interface Props {} +interface Salary { + id: string; + salaryPeriod: string; + programs: string[]; + genders: string[]; + categories: string[]; + minCPI: number; + tenthMarks: number; + twelthMarks: number; + facultyApprovals: string[]; + baseSalary: number; + totalCTC: number; + takeHomeSalary: number; + grossSalary: number; + otherCompensations: number; +} + +const SalaryPage = ({ params }: { params: { jobId: string } }) => { + const [salaryData, setSalaryData] = useState([]); + + useEffect(() => { + const fetchSalaryData = async () => { + const data = await GetJobById(params.jobId); + setSalaryData(data.salaries); + }; + + fetchSalaryData(); + }, [params.jobId]); + + return ( +
+
Salaries
+ {salaryData.length===0? ( +
+ No Data +
+ ): (salaryData.map((item,index)=>( +
+ +
+ )))} +
+ ); +}; + +export default SalaryPage; diff --git a/src/app/(routes)/student/jobs/salary/[salaryId]/page.tsx b/src/app/(routes)/student/jobs/salary/[salaryId]/page.tsx deleted file mode 100644 index 16afe1cc..00000000 --- a/src/app/(routes)/student/jobs/salary/[salaryId]/page.tsx +++ /dev/null @@ -1,21 +0,0 @@ -"use client"; -import React from "react"; - -interface Props {} - -const SalaryPage = ({ - params, -}: { - params: { - salaryId: String; - }; -}) => { - return ( -
-

Salary Page

-

Salary ID: {params.salaryId}

-
- ); -}; - -export default SalaryPage; diff --git a/src/app/(routes)/student/offCampus/page.tsx b/src/app/(routes)/student/offCampus/page.tsx new file mode 100644 index 00000000..a3671d74 --- /dev/null +++ b/src/app/(routes)/student/offCampus/page.tsx @@ -0,0 +1,43 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import OffCampusCard from "@/components/jobs/OffCampusCard"; +import { OffCampusOffer } from "@/helpers/student/types"; +import { GetOffCampusOffers } from "@/helpers/student/api"; +import Cookies from "js-cookie"; + +interface Props {} + +const OffCampusPage = () => { + + const [offCampusOffers, setOffCampusOffers] = useState([]); + + useEffect(() => { + const fetchJobs = async () => { + const oco = await GetOffCampusOffers(Cookies.get("accessToken")); + setOffCampusOffers(oco); + }; + + fetchJobs(); + // setJobs(Jobs); + }, []); + + + return ( +
+
+

Apply

+
+ {offCampusOffers.length===0? ( +
+ No Jobs +
+ ): (offCampusOffers.map((job)=>( +
+ +
+ )))} +
+ ); +}; + +export default OffCampusPage; diff --git a/src/app/(routes)/student/onCampus/page.tsx b/src/app/(routes)/student/onCampus/page.tsx new file mode 100644 index 00000000..4c3c0f2e --- /dev/null +++ b/src/app/(routes)/student/onCampus/page.tsx @@ -0,0 +1,51 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import JobCard from "@/components/jobs/JobCard"; +import { SampleJobData } from "@/dummyData/job"; +import InterviewExperiences from "@/app/(routes)/student/interviewExperiences/page"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import Cookies from "js-cookie"; +import { GetOnCampusOffers, GetResumes } from "@/helpers/student/api"; +import { OnCampusOffers, Resume } from "@/helpers/student/types"; + +interface Props {} + +const StudentPage = () => { + + const [onCampusOffers, setOnCampusOffers] = useState([]); + const [resumes, setResumes] = useState([]) + + useEffect(() => { + const fetchJobs = async () => { + const oco = await GetOnCampusOffers(Cookies.get("accessToken")); + setOnCampusOffers(oco); + + const res = await GetResumes(Cookies.get("accessToken")); + setResumes(res); + + }; + + fetchJobs(); + // setJobs(Jobs); + }, []); + + return ( +
+
+

Apply

+
+ {onCampusOffers.length===0? ( +
+ No Jobs +
+ ): (onCampusOffers.map((job)=>( +
+ +
+ )))} +
+ ); +}; + +export default StudentPage; diff --git a/src/app/(routes)/student/profile/page.tsx b/src/app/(routes)/student/profile/page.tsx new file mode 100644 index 00000000..d4bc60d2 --- /dev/null +++ b/src/app/(routes)/student/profile/page.tsx @@ -0,0 +1,179 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, +} from "@/components/ui/table"; +import { Separator } from "@/components/ui/separator"; +import { Button } from "@/components/ui/button"; +import { StudentDataType } from "@/helpers/student/types"; +import { GetStudentData } from "@/helpers/student/api"; +import Cookies from "js-cookie"; + +interface Props {} + +const ProfilePage = () => { + + const [studentData, setStudentData] = useState(null); + + const [totalPenalty, setTotalPenalty] = useState(0); + + useEffect(() => { + const fetchStudentData = async () => { + const data = await GetStudentData(Cookies.get("accessToken")); + setStudentData(data); + + if(data){ + const total = data.penalties.reduce((sum: number, penalty) => sum + penalty.penalty, 0); + setTotalPenalty(total); + } + } + + fetchStudentData(); + + }) + + return ( + <> + {studentData===null? ( +
No Data
+ ): ( +
+
+
+ {studentData.user.name} +
+
+ {studentData.rollNo} +
+
+
+ +
+

Program

+
+
+
Course
{" "} +
{studentData.program.course}
+
+
+
Branch
{" "} +
{studentData.program.branch}
+
+
+
Department
{" "} +
{studentData.program.department}
+
+
+
Year
{" "} +
{studentData.program.year}
+
+
+ +
+ +
+ +

Academics

+
+
+
Category
{" "} +
{studentData.category}
+
+
+
Gender
{" "} +
{studentData.gender}
+
+
+
CPI
{" "} +
{studentData.cpi}
+
+
+
Tenth Marks
{" "} +
{studentData.tenthMarks}
+
+
+
Twelth Marks
{" "} +
{studentData.twelthMarks}
+
+
+ + +
+ +
+ +

Penalties

+ + + + Sr. + Reason + Penalty + + + + {studentData.penalties.map((item,index)=>( + + {index+1} + {item.reason} + {item.penalty} + + ))} + + + + + + {totalPenalty} + + +
+
+ +
+ +

Seasons

+ + + + Sr. + Year + Type + Action + + + + {studentData.registrations.map((item,index)=>( + + {index+1} + {item.season.year} + {item.season.type} + + + + + ))} + +
+ +
+ +
+ +
+
+
+ )} + + ); +}; + +export default ProfilePage; diff --git a/src/app/(routes)/student/resumes/page.tsx b/src/app/(routes)/student/resumes/page.tsx new file mode 100644 index 00000000..1293e386 --- /dev/null +++ b/src/app/(routes)/student/resumes/page.tsx @@ -0,0 +1,156 @@ +"use client"; +import React, { useEffect, useState, ChangeEvent, FormEvent } from "react"; +import { + Table, + TableHeader, + TableBody, + TableHead, + TableRow, + TableCell, +} from "@/components/ui/table"; +import { Separator } from "@/components/ui/separator"; +import { Button } from "@/components/ui/button"; +import Cookies from "js-cookie"; +import Link from "next/link"; +import { Resume } from "@/helpers/student/types"; +import { GetResumes, deleteResume } from "@/helpers/student/api"; +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { uploadResume } from "@/helpers/student/api"; +import toast, { Toaster } from "react-hot-toast"; + + +const baseUrl = process.env.NEXT_PUBLIC_BACKEND_URL; + +const url = (NextUrl: string) => { + return `${baseUrl}/api/v1${NextUrl}`; +}; + +// http://localhost:5000/api/v1/resumes/file/0c5dee48-c869-4219-b8c0-80cb6ce0e74d.pdf + +const ResumePage = () => { + + const [resumeData, setResumeData] = useState([]); + const [showUploadForm, setShowUploadForm] = useState(false); + + const fetchResumes = async () => { + const data = await GetResumes(Cookies.get("accessToken")); + setResumeData(data); + } + + const handleUploadForm = () => { + setShowUploadForm(true); + } + + const [file, setFile] = useState(null); + + const handleFileChange = (event: ChangeEvent) => { + if (event.target.files) { + setFile(event.target.files[0]); + } + }; + + const handleSubmit = async (event: FormEvent) => { + event.preventDefault(); + + if (!file) { + toast.error('Please select a file to upload.'); + return; + } + + const formData = new FormData(); + formData.append('resume', file, file.name); + + const data = await uploadResume(formData, Cookies.get("accessToken")); + + if(data===201||data===204){ + toast.success("Uploaded Successfully"); + fetchResumes(); + setFile(null); + } + else{ + toast.error("Error uploading file") + } + }; + + const handleDelete = async (filename: string) => { + const res = await deleteResume(filename, Cookies.get("accessToken")); + + if(res===200){ + toast.success("Deleted Successfully"); + fetchResumes(); + } + else{ + toast.error("Error deleting file") + } + + } + + + useEffect(()=>{ + + if(resumeData.length===0){ + fetchResumes(); + } + }) + + return ( + <> +
+
+ Resumes +
+
+ +
+ {resumeData.length===0? ( +
No Resumes
+ ): ( + + + + Sr. + Name + Status + Delete + + + + {resumeData.map((item,index)=>( + + {index+1} + + + {item.filepath} + + + {item.verified? "Verified": "Not Verified"} + + + + + ))} + +
+ )} +
+ +
+ +
+ {showUploadForm && ( +
+
+
+ + +
+ +
+
+ )} + + ); +}; + +export default ResumePage; diff --git a/src/components/HorizontalTimeline.tsx b/src/components/HorizontalTimeline.tsx index 6825a218..b6451e28 100644 --- a/src/components/HorizontalTimeline.tsx +++ b/src/components/HorizontalTimeline.tsx @@ -1,28 +1,33 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +/* eslint-disable react-hooks/exhaustive-deps */ import React, { useEffect, useState } from "react"; // import styles from "@/styles/Timeline.css"; +import { + HoverCard, + HoverCardContent, + HoverCardTrigger, +} from "@/components/ui/hover-card" +import { Separator } from "./ui/separator"; interface Event { date: string; status:string; - // title: string; + title: string; // description: string; // bgColor: string; // textColor: string; } interface Props { - events: Event[]; + eventsData: Event[]; } -const HorizontalTimeline: React.FC = ({ events }) => { +const HorizontalTimeline: React.FC = ({ eventsData }) => { const styles = ` body{ - margin-top:20px; background:#eee; } - - .cd-horizontal-timeline ol, .cd-horizontal-timeline ul { list-style: none; } @@ -432,6 +437,43 @@ const HorizontalTimeline: React.FC = ({ events }) => { margin-left: -1.5px; background-color: #eeeeee; } + .event-trigger { + position: absolute; + bottom: 0; + z-index: 2; + text-align: center; + font-size: 1rem; + padding-bottom: 15px; + cursor: pointer; + transform: translateZ(0); + } + + .event-trigger .event-marker { + position: absolute; + left: 50%; + bottom: -5px; + height: 12px; + width: 12px; + border-radius: 50%; + border: 2px solid #dfdfdf; + background-color: #f8f8f8; + transform: translateX(-50%); + transition: background-color 0.3s, border-color 0.3s; + } + + .no-touch .event-trigger:hover .event-marker { + background-color: #313740; + border-color: #313740; + } + + .event-trigger.selected .event-marker { + background-color: #313740; + border-color: #313740; + } + + .event-trigger.older-event .event-marker { + border-color: #313740; + } `; let selectedIndex = -1; @@ -441,17 +483,17 @@ const HorizontalTimeline: React.FC = ({ events }) => { const [a,seta] = useState(""); useEffect(()=>{ // Find the index of the selected event - for (let i = 0; i < events.length; i++) { - if (events[i].status === "selected") { + for (let i = 0; i < eventsData.length; i++) { + if (eventsData[i].status === "selected") { selectedIndex = i; break; } } // Calculate the width of the filling line - fillingLineWidth = selectedIndex !== -1 ? `${(selectedIndex + 1) * (180 / events.length)}px` : "0"; - mul= selectedIndex * (720/events.length) - mul+=38; + fillingLineWidth = selectedIndex !== -1 ? `${(selectedIndex + 1) * (180 / eventsData.length)}px` : "0"; + mul= selectedIndex * (720/eventsData.length) + mul+=39; mul/=len; seta('scaleX('+mul+')'); @@ -465,16 +507,41 @@ const HorizontalTimeline: React.FC = ({ events }) => {
-
+
-
+
    - {events.map((event, index) => ( + {eventsData.map((event, index) => (
  1. - - {event.date} - + + +
    + {event.date} + +
    +
    + + {event.title} + + {event.status} + +
  2. ))}
@@ -496,4 +563,4 @@ const HorizontalTimeline: React.FC = ({ events }) => { ); }; -export default HorizontalTimeline; +export default HorizontalTimeline; \ No newline at end of file diff --git a/src/components/JAF/JafCard.tsx b/src/components/JAF/JafCard.tsx index bd718cb1..2d13fe84 100644 --- a/src/components/JAF/JafCard.tsx +++ b/src/components/JAF/JafCard.tsx @@ -336,7 +336,7 @@ const JafCard = ({ JAF }: Props) => {

Salaries

{JAF.salaries.map((salary, index) => ( -
+

Salary Distribution

diff --git a/src/components/NavButtonGroup.tsx b/src/components/NavButtonGroup.tsx index 2297f2b4..7566d180 100644 --- a/src/components/NavButtonGroup.tsx +++ b/src/components/NavButtonGroup.tsx @@ -133,4 +133,4 @@ const NavButtonGroup = () => { ); }; -export default NavButtonGroup; +export default NavButtonGroup; \ No newline at end of file diff --git a/src/components/SideBar/Roles/admin.tsx b/src/components/SideBar/Roles/admin.tsx new file mode 100644 index 00000000..3fdc5331 --- /dev/null +++ b/src/components/SideBar/Roles/admin.tsx @@ -0,0 +1,265 @@ +"use client"; +import { useContext, useEffect, useState } from "react"; +import { ToggleContext } from "@/contextProviders/ToggleProvider"; +import { motion } from "framer-motion"; +import { useMediaQuery } from "react-responsive"; +import Cookies from "js-cookie"; +import Link from "next/link"; +import NavButtonGroup from "@/components/NavButtonGroup"; +import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"; + +const AdminDashboard = () => { + const isSmallScreen = useMediaQuery({ query: "(max-width: 768px)" }); + + const context = useContext(ToggleContext); + return ( + <> +
+ +
+
+
+ + + +
+ + Home + +
+
+ +
+ +
+
+ + + +
+ + Companies + +
+ + {/* */} +
+ +
+
+
+ + + +
+ + Jobs + +
+ + {/* */} +
+ + +
+
+
+ + + +
+ + Students + +
+ + {/* */} +
+ + +
+
+
+ + + +
+ + Recruiters + +
+ {/* */} +
+ +
+
+
+ + + +
+ + Seasons + +
+ {/* */} +
+
+
+
+ + + +
+ + Schedule + +
+ {/* */} +
+
+ + ) +} + +export default AdminDashboard; diff --git a/src/components/SideBar/Roles/recruiter.tsx b/src/components/SideBar/Roles/recruiter.tsx new file mode 100644 index 00000000..e6d506b0 --- /dev/null +++ b/src/components/SideBar/Roles/recruiter.tsx @@ -0,0 +1,128 @@ +"use client"; +import { useContext, useEffect, useState } from "react"; +import { ToggleContext } from "@/contextProviders/ToggleProvider"; +import { motion } from "framer-motion"; +import { useMediaQuery } from "react-responsive"; +import Cookies from "js-cookie"; +import Link from "next/link"; +import NavButtonGroup from "@/components/NavButtonGroup"; +import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"; + +const RecruiterDashboard = () => { + const isSmallScreen = useMediaQuery({ query: "(max-width: 768px)" }); + + const context = useContext(ToggleContext); + + const studentId=2; + return ( + <> +
+ +
+
+
+ + + +
+ + Home + +
+
+ + +
+
+
+ + + +
+ + JAF + +
+
+ + +
+
+
+ + + +
+ + Previous JAFs + +
+
+ +
+ + ) +} + +export default RecruiterDashboard; + diff --git a/src/components/SideBar/Roles/student.tsx b/src/components/SideBar/Roles/student.tsx new file mode 100644 index 00000000..9da4d50f --- /dev/null +++ b/src/components/SideBar/Roles/student.tsx @@ -0,0 +1,197 @@ +"use client"; +import { useContext, useEffect, useState } from "react"; +import { ToggleContext } from "@/contextProviders/ToggleProvider"; +import { motion } from "framer-motion"; +import { useMediaQuery } from "react-responsive"; +import Cookies from "js-cookie"; +import Link from "next/link"; +import NavButtonGroup from "@/components/NavButtonGroup"; +import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"; + +const StudentDashboard = () => { + const isSmallScreen = useMediaQuery({ query: "(max-width: 768px)" }); + + const context = useContext(ToggleContext); + return ( + <> +
+ +
+
+
+ + + +
+ + Home + +
+
+ +
+ +
+
+ + + +
+ + Resumes + +
+ + {/* */} +
+ +
+
+
+ + + +
+ + On Campus Offers + +
+ + {/* */} +
+ + +
+
+
+ + + +
+ + Off Campus Offers + +
+ + {/* */} +
+ + +
+
+
+ + + +
+ + Interview Experiences + +
+ {/* */} +
+ +
+ + ) +} + +export default StudentDashboard; + diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 647f22f1..e590ccee 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -12,6 +12,9 @@ import { JobDropDown } from "./SideBar/DropDowns/JobDropDown"; import { StudentDropDown } from "./SideBar/DropDowns/StudentDropDown"; import { FacultyDropDown } from "./SideBar/DropDowns/FacultyDropDown"; import { RecruiterDropDown } from "./SideBar/DropDowns/RecuiterDropDown"; +import AdminDashboard from "./SideBar/Roles/admin"; +import StudentDashboard from "./SideBar/Roles/student"; +import RecruiterDashboard from "./SideBar/Roles/recruiter"; interface Framework { value: string; @@ -43,6 +46,8 @@ const Sidebar = () => { // const isAdmin = true; const isRecruiter = user?.userType === "RECRUITER" // const isRecruiter = true; + const isStudent = user?.userType === "STUDENT" + // const isStudent = true; const userRole = user?.userType?.toLowerCase(); return ( @@ -154,360 +159,17 @@ const Sidebar = () => {
{isAdmin && ( -
- -
-
-
- - - -
- - Home - -
-
- -
- -
-
- - - -
- - Companies - -
- - {/* */} -
- -
-
-
- - - -
- - Jobs - -
- - {/* */} -
- - -
-
-
- - - -
- - Students - -
- - {/* */} -
- - -
-
-
- - - -
- - Recruiters - -
- {/* */} -
- -
-
-
- - - -
- - Seasons - -
- {/* */} -
-
-
-
- - - -
- - Schedule - -
- {/* */} -
-
+ + )} + {isStudent && ( + )} {isRecruiter&& ( -
- -
-
-
- - - -
- - Home - -
-
- - -
-
-
- - - -
- - JAF - -
-
- - -
-
-
- - - -
- - Previous JAFs - -
-
- -
+ )}
); }; -export default Sidebar; +export default Sidebar; \ No newline at end of file diff --git a/src/components/jobs/JobCard.tsx b/src/components/jobs/JobCard.tsx index 58a794e6..f911807d 100644 --- a/src/components/jobs/JobCard.tsx +++ b/src/components/jobs/JobCard.tsx @@ -2,11 +2,9 @@ import React from "react"; import Link from "next/link"; import { Separator } from "../ui/separator"; import { fetchJobSalary } from "@/helpers/api"; -import { cookies } from "next/headers"; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import {JobDetails} from "@/dummyData/jobdetails" import { Button } from "@/components/ui/button"; -// import { Pointer } from "lucide-react"; import { Select, SelectContent, @@ -16,87 +14,29 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select" -interface Event { - id: string; - name: string; - date: string; -} - -interface Coordinator { - id: string; - name: string; -} - -interface FacultyCoordinatorApproval { - id: string; - facultyId: string; - approvalStatus: string; -} - -interface OnCampusOffer { - id: string; - name: string; - offerStatus: string; +import { OnCampusOffers, Salary, Resume } from "@/helpers/student/types"; +import { ApplyJob, GetSalaryById } from "@/helpers/student/api"; +import Cookies from "js-cookie"; +import toast, { Toaster } from "react-hot-toast"; +interface Props { + jobItem: OnCampusOffers; + salaryId: string; + resumes: Resume[]; } -interface RoleOffered { - id: string; - roleName: string; -} +const JobCard = ({ jobItem, salaryId, resumes }: Props) => { + const [salary, setSalary] = useState(null); -// interface Props { -// jobItem: { -// id: string, -// seasonId: string, -// "companyId": string, -// "role": string, -// "recruiterId": string, -// "active": boolean, -// "eligibility": any, -// "currentStatusId": string, -// "metadata": any, -// "createdAt": string, -// "updatedAt": string -// }; -// salary: null | { -// salary: string, -// salaryPeriod: string, -// metadata: any, -// constraints: any -// } -// } -interface Props { - jobItem: { - id: string; - seasonId: string; - recruiterId: string; - companyId: string; - role: string; - active: boolean; - currentStatus: string; - season: { - id: string; - year: string; - type: string; - }; - company: { - id: string; - name: string; + useEffect(() => { + const fetchSalary = async () => { + const data = await GetSalaryById(salaryId); + setSalary(data); + console.log(data); }; - }; - salary: null | { - salary: string, - } - resumes: null | { - id: string; - filepath: string; - verified: boolean; - }[]; -} -const JobCard = ({ jobItem, salary, resumes }: Props) => { - // const salary = await fetchJobSalary(cookies()?.get('accessToken')?.value, jobItem.id) - // console.log(salary) + fetchSalary(); + // setJobs(Jobs); + }, [salaryId]); const [selectedResume, setSelectedResume] = useState(null); const [showDescription, setShowDescription] = useState(false); @@ -107,11 +47,42 @@ const JobCard = ({ jobItem, salary, resumes }: Props) => { setShowDescription(!showDescription); }; + const handleApply = async () => { + const data = await ApplyJob(Cookies.get("accessToken"),salaryId,selectedResume); + if(data.status===201){ + toast.success("Applied Successfully"); + } + else{ + toast.error("Cannot Apply"); + } + } + + const roundOff = (n: number) => { + return Math.round((n + Number.EPSILON) * 100) / 100; + }; + + function formatNumber(num: number): string { + if (num >= 1e7) { + // Convert to Crores + const crores = num / 1e7; + return `₹${crores.toFixed(2)} Crores`; + } else if (num >= 1e5) { + // Convert to Lakhs + const lakhs = num / 1e5; + return `₹${lakhs.toFixed(2)} Lakhs`; + } else { + return `₹${num.toString()}`; + } + } + return ( -
-
+
+ {salary===null? ( +
No Data
+ ): ( +
- {jobItem.company.name} + {jobItem.salary.job.company.name}
@@ -119,23 +90,23 @@ const JobCard = ({ jobItem, salary, resumes }: Props) => {
Role
{" "} -
{jobItem.role}
+
{jobItem.salary.job.role}
-
Duration
{" "} -
3 Months
+
Period
{" "} +
{salary.salaryPeriod}
-
Stipend
{" "} -
{salary?.salary}
+
CTC
{" "} +
{formatNumber(salary.totalCTC)}
-
Apply By
{" "} -
1st Jan 2024
+
Base Salary
{" "} +
{formatNumber(salary.baseSalary)}
-
Eligibility CPI
{" "} -
7.5
+
Minimum CPI
{" "} +
{roundOff(salary.minCPI)}
{showDescription && ( @@ -147,7 +118,7 @@ const JobCard = ({ jobItem, salary, resumes }: Props) => {

About The Work

- + View Details {'>'}
@@ -213,7 +184,7 @@ const JobCard = ({ jobItem, salary, resumes }: Props) => {
-