diff --git a/orchestrator/src/client/pages/SettingsPage.tsx b/orchestrator/src/client/pages/SettingsPage.tsx index 49a1506..37b4c90 100644 --- a/orchestrator/src/client/pages/SettingsPage.tsx +++ b/orchestrator/src/client/pages/SettingsPage.tsx @@ -129,12 +129,12 @@ const getDerivedSettings = (settings: AppSettings | null) => { default: settings?.defaultJobCompleteWebhookUrl ?? "", }, ukvisajobs: { - effectiveMaxJobs: settings?.ukvisajobsMaxJobs ?? 50, - defaultMaxJobs: settings?.defaultUkvisajobsMaxJobs ?? 50, + effective: settings?.ukvisajobsMaxJobs ?? 50, + default: settings?.defaultUkvisajobsMaxJobs ?? 50, }, gradcracker: { - effectiveMaxJobsPerTerm: settings?.gradcrackerMaxJobsPerTerm ?? 50, - defaultMaxJobsPerTerm: settings?.defaultGradcrackerMaxJobsPerTerm ?? 50, + effective: settings?.gradcrackerMaxJobsPerTerm ?? 50, + default: settings?.defaultGradcrackerMaxJobsPerTerm ?? 50, }, searchTerms: { effective: settings?.searchTerms ?? [], @@ -167,8 +167,8 @@ const getDerivedSettings = (settings: AppSettings | null) => { }, }, display: { - effectiveShowSponsorInfo: settings?.showSponsorInfo ?? true, - defaultShowSponsorInfo: settings?.defaultShowSponsorInfo ?? true, + effective: settings?.showSponsorInfo ?? true, + default: settings?.defaultShowSponsorInfo ?? true, }, defaultResumeProjects: settings?.defaultResumeProjects ?? null, profileProjects, @@ -253,8 +253,8 @@ export const SettingsPage: React.FC = () => { pipelineWebhookUrl: normalizeString(data.pipelineWebhookUrl), jobCompleteWebhookUrl: normalizeString(data.jobCompleteWebhookUrl), resumeProjects: resumeProjectsOverride, - ukvisajobsMaxJobs: nullIfSame(data.ukvisajobsMaxJobs, ukvisajobs.defaultMaxJobs), - gradcrackerMaxJobsPerTerm: nullIfSame(data.gradcrackerMaxJobsPerTerm, gradcracker.defaultMaxJobsPerTerm), + ukvisajobsMaxJobs: nullIfSame(data.ukvisajobsMaxJobs, ukvisajobs.default), + gradcrackerMaxJobsPerTerm: nullIfSame(data.gradcrackerMaxJobsPerTerm, gradcracker.default), searchTerms: nullIfSameList(data.searchTerms, searchTerms.default), jobspyLocation: nullIfSame(data.jobspyLocation, jobspy.location.default), jobspyResultsWanted: nullIfSame(data.jobspyResultsWanted, jobspy.resultsWanted.default), @@ -265,7 +265,7 @@ export const SettingsPage: React.FC = () => { data.jobspyLinkedinFetchDescription, jobspy.linkedinFetchDescription.default ), - showSponsorInfo: nullIfSame(data.showSponsorInfo, display.defaultShowSponsorInfo), + showSponsorInfo: nullIfSame(data.showSponsorInfo, display.default), } const updated = await api.updateSettings(payload) @@ -361,57 +361,37 @@ export const SettingsPage: React.FC = () => {
@@ -423,8 +403,7 @@ export const SettingsPage: React.FC = () => { isSaving={isSaving} /> diff --git a/orchestrator/src/client/pages/settings/components/DisplaySettingsSection.tsx b/orchestrator/src/client/pages/settings/components/DisplaySettingsSection.tsx index 60c1e6d..00ee4ab 100644 --- a/orchestrator/src/client/pages/settings/components/DisplaySettingsSection.tsx +++ b/orchestrator/src/client/pages/settings/components/DisplaySettingsSection.tsx @@ -5,20 +5,20 @@ import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ import { Checkbox } from "@/components/ui/checkbox" import { Separator } from "@/components/ui/separator" import { UpdateSettingsInput } from "@shared/settings-schema" +import type { DisplayValues } from "@client/pages/settings/types" type DisplaySettingsSectionProps = { - defaultShowSponsorInfo: boolean - effectiveShowSponsorInfo: boolean + values: DisplayValues isLoading: boolean isSaving: boolean } export const DisplaySettingsSection: React.FC = ({ - defaultShowSponsorInfo, - effectiveShowSponsorInfo, + values, isLoading, isSaving, }) => { + const { default: defaultShowSponsorInfo, effective: effectiveShowSponsorInfo } = values const { control } = useFormContext() return ( @@ -79,4 +79,3 @@ export const DisplaySettingsSection: React.FC = ({ ) } - diff --git a/orchestrator/src/client/pages/settings/components/GradcrackerSection.tsx b/orchestrator/src/client/pages/settings/components/GradcrackerSection.tsx index 8085491..1c34166 100644 --- a/orchestrator/src/client/pages/settings/components/GradcrackerSection.tsx +++ b/orchestrator/src/client/pages/settings/components/GradcrackerSection.tsx @@ -5,20 +5,20 @@ import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" import { UpdateSettingsInput } from "@shared/settings-schema" +import type { NumericSettingValues } from "@client/pages/settings/types" type GradcrackerSectionProps = { - defaultGradcrackerMaxJobsPerTerm: number - effectiveGradcrackerMaxJobsPerTerm: number + values: NumericSettingValues isLoading: boolean isSaving: boolean } export const GradcrackerSection: React.FC = ({ - defaultGradcrackerMaxJobsPerTerm, - effectiveGradcrackerMaxJobsPerTerm, + values, isLoading, isSaving, }) => { + const { effective: effectiveGradcrackerMaxJobsPerTerm, default: defaultGradcrackerMaxJobsPerTerm } = values const { control, formState: { errors } } = useFormContext() return ( @@ -75,4 +75,3 @@ export const GradcrackerSection: React.FC = ({ ) } - diff --git a/orchestrator/src/client/pages/settings/components/JobCompleteWebhookSection.tsx b/orchestrator/src/client/pages/settings/components/JobCompleteWebhookSection.tsx index 3b8b5f9..1d4b775 100644 --- a/orchestrator/src/client/pages/settings/components/JobCompleteWebhookSection.tsx +++ b/orchestrator/src/client/pages/settings/components/JobCompleteWebhookSection.tsx @@ -5,20 +5,20 @@ import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" import { UpdateSettingsInput } from "@shared/settings-schema" +import type { WebhookValues } from "@client/pages/settings/types" type JobCompleteWebhookSectionProps = { - defaultJobCompleteWebhookUrl: string - effectiveJobCompleteWebhookUrl: string + values: WebhookValues isLoading: boolean isSaving: boolean } export const JobCompleteWebhookSection: React.FC = ({ - defaultJobCompleteWebhookUrl, - effectiveJobCompleteWebhookUrl, + values, isLoading, isSaving, }) => { + const { default: defaultJobCompleteWebhookUrl, effective: effectiveJobCompleteWebhookUrl } = values const { register, formState: { errors } } = useFormContext() return ( @@ -58,4 +58,3 @@ export const JobCompleteWebhookSection: React.FC ) } - diff --git a/orchestrator/src/client/pages/settings/components/JobspySection.test.tsx b/orchestrator/src/client/pages/settings/components/JobspySection.test.tsx index a4d2960..bba0bdf 100644 --- a/orchestrator/src/client/pages/settings/components/JobspySection.test.tsx +++ b/orchestrator/src/client/pages/settings/components/JobspySection.test.tsx @@ -22,18 +22,14 @@ const JobspyHarness = () => { diff --git a/orchestrator/src/client/pages/settings/components/JobspySection.tsx b/orchestrator/src/client/pages/settings/components/JobspySection.tsx index 7a41c68..9af1b53 100644 --- a/orchestrator/src/client/pages/settings/components/JobspySection.tsx +++ b/orchestrator/src/client/pages/settings/components/JobspySection.tsx @@ -6,40 +6,27 @@ import { Checkbox } from "@/components/ui/checkbox" import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" import { UpdateSettingsInput } from "@shared/settings-schema" +import type { JobspyValues } from "@client/pages/settings/types" type JobspySectionProps = { - defaultJobspySites: string[] - effectiveJobspySites: string[] - defaultJobspyLocation: string - effectiveJobspyLocation: string - defaultJobspyResultsWanted: number - effectiveJobspyResultsWanted: number - defaultJobspyHoursOld: number - effectiveJobspyHoursOld: number - defaultJobspyCountryIndeed: string - effectiveJobspyCountryIndeed: string - defaultJobspyLinkedinFetchDescription: boolean - effectiveJobspyLinkedinFetchDescription: boolean + values: JobspyValues isLoading: boolean isSaving: boolean } export const JobspySection: React.FC = ({ - defaultJobspySites, - effectiveJobspySites, - defaultJobspyLocation, - effectiveJobspyLocation, - defaultJobspyResultsWanted, - effectiveJobspyResultsWanted, - defaultJobspyHoursOld, - effectiveJobspyHoursOld, - defaultJobspyCountryIndeed, - effectiveJobspyCountryIndeed, - defaultJobspyLinkedinFetchDescription, - effectiveJobspyLinkedinFetchDescription, + values, isLoading, isSaving, }) => { + const { + sites, + location, + resultsWanted, + hoursOld, + countryIndeed, + linkedinFetchDescription, + } = values const { control, register, formState: { errors } } = useFormContext() return ( @@ -59,9 +46,9 @@ export const JobspySection: React.FC = ({ render={({ field }) => ( { - const current = field.value ?? defaultJobspySites + const current = field.value ?? sites.default let next = [...current] if (checked) { if (!next.includes('indeed')) next.push('indeed') @@ -83,9 +70,9 @@ export const JobspySection: React.FC = ({ render={({ field }) => ( { - const current = field.value ?? defaultJobspySites + const current = field.value ?? sites.default let next = [...current] if (checked) { if (!next.includes('linkedin')) next.push('linkedin') @@ -106,8 +93,8 @@ export const JobspySection: React.FC = ({ Select which sites JobSpy should scrape.
- Effective: {(effectiveJobspySites || []).join(', ') || "None"} - Default: {(defaultJobspySites || []).join(', ')} + Effective: {(sites.effective || []).join(', ') || "None"} + Default: {(sites.default || []).join(', ')}
@@ -116,7 +103,7 @@ export const JobspySection: React.FC = ({
Location
{errors.jobspyLocation &&

{errors.jobspyLocation.message}

} @@ -124,8 +111,8 @@ export const JobspySection: React.FC = ({ Location to search for jobs (e.g. "UK", "London", "Remote").
- Effective: {effectiveJobspyLocation || "—"} - Default: {defaultJobspyLocation || "—"} + Effective: {location.effective || "—"} + Default: {location.default || "—"}
@@ -158,8 +145,8 @@ export const JobspySection: React.FC = ({ Number of results to fetch per term per site. Max 1000.
- Effective: {effectiveJobspyResultsWanted} - Default: {defaultJobspyResultsWanted} + Effective: {resultsWanted.effective} + Default: {resultsWanted.default}
@@ -192,8 +179,8 @@ export const JobspySection: React.FC = ({ Max age of jobs in hours (e.g. 72 for 3 days). Max 720 (30 days).
- Effective: {effectiveJobspyHoursOld}h - Default: {defaultJobspyHoursOld}h + Effective: {hoursOld.effective}h + Default: {hoursOld.default}h
@@ -201,7 +188,7 @@ export const JobspySection: React.FC = ({
Indeed Country
{errors.jobspyCountryIndeed &&

{errors.jobspyCountryIndeed.message}

} @@ -209,8 +196,8 @@ export const JobspySection: React.FC = ({ Country domain for Indeed (e.g. "UK" for indeed.co.uk).
- Effective: {effectiveJobspyCountryIndeed || "—"} - Default: {defaultJobspyCountryIndeed || "—"} + Effective: {countryIndeed.effective || "—"} + Default: {countryIndeed.default || "—"}
@@ -224,7 +211,7 @@ export const JobspySection: React.FC = ({ render={({ field }) => ( field.onChange(!!checked)} disabled={isLoading || isSaving} /> @@ -241,8 +228,8 @@ export const JobspySection: React.FC = ({ If enabled, JobSpy will make extra requests to fetch full descriptions. Slower but better data.

- Effective: {effectiveJobspyLinkedinFetchDescription ? "Yes" : "No"} - Default: {defaultJobspyLinkedinFetchDescription ? "Yes" : "No"} + Effective: {linkedinFetchDescription.effective ? "Yes" : "No"} + Default: {linkedinFetchDescription.default ? "Yes" : "No"}
@@ -251,4 +238,3 @@ export const JobspySection: React.FC = ({ ) } - diff --git a/orchestrator/src/client/pages/settings/components/ModelSettingsSection.tsx b/orchestrator/src/client/pages/settings/components/ModelSettingsSection.tsx index f37ca99..b08e414 100644 --- a/orchestrator/src/client/pages/settings/components/ModelSettingsSection.tsx +++ b/orchestrator/src/client/pages/settings/components/ModelSettingsSection.tsx @@ -5,26 +5,20 @@ import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" import { UpdateSettingsInput } from "@shared/settings-schema" +import type { ModelValues } from "@client/pages/settings/types" type ModelSettingsSectionProps = { - effectiveModel: string - effectiveModelScorer: string - effectiveModelTailoring: string - effectiveModelProjectSelection: string - defaultModel: string + values: ModelValues isLoading: boolean isSaving: boolean } export const ModelSettingsSection: React.FC = ({ - effectiveModel, - effectiveModelScorer, - effectiveModelTailoring, - effectiveModelProjectSelection, - defaultModel, + values, isLoading, isSaving, }) => { + const { effective, default: defaultModel, scorer, tailoring, projectSelection } = values const { register, formState: { errors } } = useFormContext() return ( @@ -57,12 +51,12 @@ export const ModelSettingsSection: React.FC = ({
Scoring Model
{errors.modelScorer &&

{errors.modelScorer.message}

}
- Effective: {effectiveModelScorer || effectiveModel} + Effective: {scorer || effective}
@@ -70,12 +64,12 @@ export const ModelSettingsSection: React.FC = ({
Tailoring Model
{errors.modelTailoring &&

{errors.modelTailoring.message}

}
- Effective: {effectiveModelTailoring || effectiveModel} + Effective: {tailoring || effective}
@@ -83,12 +77,12 @@ export const ModelSettingsSection: React.FC = ({
Project Selection Model
{errors.modelProjectSelection &&

{errors.modelProjectSelection.message}

}
- Effective: {effectiveModelProjectSelection || effectiveModel} + Effective: {projectSelection || effective}
@@ -99,7 +93,7 @@ export const ModelSettingsSection: React.FC = ({
Global Effective
-
{effectiveModel || "—"}
+
{effective || "—"}
Default (env)
@@ -111,4 +105,3 @@ export const ModelSettingsSection: React.FC = ({ ) } - diff --git a/orchestrator/src/client/pages/settings/components/PipelineWebhookSection.tsx b/orchestrator/src/client/pages/settings/components/PipelineWebhookSection.tsx index 61e0ff4..8d92275 100644 --- a/orchestrator/src/client/pages/settings/components/PipelineWebhookSection.tsx +++ b/orchestrator/src/client/pages/settings/components/PipelineWebhookSection.tsx @@ -5,20 +5,20 @@ import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" import { UpdateSettingsInput } from "@shared/settings-schema" +import type { WebhookValues } from "@client/pages/settings/types" type PipelineWebhookSectionProps = { - defaultPipelineWebhookUrl: string - effectivePipelineWebhookUrl: string + values: WebhookValues isLoading: boolean isSaving: boolean } export const PipelineWebhookSection: React.FC = ({ - defaultPipelineWebhookUrl, - effectivePipelineWebhookUrl, + values, isLoading, isSaving, }) => { + const { default: defaultPipelineWebhookUrl, effective: effectivePipelineWebhookUrl } = values const { register, formState: { errors } } = useFormContext() return ( @@ -58,4 +58,3 @@ export const PipelineWebhookSection: React.FC = ({ ) } - diff --git a/orchestrator/src/client/pages/settings/components/SearchTermsSection.tsx b/orchestrator/src/client/pages/settings/components/SearchTermsSection.tsx index 48dc91a..595f517 100644 --- a/orchestrator/src/client/pages/settings/components/SearchTermsSection.tsx +++ b/orchestrator/src/client/pages/settings/components/SearchTermsSection.tsx @@ -4,20 +4,20 @@ import { useFormContext, Controller } from "react-hook-form" import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion" import { Separator } from "@/components/ui/separator" import { UpdateSettingsInput } from "@shared/settings-schema" +import type { SearchTermsValues } from "@client/pages/settings/types" type SearchTermsSectionProps = { - defaultSearchTerms: string[] - effectiveSearchTerms: string[] + values: SearchTermsValues isLoading: boolean isSaving: boolean } export const SearchTermsSection: React.FC = ({ - defaultSearchTerms, - effectiveSearchTerms, + values, isLoading, isSaving, }) => { + const { default: defaultSearchTerms, effective: effectiveSearchTerms } = values const { control, formState: { errors } } = useFormContext() return ( @@ -75,4 +75,3 @@ export const SearchTermsSection: React.FC = ({ ) } - diff --git a/orchestrator/src/client/pages/settings/components/UkvisajobsSection.tsx b/orchestrator/src/client/pages/settings/components/UkvisajobsSection.tsx index b980c88..3b7f0b0 100644 --- a/orchestrator/src/client/pages/settings/components/UkvisajobsSection.tsx +++ b/orchestrator/src/client/pages/settings/components/UkvisajobsSection.tsx @@ -5,20 +5,20 @@ import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" import { UpdateSettingsInput } from "@shared/settings-schema" +import type { NumericSettingValues } from "@client/pages/settings/types" type UkvisajobsSectionProps = { - defaultUkvisajobsMaxJobs: number - effectiveUkvisajobsMaxJobs: number + values: NumericSettingValues isLoading: boolean isSaving: boolean } export const UkvisajobsSection: React.FC = ({ - defaultUkvisajobsMaxJobs, - effectiveUkvisajobsMaxJobs, + values, isLoading, isSaving, }) => { + const { effective: effectiveUkvisajobsMaxJobs, default: defaultUkvisajobsMaxJobs } = values const { control, formState: { errors } } = useFormContext() return ( @@ -75,4 +75,3 @@ export const UkvisajobsSection: React.FC = ({ ) } - diff --git a/orchestrator/src/client/pages/settings/types.ts b/orchestrator/src/client/pages/settings/types.ts new file mode 100644 index 0000000..227be46 --- /dev/null +++ b/orchestrator/src/client/pages/settings/types.ts @@ -0,0 +1,24 @@ +export type EffectiveDefault = { + effective: T + default: T +} + +export type ModelValues = EffectiveDefault & { + scorer: string + tailoring: string + projectSelection: string +} + +export type WebhookValues = EffectiveDefault +export type NumericSettingValues = EffectiveDefault +export type SearchTermsValues = EffectiveDefault +export type DisplayValues = EffectiveDefault + +export type JobspyValues = { + sites: EffectiveDefault + location: EffectiveDefault + resultsWanted: EffectiveDefault + hoursOld: EffectiveDefault + countryIndeed: EffectiveDefault + linkedinFetchDescription: EffectiveDefault +}