diff --git a/orchestrator/src/client/components/JobHeader.tsx b/orchestrator/src/client/components/JobHeader.tsx index 704d9d4..f42da72 100644 --- a/orchestrator/src/client/components/JobHeader.tsx +++ b/orchestrator/src/client/components/JobHeader.tsx @@ -7,10 +7,11 @@ import { cn, formatDate, sourceLabel } from "@/lib/utils"; import type { Job, JobStatus } from "../../shared/types"; import { defaultStatusToken, statusTokens } from "../pages/orchestrator/constants"; +import { useSettings } from "../hooks/useSettings"; + interface JobHeaderProps { job: Job; className?: string; - showSponsorInfo?: boolean; onCheckSponsor?: () => Promise; } @@ -83,16 +84,16 @@ const SponsorPill: React.FC = ({ score, names, onCheck }) => { @@ -133,7 +134,8 @@ const SponsorPill: React.FC = ({ score, names, onCheck }) => { ); }; -export const JobHeader: React.FC = ({ job, className, showSponsorInfo = true, onCheckSponsor }) => { +export const JobHeader: React.FC = ({ job, className, onCheckSponsor }) => { + const { showSponsorInfo } = useSettings(); const deadline = formatDate(job.deadline); return ( diff --git a/orchestrator/src/client/components/ReadyPanel.tsx b/orchestrator/src/client/components/ReadyPanel.tsx index 068d4ea..acc2ade 100644 --- a/orchestrator/src/client/components/ReadyPanel.tsx +++ b/orchestrator/src/client/components/ReadyPanel.tsx @@ -50,7 +50,6 @@ interface ReadyPanelProps { onJobMoved: (jobId: string) => void; onEditTailoring: () => void; onEditDescription: () => void; - showSponsorInfo?: boolean; } const safeFilenamePart = (value: string | null | undefined) => @@ -62,7 +61,6 @@ export const ReadyPanel: React.FC = ({ onJobMoved, onEditTailoring, onEditDescription, - showSponsorInfo, }) => { const [isMarkingApplied, setIsMarkingApplied] = useState(false); const [isRegenerating, setIsRegenerating] = useState(false); @@ -224,7 +222,6 @@ export const ReadyPanel: React.FC = ({ await api.checkSponsor(job.id); await onJobUpdated(); }} - showSponsorInfo={showSponsorInfo} /> {/* ───────────────────────────────────────────────────────────────────── diff --git a/orchestrator/src/client/components/discovered-panel/DecideMode.tsx b/orchestrator/src/client/components/discovered-panel/DecideMode.tsx index 6d7418b..777ea89 100644 --- a/orchestrator/src/client/components/discovered-panel/DecideMode.tsx +++ b/orchestrator/src/client/components/discovered-panel/DecideMode.tsx @@ -15,7 +15,6 @@ interface DecideModeProps { onSkip: () => void; isSkipping: boolean; onCheckSponsor?: () => Promise; - showSponsorInfo?: boolean; } export const DecideMode: React.FC = ({ @@ -24,7 +23,6 @@ export const DecideMode: React.FC = ({ onSkip, isSkipping, onCheckSponsor, - showSponsorInfo, }) => { const [showDescription, setShowDescription] = useState(false); const jobLink = job.applicationLink || job.jobUrl; @@ -40,7 +38,6 @@ export const DecideMode: React.FC = ({
diff --git a/orchestrator/src/client/components/discovered-panel/DiscoveredPanel.tsx b/orchestrator/src/client/components/discovered-panel/DiscoveredPanel.tsx index 9593b0f..3065cec 100644 --- a/orchestrator/src/client/components/discovered-panel/DiscoveredPanel.tsx +++ b/orchestrator/src/client/components/discovered-panel/DiscoveredPanel.tsx @@ -14,14 +14,12 @@ interface DiscoveredPanelProps { job: Job | null; onJobUpdated: () => void | Promise; onJobMoved: (jobId: string) => void; - showSponsorInfo?: boolean; } export const DiscoveredPanel: React.FC = ({ job, onJobUpdated, onJobMoved, - showSponsorInfo, }) => { const [mode, setMode] = useState("decide"); const [isSkipping, setIsSkipping] = useState(false); @@ -91,7 +89,6 @@ export const DiscoveredPanel: React.FC = ({ await api.checkSponsor(job.id); await onJobUpdated(); }} - showSponsorInfo={showSponsorInfo} /> ) : ( void> = new Set(); +let isFetching = false; + +export function useSettings() { + const [settings, setSettings] = useState(settingsCache); + + useEffect(() => { + if (settingsCache) { + setSettings(settingsCache); + } + + const handleUpdate = (newSettings: AppSettings) => { + setSettings(newSettings); + }; + + subscribers.add(handleUpdate); + + if (!settingsCache && !isFetching) { + isFetching = true; + api.getSettings() + .then((data) => { + settingsCache = data; + subscribers.forEach(sub => sub(data)); + }) + .finally(() => { + isFetching = false; + }); + } + + return () => { + subscribers.delete(handleUpdate); + }; + }, []); + + const refreshSettings = async () => { + isFetching = true; + try { + const data = await api.getSettings(); + settingsCache = data; + subscribers.forEach(sub => sub(data)); + return data; + } finally { + isFetching = false; + } + }; + + return { + settings, + isLoading: !settings && isFetching, + showSponsorInfo: settings?.showSponsorInfo ?? true, + refreshSettings, + }; +} diff --git a/orchestrator/src/client/pages/OrchestratorPage.tsx b/orchestrator/src/client/pages/OrchestratorPage.tsx index 612fe23..c6fc6ee 100644 --- a/orchestrator/src/client/pages/OrchestratorPage.tsx +++ b/orchestrator/src/client/pages/OrchestratorPage.tsx @@ -122,9 +122,7 @@ export const OrchestratorPage: React.FC = () => { }; const { pipelineSources, setPipelineSources, toggleSource } = usePipelineSources(); - const { jobs, stats, settings, isLoading, isPipelineRunning, setIsPipelineRunning, loadJobs } = useOrchestratorData(); - - const showSponsorInfo = settings?.showSponsorInfo ?? true; + const { jobs, stats, isLoading, isPipelineRunning, setIsPipelineRunning, loadJobs } = useOrchestratorData(); const activeJobs = useFilteredJobs(jobs, activeTab, sourceFilter, searchQuery, sort); const counts = useMemo(() => getJobCounts(jobs), [jobs]); @@ -278,7 +276,6 @@ export const OrchestratorPage: React.FC = () => { onSelectJobId={handleSelectJobId} onJobUpdated={loadJobs} onSetActiveTab={setActiveTab} - showSponsorInfo={showSponsorInfo} />
)} diff --git a/orchestrator/src/client/pages/orchestrator/JobDetailPanel.tsx b/orchestrator/src/client/pages/orchestrator/JobDetailPanel.tsx index 563c0bc..e7a1297 100644 --- a/orchestrator/src/client/pages/orchestrator/JobDetailPanel.tsx +++ b/orchestrator/src/client/pages/orchestrator/JobDetailPanel.tsx @@ -41,7 +41,6 @@ interface JobDetailPanelProps { onSelectJobId: (jobId: string | null) => void; onJobUpdated: () => Promise; onSetActiveTab: (tab: FilterTab) => void; - showSponsorInfo?: boolean; } export const JobDetailPanel: React.FC = ({ @@ -51,7 +50,6 @@ export const JobDetailPanel: React.FC = ({ onSelectJobId, onJobUpdated, onSetActiveTab, - showSponsorInfo, }) => { const [detailTab, setDetailTab] = useState<"overview" | "tailoring" | "description">("overview"); const [isEditingDescription, setIsEditingDescription] = useState(false); @@ -235,7 +233,6 @@ export const JobDetailPanel: React.FC = ({ job={selectedJob} onJobUpdated={onJobUpdated} onJobMoved={handleJobMoved} - showSponsorInfo={showSponsorInfo} /> ); } @@ -257,7 +254,6 @@ export const JobDetailPanel: React.FC = ({ setIsEditingDescription(true); }, 50); }} - showSponsorInfo={showSponsorInfo} /> ); } @@ -279,7 +275,6 @@ export const JobDetailPanel: React.FC = ({ await api.checkSponsor(selectedJob.id); await onJobUpdated(); }} - showSponsorInfo={showSponsorInfo} />
diff --git a/orchestrator/src/client/pages/orchestrator/useOrchestratorData.ts b/orchestrator/src/client/pages/orchestrator/useOrchestratorData.ts index 0f6dac9..2c440cb 100644 --- a/orchestrator/src/client/pages/orchestrator/useOrchestratorData.ts +++ b/orchestrator/src/client/pages/orchestrator/useOrchestratorData.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; -import type { Job, JobStatus, AppSettings } from "../../../shared/types"; +import type { Job, JobStatus } from "../../../shared/types"; import * as api from "../../api"; const initialStats: Record = { @@ -16,20 +16,15 @@ const initialStats: Record = { export const useOrchestratorData = () => { const [jobs, setJobs] = useState([]); const [stats, setStats] = useState>(initialStats); - const [settings, setSettings] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isPipelineRunning, setIsPipelineRunning] = useState(false); const loadJobs = useCallback(async () => { try { setIsLoading(true); - const [jobsData, settingsData] = await Promise.all([ - api.getJobs(), - api.getSettings(), - ]); - setJobs(jobsData.jobs); - setStats(jobsData.byStatus); - setSettings(settingsData); + const data = await api.getJobs(); + setJobs(data.jobs); + setStats(data.byStatus); } catch (error) { const message = error instanceof Error ? error.message : "Failed to load jobs"; toast.error(message); @@ -59,5 +54,5 @@ export const useOrchestratorData = () => { return () => clearInterval(interval); }, [loadJobs, checkPipelineStatus]); - return { jobs, stats, settings, isLoading, isPipelineRunning, setIsPipelineRunning, loadJobs, checkPipelineStatus }; + return { jobs, stats, isLoading, isPipelineRunning, setIsPipelineRunning, loadJobs, checkPipelineStatus }; };