feat(S05/T01): Unified workflow trust signals across the API, table, da…
- JobTrackerApi/Controllers/JobApplicationsController.cs - JobTrackerApi.Tests/JobApplicationsWorkflowSignalsTests.cs - job-tracker-ui/src/jobWorkflowSignals.ts - job-tracker-ui/src/components/JobTable.tsx - job-tracker-ui/src/components/DashboardView.tsx - job-tracker-ui/src/components/RemindersView.tsx - job-tracker-ui/src/workflow-trust-signals.test.tsx
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { JobApplication, WorkflowSignal } from "./types";
|
||||
import { buildJobWorkspacePath, JOB_DETAILS_TABS } from "./jobWorkspaceRoute";
|
||||
|
||||
const TAB_BY_WORKSPACE: Record<string, number> = {
|
||||
"tailored-cv": JOB_DETAILS_TABS.tailoredCv,
|
||||
"follow-up": JOB_DETAILS_TABS.followUp,
|
||||
"interview-prep": JOB_DETAILS_TABS.interviewPrep,
|
||||
readiness: JOB_DETAILS_TABS.readiness,
|
||||
};
|
||||
|
||||
export type WorkflowActionPresentation = {
|
||||
key: WorkflowSignal["actionKey"];
|
||||
label: string;
|
||||
detail: string;
|
||||
path: string;
|
||||
tab: number;
|
||||
followMode?: string;
|
||||
};
|
||||
|
||||
export type ReminderGroupKey = "missingCv" | "missingInterviewNotes" | "overdueFollowUp" | "other";
|
||||
|
||||
export function getWorkflowSignal(job: Pick<JobApplication, "workflowSignal">): WorkflowSignal | null {
|
||||
return job.workflowSignal ?? null;
|
||||
}
|
||||
|
||||
export function buildWorkflowPath(job: Pick<JobApplication, "id" | "workflowSignal">): string {
|
||||
const signal = getWorkflowSignal(job);
|
||||
const tab = signal ? TAB_BY_WORKSPACE[signal.workspaceTab] ?? JOB_DETAILS_TABS.overview : JOB_DETAILS_TABS.overview;
|
||||
return buildJobWorkspacePath(job.id, {
|
||||
tab,
|
||||
followMode: signal?.followMode ?? undefined,
|
||||
});
|
||||
}
|
||||
|
||||
export function getWorkflowAction(
|
||||
job: Pick<JobApplication, "id" | "workflowSignal">,
|
||||
labels: {
|
||||
packageWork: string;
|
||||
followUp: string;
|
||||
interviewPrep: string;
|
||||
readiness: string;
|
||||
},
|
||||
): WorkflowActionPresentation | null {
|
||||
const signal = getWorkflowSignal(job);
|
||||
if (!signal || !signal.needsAttention) return null;
|
||||
|
||||
const label = signal.actionKey === "package-work"
|
||||
? labels.packageWork
|
||||
: signal.actionKey === "interview-prep"
|
||||
? labels.interviewPrep
|
||||
: signal.actionKey === "follow-up"
|
||||
? labels.followUp
|
||||
: labels.readiness;
|
||||
|
||||
return {
|
||||
key: signal.actionKey,
|
||||
label,
|
||||
detail: signal.reason,
|
||||
path: buildWorkflowPath(job),
|
||||
tab: TAB_BY_WORKSPACE[signal.workspaceTab] ?? JOB_DETAILS_TABS.overview,
|
||||
followMode: signal.followMode ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function needsWorkflowWork(job: Pick<JobApplication, "workflowSignal">): boolean {
|
||||
const signal = getWorkflowSignal(job);
|
||||
return Boolean(signal?.hasPackageGap || signal?.needsInterviewPrep);
|
||||
}
|
||||
|
||||
export function needsInterviewPrep(job: Pick<JobApplication, "workflowSignal">): boolean {
|
||||
return Boolean(getWorkflowSignal(job)?.needsInterviewPrep);
|
||||
}
|
||||
|
||||
export function getReminderGroup(job: Pick<JobApplication, "workflowSignal">): ReminderGroupKey {
|
||||
const actionKey = getWorkflowSignal(job)?.actionKey;
|
||||
if (actionKey === "package-work") return "missingCv";
|
||||
if (actionKey === "interview-prep") return "missingInterviewNotes";
|
||||
if (actionKey === "follow-up") return "overdueFollowUp";
|
||||
return "other";
|
||||
}
|
||||
Reference in New Issue
Block a user