diff --git a/orchestrator/src/client/components/JobList.tsx b/orchestrator/src/client/components/JobList.tsx index b193790..7c9a9de 100644 --- a/orchestrator/src/client/components/JobList.tsx +++ b/orchestrator/src/client/components/JobList.tsx @@ -3,7 +3,7 @@ */ import React, { useEffect, useMemo, useState } from "react"; -import { ArrowUpDown, LayoutGrid, Search, Table2, X } from "lucide-react"; +import { ArrowUpDown, Filter, LayoutGrid, Search, Table2, X } from "lucide-react"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { toast } from "sonner"; @@ -23,7 +23,7 @@ import { import { Input } from "@/components/ui/input"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { cn } from "@/lib/utils"; -import type { Job, JobStatus } from "../../shared/types"; +import type { Job, JobStatus, JobSource } from "../../shared/types"; import { JobCard } from "./JobCard"; import { JobTable, type JobSort } from "./JobTable"; @@ -51,6 +51,13 @@ const sortLabels: Record = { status: "Status", }; +const sourceLabels: Record = { + gradcracker: "Gradcracker", + indeed: "Indeed", + linkedin: "LinkedIn", + ukvisajobs: "UK Visa Jobs", +}; + const tabs: Array<{ id: FilterTab; label: string; statuses: JobStatus[] }> = [ { id: "ready", label: "Ready", statuses: ["ready"] }, { id: "discovered", label: "Discovered", statuses: ["discovered", "processing"] }, @@ -183,6 +190,7 @@ export const JobList: React.FC = ({ }) => { const [activeTab, setActiveTab] = useState("ready"); const [searchQuery, setSearchQuery] = useState(""); + const [sourceFilter, setSourceFilter] = useState("all"); const [sort, setSort] = useState(DEFAULT_SORT); const [selectedJobIds, setSelectedJobIds] = useState>(() => new Set()); const [batchAction, setBatchAction] = useState(null); @@ -267,14 +275,22 @@ export const JobList: React.FC = ({ const normalizedQuery = searchQuery.trim().toLowerCase(); for (const tab of tabs) { - const base = jobsForTab.get(tab.id) ?? []; - const filtered = normalizedQuery ? base.filter((job) => jobMatchesQuery(job, normalizedQuery)) : base; + let filtered = jobsForTab.get(tab.id) ?? []; + + if (sourceFilter !== "all") { + filtered = filtered.filter((job) => job.source === sourceFilter); + } + + if (normalizedQuery) { + filtered = filtered.filter((job) => jobMatchesQuery(job, normalizedQuery)); + } + const sorted = [...filtered].sort((a, b) => compareJobs(a, b, sort)); map.set(tab.id, sorted); } return map; - }, [jobsForTab, searchQuery, sort]); + }, [jobsForTab, searchQuery, sourceFilter, sort]); const activeTabJobs = visibleJobsForTab.get(activeTab) ?? []; const highlightedJob = useMemo( @@ -303,6 +319,7 @@ export const JobList: React.FC = ({ const activeResultsCount = visibleJobsForTab.get(activeTab)?.length ?? 0; const hasActiveFilters = searchQuery.trim().length > 0 || + sourceFilter !== "all" || sort.key !== DEFAULT_SORT.key || sort.direction !== DEFAULT_SORT.direction; @@ -467,6 +484,33 @@ export const JobList: React.FC = ({
+ + + + + + Filter by source + + setSourceFilter(value as JobSource | "all")} + > + All Sources + {(Object.keys(sourceLabels) as JobSource[]).map((key) => ( + + {sourceLabels[key]} + + ))} + + + +