feature: a mocked implementation of v1 UI done.
This commit is contained in:
198
frontend/src/lib/mocks/treatmentMockApi.ts
Normal file
198
frontend/src/lib/mocks/treatmentMockApi.ts
Normal 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 };
|
||||
}
|
||||
Reference in New Issue
Block a user