feature: a mocked implementation of v1 UI done.

This commit is contained in:
2026-05-07 03:40:29 +03:30
parent 2161926fc9
commit a70cbc7860
11 changed files with 1276 additions and 6 deletions

View File

@@ -0,0 +1,198 @@
import {
addCalendarDays,
isSameLocalCalendarDay,
startOfLocalDay,
} from '@/lib/appointmentTime';
import type {
FdiToothId,
LinkedOrganizationOption,
PastTreatment,
SaveTreatmentPayload,
SendTreatmentRecordPayload,
TreatmentAppointment,
} from '@/types/treatment';
const MOCK_DELAY_MS = 280;
function sleep(ms = MOCK_DELAY_MS) {
return new Promise<void>((resolve) => setTimeout(resolve, ms));
}
function pad2(n: number) {
return String(n).padStart(2, '0');
}
/** Builds deterministic ids so React keys stay stable across hot reloads */
function mockApptId(dayKey: string, slotIndex: number | string) {
return `mock-appt-${dayKey}-${slotIndex}`;
}
function buildSlotsForDay(day: Date, userId: string): TreatmentAppointment[] {
const dayStart = startOfLocalDay(day);
const y = dayStart.getFullYear();
const m = dayStart.getMonth();
const d = dayStart.getDate();
const dayKey = `${y}-${pad2(m + 1)}-${pad2(d)}`;
const base: Omit<TreatmentAppointment, 'id' | 'startAt' | 'endAt'>[] = [
{
patientId: 'pat-alice',
patientFirstName: 'Alice',
patientLastName: 'Moradi',
providerUserId: userId,
purpose: 'consultation',
},
{
patientId: 'pat-babak',
patientFirstName: 'Babak',
patientLastName: 'Karimi',
providerUserId: userId,
purpose: 'filling',
},
{
patientId: 'pat-sara',
patientFirstName: 'Sara',
patientLastName: 'Hosseini',
providerUserId: userId,
purpose: 'endo',
},
];
const slots: TreatmentAppointment[] = [];
const times = [
[9, 0, 9, 45],
[11, 15, 12, 0],
[14, 30, 15, 30],
];
times.forEach(([h1, m1, h2, m2], i) => {
const baseSlot = base[i % base.length];
const startAt = new Date(y, m, d, h1, m1, 0, 0).toISOString();
const endAt = new Date(y, m, d, h2, m2, 0, 0).toISOString();
slots.push({
id: mockApptId(dayKey, i),
...baseSlot,
startAt,
endAt,
});
});
const today = new Date();
if (isSameLocalCalendarDay(day, today)) {
const now = today.getTime();
const start = new Date(now - 12 * 60 * 1000);
const end = new Date(now + 48 * 60 * 1000);
slots.unshift({
id: mockApptId(dayKey, 'now'),
patientId: 'pat-hesam',
patientFirstName: 'Hesam',
patientLastName: 'Aghaie',
providerUserId: userId,
startAt: start.toISOString(),
endAt: end.toISOString(),
purpose: 'visit',
});
}
return slots.sort((a, b) => new Date(a.startAt).getTime() - new Date(b.startAt).getTime());
}
const MOCK_HISTORY: Record<string, PastTreatment[]> = {
'pat-hesam': [
{
id: 'pt-h-1',
patientId: 'pat-hesam',
title: 'Root canal 45',
treatmentAt: new Date(2026, 8, 12).toISOString(),
status: 'completed',
records: [
{
id: 'ptr-1',
teeth: ['45'] as FdiToothId[],
notes: 'Instrumented and temporized.',
},
],
documents: [
{
id: 'doc-1',
fileName: 'periapical-45.png',
mimeType: 'image/png',
sizeBytes: 842_120,
},
{
id: 'doc-2',
fileName: 'consent-signed.pdf',
mimeType: 'application/pdf',
sizeBytes: 312_000,
},
],
},
{
id: 'pt-h-2',
patientId: 'pat-hesam',
title: 'Filling 14, 15',
treatmentAt: new Date(2025, 9, 10).toISOString(),
status: 'completed',
records: [
{
id: 'ptr-2',
teeth: ['14', '15'] as FdiToothId[],
notes: 'Composite restoration.',
},
],
documents: [
{
id: 'doc-3',
fileName: 'notes.txt',
mimeType: 'text/plain',
sizeBytes: 420,
},
],
},
],
'pat-alice': [
{
id: 'pt-a-1',
patientId: 'pat-alice',
title: 'Hygiene visit',
treatmentAt: addCalendarDays(new Date(), -21).toISOString(),
status: 'completed',
records: [{ id: 'ptr-a', teeth: [], notes: 'Scale & polish.' }],
documents: [],
},
],
};
const MOCK_ORGS: LinkedOrganizationOption[] = [
{ id: 'org-lab-north', name: 'North Dental Lab', active: true },
{ id: 'org-lab-smile', name: 'Smile Ceramics', active: true },
{ id: 'org-lab-old', name: 'Legacy Lab (inactive)', active: false },
];
export async function fetchMyAppointmentsForDay(
userId: string,
day: Date,
): Promise<TreatmentAppointment[]> {
await sleep();
return buildSlotsForDay(day, userId);
}
export async function fetchPastTreatments(patientId: string): Promise<PastTreatment[]> {
await sleep();
return MOCK_HISTORY[patientId] ?? [];
}
export async function fetchLinkedOrganizations(): Promise<LinkedOrganizationOption[]> {
await sleep(180);
return MOCK_ORGS;
}
export async function saveTreatmentDraft(_payload: SaveTreatmentPayload): Promise<{ ok: true }> {
await sleep();
return { ok: true };
}
export async function sendTreatmentRecord(_payload: SendTreatmentRecordPayload): Promise<{ ok: true }> {
await sleep();
return { ok: true };
}

View File

@@ -0,0 +1,25 @@
import { isSameLocalCalendarDay } from '@/lib/appointmentTime';
import type { TreatmentAppointment } from '@/types/treatment';
/**
* For the selected calendar day: if it is today, pick the appointment whose time range contains now;
* otherwise pick the first appointment of that day. Returns null when there are no appointments.
*/
export function pickAutoAppointment(
appointments: TreatmentAppointment[],
selectedDay: Date,
): string | null {
if (appointments.length === 0) return null;
const now = new Date();
if (isSameLocalCalendarDay(selectedDay, now)) {
const t = now.getTime();
for (const a of appointments) {
const s = new Date(a.startAt).getTime();
const e = new Date(a.endAt).getTime();
if (t >= s && t <= e) return a.id;
}
}
return appointments[0].id;
}