diff --git a/job-tracker-ui/src/components/Correspondence.tsx b/job-tracker-ui/src/components/Correspondence.tsx index 799a59e..5c073b6 100644 --- a/job-tracker-ui/src/components/Correspondence.tsx +++ b/job-tracker-ui/src/components/Correspondence.tsx @@ -107,7 +107,30 @@ interface PagedResult { items: T[]; } -export default function Correspondence({ jobId, job }: { jobId: number; job: JobApplication | null }) { +export type CorrespondenceJobContext = { + companyName?: string | null; + recruiterEmail?: string | null; + jobTitle?: string | null; +}; + +export function buildSuggestedCorrespondenceQueries( + context: CorrespondenceJobContext, + subjects: Array, +) { + const uniqueSubjects = Array.from(new Set(subjects.filter(Boolean) as string[])).slice(0, 2); + const companyName = context.companyName?.trim(); + const recruiterEmail = context.recruiterEmail?.trim(); + const jobTitle = context.jobTitle?.trim(); + + return [ + recruiterEmail ? { label: "Recruiter mailbox", value: `(from:${recruiterEmail} OR to:${recruiterEmail}) newer_than:365d` } : null, + companyName && jobTitle ? { label: "Company + role", value: `"${companyName}" "${jobTitle}" newer_than:365d` } : null, + companyName ? { label: "Company mail", value: `"${companyName}" (application OR interview OR recruiter) newer_than:365d` } : null, + ...uniqueSubjects.map((subject) => ({ label: `Subject: ${subject}`, value: `subject:"${subject}" newer_than:365d` })), + ].filter(Boolean).slice(0, 6) as Array<{ label: string; value: string }>; +} + +export default function Correspondence({ jobId, jobContext }: { jobId: number; jobContext: CorrespondenceJobContext }) { const theme = useTheme(); const { toast } = useToast(); const { t } = useI18n(); @@ -274,20 +297,7 @@ export default function Correspondence({ jobId, job }: { jobId: number; job: Job const canSend = useMemo(() => text.trim().length > 0, [text]); - const suggestedQueries = useMemo(() => { - const fromSubjects = messages.map((m) => m.subject).filter(Boolean) as string[]; - const uniqueSubjects = Array.from(new Set(fromSubjects)).slice(0, 2); - const companyName = job?.company?.name?.trim(); - const recruiterEmail = job?.company?.recruiterEmail?.trim(); - const jobTitle = job?.jobTitle?.trim(); - - return [ - recruiterEmail ? { label: "Recruiter mailbox", value: `(from:${recruiterEmail} OR to:${recruiterEmail}) newer_than:365d` } : null, - companyName && jobTitle ? { label: "Company + role", value: `"${companyName}" "${jobTitle}" newer_than:365d` } : null, - companyName ? { label: "Company mail", value: `"${companyName}" (application OR interview OR recruiter) newer_than:365d` } : null, - ...uniqueSubjects.map((subject) => ({ label: `Subject: ${subject}`, value: `subject:"${subject}" newer_than:365d` })), - ].filter(Boolean).slice(0, 6) as Array<{ label: string; value: string }>; - }, [job?.company?.name, job?.company?.recruiterEmail, job?.jobTitle, messages]); + const suggestedQueries = buildSuggestedCorrespondenceQueries(jobContext, messages.map((message) => message.subject)); const send = async () => { if (!canSend) return; @@ -595,9 +605,9 @@ export default function Correspondence({ jobId, job }: { jobId: number; job: Job {gmailLoading ? t("correspondenceCheckingConnection") : gmailStatus?.connected ? t("correspondenceConnectedAs", { email: gmailStatus.gmailAddress || "" }) : t("correspondenceConnectGmailHint")} - {job ? ( + {jobContext.companyName || jobContext.jobTitle ? ( - Matching against {job.company?.name || "this company"} / {job.jobTitle} + Matching against {jobContext.companyName || "this company"} / {jobContext.jobTitle || "this role"} ) : null} diff --git a/job-tracker-ui/src/components/JobDetailsDialog.tsx b/job-tracker-ui/src/components/JobDetailsDialog.tsx index 263b190..2ea0181 100644 --- a/job-tracker-ui/src/components/JobDetailsDialog.tsx +++ b/job-tracker-ui/src/components/JobDetailsDialog.tsx @@ -919,7 +919,7 @@ export default function JobDetailsDialog({ open, jobId, onClose, initialTab = 0, )} - {tab === 1 && jobId && } + {tab === 1 && jobId && } {tab === 2 && jobId && } {tab === 3 && ( diff --git a/job-tracker-ui/src/correspondence-search-context.test.ts b/job-tracker-ui/src/correspondence-search-context.test.ts new file mode 100644 index 0000000..4596a76 --- /dev/null +++ b/job-tracker-ui/src/correspondence-search-context.test.ts @@ -0,0 +1,17 @@ +import { buildSuggestedCorrespondenceQueries } from "./components/Correspondence"; + +test("builds contextual suggestions for the shared application correspondence view", () => { + expect(buildSuggestedCorrespondenceQueries( + { companyName: "Acme AS", recruiterEmail: "recruiter@acme.test", jobTitle: "Utvikler" }, + ["Interview invitation", "Interview invitation"], + )).toEqual([ + { label: "Recruiter mailbox", value: "(from:recruiter@acme.test OR to:recruiter@acme.test) newer_than:365d" }, + { label: "Company + role", value: "\"Acme AS\" \"Utvikler\" newer_than:365d" }, + { label: "Company mail", value: "\"Acme AS\" (application OR interview OR recruiter) newer_than:365d" }, + { label: "Subject: Interview invitation", value: "subject:\"Interview invitation\" newer_than:365d" }, + ]); +}); + +test("does not invent weak context when the workspace has no company details", () => { + expect(buildSuggestedCorrespondenceQueries({}, [])).toEqual([]); +}); diff --git a/job-tracker-ui/src/views/ApplicationWorkspacePage.tsx b/job-tracker-ui/src/views/ApplicationWorkspacePage.tsx index 40bd871..7b9329b 100644 --- a/job-tracker-ui/src/views/ApplicationWorkspacePage.tsx +++ b/job-tracker-ui/src/views/ApplicationWorkspacePage.tsx @@ -111,7 +111,9 @@ export default function ApplicationWorkspacePage() { )} {section === "communication" && jobId > 0 && ( - + + + )} {section === "checklist" && jobId > 0 && (