feature: a minimal v1 backend implemented for treatments feature.

This commit is contained in:
2026-05-19 22:29:29 +03:30
parent 935bb8c214
commit f743046b38
27 changed files with 1636 additions and 656 deletions

View File

@@ -0,0 +1,53 @@
import { TreatmentStatus } from '@prisma/client';
const TREATMENT_TYPES = ['consultation', 'filling', 'endo', 'visit', 'hygiene'] as const;
export type TreatmentTypeValue = (typeof TREATMENT_TYPES)[number];
export function isTreatmentType(value: string): value is TreatmentTypeValue {
return (TREATMENT_TYPES as readonly string[]).includes(value);
}
const FDI_TOOTH_IDS = new Set([
'11', '12', '13', '14', '15', '16', '17', '18',
'21', '22', '23', '24', '25', '26', '27', '28',
'31', '32', '33', '34', '35', '36', '37', '38',
'41', '42', '43', '44', '45', '46', '47', '48',
]);
export function normalizeTeeth(teeth: unknown): string[] {
if (!Array.isArray(teeth)) {
return [];
}
const unique = new Set<string>();
for (const tooth of teeth) {
if (typeof tooth !== 'string') continue;
const trimmed = tooth.trim();
if (FDI_TOOTH_IDS.has(trimmed)) {
unique.add(trimmed);
}
}
return [...unique].sort();
}
export function generateTreatmentTitle(
cases: { treatmentType: string; teeth: string[] }[],
): string {
if (cases.length === 0) {
return 'Treatment';
}
const parts = cases.map((c) => {
const label = c.treatmentType.charAt(0).toUpperCase() + c.treatmentType.slice(1);
if (c.teeth.length > 0) {
return `${label} ${c.teeth.join(', ')}`;
}
return label;
});
return parts.join(' · ');
}
export function mapTreatmentStatusForApi(status: TreatmentStatus): string {
return status === TreatmentStatus.DRAFT ? 'draft' : 'completed';
}