refactor(ui): drive status from a single shared pipeline module
Introduces pipeline.ts (mirrors backend JobPipeline) as the one frontend source of truth for canonical stages, synonym normalization, tone, and localized labels. Replaces the status list/logic previously duplicated across KanbanBoard, JobTable, AddJobModal and EditJobDialog. - KanbanBoard/AddJobModal/EditJobDialog render from PIPELINE_STATUSES - JobTable uses shared statusTone + statusLabel (status chips now localized; NB gets proper labels, English unchanged) - Edit dialog status dropdown is now localized too - 5 unit tests; full frontend suite green (19 suites / 41 tests) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
// Single frontend source of truth for the canonical job pipeline.
|
||||
// Mirrors the backend JobPipeline (JobTrackerApi/Services/JobPipeline.cs); keep the two in sync.
|
||||
|
||||
export const PIPELINE_STATUSES = ["Applied", "Waiting", "Interview", "Offer", "Rejected", "Ghosted"] as const;
|
||||
|
||||
export type PipelineStatus = (typeof PIPELINE_STATUSES)[number];
|
||||
|
||||
export type StatusTone = "primary" | "info" | "success" | "warning" | "error" | "default";
|
||||
|
||||
// Legacy/synonym spellings collapse onto a canonical stage (matches the backend alias map).
|
||||
const ALIASES: Record<string, PipelineStatus> = {
|
||||
interviewing: "Interview",
|
||||
interviews: "Interview",
|
||||
interviewed: "Interview",
|
||||
declined: "Rejected",
|
||||
"no response": "Ghosted",
|
||||
"no reply": "Ghosted",
|
||||
pending: "Waiting",
|
||||
"awaiting response": "Waiting",
|
||||
};
|
||||
|
||||
/** Canonical status for a raw value, or "Other" for unknown/custom statuses. */
|
||||
export function normalizeStatus(status?: string | null): PipelineStatus | "Other" {
|
||||
const trimmed = (status ?? "").trim();
|
||||
if (!trimmed) return "Applied";
|
||||
const exact = PIPELINE_STATUSES.find((s) => s.toLowerCase() === trimmed.toLowerCase());
|
||||
if (exact) return exact;
|
||||
const alias = ALIASES[trimmed.toLowerCase()];
|
||||
return alias ?? "Other";
|
||||
}
|
||||
|
||||
/** MUI palette key for a status; both chip color and board accent derive from this. */
|
||||
export function statusTone(status?: string | null): StatusTone {
|
||||
switch (normalizeStatus(status)) {
|
||||
case "Offer":
|
||||
return "success";
|
||||
case "Rejected":
|
||||
return "error";
|
||||
case "Waiting":
|
||||
case "Ghosted":
|
||||
return "warning";
|
||||
case "Interview":
|
||||
return "info";
|
||||
case "Applied":
|
||||
return "primary";
|
||||
default:
|
||||
return "default";
|
||||
}
|
||||
}
|
||||
|
||||
const LABEL_KEYS: Record<PipelineStatus, string> = {
|
||||
Applied: "statusApplied",
|
||||
Waiting: "statusWaiting",
|
||||
Interview: "statusInterview",
|
||||
Offer: "statusOffer",
|
||||
Rejected: "statusRejected",
|
||||
Ghosted: "statusGhosted",
|
||||
};
|
||||
|
||||
/** Localized label for a status, falling back to the raw value for custom statuses. */
|
||||
export function statusLabel(t: (key: any, params?: any) => string, status: string): string {
|
||||
const normalized = normalizeStatus(status);
|
||||
return normalized === "Other" ? status : t(LABEL_KEYS[normalized]);
|
||||
}
|
||||
Reference in New Issue
Block a user