Files
dyolink/frontend/src/components/treatment/prosthesisJobPath.ts
Admin 5d3597f973
Some checks failed
Production — tag build, push, deploy / build-and-push (push) Failing after 23s
Production — tag build, push, deploy / deploy (push) Has been skipped
improvement: icons added to prosthesis types catalog.
2026-09-05 01:29:53 +03:30

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(' + ');
}