84 lines
2.7 KiB
TypeScript
84 lines
2.7 KiB
TypeScript
import type { ProsthesisCatalogEntry } from '@/types/treatment-catalog';
|
|
import { splitProsthesisGroupCode } from '@/components/treatment/prosthesisTypeDisplay';
|
|
|
|
/** Non-breaking spaces so the gap around · does not collapse between inline spans. */
|
|
export const PROSTHESIS_JOB_PATH_SEP = '\u00a0·\u00a0';
|
|
|
|
type CatalogLite = readonly Pick<
|
|
ProsthesisCatalogEntry,
|
|
'code' | 'label' | 'category' | 'subcategory'
|
|
>[];
|
|
|
|
export type ProsthesisMessageFn = (key: string) => string;
|
|
|
|
function sameLabel(a: string, b: string): boolean {
|
|
return a.localeCompare(b, undefined, { sensitivity: 'accent' }) === 0;
|
|
}
|
|
|
|
function leafFromCatalogLabel(label: string): string {
|
|
const parts = label
|
|
.split(/\s*·\s*/)
|
|
.map((part) => part.trim())
|
|
.filter(Boolean);
|
|
return parts.length > 1 ? parts[parts.length - 1] : label;
|
|
}
|
|
|
|
function humanizeCode(code: string): string {
|
|
return code
|
|
.split('_')
|
|
.filter(Boolean)
|
|
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
.join(' ');
|
|
}
|
|
|
|
/** Picker root whose title is a slash-joined list of its children (redundant in job titles). */
|
|
const SKIP_CATEGORY_IN_JOB_PATH = new Set(['indirect']);
|
|
|
|
/** Category → subcategory → last picker node for one catalog code. */
|
|
export function prosthesisJobPathParts(
|
|
code: string,
|
|
catalog: CatalogLite,
|
|
t: ProsthesisMessageFn,
|
|
): { ancestors: string[]; leaf: string } {
|
|
const entry = catalog.find((item) => item.code === code);
|
|
if (!entry) {
|
|
return { ancestors: [], leaf: humanizeCode(code) };
|
|
}
|
|
|
|
const ancestors: string[] = [];
|
|
const subcategory = entry.subcategory?.trim() ?? '';
|
|
const skipCategory = SKIP_CATEGORY_IN_JOB_PATH.has(entry.category);
|
|
if (entry.category && !skipCategory) {
|
|
ancestors.push(t(`category_${entry.category}`));
|
|
}
|
|
if (subcategory) {
|
|
ancestors.push(t(`sub_${subcategory}`));
|
|
}
|
|
|
|
const leaf = leafFromCatalogLabel(entry.label) || entry.label || code;
|
|
const uniqueAncestors: string[] = [];
|
|
for (const ancestor of ancestors) {
|
|
if (!ancestor || sameLabel(ancestor, leaf)) continue;
|
|
if (uniqueAncestors.some((existing) => sameLabel(existing, ancestor))) continue;
|
|
uniqueAncestors.push(ancestor);
|
|
}
|
|
return { ancestors: uniqueAncestors, leaf };
|
|
}
|
|
|
|
function formatProsthesisJobPath(
|
|
parts: { ancestors: string[]; leaf: string },
|
|
): string {
|
|
return [...parts.ancestors, parts.leaf].filter(Boolean).join(PROSTHESIS_JOB_PATH_SEP);
|
|
}
|
|
|
|
/** Full picker path for a type or stacked `code+code` group. */
|
|
export function prosthesisJobPathLabel(
|
|
code: string,
|
|
catalog: CatalogLite,
|
|
t: ProsthesisMessageFn,
|
|
): string {
|
|
return splitProsthesisGroupCode(code)
|
|
.map((part) => formatProsthesisJobPath(prosthesisJobPathParts(part, catalog, t)))
|
|
.join(' + ');
|
|
}
|