/** * Individual job card component. */ import React from "react"; import { Calendar, CheckCircle2, Copy, DollarSign, Download, ExternalLink, GraduationCap, Loader2, MapPin, RefreshCcw, XCircle, } from "lucide-react"; import { toast } from "sonner"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { copyTextToClipboard, formatJobForLlmContext } from "@client/lib/jobCopy"; import type { Job } from "../../shared/types"; import { ScoreIndicator } from "./ScoreIndicator"; import { StatusBadge } from "./StatusBadge"; interface JobCardProps { job: Job; onApply: (id: string) => void | Promise; onReject: (id: string) => void | Promise; onProcess: (id: string) => void | Promise; isProcessing: boolean; highlightedJobId?: string | null; onHighlightChange?: (jobId: string | null) => void; } const formatDate = (dateStr: string | null) => { if (!dateStr) return null; try { return new Date(dateStr).toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric", }); } catch { return dateStr; } }; const safeFilenamePart = (value: string) => value.replace(/[^a-z0-9]/gi, "_"); export const JobCard: React.FC = ({ job, onApply, onReject, onProcess, isProcessing, highlightedJobId, onHighlightChange, }) => { const sourceLabel: Record = { gradcracker: "Gradcracker", indeed: "Indeed", linkedin: "LinkedIn", }; const hasPdf = !!job.pdfPath; const canApply = job.status === "ready"; const canProcess = job.status === "discovered"; const canReject = ["discovered", "ready"].includes(job.status); const jobLink = job.applicationLink || job.jobUrl; const pdfHref = `/pdfs/resume_${job.id}.pdf`; const deadline = formatDate(job.deadline); const isHighlighted = highlightedJobId === job.id; const handleCopyInfo = async () => { try { await copyTextToClipboard(formatJobForLlmContext(job)); toast.success("Copied job info", { description: "LLM-ready context copied to clipboard." }); } catch { toast.error("Could not copy job info"); } }; return (
{job.title}
{job.employer}
{sourceLabel[job.source]}
{job.location && ( {job.location} )} {deadline && ( {deadline} )} {job.salary && ( {job.salary} )} {job.degreeRequired && ( {job.degreeRequired} )}
{(job.suitabilityReason || canApply || canReject || canProcess || hasPdf) && ( {job.suitabilityReason && (

"{job.suitabilityReason}"

)}
)} {onHighlightChange && ( )} {hasPdf && ( )} {hasPdf && ( )} {canProcess && ( )} {canReject && ( )} {canApply && ( )}
); };