Skip to content

Commit 089013d

Browse files
bug fixes (#434)
2 parents 472688c + a390bd3 commit 089013d

10 files changed

Lines changed: 159 additions & 48 deletions

File tree

.env.development

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
NEXT_PUBLIC_BACKEND_URL=https://tpc.jxp.codes
2-
NEXT_PUBLIC_FRONTEND_URL=https://tpc.jxp.codes
3-
NEXTAUTH_URL=https://tpc.jxp.codes/portal/api/auth
4-
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=6Ldd9KYrAAAAAO2UAAHuvK-lSFU3qj6HqXHzf5o1
1+
NEXT_PUBLIC_BACKEND_URL=https://placement.iiti.ac.in
2+
NEXT_PUBLIC_FRONTEND_URL=https://placement.iiti.ac.in
3+
NEXTAUTH_URL=https://placement.iiti.ac.in/portal/api/auth
4+
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=6LedhmcrAAAAAGugyAoh4uyYPKGpOSFQf6trz-y4
55
NEXT_PUBLIC_BASE_PATH="/portal"
66
NEXT_PUBLIC_ASSET_PREFIX="/portal"

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"antd": "^5.19.1",
3737
"axios": "^1.6.3",
3838
"chartjs": "^0.3.24",
39+
"class-validator": "^0.15.1",
3940
"class-variance-authority": "^0.7.0",
4041
"clsx": "^2.1.1",
4142
"cmdk": "^0.2.0",

src/app/(routes)/admin/jobs/[jobId]/page.tsx

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ import Salaries from "@/components/Admin/Job/Salaries";
2525
import SelectionProcedure from "@/components/Admin/Job/SelectionProcedure";
2626
import Clashes from "@/components/Admin/Job/Clashes";
2727
import JobAnalytics from "@/components/Admin/Job/JobAnalytics";
28-
import { ClashesFC } from "@/dto/Clashes";
28+
import {
29+
ClashesFC,
30+
EMPTY_CLASHES,
31+
isValidClashesResponse,
32+
normalizeClashesResponse,
33+
} from "@/dto/Clashes";
2934
const currentStatusOptions = [
3035
"INITIALIZED",
3136
"SCHEDULED",
@@ -42,7 +47,7 @@ const currentStatusOptions = [
4247

4348
const JobDetailPage = ({ params }: { params: { jobId: string } }) => {
4449
const [job, setData] = useState<JobDetailFC>(null);
45-
const [clashes, setClashes] = useState<ClashesFC>(null);
50+
const [clashes, setClashes] = useState<ClashesFC>(EMPTY_CLASHES);
4651
const [loading, setLoading] = useState(true);
4752
const [editMode, setEditMode] = useState(false);
4853
const [formData, setFormData] = useState(null);
@@ -87,25 +92,32 @@ const JobDetailPage = ({ params }: { params: { jobId: string } }) => {
8792
useEffect(() => {
8893
const fetchData = async () => {
8994
try {
90-
const [
91-
jobDetailData,
92-
jafDetailsData,
93-
recruiterData,
94-
facultyData,
95-
clashes,
96-
] = await Promise.all([
95+
const [jobDetailData, jafDetailsData, recruiterData, facultyData] =
96+
await Promise.all([
9797
fetchJobById(params.jobId),
9898
getJafDetails(),
9999
fetchRecruiterData(),
100100
fetchFaculties(),
101-
fetchClashes(params.jobId),
102101
]);
103102
setJafDetails(jafDetailsData);
104103
setData(jobDetailData);
105104
setFormData(jobDetailData);
106105
setFacultyData(facultyData);
107-
setClashes(clashes);
106+
try {
107+
const clashesData = await fetchClashes(params.jobId);
108+
const normalizedClashes = normalizeClashesResponse(clashesData);
109+
110+
if (!isValidClashesResponse(clashesData)) {
111+
toast.error("Could not load clashes for this job");
112+
}
113+
114+
setClashes(normalizedClashes);
115+
} catch (_error) {
116+
setClashes(EMPTY_CLASHES);
117+
toast.error("Could not load clashes for this job");
118+
}
108119
} catch (error) {
120+
setClashes(EMPTY_CLASHES);
109121
toast.error("Error fetching data");
110122
} finally {
111123
setLoading(false);
@@ -702,7 +714,7 @@ const JobDetailPage = ({ params }: { params: { jobId: string } }) => {
702714
loading={loading}
703715
/>
704716
{job && <JobAnalytics jobId={job.id} />}
705-
{clashes ? <Clashes clashes={clashes} /> : <Loader />}
717+
<Clashes clashes={clashes} />
706718
</div>
707719
)}
708720
</div>

src/app/globals.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,9 @@ body {
2020
color: rgb(var(--foreground-rgb));
2121
background: rgb(232, 232, 232);
2222
}
23+
24+
/* Removes square blue outline */
25+
.ant-select-single .ant-select-selection-search-input:focus {
26+
outline: none !important;
27+
box-shadow: none !important;
28+
}

src/components/Admin/Job/Clashes.tsx

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
1-
import { ClashesFC } from "@/dto/Clashes";
1+
import { ClashesFC, Event, OffCampus, OnCampus } from "@/dto/Clashes";
22
import React, { useState } from "react";
33
import Modal from "@mui/material/Modal";
44
import TableComponent from "@/components/NewTableComponent/Table";
55
import generateColumns from "@/components/NewTableComponent/ColumnMapping";
66
import { Button } from "@/components/ui/button";
77
import { EventModal, OffCampusModal, OnCampusModal } from "./ClashModal";
88

9+
type ClashDateFields = {
10+
startDateTime?: string;
11+
cstartDateTime?: string;
12+
endDateTime?: string;
13+
cendDateTime?: string;
14+
};
15+
16+
type ClashModalType = "event" | "onCampus" | "offCampus";
17+
type ClashModalData = Event | OnCampus | OffCampus;
18+
919
const eventColumns = [
1020
{
1121
viewMore: "View",
@@ -25,21 +35,37 @@ const eventColumns = [
2535
];
2636

2737
const Clashes = ({ clashes }: { clashes: ClashesFC }) => {
28-
const [selectedClash, setSelectedClash] = useState<any>(null);
29-
const [modalType, setModalType] = useState<string>("");
38+
const [selectedClash, setSelectedClash] = useState<ClashModalData | null>(
39+
null,
40+
);
41+
const [modalType, setModalType] = useState<ClashModalType | "">("");
42+
43+
const toDisplayDate = (value?: string) => {
44+
if (!value) return "N/A";
3045

31-
const formatClashes = (clashesEvent: any, modalType: string) => {
32-
return clashesEvent.map((clash) => ({
46+
const parsedDate = new Date(value);
47+
return Number.isNaN(parsedDate.getTime())
48+
? "N/A"
49+
: parsedDate.toLocaleString();
50+
};
51+
52+
const formatClashes = <T extends ClashModalData & ClashDateFields>(
53+
clashesEvent: T[],
54+
currentModalType: ClashModalType,
55+
) => {
56+
const safeClashes = Array.isArray(clashesEvent) ? clashesEvent : [];
57+
58+
return safeClashes.map((clash) => ({
3359
...clash,
34-
startDateTime: new Date(clash.startDateTime).toLocaleString(),
35-
cstartDateTime: new Date(clash.cstartDateTime).toLocaleString(),
36-
endDateTime: new Date(clash.endDateTime).toLocaleString(),
37-
cendDateTime: new Date(clash.cendDateTime).toLocaleString(),
60+
startDateTime: toDisplayDate(clash.startDateTime),
61+
cstartDateTime: toDisplayDate(clash.cstartDateTime),
62+
endDateTime: toDisplayDate(clash.endDateTime),
63+
cendDateTime: toDisplayDate(clash.cendDateTime),
3864
viewMore: (
3965
<Button
4066
onClick={() => {
4167
setSelectedClash(clash);
42-
setModalType(modalType);
68+
setModalType(currentModalType);
4369
}}
4470
>
4571
View More
@@ -48,10 +74,6 @@ const Clashes = ({ clashes }: { clashes: ClashesFC }) => {
4874
}));
4975
};
5076

51-
const onViewClash = async (event: any) => {
52-
setSelectedClash(event);
53-
};
54-
5577
const onCloseModal = () => {
5678
setSelectedClash(null);
5779
};
@@ -65,18 +87,22 @@ const Clashes = ({ clashes }: { clashes: ClashesFC }) => {
6587
return (
6688
<div className="bg-white p-4 px-8 rounded-lg border-gray-300 hover:border-blue-200 border-2">
6789
<Modal
68-
open={selectedClash}
90+
open={Boolean(selectedClash)}
6991
onClose={onCloseModal}
7092
aria-labelledby="Event Details"
7193
aria-describedby="Event Details"
7294
className="flex justify-center items-center"
7395
>
7496
<>
75-
{modalType === "event" && <EventModal event={selectedClash} />}
76-
{modalType === "onCampus" && (
97+
{modalType === "event" &&
98+
selectedClash &&
99+
"ceventId" in selectedClash && (
100+
<EventModal event={selectedClash} />
101+
)}
102+
{modalType === "onCampus" && selectedClash && "baseSalary" in selectedClash && (
77103
<OnCampusModal onCampusEvent={selectedClash} />
78104
)}
79-
{modalType === "offCampus" && (
105+
{modalType === "offCampus" && selectedClash && "salary" in selectedClash && (
80106
<OffCampusModal offCampusEvent={selectedClash} />
81107
)}
82108
</>

src/components/JAF/JAF.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
jobDetailsValidationSchema,
1919
} from "../../validation/jaf.validation";
2020
import ReCAPTCHA from "react-google-recaptcha";
21+
import { isEmail } from "class-validator";
2122

2223
const baseUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
2324
const { Step } = Steps;
@@ -103,11 +104,12 @@ function JAF() {
103104
}))
104105
.filter(
105106
(r) =>
106-
r.name.trim() ||
107-
r.designation.trim() ||
108-
r.email.trim() ||
109-
r.contact.trim() ||
110-
r.landline.trim(),
107+
(r.name.trim() ||
108+
r.designation.trim() ||
109+
r.email.trim() ||
110+
r.contact.trim() ||
111+
r.landline.trim()) &&
112+
isEmail(r.email),
111113
);
112114

113115
if (recruiters.length === 0) {

src/components/JAF/JobDetails.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -820,7 +820,7 @@ const JobDetails = ({
820820
}}
821821
maxLength={FIELD_LIMITS.JOB_TITLE_MAX}
822822
showCount
823-
className="rounded-md shadow-sm border-gray-300 text-xs md:text-sm"
823+
className="rounded-md shadow-sm border-gray-300 text-xs md:text-sm h-10"
824824
/>
825825
</Form.Item>
826826
</Col>
@@ -880,7 +880,7 @@ const JobDetails = ({
880880
}}
881881
maxLength={FIELD_LIMITS.LOCATION_MAX}
882882
showCount
883-
className="rounded-md shadow-sm border-gray-300 text-xs md:text-sm"
883+
className="rounded-md shadow-sm border-gray-300 text-xs md:text-sm h-10"
884884
/>
885885
</Form.Item>
886886
</Col>
@@ -909,7 +909,7 @@ const JobDetails = ({
909909
}}
910910
min={0}
911911
max={FIELD_LIMITS.HIRES_MAX}
912-
className="rounded-md shadow-sm border-gray-300 text-xs md:text-sm"
912+
className="rounded-md shadow-sm border-gray-300 text-xs md:text-sm h-10"
913913
/>
914914
</Form.Item>
915915
</Col>
@@ -1020,6 +1020,7 @@ const JobDetails = ({
10201020
help={getFieldError("skills")}
10211021
>
10221022
<Select
1023+
className="h-10"
10231024
mode="tags"
10241025
style={{
10251026
width: "100%",
@@ -1729,7 +1730,7 @@ const JobDetails = ({
17291730
</Form.Item>
17301731
</Col>
17311732
</Row> */}
1732-
<Row gutter={[16, 12]} className="md:gutter-24">
1733+
<Row gutter={[16, 12]} className="md:gutter-24 py-5">
17331734
<Col xs={24} md={12}>
17341735
<Form.Item
17351736
label={
@@ -1966,7 +1967,7 @@ const JobDetails = ({
19661967
value,
19671968
);
19681969
}}
1969-
className="rounded-md"
1970+
className="rounded-md h-10"
19701971
/>
19711972
</Form.Item>
19721973
</Col>
@@ -2829,7 +2830,7 @@ const JobDetails = ({
28292830
>
28302831
<Select
28312832
placeholder="Select accommodation provision"
2832-
className="rounded-md"
2833+
className="rounded-md h-10"
28332834
options={[
28342835
{
28352836
value: true,
@@ -2858,7 +2859,7 @@ const JobDetails = ({
28582859
>
28592860
<Select
28602861
placeholder="Select PPO provision"
2861-
className="rounded-md"
2862+
className="rounded-md h-10"
28622863
options={[
28632864
{
28642865
value: true,
@@ -3186,6 +3187,7 @@ const JobDetails = ({
31863187
help={getFieldError(`tests.${index}.type`)}
31873188
>
31883189
<Select
3190+
className="h-10"
31893191
placeholder="Select type"
31903192
options={testType}
31913193
showSearch
@@ -3345,6 +3347,7 @@ const JobDetails = ({
33453347
help={getFieldError(`interviews.${index}.type`)}
33463348
>
33473349
<Select
3350+
className="h-10"
33483351
placeholder="Select type"
33493352
options={interviewType}
33503353
showSearch

src/components/dashboard/data-ribbon.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ interface DataRibbonProps {
6969

7070
export function DataRibbon({ stats, seasonType }: DataRibbonProps) {
7171
const overallStats = stats.overallStats;
72+
const unplacedCount =
73+
overallStats.totalRegisteredStudentsCount - overallStats.placedStudentsCount;
7274
let packageLabel = "Stipend";
7375
if (seasonType === "PLACEMENT") {
7476
packageLabel = "Package";
@@ -114,9 +116,10 @@ export function DataRibbon({ stats, seasonType }: DataRibbonProps) {
114116
info="Number of students placed"
115117
/>
116118
<StatCard
117-
value={`${overallStats.unplacedPercentage.toFixed(2)}%`}
119+
value={unplacedCount}
118120
label="Total Unplaced Students"
119-
info="Percentage of eligible students yet to be placed"
121+
subtext={`${overallStats.unplacedPercentage.toFixed(2)}% unplaced`}
122+
info="Unplaced students among total registered"
120123
/>
121124
<StatCard
122125
value={overallStats.lowestPackage.toFixed(2)}

src/dto/Clashes.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,40 @@ export interface ClashesFC {
8585
offCampus: OffCampus[];
8686
}
8787

88+
type MaybeClashesPayload = Partial<
89+
Record<keyof ClashesFC, unknown>
90+
>;
91+
92+
const asArray = <T>(value: unknown): T[] => {
93+
return Array.isArray(value) ? (value as T[]) : [];
94+
};
95+
96+
export const EMPTY_CLASHES: ClashesFC = {
97+
onCampus: [],
98+
event: [],
99+
offCampus: [],
100+
};
101+
102+
export const normalizeClashesResponse = (
103+
payload: MaybeClashesPayload | null | undefined,
104+
): ClashesFC => {
105+
return {
106+
onCampus: asArray<OnCampus>(payload?.onCampus),
107+
event: asArray<Event>(payload?.event),
108+
offCampus: asArray<OffCampus>(payload?.offCampus),
109+
};
110+
};
111+
112+
export const isValidClashesResponse = (
113+
payload: MaybeClashesPayload | null | undefined,
114+
): boolean => {
115+
return (
116+
Array.isArray(payload?.onCampus) &&
117+
Array.isArray(payload?.event) &&
118+
Array.isArray(payload?.offCampus)
119+
);
120+
};
121+
88122
export const clashesDto = {
89123
onCampus: [
90124
{

0 commit comments

Comments
 (0)