2026-01-15 18:26:44 +00:00

207 lines
6.0 KiB
TypeScript

/**
* Header component with logo and pipeline trigger.
*/
import React from "react";
import {
ChevronDown,
Loader2,
Play,
RefreshCcw,
Rocket,
Settings,
Shield,
Trash2,
} from "lucide-react";
import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import type { JobSource } from "../../shared/types";
interface HeaderProps {
onRunPipeline: () => void;
onRefresh: () => void;
isPipelineRunning: boolean;
isLoading: boolean;
pipelineSources: JobSource[];
onPipelineSourcesChange: (sources: JobSource[]) => void;
}
export const Header: React.FC<HeaderProps> = ({
onRunPipeline,
onRefresh,
isPipelineRunning,
isLoading,
pipelineSources,
onPipelineSourcesChange,
}) => {
const sourceLabel: Record<JobSource, string> = {
gradcracker: "Gradcracker",
indeed: "Indeed",
linkedin: "LinkedIn",
ukvisajobs: "UK Visa Jobs",
};
const orderedSources: JobSource[] = ["gradcracker", "indeed", "linkedin", "ukvisajobs"];
const toggleSource = (source: JobSource, checked: boolean) => {
const next = checked
? Array.from(new Set([...pipelineSources, source]))
: pipelineSources.filter((s) => s !== source);
if (next.length === 0) return;
onPipelineSourcesChange(next);
};
return (
<header className='sticky top-0 z-40 border-b bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/60'>
<div className='container mx-auto flex max-w-7xl items-center justify-between gap-4 px-4 py-4'>
<Link
to='/'
className='flex items-center gap-3 hover:opacity-80 transition-opacity'
>
<div className='flex h-9 w-9 items-center justify-center rounded-lg bg-primary text-primary-foreground shadow-sm'>
<Rocket className='h-5 w-5' />
</div>
<div className='leading-tight'>
<div className='text-sm font-semibold tracking-tight'>Job Ops</div>
<div className='text-xs text-muted-foreground'>Orchestrator</div>
</div>
</Link>
<div className='flex flex-wrap items-center gap-1.5'>
<Button
variant='outline'
size='sm'
onClick={onRefresh}
disabled={isLoading}
>
<RefreshCcw className='h-4 w-4' />
<span className='hidden sm:inline'>Refresh</span>
</Button>
<Button
asChild
variant='outline'
size='sm'
>
<Link to='/visa-sponsors'>
<Shield className='h-4 w-4' />
<span className='hidden sm:inline'>Visa Sponsors</span>
</Link>
</Button>
<Button
asChild
variant='outline'
size='sm'
>
<Link to='/settings'>
<Settings className='h-4 w-4' />
<span className='hidden sm:inline'>Settings</span>
</Link>
</Button>
<div>
<Button
size='sm'
onClick={onRunPipeline}
disabled={isPipelineRunning}
className='rounded-r-none'
>
{isPipelineRunning ? (
<>
<Loader2 className='h-4 w-4 animate-spin' />
Running...
</>
) : (
<>
<Play className='h-4 w-4' />
Run Pipeline
</>
)}
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size='sm'
disabled={isPipelineRunning}
className='rounded-l-none border-l border-primary-foreground/20'
aria-label='Select pipeline sources'
>
<ChevronDown className='h-4 w-4' />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align='end'
className='w-56'
>
<DropdownMenuLabel>Sources</DropdownMenuLabel>
<DropdownMenuSeparator />
{orderedSources.map((source) => (
<DropdownMenuCheckboxItem
key={source}
checked={pipelineSources.includes(source)}
onCheckedChange={(checked) =>
toggleSource(source, Boolean(checked))
}
onSelect={(e) => e.preventDefault()}
>
{sourceLabel[source]}
</DropdownMenuCheckboxItem>
))}
<DropdownMenuSeparator />
<DropdownMenuItem
onSelect={(e) => {
e.preventDefault();
onPipelineSourcesChange(orderedSources);
}}
>
All sources
</DropdownMenuItem>
<DropdownMenuItem
onSelect={(e) => {
e.preventDefault();
onPipelineSourcesChange(["gradcracker"]);
}}
>
Gradcracker only
</DropdownMenuItem>
<DropdownMenuItem
onSelect={(e) => {
e.preventDefault();
onPipelineSourcesChange(["indeed", "linkedin"]);
}}
>
Indeed + LinkedIn only
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
</header>
);
};