From fb5ab1d695aa3603dcd041efc16f543103c2e987 Mon Sep 17 00:00:00 2001 From: itzzavdhesh Date: Sun, 14 Jun 2026 14:02:08 +0530 Subject: [PATCH 1/8] feat: implement hiring management platform and fix unused imports/type issues --- src/Component/ui/Url.tsx | 4 +- .../v1/Component/Administrative_MetaData.tsx | 1 - .../v1/Component/MemberShip_Status.tsx | 1 - .../v1/Sections/ProfessionalDetails.tsx | 6 - src/features/Hiring/v1/Hooks/useHiring.ts | 314 +++++++ .../Hiring/v1/Pages/ApplicantDetailsPage.tsx | 419 +++++++++ .../Hiring/v1/Pages/ApplicantsPage.tsx | 401 ++++++++ .../Hiring/v1/Pages/CompanyProfilePage.tsx | 301 ++++++ .../Hiring/v1/Pages/CreateJobPage.tsx | 853 ++++++++++++++++++ src/features/Hiring/v1/Pages/EditJobPage.tsx | 413 +++++++++ .../Hiring/v1/Pages/JobDetailsPage.tsx | 495 ++++++++++ .../Hiring/v1/Pages/JobsDashboardPage.tsx | 471 ++++++++++ .../Hiring/v1/Pages/MediaManagementPage.tsx | 188 ++++ .../Hiring/v1/Pages/ModerationPage.tsx | 133 +++ .../v1/Pages/PublicCompanyProfilePage.tsx | 201 +++++ .../Hiring/v1/Pages/PublicJobDetailsPage.tsx | 303 +++++++ src/features/Hiring/v1/Pages/SendMailPage.tsx | 243 +++++ src/features/Hiring/v1/Store/hiringStore.ts | 769 ++++++++++++++++ src/features/Hiring/v1/Types/Hiring.types.ts | 130 +++ src/features/SideBar/v1/Section/SideBar.tsx | 16 +- src/layouts/MemberLayout/MemberLayout.tsx | 1 - src/routes/MemberRoutes.tsx | 7 + src/routes/OrgRoute.tsx | 29 + 23 files changed, 5687 insertions(+), 12 deletions(-) create mode 100644 src/features/Hiring/v1/Hooks/useHiring.ts create mode 100644 src/features/Hiring/v1/Pages/ApplicantDetailsPage.tsx create mode 100644 src/features/Hiring/v1/Pages/ApplicantsPage.tsx create mode 100644 src/features/Hiring/v1/Pages/CompanyProfilePage.tsx create mode 100644 src/features/Hiring/v1/Pages/CreateJobPage.tsx create mode 100644 src/features/Hiring/v1/Pages/EditJobPage.tsx create mode 100644 src/features/Hiring/v1/Pages/JobDetailsPage.tsx create mode 100644 src/features/Hiring/v1/Pages/JobsDashboardPage.tsx create mode 100644 src/features/Hiring/v1/Pages/MediaManagementPage.tsx create mode 100644 src/features/Hiring/v1/Pages/ModerationPage.tsx create mode 100644 src/features/Hiring/v1/Pages/PublicCompanyProfilePage.tsx create mode 100644 src/features/Hiring/v1/Pages/PublicJobDetailsPage.tsx create mode 100644 src/features/Hiring/v1/Pages/SendMailPage.tsx create mode 100644 src/features/Hiring/v1/Store/hiringStore.ts create mode 100644 src/features/Hiring/v1/Types/Hiring.types.ts diff --git a/src/Component/ui/Url.tsx b/src/Component/ui/Url.tsx index e120bd8..1b8719e 100644 --- a/src/Component/ui/Url.tsx +++ b/src/Component/ui/Url.tsx @@ -22,8 +22,8 @@ const Url: React.FC = ({ style, ariaLabel = "URL display", }) => { - const valid = isValidDomain(domain); - const [Domain, setDomain] = React.useState(domain); + const valid = isValidDomain(domain || ""); + const [Domain, setDomain] = React.useState(domain || ""); return (
diff --git a/src/features/AddMember/v1/Component/Administrative_MetaData.tsx b/src/features/AddMember/v1/Component/Administrative_MetaData.tsx index 36ccf86..a11e8df 100644 --- a/src/features/AddMember/v1/Component/Administrative_MetaData.tsx +++ b/src/features/AddMember/v1/Component/Administrative_MetaData.tsx @@ -1,5 +1,4 @@ import MemberShip_Status from "./MemberShip_Status"; -import AccessLevel from "./AccessLevel"; const Administrative_MetaData = () => { return ( diff --git a/src/features/AddMember/v1/Component/MemberShip_Status.tsx b/src/features/AddMember/v1/Component/MemberShip_Status.tsx index 54e6d1d..7c2e4d1 100644 --- a/src/features/AddMember/v1/Component/MemberShip_Status.tsx +++ b/src/features/AddMember/v1/Component/MemberShip_Status.tsx @@ -1,4 +1,3 @@ -import { useState } from "react"; import { useFormContext } from "react-hook-form"; import type { MemberFormValues } from "../Validator/AddMember.Validator"; diff --git a/src/features/AddMember/v1/Sections/ProfessionalDetails.tsx b/src/features/AddMember/v1/Sections/ProfessionalDetails.tsx index 39cd02a..2fd2ebc 100644 --- a/src/features/AddMember/v1/Sections/ProfessionalDetails.tsx +++ b/src/features/AddMember/v1/Sections/ProfessionalDetails.tsx @@ -3,7 +3,6 @@ import { IoBag } from "react-icons/io5"; import { Input } from "../../../../Component/ui/Input"; import DropDown from "../../../../Component/ui/DropDown"; import { Roles } from "../Constant/Role.constant"; -import { SkillColor } from "../Constant/Skill.constant"; import { useFormContext } from "react-hook-form"; import type { MemberFormValues } from "../Validator/AddMember.Validator"; import { theme } from "@/theme"; @@ -27,11 +26,6 @@ const ProfessionalDetails = () => { } }; - const getSkillColor = (skill: string) => { - const index = skills.indexOf(skill); - return SkillColor[index % SkillColor.length]; - }; - return (
new Promise((resolve) => setTimeout(resolve, ms)); + +// ========================================== +// COMPANY PROFILE HOOKS +// ========================================== + +export function useCompanyProfile() { + const profile = useHiringStore((state) => state.companyProfile); + return useQuery({ + queryKey: ["hiring-company-profile"], + queryFn: async () => { + await delay(300); + return profile; + }, + }); +} + +export function useUpdateCompanyProfile() { + const queryClient = useQueryClient(); + const updateProfile = useHiringStore((state) => state.updateCompanyProfile); + + return useMutation({ + mutationFn: async (patch: Parameters[0]) => { + await delay(500); + updateProfile(patch); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["hiring-company-profile"] }); + }, + }); +} + +// ========================================== +// JOBS HOOKS +// ========================================== + +export function useJobs(filters?: { search?: string; department?: string; status?: string }) { + const jobs = useHiringStore((state) => state.jobs); + + return useQuery({ + queryKey: ["hiring-jobs", filters], + queryFn: async () => { + await delay(400); + + return jobs.filter((job) => { + if (filters?.search) { + const q = filters.search.toLowerCase(); + const matchTitle = job.title.toLowerCase().includes(q); + const matchDept = job.department.toLowerCase().includes(q); + const matchManager = job.hiringManager.toLowerCase().includes(q); + const matchId = job.id.toLowerCase().includes(q); + if (!matchTitle && !matchDept && !matchManager && !matchId) return false; + } + + if (filters?.department && filters.department !== "all") { + if (job.department.toLowerCase() !== filters.department.toLowerCase()) return false; + } + + if (filters?.status && filters.status !== "all") { + if (job.status.toLowerCase() !== filters.status.toLowerCase()) return false; + } + + return true; + }); + }, + }); +} + +export function useJobDetail(id: string | undefined) { + const jobs = useHiringStore((state) => state.jobs); + + return useQuery({ + queryKey: ["hiring-job", id], + queryFn: async () => { + await delay(300); + const job = jobs.find((j) => j.id === id); + if (!job) throw new Error("Job not found"); + return job; + }, + enabled: !!id, + }); +} + +export function useCreateJob() { + const queryClient = useQueryClient(); + const addJob = useHiringStore((state) => state.addJob); + + return useMutation({ + mutationFn: async (payload: Parameters[0]) => { + await delay(500); + return addJob(payload); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["hiring-jobs"] }); + }, + }); +} + +export function useUpdateJob() { + const queryClient = useQueryClient(); + const updateJob = useHiringStore((state) => state.updateJob); + + return useMutation({ + mutationFn: async ({ id, patch }: { id: string; patch: Parameters[1] }) => { + await delay(400); + updateJob(id, patch); + }, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: ["hiring-jobs"] }); + queryClient.invalidateQueries({ queryKey: ["hiring-job", variables.id] }); + }, + }); +} + +export function useDeleteJob() { + const queryClient = useQueryClient(); + const deleteJob = useHiringStore((state) => state.deleteJob); + + return useMutation({ + mutationFn: async (id: string) => { + await delay(400); + deleteJob(id); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["hiring-jobs"] }); + }, + }); +} + +// ========================================== +// APPLICANTS HOOKS +// ========================================== + +export function useApplicants(jobId: string | undefined, search?: string, status?: string) { + const applicants = useHiringStore((state) => state.applicants); + + return useQuery({ + queryKey: ["hiring-applicants", jobId, search, status], + queryFn: async () => { + await delay(400); + let list = applicants.filter((a) => a.jobId === jobId); + + if (search) { + const q = search.toLowerCase(); + list = list.filter( + (a) => + a.name.toLowerCase().includes(q) || + a.email.toLowerCase().includes(q) || + a.skills.some((s) => s.toLowerCase().includes(q)) + ); + } + + if (status && status !== "all") { + if (status === "strong") { + list = list.filter((a) => a.matchScore >= 85); + } else { + list = list.filter((a) => a.status === status); + } + } + + return list; + }, + enabled: !!jobId, + }); +} + +export function useApplicantDetail(id: string | undefined) { + const applicants = useHiringStore((state) => state.applicants); + + return useQuery({ + queryKey: ["hiring-applicant", id], + queryFn: async () => { + await delay(300); + const app = applicants.find((a) => a.id === id); + if (!app) throw new Error("Applicant not found"); + return app; + }, + enabled: !!id, + }); +} + +export function useApplyToJob() { + const queryClient = useQueryClient(); + const applyToJob = useHiringStore((state) => state.applyToJob); + + return useMutation({ + mutationFn: async ({ jobId, payload }: { jobId: string; payload: Parameters[1] }) => { + await delay(600); + return applyToJob(jobId, payload); + }, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: ["hiring-applicants", variables.jobId] }); + queryClient.invalidateQueries({ queryKey: ["hiring-jobs"] }); + }, + }); +} + +export function useUpdateApplicantStatus() { + const queryClient = useQueryClient(); + const updateStatus = useHiringStore((state) => state.updateApplicantStatus); + + return useMutation({ + mutationFn: async ({ id, status, user }: { id: string; status: ApplicantStatus; user: string }) => { + await delay(400); + updateStatus(id, status, user); + }, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: ["hiring-applicant", variables.id] }); + queryClient.invalidateQueries({ queryKey: ["hiring-applicants"] }); + }, + }); +} + +export function useAddRecruiterNote() { + const queryClient = useQueryClient(); + const addNote = useHiringStore((state) => state.addRecruiterNote); + + return useMutation({ + mutationFn: async ({ applicantId, note }: { applicantId: string; note: Parameters[1] }) => { + await delay(300); + addNote(applicantId, note); + }, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: ["hiring-applicant", variables.applicantId] }); + }, + }); +} + +// ========================================== +// MAIL HOOKS +// ========================================== + +export function useMailLogs(applicantId: string | undefined) { + const mailLogs = useHiringStore((state) => state.mailLogs); + + return useQuery({ + queryKey: ["hiring-mail-logs", applicantId], + queryFn: async () => { + await delay(300); + return mailLogs.filter((m) => m.applicantId === applicantId); + }, + enabled: !!applicantId, + }); +} + +export function useSendMail() { + const queryClient = useQueryClient(); + const sendMail = useHiringStore((state) => state.sendMail); + + return useMutation({ + mutationFn: async ({ applicantId, mail }: { applicantId: string; mail: Parameters[1] }) => { + await delay(500); + sendMail(applicantId, mail); + }, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: ["hiring-mail-logs", variables.applicantId] }); + queryClient.invalidateQueries({ queryKey: ["hiring-applicant", variables.applicantId] }); + }, + }); +} + +// ========================================== +// AUDIT LOGS HOOKS +// ========================================== + +export function useAuditLogs(jobId: string | undefined) { + const auditLogs = useHiringStore((state) => state.auditLogs); + + return useQuery({ + queryKey: ["hiring-audit-logs", jobId], + queryFn: async () => { + await delay(300); + return auditLogs.filter((a) => a.jobId === jobId); + }, + enabled: !!jobId, + }); +} + +// ========================================== +// MODERATION HOOKS +// ========================================== + +export function useModerationJobs() { + const jobs = useHiringStore((state) => state.jobs); + + return useQuery({ + queryKey: ["hiring-moderation-jobs"], + queryFn: async () => { + await delay(400); + // Moderation queue has pending status jobs (e.g. draft, paused, or we can seed a specific pending job, or just filter jobs with 'draft' or 'paused') + return jobs; + }, + }); +} + +export function useModerateJob() { + const queryClient = useQueryClient(); + const moderateJob = useHiringStore((state) => state.moderateJob); + + return useMutation({ + mutationFn: async ({ jobId, action, user }: { jobId: string; action: "approve" | "reject"; user: string }) => { + await delay(500); + moderateJob(jobId, action, user); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["hiring-jobs"] }); + queryClient.invalidateQueries({ queryKey: ["hiring-moderation-jobs"] }); + }, + }); +} diff --git a/src/features/Hiring/v1/Pages/ApplicantDetailsPage.tsx b/src/features/Hiring/v1/Pages/ApplicantDetailsPage.tsx new file mode 100644 index 0000000..f2898ce --- /dev/null +++ b/src/features/Hiring/v1/Pages/ApplicantDetailsPage.tsx @@ -0,0 +1,419 @@ +import React, { useState } from "react"; +import { useTheme } from "@/theme"; +import { useNavigate, useParams } from "react-router-dom"; +import { useApplicantDetail, useUpdateApplicantStatus, useAddRecruiterNote } from "../Hooks/useHiring"; +import { ApplicantStatus } from "../Types/Hiring.types"; +import Button from "@/Component/ui/Button"; +import { FiCalendar, FiMail, FiArrowLeft, FiPlus, FiChevronRight } from "react-icons/fi"; +import { MdCheckCircle } from "react-icons/md"; + +const ApplicantDetailsPage = () => { + const { theme } = useTheme(); + const navigate = useNavigate(); + const { id: jobId, applicantId } = useParams<{ id: string; applicantId: string }>(); + + // Fetch applicant + const { data: applicant, isLoading } = useApplicantDetail(applicantId); + const updateStatusMutation = useUpdateApplicantStatus(); + const addNoteMutation = useAddRecruiterNote(); + + // Note composition state + const [noteContent, setNoteContent] = useState(""); + const [noteRec, setNoteRec] = useState<"strong_hire" | "hire" | "neutral" | "no_hire">("hire"); + const [showNoteForm, setShowNoteForm] = useState(false); + + if (isLoading) { + return ( +
+
+ Loading candidate profile file... +
+
+ ); + } + + if (!applicant) { + return ( +
+ Candidate file not found. +
+ ); + } + + const handleMoveStage = (status: ApplicantStatus) => { + updateStatusMutation.mutate({ id: applicant.id, status, user: "Sarah Jenkins" }); + }; + + const handleAddNote = (e: React.FormEvent) => { + e.preventDefault(); + if (!noteContent.trim()) return; + + addNoteMutation.mutate( + { + applicantId: applicant.id, + note: { + author: "Sarah Jenkins", + content: noteContent, + recommendation: noteRec, + avatar: "/avatars/sarah.jpg", + }, + }, + { + onSuccess: () => { + setNoteContent(""); + setShowNoteForm(false); + }, + } + ); + }; + + const getNextStage = (status: ApplicantStatus): ApplicantStatus => { + const sequence: ApplicantStatus[] = ["applied", "screening", "technical", "interview", "hr", "offer", "hired"]; + const idx = sequence.indexOf(status); + if (idx !== -1 && idx < sequence.length - 1) return sequence[idx + 1]; + return status; + }; + + const nextStage = getNextStage(applicant.status); + + return ( +
+
+ {/* Top bar */} +
+
+ +
+
+

+ {applicant.name} +

+ + {applicant.matchScore}% MATCH + +
+ + Applied for Senior Product Designer • Applied {applicant.appliedDate} + +
+
+ +
+
+
+ + {/* Layout Grid */} +
+ {/* LEFT: CANDIDATE INFO & METRIC */} +
+ {/* Profile Summary Card */} +
+
+ {applicant.name[0]} +
+
+

{applicant.name}

+ San Francisco, CA +
+ +
+
+ Experience + {applicant.experience} +
+
+ Education + RISD BFA +
+
+
+ + {/* Evaluation Score Card */} +
+
+

Evaluation Matrix

+
+
+ Technical Fit + {applicant.rating.technical || "Pending"}/10 +
+
+ Culture Fit + {applicant.rating.culture || "Pending"}/10 +
+
+ Communication + {applicant.rating.communication || "Pending"}/10 +
+
+
+ +
+ Rating + {applicant.rating.rating} +
+
+ + {/* Skills & tags */} +
+

Skills Tags

+
+ {applicant.skills.map((skill) => ( + + {skill} + + ))} +
+
+
+ + {/* MIDDLE: TIMELINES & NOTES */} +
+ {/* Reviewer Feedback Panel */} +
+
+

+ Internal Reviewer Feedback +

+ +
+ + {/* Note Submission Form */} + {showNoteForm && ( +
+