/** * Table-based job list view. */ import React from "react"; import { ArrowDown, ArrowUp, ArrowUpDown, CheckCircle2, Download, ExternalLink, MoreHorizontal, RefreshCcw, XCircle, } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { cn } from "@/lib/utils"; import type { Job } from "../../shared/types"; import { StatusBadge } from "./StatusBadge"; export type JobSortKey = | "title" | "employer" | "source" | "location" | "status" | "score" | "discoveredAt"; export type JobSortDirection = "asc" | "desc"; export interface JobSort { key: JobSortKey; direction: JobSortDirection; } export interface JobTableProps { jobs: Job[]; sort: JobSort; onSortChange: (sort: JobSort) => void; onApply: (id: string) => void; onReject: (id: string) => void; onProcess: (id: string) => void; processingJobId: string | null; } const sourceLabel: Record = { gradcracker: "Gradcracker", indeed: "Indeed", linkedin: "LinkedIn", }; const defaultSortDirection: Record = { title: "asc", employer: "asc", source: "asc", location: "asc", status: "asc", score: "desc", discoveredAt: "desc", }; 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, "_"); const SortButton: React.FC<{ label: string; sortKey: JobSortKey; sort: JobSort; onSortChange: (sort: JobSort) => void; className?: string; }> = ({ label, sortKey, sort, onSortChange, className }) => { const isActive = sort.key === sortKey; const Icon = isActive ? (sort.direction === "asc" ? ArrowUp : ArrowDown) : ArrowUpDown; return ( ); }; export const JobTable: React.FC = ({ jobs, sort, onSortChange, onApply, onReject, onProcess, processingJobId, }) => { return ( Actions {jobs.map((job) => { const jobLink = job.applicationLink || job.jobUrl; const hasPdf = !!job.pdfPath; const pdfHref = `/pdfs/resume_${job.id}.pdf`; const canApply = job.status === "ready"; const canProcess = job.status === "discovered"; const canReject = ["discovered", "ready"].includes(job.status); const isProcessing = processingJobId === job.id; return ( {job.employer} {sourceLabel[job.source]} {job.location || "—"} {job.suitabilityScore ?? "—"} {formatDate(job.discoveredAt)} View Job {hasPdf && ( <> View PDF Download PDF )} {(canProcess || canReject || canApply) && } {canProcess && ( onProcess(job.id)} disabled={isProcessing} > {isProcessing ? "Processing..." : "Generate Resume"} )} {canReject && ( onReject(job.id)} > Skip )} {canApply && ( onApply(job.id)} > Mark Applied )} ); })}
); };