diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 6bb7b38..f421a96 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -18,6 +18,11 @@ export default function AdminJobs() { const [expiredJobData, setExpiredJobData] = useState(null); const setExpiredJobs = async (jobs: IJob[]) => { + if (!Array.isArray(jobs)) { + console.error("Expected jobs to be an array but received:", typeof jobs); + return; + } + console.log("Setting Expired"); const jobsToExpire = jobs.filter((job) => job.jobStatus !== "expired" && isMoreThanThirtyDaysAgo(job.approvedDate)); for (const job of jobsToExpire) { @@ -29,16 +34,28 @@ export default function AdminJobs() { const fetchData = async () => { try { - const response = await fetch("/api/jobs"); + const response = await fetch("/api/jobs?jobStatus=approved&admin=true"); + if (!response.ok) { + console.error("Failed to fetch jobs:", response.status, response.statusText); + return; + } const result: IJob[] = await response.json(); - //const result: IJob[] = []; // Check and update expired jobs await setExpiredJobs(result); // Fetch all job statuses in parallel const jobStatuses = ["pending", "approved", "rejected", "expired"]; - const responses = await Promise.all(jobStatuses.map((status) => fetch(`/api/jobs?jobStatus=${status}`))); + const responses = await Promise.all( + jobStatuses.map((status) => fetch(`/api/jobs?jobStatus=${status}&admin=true`)), + ); + + // Check if any of the parallel requests failed + const failedResponses = responses.filter((res) => !res.ok); + if (failedResponses.length > 0) { + console.error("Some job status requests failed:", failedResponses); + return; + } const [incomingData, liveData, completeData, expiredData] = await Promise.all(responses.map((res) => res.json())); @@ -114,7 +131,6 @@ export default function AdminJobs() { throw new Error("Failed to update job status"); } - // Show toast notification based on status if (status === "approved") { toast({ title: "Job Approved", @@ -162,7 +178,6 @@ export default function AdminJobs() { switch (status) { case "approved": setLiveJobData((prev) => (prev ? [...prev, updatedJob] : [updatedJob])); - setExpiredJobData((prev) => (prev ? prev.filter((job) => job._id !== jobId) : [])); break; case "rejected": setCompleteJobData((prev) => (prev ? [...prev, updatedJob] : [updatedJob])); diff --git a/src/app/api/__test__/AdminPage.test.tsx b/src/app/api/__test__/AdminPage.test.tsx index 325ff21..8b1eaa5 100644 --- a/src/app/api/__test__/AdminPage.test.tsx +++ b/src/app/api/__test__/AdminPage.test.tsx @@ -125,22 +125,22 @@ describe("Admin Jobs Page", () => { ok: true, json: async () => fakeJobs, }); - } else if (url === "/api/jobs?jobStatus=pending") { + } else if (url === "/api/jobs?jobStatus=pending&admin=true") { return Promise.resolve({ ok: true, json: async () => pendingJobs, }); - } else if (url === "/api/jobs?jobStatus=approved") { + } else if (url === "/api/jobs?jobStatus=approved&admin=true") { return Promise.resolve({ ok: true, json: async () => approvedJobs, }); - } else if (url === "/api/jobs?jobStatus=rejected") { + } else if (url === "/api/jobs?jobStatus=rejected&admin=true") { return Promise.resolve({ ok: true, json: async () => rejectedJobs, }); - } else if (url === "/api/jobs?jobStatus=expired") { + } else if (url === "/api/jobs?jobStatus=expired&admin=true") { return Promise.resolve({ ok: true, json: async () => expiredJobs, diff --git a/src/app/api/jobs/[jobId]/route.ts b/src/app/api/jobs/[jobId]/route.ts index 8263ced..3ab386e 100644 --- a/src/app/api/jobs/[jobId]/route.ts +++ b/src/app/api/jobs/[jobId]/route.ts @@ -28,7 +28,7 @@ export const DELETE = withApiAuth( } //Deleting based on _id - const result = await Job.findByIdAndDelete(jobId); + await Job.findByIdAndDelete(jobId); return NextResponse.json({ message: "Deleted successfully" }, { status: 200 }); } catch (error) { @@ -49,7 +49,7 @@ export const PUT = withApiAuth( const jobId = req.nextUrl.pathname.split("/").pop(); const requestData = await req.json(); - const { previousStatus, newStatus, ...jobData } = requestData; + const { previousStatus, newStatus, isRenewal, ...jobData } = requestData; // Handle both cases - with and without status transition data const hasStatusTransition = previousStatus !== undefined && newStatus !== undefined; @@ -76,6 +76,21 @@ export const PUT = withApiAuth( return NextResponse.json({ error: "Insufficient permissions to update this job" }, { status: 403 }); } + // Handle job renewal + if (isRenewal) { + const updatedJob = await Job.findByIdAndUpdate( + jobId, + { + jobStatus: JobStatus.approved, + postDate: new Date(), + approvedDate: new Date(), + modifiedDate: new Date(), + }, + { new: true }, + ); + return NextResponse.json({ message: "Job renewed successfully", job: updatedJob }); + } + const updatedJob = { ...jobData, jobStatus: hasStatusTransition @@ -92,7 +107,7 @@ export const PUT = withApiAuth( await Job.findByIdAndUpdate(jobId, updatedJob, { new: true }); return NextResponse.json({ message: "Job updated successfully" }); - } catch (error: any) { + } catch (error) { console.error("PUT Error:", error); return NextResponse.json({ message: "Error updating job: ", error }, { status: 500 }); } diff --git a/src/app/api/jobs/route.ts b/src/app/api/jobs/route.ts index 002714e..e0ac903 100644 --- a/src/app/api/jobs/route.ts +++ b/src/app/api/jobs/route.ts @@ -14,9 +14,12 @@ export const GET = withApiAuth( await connectDB(); const { searchParams } = new URL(req.url); - const page = Math.max(parseInt(searchParams.get("page") || "1", 10), 1); - const limit = Math.max(parseInt(searchParams.get("limit") || "10", 10), 1); - const skip = (page - 1) * limit; // This will always be >= 0 + const isAdminRequest = searchParams.get("admin") === "true"; + + // Only apply pagination for non-admin requests + const page = isAdminRequest ? 1 : Math.max(parseInt(searchParams.get("page") || "1", 10), 1); + const limit = isAdminRequest ? 0 : Math.max(parseInt(searchParams.get("limit") || "10", 10), 1); + const skip = isAdminRequest ? 0 : (page - 1) * limit; // This will always be >= 0 // Filter parameters const employmentFilters = searchParams.getAll("employmentType"); @@ -53,7 +56,7 @@ export const GET = withApiAuth( }; // Filter out expired jobs that are already approved - if (statusFilter == "approved") { + if (statusFilter == "approved" && !isAdminRequest) { filter.approvedDate = { $gte: getThirtyDaysAgo(), }; diff --git a/src/app/dashboard/DashboardPage.tsx b/src/app/dashboard/DashboardPage.tsx index 814ab71..c63b940 100644 --- a/src/app/dashboard/DashboardPage.tsx +++ b/src/app/dashboard/DashboardPage.tsx @@ -44,6 +44,14 @@ export default function DashboardPage({ organizationName }: DashboardProps) { } }, [user, isLoaded]); + const handleJobRenewed = (renewedJob: IJob) => { + setUserJobs((prevJobs) => { + // Update the job in the array + const updatedJobs = prevJobs.map((job) => (job._id === renewedJob._id ? renewedJob : job)); + return updatedJobs; + }); + }; + if (loading) { return (
@@ -136,7 +144,9 @@ export default function DashboardPage({ organizationName }: DashboardProps) {
Expired Applications
{expiredJobs.length > 0 ? ( - expiredJobs.map((job, index) => ) + expiredJobs.map((job, index) => ( + + )) ) : (
No expired applications available diff --git a/src/components/JobCard/AdminCard.tsx b/src/components/JobCard/AdminCard.tsx index af48b9f..20aae5c 100644 --- a/src/components/JobCard/AdminCard.tsx +++ b/src/components/JobCard/AdminCard.tsx @@ -10,6 +10,7 @@ import JobCardModal from "./JobCardModal"; import { useRouter } from "next/navigation"; import RejectButton from "@/components/RejectButton"; import Link from "next/link"; +import { useToast } from "@chakra-ui/react"; interface JobCardProps { job: IJob; @@ -26,6 +27,7 @@ export default function AdminCard({ job, onUpdateJob, innerRef }: JobCardProps) const router = useRouter(); const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); + const toast = useToast(); // Check localStorage for dismissed state on component mount useEffect(() => { @@ -68,6 +70,42 @@ export default function AdminCard({ job, onUpdateJob, innerRef }: JobCardProps) if (onUpdateJob) { await onUpdateJob(job._id, "approved", new Date()); } + } else if (selectedAction === "renew") { + setIsLoading("approve"); + try { + const response = await fetch(`/api/jobs/${job._id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + isRenewal: true, + previousStatus: job.jobStatus, + newStatus: "approved", + }), + }); + + if (!response.ok) { + throw new Error("Failed to renew job"); + } + + const data = await response.json(); + + // Update the job status in the UI + if (onUpdateJob) { + await onUpdateJob(job._id, "approved", new Date()); + } + } catch (error) { + console.error("Error renewing job:", error); + toast({ + title: "Error", + description: "Failed to renew job. Please try again.", + status: "error", + duration: 5000, + isClosable: true, + position: "top-right", + }); + } } setIsLoading(null); closeModal(); diff --git a/src/components/JobCard/OrgCard.tsx b/src/components/JobCard/OrgCard.tsx index 801c9fb..be3e494 100644 --- a/src/components/JobCard/OrgCard.tsx +++ b/src/components/JobCard/OrgCard.tsx @@ -15,8 +15,9 @@ import { ModalCloseButton, useDisclosure, Text, + useToast, } from "@chakra-ui/react"; -import { FiEdit, FiMessageSquare } from "react-icons/fi"; +import { FiEdit, FiMessageSquare, FiRefreshCw } from "react-icons/fi"; import { useRouter } from "next/navigation"; import JobDateInfo, { JobDateKind } from "./JobDateInfo"; import { isMoreThanThirtyDaysAgo } from "@/lib/utils"; @@ -25,6 +26,7 @@ export interface OrgCardProps extends ComponentProps<"div"> { className?: string; job: IJob; types: JobDateKind[]; + onJobRenewed?: (job: IJob) => void; } function getJobDate(job: IJob, type: JobDateKind) { @@ -53,16 +55,68 @@ function getJobDate(job: IJob, type: JobDateKind) { } export const OrgCard = forwardRef( - ({ children, className, job, types, ...props }, ref) => { + ({ children, className, job, types, onJobRenewed, ...props }, ref) => { const { isOpen, onOpen, onClose } = useDisclosure(); const isActuallyExpired = isMoreThanThirtyDaysAgo(job.approvedDate); const router = useRouter(); + const toast = useToast(); function handleEditApplicationButton(e: React.ChangeEvent) { e.preventDefault(); router.push(`/jobform?jobId=${job._id}&returnURL=/dashboard`); } + async function handleRenewJob(e: React.ChangeEvent) { + e.preventDefault(); + try { + const response = await fetch(`/api/jobs/${job._id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + isRenewal: true, + previousStatus: job.jobStatus, + newStatus: "approved", + }), + }); + + if (!response.ok) { + throw new Error("Failed to renew job"); + } + + const data = await response.json(); + + // Show success toast + toast({ + title: "Job Renewed", + description: `Successfully renewed "${job.title}"`, + status: "success", + duration: 5000, + isClosable: true, + position: "top-right", + }); + + // Call the callback if provided + if (onJobRenewed) { + onJobRenewed(data.job); + } else { + // Fallback to page refresh if no callback provided + window.location.reload(); + } + } catch (error) { + console.error("Error renewing job:", error); + toast({ + title: "Error", + description: "Failed to renew job. Please try again.", + status: "error", + duration: 5000, + isClosable: true, + position: "top-right", + }); + } + } + return ( <>
( )}
+ {isActuallyExpired && ( + + )}