job list source filter

This commit is contained in:
DaKheera47
2026-01-02 17:06:08 +00:00
parent e070ff9d60
commit 5e623e8504
+50 -5
View File
@@ -3,7 +3,7 @@
*/ */
import React, { useEffect, useMemo, useState } from "react"; 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 ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm"; import remarkGfm from "remark-gfm";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -23,7 +23,7 @@ import {
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils"; 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 { JobCard } from "./JobCard";
import { JobTable, type JobSort } from "./JobTable"; import { JobTable, type JobSort } from "./JobTable";
@@ -51,6 +51,13 @@ const sortLabels: Record<JobSort["key"], string> = {
status: "Status", status: "Status",
}; };
const sourceLabels: Record<JobSource, string> = {
gradcracker: "Gradcracker",
indeed: "Indeed",
linkedin: "LinkedIn",
ukvisajobs: "UK Visa Jobs",
};
const tabs: Array<{ id: FilterTab; label: string; statuses: JobStatus[] }> = [ const tabs: Array<{ id: FilterTab; label: string; statuses: JobStatus[] }> = [
{ id: "ready", label: "Ready", statuses: ["ready"] }, { id: "ready", label: "Ready", statuses: ["ready"] },
{ id: "discovered", label: "Discovered", statuses: ["discovered", "processing"] }, { id: "discovered", label: "Discovered", statuses: ["discovered", "processing"] },
@@ -183,6 +190,7 @@ export const JobList: React.FC<JobListProps> = ({
}) => { }) => {
const [activeTab, setActiveTab] = useState<FilterTab>("ready"); const [activeTab, setActiveTab] = useState<FilterTab>("ready");
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [sourceFilter, setSourceFilter] = useState<JobSource | "all">("all");
const [sort, setSort] = useState<JobSort>(DEFAULT_SORT); const [sort, setSort] = useState<JobSort>(DEFAULT_SORT);
const [selectedJobIds, setSelectedJobIds] = useState<Set<string>>(() => new Set()); const [selectedJobIds, setSelectedJobIds] = useState<Set<string>>(() => new Set());
const [batchAction, setBatchAction] = useState<null | "process" | "reject" | "apply">(null); const [batchAction, setBatchAction] = useState<null | "process" | "reject" | "apply">(null);
@@ -267,14 +275,22 @@ export const JobList: React.FC<JobListProps> = ({
const normalizedQuery = searchQuery.trim().toLowerCase(); const normalizedQuery = searchQuery.trim().toLowerCase();
for (const tab of tabs) { for (const tab of tabs) {
const base = jobsForTab.get(tab.id) ?? []; let filtered = jobsForTab.get(tab.id) ?? [];
const filtered = normalizedQuery ? base.filter((job) => jobMatchesQuery(job, normalizedQuery)) : base;
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)); const sorted = [...filtered].sort((a, b) => compareJobs(a, b, sort));
map.set(tab.id, sorted); map.set(tab.id, sorted);
} }
return map; return map;
}, [jobsForTab, searchQuery, sort]); }, [jobsForTab, searchQuery, sourceFilter, sort]);
const activeTabJobs = visibleJobsForTab.get(activeTab) ?? []; const activeTabJobs = visibleJobsForTab.get(activeTab) ?? [];
const highlightedJob = useMemo( const highlightedJob = useMemo(
@@ -303,6 +319,7 @@ export const JobList: React.FC<JobListProps> = ({
const activeResultsCount = visibleJobsForTab.get(activeTab)?.length ?? 0; const activeResultsCount = visibleJobsForTab.get(activeTab)?.length ?? 0;
const hasActiveFilters = const hasActiveFilters =
searchQuery.trim().length > 0 || searchQuery.trim().length > 0 ||
sourceFilter !== "all" ||
sort.key !== DEFAULT_SORT.key || sort.key !== DEFAULT_SORT.key ||
sort.direction !== DEFAULT_SORT.direction; sort.direction !== DEFAULT_SORT.direction;
@@ -467,6 +484,33 @@ export const JobList: React.FC<JobListProps> = ({
</div> </div>
<div className="flex flex-wrap items-center gap-2 self-start sm:self-auto"> <div className="flex flex-wrap items-center gap-2 self-start sm:self-auto">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="h-9">
<Filter className="h-4 w-4" />
<span className="hidden sm:inline">
Source: {sourceFilter === "all" ? "All" : sourceLabels[sourceFilter]}
</span>
<span className="sm:hidden">Source</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuLabel>Filter by source</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuRadioGroup
value={sourceFilter}
onValueChange={(value) => setSourceFilter(value as JobSource | "all")}
>
<DropdownMenuRadioItem value="all">All Sources</DropdownMenuRadioItem>
{(Object.keys(sourceLabels) as JobSource[]).map((key) => (
<DropdownMenuRadioItem key={key} value={key}>
{sourceLabels[key]}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="h-9"> <Button variant="outline" size="sm" className="h-9">
@@ -519,6 +563,7 @@ export const JobList: React.FC<JobListProps> = ({
size="sm" size="sm"
onClick={() => { onClick={() => {
setSearchQuery(""); setSearchQuery("");
setSourceFilter("all");
setSort(DEFAULT_SORT); setSort(DEFAULT_SORT);
}} }}
> >