fix(email): share application context

This commit is contained in:
cesnimda
2026-08-10 09:36:11 +02:00
parent 75bf14417c
commit ff547df175
4 changed files with 48 additions and 19 deletions
@@ -107,7 +107,30 @@ interface PagedResult<T> {
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<string | null | undefined>,
) {
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
<Typography variant="body2" sx={{ color: "text.secondary" }}>
{gmailLoading ? t("correspondenceCheckingConnection") : gmailStatus?.connected ? t("correspondenceConnectedAs", { email: gmailStatus.gmailAddress || "" }) : t("correspondenceConnectGmailHint")}
</Typography>
{job ? (
{jobContext.companyName || jobContext.jobTitle ? (
<Typography variant="body2" sx={{ color: "text.secondary", mt: 0.5 }}>
Matching against {job.company?.name || "this company"} / {job.jobTitle}
Matching against {jobContext.companyName || "this company"} / {jobContext.jobTitle || "this role"}
</Typography>
) : null}
</Box>
@@ -919,7 +919,7 @@ export default function JobDetailsDialog({ open, jobId, onClose, initialTab = 0,
</Box>
)}
{tab === 1 && jobId && <Correspondence jobId={jobId} job={job} />}
{tab === 1 && jobId && <Correspondence jobId={jobId} jobContext={{ companyName: job?.company?.name, recruiterEmail: job?.company?.recruiterEmail, jobTitle: job?.jobTitle }} />}
{tab === 2 && jobId && <Attachments jobId={jobId} />}
{tab === 3 && (
@@ -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([]);
});
@@ -111,7 +111,9 @@ export default function ApplicationWorkspacePage() {
<Paper sx={{ p: 2, borderRadius: 3 }}><Attachments jobId={jobId} /></Paper>
)}
{section === "communication" && jobId > 0 && (
<Paper sx={{ p: 2, borderRadius: 3 }}><Correspondence jobId={jobId} job={null as any} /></Paper>
<Paper sx={{ p: 2, borderRadius: 3 }}>
<Correspondence jobId={jobId} jobContext={{ companyName: overview?.company, jobTitle: overview?.jobTitle }} />
</Paper>
)}
{section === "checklist" && jobId > 0 && (
<ApplicationChecklist jobId={jobId} onChanged={load} />