From 0540e84ff6d5106946b481a2e24484d0b0898583 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Mon, 9 Nov 2026 22:57:07 +0100 Subject: [PATCH] feat(discovery): lead with the existing search controls --- job-tracker-ui/src/i18n/translations.ts | 14 ++++++++------ job-tracker-ui/src/job-discovery.test.tsx | 15 ++++++++++----- job-tracker-ui/src/views/JobDiscoveryPage.tsx | 16 ++++++++++------ 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/job-tracker-ui/src/i18n/translations.ts b/job-tracker-ui/src/i18n/translations.ts index 0acbe055..1c8c9c3f 100644 --- a/job-tracker-ui/src/i18n/translations.ts +++ b/job-tracker-ui/src/i18n/translations.ts @@ -564,7 +564,7 @@ export const translations = { languageEnglish: "English", languageNorwegianBokmal: "Norsk bokmål", discoverJobs: "Discover jobs", - discoverJobsSubtitle: "Search official job-board feeds and save opportunities to your tracker.", + discoverJobsSubtitle: "Search vacancies and save opportunities to your tracker.", landingSignIn: "Sign in", landingSignInArrow: "Sign in →", landingEyebrow: "AI-ASSISTED JOB SEARCH WORKSPACE", @@ -636,11 +636,12 @@ export const translations = { jobDiscoveryRoleCompany: "Role or company", jobDiscoveryFilters: "Search filters", jobDiscoveryJumpResults: "Jump to results", - jobDiscoveryReturnFilters: "Return to search filters", + jobDiscoveryReturnFilters: "Return to search", jobDiscoveryMunicipality: "Municipality", jobDiscoverySearchNav: "Search NAV", jobDiscoveryResults: "Search results", - jobDiscoverySourceHelp: "Official Norwegian vacancies from NAV. FINN, Indeed and LinkedIn jobs can still be captured by URL.", + jobDiscoverySourceCoverage: "Sources and coverage", + jobDiscoverySourceHelp: "Search uses NAV for Norwegian vacancies. To track a job from another site, paste its URL and enter any details Jobjakt cannot retrieve.", jobDiscoveryUnavailable: "NAV job discovery is temporarily unavailable.", jobDiscoveryGuidance: "Search recent active vacancies by role, company, or municipality.", jobDiscoveryNoResults: "No active vacancies matched this search. Try a broader role, company, or municipality.", @@ -3787,7 +3788,7 @@ export const translations = { languageEnglish: "Engelsk", languageNorwegianBokmal: "Norsk bokmål", discoverJobs: "Finn jobber", - discoverJobsSubtitle: "Søk i offisielle jobbportaler og lagre muligheter i oversikten din.", + discoverJobsSubtitle: "Søk etter stillinger og lagre dem i jobboversikten din.", landingSignIn: "Logg inn", landingSignInArrow: "Logg inn →", landingEyebrow: "AI-STØTTET ARBEIDSOMRÅDE FOR JOBBSØK", @@ -3859,11 +3860,12 @@ export const translations = { jobDiscoveryRoleCompany: "Rolle eller bedrift", jobDiscoveryFilters: "Søkefiltre", jobDiscoveryJumpResults: "Gå til resultater", - jobDiscoveryReturnFilters: "Tilbake til søkefiltre", + jobDiscoveryReturnFilters: "Tilbake til søk", jobDiscoveryMunicipality: "Kommune", jobDiscoverySearchNav: "Søk i NAV", jobDiscoveryResults: "Søkeresultater", - jobDiscoverySourceHelp: "Offisielle norske stillinger fra NAV. Stillinger fra FINN, Indeed og LinkedIn kan fortsatt hentes inn via nettadresse.", + jobDiscoverySourceCoverage: "Kilder og dekning", + jobDiscoverySourceHelp: "Søket bruker NAV for norske stillinger. For å følge en stilling fra et annet nettsted kan du lime inn nettadressen og fylle ut opplysningene Jobjakt ikke kan hente.", jobDiscoveryUnavailable: "NAVs jobbsøk er midlertidig utilgjengelig.", jobDiscoveryGuidance: "Søk i nylige aktive stillinger etter rolle, bedrift eller kommune.", jobDiscoveryNoResults: "Ingen aktive stillinger samsvarte med søket. Prøv en bredere rolle, bedrift eller kommune.", diff --git a/job-tracker-ui/src/job-discovery.test.tsx b/job-tracker-ui/src/job-discovery.test.tsx index fc4d385d..9966026a 100644 --- a/job-tracker-ui/src/job-discovery.test.tsx +++ b/job-tracker-ui/src/job-discovery.test.tsx @@ -24,20 +24,25 @@ beforeEach(() => { jest.resetAllMocks(); window.localStorage.clear(); searchResp afterEach(() => jest.clearAllMocks()); test.each([ - ['en', 'Search filters', 'Jump to results', 'Return to search filters'], - ['nb', 'Søkefiltre', 'Gå til resultater', 'Tilbake til søkefiltre'], -] as const)('keeps %s filters before results with keyboard jump/return and no automatic provider search', async (language, filtersName, jumpName, returnName) => { + ['en', 'Search filters', 'Jump to results', 'Return to search', 'Sources and coverage'], + ['nb', 'Søkefiltre', 'Gå til resultater', 'Tilbake til søk', 'Kilder og dekning'], +] as const)('keeps %s search before filters and results with keyboard jump/return and no automatic provider search', async (language, filtersName, jumpName, returnName, sourceName) => { localStorage.setItem('uiLanguage', language); renderPage(); await screen.findByText(translations[language].jobDiscoveryNoSavedSearches); const filters = screen.getByRole('complementary', { name: filtersName }); + const form = screen.getByRole('form', { name: translations[language].jobDiscoverySearchNav }); + const query = within(form).getByRole('textbox', { name: translations[language].jobDiscoveryRoleCompany }); + const municipality = within(filters).getByRole('textbox', { name: translations[language].jobDiscoveryMunicipality }) as HTMLInputElement; const results = screen.getByRole('region', { name: translations[language].jobDiscoveryResults }); + expect(form.compareDocumentPosition(filters) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); expect(filters.compareDocumentPosition(results) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); - expect(within(filters).getByRole('form', { name: filtersName })).toBeInTheDocument(); + expect(municipality.form).toBe(form); + expect(within(filters).getByText(sourceName).closest('details')).not.toHaveAttribute('open'); fireEvent.click(within(filters).getByRole('button', { name: jumpName })); expect(results).toHaveFocus(); fireEvent.click(within(results).getByRole('button', { name: returnName })); - expect(screen.getByRole('textbox', { name: translations[language].jobDiscoveryRoleCompany })).toHaveFocus(); + expect(query).toHaveFocus(); expect(mockedApi.get.mock.calls.map(([url]) => url)).toEqual(['/job-discovery/saved-searches']); expect(mockedApi.post).not.toHaveBeenCalled(); }); diff --git a/job-tracker-ui/src/views/JobDiscoveryPage.tsx b/job-tracker-ui/src/views/JobDiscoveryPage.tsx index 5a770588..58ad10b4 100644 --- a/job-tracker-ui/src/views/JobDiscoveryPage.tsx +++ b/job-tracker-ui/src/views/JobDiscoveryPage.tsx @@ -1,4 +1,4 @@ -import { FormEvent, useEffect, useMemo, useRef, useState } from "react"; +import { FormEvent, useEffect, useId, useMemo, useRef, useState } from "react"; import { Alert, Box, Button, Card, CardActions, CardContent, Chip, CircularProgress, FormControlLabel, MenuItem, Stack, Switch, TextField, Typography } from "@mui/material"; import SearchIcon from "@mui/icons-material/Search"; import AddIcon from "@mui/icons-material/Add"; @@ -36,6 +36,7 @@ type SortOrder = "updated" | "deadline" | "title"; export default function JobDiscoveryPage() { const navigate = useNavigate(); + const searchFormId = useId(); const { language, t } = useI18n(); const { confirmAction } = useDialogActions(); const [query, setQuery] = useState(""); @@ -183,14 +184,17 @@ export default function JobDiscoveryPage() { return ( - {t("jobDiscoverySourceHelp")} + + setQuery(event.target.value)} /> + + resultsRef.current?.focus()}>{t("jobDiscoveryJumpResults")}} /> - - setQuery(event.target.value)} /> - setLocation(event.target.value)} /> - + setLocation(event.target.value)} slotProps={{ htmlInput: { form: searchFormId } }} /> + + {t("jobDiscoverySourceCoverage")} + {t("jobDiscoverySourceHelp")}