export interface AnatomicalSystem { id: string; slug: string; namePt: string; nameEn: string; colorHex: string; order: number; _count?: { structures: number }; } export interface Structure { id: string; fmaId: string | null; slug: string; namePt: string; nameEn: string; synonyms: string[]; description: string | null; regionPath: string[]; modelFile: string; meshName: string; systemId: string; system: AnatomicalSystem; } export interface QuizCard { id: string; structureId: string; easeFactor: number; intervalDays: number; repetitions: number; dueAt: string; } export interface PageVisit { id: string; path: string; system: string | null; ip: string; port: number | null; country: string | null; city: string | null; userAgent: string | null; createdAt: string; } export interface FeedbackEntry { id: string; message: string; email: string | null; rating: number | null; page: string | null; ip: string | null; createdAt: string; } export interface AdminStats { onlineNow: number; totalVisits: number; visitsLast24h: number; visitsLast7d: number; visitsBySystem: { system: string | null; count: number }[]; visitsByCountry: { country: string; count: number }[]; recentVisits: PageVisit[]; feedback: FeedbackEntry[]; } const BASE = "/api"; async function getJson(url: string): Promise { const res = await fetch(url); if (!res.ok) throw new Error(`GET ${url} failed: ${res.status}`); return res.json() as Promise; } async function postJson(url: string, body: unknown, token?: string): Promise { const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, body: JSON.stringify(body), }); if (!res.ok) { const data = await res.json().catch(() => null); throw new Error(data?.error ?? `POST ${url} failed: ${res.status}`); } return res.json() as Promise; } export const api = { systems: () => getJson(`${BASE}/systems`), structures: (params: { system?: string; q?: string } = {}) => { const usp = new URLSearchParams(); if (params.system) usp.set("system", params.system); if (params.q) usp.set("q", params.q); const qs = usp.toString(); return getJson(`${BASE}/structures${qs ? `?${qs}` : ""}`); }, structure: (id: string) => getJson(`${BASE}/structures/${id}`), quizNext: (params: { system?: string; limit?: number } = {}) => { const usp = new URLSearchParams(); if (params.system) usp.set("system", params.system); if (params.limit) usp.set("limit", String(params.limit)); const qs = usp.toString(); return getJson<(Structure & { quizCard: QuizCard | null })[]>( `${BASE}/quiz/next${qs ? `?${qs}` : ""}`, ); }, quizReview: async (structureId: string, quality: number): Promise => { const res = await fetch(`${BASE}/quiz/${structureId}/review`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ quality }), }); if (!res.ok) throw new Error(`quizReview failed: ${res.status}`); return res.json(); }, onlineCount: () => getJson<{ count: number }>(`${BASE}/analytics/online-count`), trackVisit: (path: string, system?: string) => postJson<{ ok: true }>(`${BASE}/analytics/visit`, { path, system }).catch(() => {}), heartbeat: (sessionId: string) => postJson<{ ok: true }>(`${BASE}/analytics/heartbeat`, { sessionId }).catch(() => {}), sendFeedback: (data: { message: string; email?: string; rating?: number; page?: string }) => postJson<{ ok: true }>(`${BASE}/feedback`, data), adminLogin: (email: string, password: string) => postJson<{ token: string }>(`${BASE}/admin/login`, { email, password }), adminStats: async (token: string): Promise => { const res = await fetch(`${BASE}/admin/stats`, { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) throw new Error(`GET admin/stats failed: ${res.status}`); return res.json(); }, }; export function modelUrl(modelFile: string): string { return `/models/${modelFile}`; }