improvement: some files replaced, lots of them i shall say. AGENT.MD file created. some rules and skills added for cursor agent.

This commit is contained in:
2026-07-12 21:08:29 +03:30
parent 1cdf853d32
commit 0d3fb0a51d
86 changed files with 4758 additions and 4260 deletions

View File

@@ -8,7 +8,7 @@ import { startOfLocalDay } from '@/components/appointments/appointmentTime';
import {
treatmentTypeBannerStyle,
treatmentTypeLabelFromCatalog,
} from '@/components/ui/treatment/treatmentTypeDisplay';
} from '@/components/shared/treatmentTypeDisplay';
import type { TreatmentCatalogEntry } from '@/types/treatment-catalog';
import type { TreatmentAppointment } from '@/types/treatment';

View File

@@ -2,7 +2,7 @@
import { useTranslations } from 'next-intl';
import { CaseSentLabel } from '@/components/ui/treatment/CaseSentLabel';
import { labNotSentBadgeClass, labSentBadgeClass } from '@/components/ui/treatment/treatmentStatusStyles';
import { labNotSentBadgeClass, labSentBadgeClass } from '@/components/treatment/treatmentStatusStyles';
import type { LinkedOrganizationOption, PastTreatmentDetail } from '@/types/treatment';
interface DetailLabSendBadgeProps {

View File

@@ -5,11 +5,11 @@ import { useTranslations } from 'next-intl';
import { Button } from '@/components/ui/shared/Button';
import { Checkbox } from '@/components/ui/shared/Checkbox';
import { Dropdown } from '@/components/ui/shared/Dropdown';
import { FORM_SELECT_CLASS } from '@/components/ui/shared/formSelectStyles';
import { FORM_SELECT_CLASS } from '@/components/shared/formSelectStyles';
import { SearchBar } from '@/components/ui/shared/SearchBar';
import { CaseSentLabel } from '@/components/ui/treatment/CaseSentLabel';
import { LabCaseCommentsPanel } from '@/components/ui/lab/LabCaseCommentsPanel';
import { treatmentTypeLabelFromCatalog } from '@/components/ui/treatment/treatmentTypeDisplay';
import { treatmentTypeLabelFromCatalog } from '@/components/shared/treatmentTypeDisplay';
import { treatmentsApi } from '@/lib/api/treatments';
import { prosthesisCatalogApi } from '@/lib/api/prosthesis-catalog';
import type { ProsthesisCatalogEntry, TreatmentCatalogEntry } from '@/types/treatment-catalog';

View File

@@ -3,7 +3,7 @@
import { memo, type ReactNode } from 'react';
import type { FdiToothId } from '@/types/treatment';
import type { ToothShapeKind } from '@/components/treatment/fdiToothMeta';
import { getToothPathModel, type ToothPathModel } from '@/components/ui/treatment/toothPathModel';
import { getToothPathModel, type ToothPathModel } from '@/components/treatment/toothPathModel';
interface ToothGlyphProps {
fdi: FdiToothId;

View File

@@ -3,7 +3,7 @@
import { useTranslations } from 'next-intl';
import { DetailLabSendBadge } from '@/components/ui/treatment/DetailLabSendBadge';
import { TreatmentTypeBadge } from '@/components/ui/treatment/TreatmentTypeBadge';
import { treatmentTypeLabelFromCatalog } from '@/components/ui/treatment/treatmentTypeDisplay';
import { treatmentTypeLabelFromCatalog } from '@/components/shared/treatmentTypeDisplay';
import type { TreatmentCatalogEntry } from '@/types/treatment-catalog';
import type { LinkedOrganizationOption, PastTreatmentDetail } from '@/types/treatment';

View File

@@ -8,10 +8,10 @@ import {
autosaveStatusClass,
labPendingBannerClass,
labSentBannerClass,
} from '@/components/ui/treatment/treatmentStatusStyles';
} from '@/components/treatment/treatmentStatusStyles';
import type { TreatmentDetailDraft } from '@/types/treatment';
import type { TreatmentCatalogEntry } from '@/types/treatment-catalog';
import { treatmentTypeColor } from '@/components/ui/treatment/treatmentTypeDisplay';
import { treatmentTypeColor } from '@/components/shared/treatmentTypeDisplay';
interface TreatmentDetailsEditorProps {
details: TreatmentDetailDraft[];

View File

@@ -2,7 +2,7 @@
import { useTranslations } from 'next-intl';
import { TreatmentTypeBadge } from '@/components/ui/treatment/TreatmentTypeBadge';
import { treatmentTypeLabelFromCatalog } from '@/components/ui/treatment/treatmentTypeDisplay';
import { treatmentTypeLabelFromCatalog } from '@/components/shared/treatmentTypeDisplay';
import type { TreatmentCatalogEntry } from '@/types/treatment-catalog';
import type { PastTreatmentDetail } from '@/types/treatment';

View File

@@ -3,7 +3,7 @@
import {
formatCodeAsLabel,
treatmentTypeBannerStyle,
} from '@/components/ui/treatment/treatmentTypeDisplay';
} from '@/components/shared/treatmentTypeDisplay';
interface TreatmentTypeBadgeProps {
type: string;

View File

@@ -10,7 +10,7 @@ import { PastTreatmentsPanel } from '@/components/ui/treatment/PastTreatmentsPan
import { TreatmentDetailsEditor } from '@/components/ui/treatment/TreatmentDetailsEditor';
import { TreatmentPreviewCard } from '@/components/ui/treatment/TreatmentPreviewCard';
import { ToastStack } from '@/components/ui/shared/Toast';
import { treatmentTypeLabelFromCatalog, treatmentTypeColor } from '@/components/ui/treatment/treatmentTypeDisplay';
import { treatmentTypeLabelFromCatalog, treatmentTypeColor } from '@/components/shared/treatmentTypeDisplay';
import {
addCalendarDays,
compareLocalDayStart,

View File

@@ -1,102 +0,0 @@
/**
* Shared pastel palette for treatment types, prosthesis types, and dashboard charts.
* Treatment and prosthesis each have dedicated hex maps — prosthesis colors are unique
* within the prosthesis catalog (no duplicate swatches on charts or badges).
*/
export const TREATMENT_TYPE_COLORS: Record<string, string> = {
restoration: '#fed7aa',
specialized_restoration: '#fdba74',
radiography: '#cbd5e1',
endo: '#fecaca',
surgery: '#fca5a5',
prosthesis: '#c4b5fd',
implant: '#a5b4fc',
orthodontics: '#93c5fd',
perio: '#86efac',
pediatrics: '#fde68a',
extraction: '#f9a8d4',
clinic_visit: '#bae6fd',
continue_treatment: '#99f6e4',
};
/** Dedicated prosthesis palette — one distinct pastel per catalog code. */
export const PROSTHESIS_TYPE_COLORS: Record<string, string> = {
pfm_crown: '#e2e8f0',
pfz_crown: '#bbf7d0',
monolithic_zirconia: '#e0f2fe',
glass_ceramic_crown: '#fef08a',
full_metal_crown: '#d4d4d8',
temporary_resin_crown: '#bae6fd',
pmma: '#7dd3fc',
peek_crown: '#5eead4',
veneer_zirconia: '#6ee7b7',
veneer_ips_press: '#fed7aa',
veneer_ips_cad: '#fdba74',
soft_structure: '#ddd6fe',
customized_abutment: '#a5b4fc',
prefabricated_abutment: '#c7d2fe',
ti_base_abutment: '#bfdbfe',
multi_unit_abutment: '#818cf8',
zirconia_abutment: '#34d399',
screw_retained: '#e9d5ff',
zirconia_overlay: '#2dd4bf',
ips_overlay: '#fef3c7',
smile_design: '#f9a8d4',
mockup: '#fbcfe8',
};
export const CATALOG_FALLBACK_COLORS = [
'#ddd6fe',
'#fed7aa',
'#fecaca',
'#bae6fd',
'#d9f99d',
'#fbcfe8',
] as const;
/** Fallback rotation for unknown prosthesis codes — drawn from the prosthesis palette. */
export const PROSTHESIS_FALLBACK_COLORS: readonly string[] = [
...new Set(Object.values(PROSTHESIS_TYPE_COLORS)),
];
/** Ordered palette for charts and rotating unknown treatment catalog codes. */
export const CATALOG_PALETTE_COLORS: readonly string[] = [
'#fed7aa',
'#fdba74',
'#cbd5e1',
'#fecaca',
'#fca5a5',
'#c4b5fd',
'#a5b4fc',
'#93c5fd',
'#86efac',
'#fde68a',
'#f9a8d4',
'#bae6fd',
'#99f6e4',
'#ddd6fe',
'#d9f99d',
'#fbcfe8',
...PROSTHESIS_FALLBACK_COLORS.filter(
(color) =>
![
'#fed7aa',
'#fdba74',
'#bae6fd',
'#f9a8d4',
'#ddd6fe',
'#fbcfe8',
'#a5b4fc',
].includes(color),
),
];
export function resolveCatalogTypeColor(
code: string,
colorMap: Record<string, string>,
index = 0,
fallbackColors: readonly string[] = CATALOG_FALLBACK_COLORS,
): string {
return colorMap[code] ?? fallbackColors[index % fallbackColors.length];
}

View File

@@ -1,38 +0,0 @@
import type { CSSProperties } from 'react';
import {
PROSTHESIS_FALLBACK_COLORS,
PROSTHESIS_TYPE_COLORS,
resolveCatalogTypeColor,
} from '@/components/ui/treatment/catalog-type-colors';
/**
* Prosthesis-type colors for lab-facing surfaces (Tasks list, Cases detail group
* headers / badges). Uses a dedicated pastel map (unique per prosthesis code).
*
* Clinic-facing dispatch flows intentionally do NOT use these colors.
*/
/** Dark ink that stays readable on every pastel in the palette. */
const BADGE_INK = '#14253d';
export function prosthesisTypeColor(code: string, index = 0): string {
return resolveCatalogTypeColor(code, PROSTHESIS_TYPE_COLORS, index, PROSTHESIS_FALLBACK_COLORS);
}
/** Filled swatch (small indicator dots). */
export function prosthesisTypeSwatchStyle(code: string, index = 0): CSSProperties {
return { backgroundColor: prosthesisTypeColor(code, index), borderColor: 'rgba(0, 0, 0, 0.18)' };
}
/** Pastel pill / banner fill with readable dark text (group headers, badges). */
export function prosthesisTypeBadgeStyle(code: string, index = 0): CSSProperties {
return {
backgroundColor: prosthesisTypeColor(code, index),
borderColor: 'rgba(0, 0, 0, 0.16)',
color: BADGE_INK,
};
}
export function formatToothList(teeth: string[]): string {
return teeth.join(', ');
}

View File

@@ -1,113 +0,0 @@
import type { FdiToothId } from '@/types/treatment';
import type { ToothShapeKind } from '@/components/treatment/fdiToothMeta';
export interface ToothPathModel {
crown: string;
roots: string[];
/** Occlusal groove etc. — open path, stroke only */
detailStroke?: { d: string; widthScale: number };
}
/** FDI-specific closed paths for the clinical (gradient + roots) chart. */
export function getToothPathModel(fdi: FdiToothId, kind: ToothShapeKind, upper?: boolean): ToothPathModel | null {
const lastDigit = Number(fdi[1]);
const isThirdMolar = lastDigit === 8;
const isSecondMolar = lastDigit === 7;
const isFirstMolar = lastDigit === 6;
const isCentralIncisor = lastDigit === 1;
const isLateralIncisor = lastDigit === 2;
switch (kind) {
case 'incisor':
if (isLateralIncisor) {
return {
crown: 'M12 9 C14 6, 22 6, 24 9 C26 12, 26 25, 23 30 C21 32, 15 32, 13 30 C10 25, 10 12, 12 9 Z',
roots: ['M15.5 30 C14.5 40, 14.5 51, 16 60 C17 65, 19 65, 20 60 C21.5 51, 21.5 40, 20.5 30 Z'],
};
}
return {
crown: 'M11 8 C13 5, 23 5, 25 8 C27 12, 27 26, 24 31 C22 33, 14 33, 12 31 C9 26, 9 12, 11 8 Z',
roots: ['M15 31 C14 42, 14 53, 16 63 C17 68, 19 68, 20 63 C22 53, 22 42, 21 31 Z'],
};
case 'canine':
return {
crown: 'M18 6 C22 6, 25 10, 26 15 C26 20, 24 30, 22 33 C20 35, 16 35, 14 33 C12 30, 10 20, 10 15 C11 10, 14 6, 18 6 Z',
roots: ['M16 33 C14 44, 13 55, 14 66 C15 72, 18 75, 20 66 C21 55, 20 44, 20 33 Z'],
};
case 'premolar':
if (upper && lastDigit === 4) {
return {
crown: 'M9 12 C11 8, 15 7, 18 9 C21 7, 25 8, 27 12 C29 16, 29 25, 26 31 C24 34, 12 34, 10 31 C7 25, 7 16, 9 12 Z',
roots: [
'M13 31 C12 39, 12 48, 13 56 C14 62, 16 64, 17 58 C18 54, 18 48, 18 41 Z',
'M23 31 C24 39, 24 48, 23 56 C22 62, 20 64, 19 58 C18 54, 18 48, 18 41 Z',
],
};
}
return {
crown: 'M9 12 C11 8, 15 7, 18 9 C21 7, 25 8, 27 12 C29 16, 29 26, 26 31 C24 34, 12 34, 10 31 C7 26, 7 16, 9 12 Z',
roots: ['M16 31 C15 42, 15 53, 16 62 C17 67, 19 67, 20 62 C21 53, 21 42, 20 31 Z'],
};
case 'molar':
if (isThirdMolar) {
const crown =
'M9 15 C11 11, 14 10, 17 11 C20 9, 24 10, 26 14 C28 18, 28 26, 25 31 C23 33, 11 33, 9 31 C6 26, 6 19, 9 15 Z';
if (upper) {
return {
crown,
roots: [
'M14 32 C13 39, 13 47, 14 54 C15 59, 17 61, 18 55 C19 50, 19 41, 18 33 Z',
'M22 32 C23 39, 23 47, 22 54 C21 59, 19 61, 18 55 C17 50, 17 41, 18 33 Z',
],
};
}
return {
crown,
roots: [
'M14 32 C12 39, 12 47, 13 55 C14 61, 16 64, 18 57 C19 52, 19 44, 18 36 Z',
'M22 32 C24 39, 24 47, 23 55 C22 61, 20 64, 18 57 C17 52, 17 44, 18 36 Z',
],
};
}
if (isSecondMolar && upper) {
return {
crown: 'M8 14 C10 9, 13 8, 16 10 C18 8, 21 8, 23 10 C26 8, 29 9, 30 14 C31 18, 31 27, 28 32 C26 35, 10 35, 8 32 C5 27, 5 18, 8 14 Z',
roots: [
'M11 35 C9 43, 9 50, 10 57 C11 62, 13 64, 14 58 C15 52, 15 44, 14 36 Z',
'M18 35 C17 44, 17 52, 18 60 C19 66, 20 66, 21 60 C22 52, 22 44, 21 35 Z',
'M25 35 C27 43, 27 50, 26 57 C25 62, 23 64, 22 58 C21 52, 21 44, 22 36 Z',
],
};
}
if (isFirstMolar && !upper) {
return {
crown: 'M7 14 C9 9, 13 8, 16 10 C18 8, 21 8, 23 10 C26 8, 30 9, 31 14 C32 18, 32 27, 29 32 C27 35, 9 35, 7 32 C4 27, 4 18, 7 14 Z',
roots: [
'M11 35 C9 43, 9 50, 10 58 C11 64, 14 67, 16 60 C17 56, 17 50, 17 44 Z',
'M25 35 C27 43, 27 50, 26 58 C25 64, 22 67, 20 60 C19 56, 19 50, 19 44 Z',
],
detailStroke: { d: 'M15 61 Q18 66 21 61', widthScale: 0.85 },
};
}
if (upper) {
return {
crown: 'M7 14 C9 9, 13 8, 16 10 C18 8, 21 8, 23 10 C26 8, 30 9, 31 14 C32 18, 32 27, 29 32 C27 35, 9 35, 7 32 C4 27, 4 18, 7 14 Z',
roots: [
'M11 35 C9 43, 9 50, 10 57 C11 62, 13 64, 14 58 C15 52, 15 44, 14 36 Z',
'M18 35 C17 44, 17 52, 18 60 C19 66, 20 66, 21 60 C22 52, 22 44, 21 35 Z',
'M25 35 C27 43, 27 50, 26 57 C25 62, 23 64, 22 58 C21 52, 21 44, 22 36 Z',
],
};
}
return {
crown: 'M7 14 C9 9, 13 8, 16 10 C18 8, 21 8, 23 10 C26 8, 30 9, 31 14 C32 18, 32 27, 29 32 C27 35, 9 35, 7 32 C4 27, 4 18, 7 14 Z',
roots: [
'M11 35 C9 43, 9 50, 10 58 C11 64, 14 67, 16 60 C17 56, 17 50, 17 44 Z',
'M25 35 C27 43, 27 50, 26 58 C25 64, 22 67, 20 60 C19 56, 19 50, 19 44 Z',
],
detailStroke: { d: 'M15 61 Q18 66 21 61', widthScale: 0.85 },
};
default:
return null;
}
}

View File

@@ -1,26 +0,0 @@
export const labSentBadgeClass =
'inline-flex items-center rounded-[var(--radius-sm)] border border-emerald-500/40 bg-emerald-500/10 px-2 py-0.5 text-[11px] font-medium text-emerald-600 dark:text-emerald-400';
export const labNotSentBadgeClass =
'inline-flex items-center rounded-[var(--radius-sm)] border border-amber-500/40 bg-amber-500/10 px-2 py-0.5 text-[11px] font-medium text-amber-600 dark:text-amber-400';
export const labSentBannerClass =
'text-xs rounded-[var(--radius-sm)] border border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 px-2 py-1.5';
export const labPendingBannerClass =
'text-xs rounded-[var(--radius-sm)] border border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-400 px-2 py-1.5';
export function autosaveStatusClass(status: 'dirty' | 'saving' | 'saved' | 'error'): string {
switch (status) {
case 'dirty':
return 'text-amber-600 dark:text-amber-400';
case 'saving':
return 'text-text-muted animate-pulse';
case 'saved':
return 'text-emerald-600 dark:text-emerald-400';
case 'error':
return 'text-red-500';
default:
return 'text-text-muted';
}
}

View File

@@ -1,65 +0,0 @@
import type { CSSProperties } from 'react';
import type { TreatmentCatalogEntry } from '@/types/treatment-catalog';
import {
resolveCatalogTypeColor,
TREATMENT_TYPE_COLORS,
} from '@/components/ui/treatment/catalog-type-colors';
/**
* Single source of truth for treatment-type colors across the app
* (treatment detail dropdown, appointment booking dropdown, appointment legend,
* appointment schedule banners, and the treatment feature's appointment cards).
*
* There is no universal dental type→color standard (only status-based blue/red
* conventions), so this is a curated pastel palette. Extend it as new treatment
* types are added; unknown codes fall back to a rotating pastel set by index.
*/
/** Dark ink that stays readable on every pastel in the palette. */
const BANNER_INK = '#14253d';
/** Dark background used behind pastel option text in native <select> dropdowns. */
export const DROPDOWN_OPTION_BG = '#14253d';
export function treatmentTypeColor(code: string, index = 0): string {
return resolveCatalogTypeColor(code, TREATMENT_TYPE_COLORS, index);
}
/** Filled swatch (legend dots, small indicators). */
export function treatmentTypeSwatchStyle(code: string, index = 0): CSSProperties {
const color = treatmentTypeColor(code, index);
return { backgroundColor: color, borderColor: 'rgba(0, 0, 0, 0.18)' };
}
/** Colored banner / card fill with readable dark text (schedule blocks, appointment cards). */
export function treatmentTypeBannerStyle(code: string, index = 0): CSSProperties {
const color = treatmentTypeColor(code, index);
return {
backgroundColor: color,
borderColor: 'rgba(0, 0, 0, 0.16)',
color: BANNER_INK,
};
}
/** Pastel option text on the dark dropdown background. */
export function treatmentTypeOptionStyle(code: string, index = 0): CSSProperties {
return { color: treatmentTypeColor(code, index), backgroundColor: DROPDOWN_OPTION_BG };
}
export function treatmentTypeLabelFromCatalog(
code: string,
catalog: TreatmentCatalogEntry[],
): string {
return catalog.find((e) => e.code === code)?.label ?? formatCodeAsLabel(code);
}
export function formatCodeAsLabel(code: string): string {
return code
.split('_')
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(' ');
}
/** @deprecated Use treatmentTypeLabelFromCatalog with API catalog */
export function treatmentTypeLabelKey(code: string): string {
return `type_${code}`;
}