mirror of
https://github.com/temetro/temetro.git
synced 2026-07-26 11:58:14 +00:00
1ecbd16404
Connects the UI-only app to the new backend over Better Auth + a small patient API client, replacing the in-memory fixture and placeholder identity. - Better Auth React client (lib/auth-client.ts) + shared access-control roles; API client (lib/api-client.ts) sending credentials cross-origin. - Designed (auth) route group: login, signup, verify-email, forgot/reset password, clinic onboarding, and accept-invite. - proxy.ts (this Next's renamed middleware) optimistic redirect + an authoritative client AppAuthGuard requiring a session and active clinic. - Real identity in nav-user (useSession + sign out); the unused team switcher repurposed as an organization (clinic) switcher; Care-team settings tab manages members and invitations. - lib/patients.ts now calls the org-scoped API (types unchanged); patients table and create/edit dialog updated for async create/update. - next.config: standalone output + Dockerfile for containerized runs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
120 lines
3.0 KiB
TypeScript
120 lines
3.0 KiB
TypeScript
// Patient domain types + data access for the temetro chat.
|
|
//
|
|
// The types below are the canonical record shape (also mirrored by the backend
|
|
// at backend/src/types/patient.ts). The data functions call the backend's
|
|
// org-scoped patient API; the session cookie is sent automatically by the
|
|
// shared fetch wrapper.
|
|
|
|
import { ApiError, apiFetch } from "@/lib/api-client";
|
|
|
|
export type AllergySeverity = "mild" | "moderate" | "severe";
|
|
export type LabFlag = "normal" | "high" | "low" | "critical";
|
|
|
|
export type Allergy = {
|
|
substance: string;
|
|
reaction: string;
|
|
severity: AllergySeverity;
|
|
};
|
|
|
|
export type Medication = {
|
|
name: string;
|
|
dose: string;
|
|
frequency: string;
|
|
};
|
|
|
|
export type Problem = {
|
|
label: string;
|
|
since: string;
|
|
};
|
|
|
|
export type Vitals = {
|
|
bp: string;
|
|
hr: string;
|
|
temp: string;
|
|
spo2: string;
|
|
takenAt: string;
|
|
};
|
|
|
|
export type Lab = {
|
|
name: string;
|
|
value: string;
|
|
flag: LabFlag;
|
|
takenAt: string;
|
|
};
|
|
|
|
export type Encounter = {
|
|
date: string;
|
|
type: string;
|
|
provider: string;
|
|
summary: string;
|
|
};
|
|
|
|
// A short series for a sparkline. `points` are most-recent-last, so the
|
|
// latest reading is points.at(-1).
|
|
export type Trend = {
|
|
label: string;
|
|
unit: string;
|
|
points: number[];
|
|
};
|
|
|
|
export type Patient = {
|
|
fileNumber: string; // MRN / file number, e.g. "10293"
|
|
name: string;
|
|
age: number;
|
|
sex: "M" | "F";
|
|
pcp: string; // primary care provider
|
|
status: "active" | "inpatient" | "discharged";
|
|
initials: string; // for AvatarFallback
|
|
allergies: Allergy[];
|
|
alerts: string[];
|
|
medications: Medication[];
|
|
problems: Problem[];
|
|
vitals: Vitals;
|
|
vitalsTrend: Trend; // headline vital plotted as a sparkline
|
|
labs: Lab[];
|
|
labTrend: Trend; // headline lab plotted as a sparkline
|
|
encounters: Encounter[];
|
|
};
|
|
|
|
// Fetch one patient in the active clinic. Returns null when not found (404).
|
|
export async function getPatient(fileNumber: string): Promise<Patient | null> {
|
|
try {
|
|
return await apiFetch<Patient>(
|
|
`/api/patients/${encodeURIComponent(fileNumber.trim())}`,
|
|
);
|
|
} catch (err) {
|
|
if (err instanceof ApiError && err.status === 404) return null;
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
// Every patient in the active clinic.
|
|
export async function listPatients(): Promise<Patient[]> {
|
|
return apiFetch<Patient[]>("/api/patients");
|
|
}
|
|
|
|
// Create a new patient. Throws ApiError(409) if the file number is taken.
|
|
export async function createPatient(patient: Patient): Promise<Patient> {
|
|
return apiFetch<Patient>("/api/patients", {
|
|
method: "POST",
|
|
body: JSON.stringify(patient),
|
|
});
|
|
}
|
|
|
|
// Replace an existing patient's full record.
|
|
export async function updatePatient(patient: Patient): Promise<Patient> {
|
|
return apiFetch<Patient>(
|
|
`/api/patients/${encodeURIComponent(patient.fileNumber)}`,
|
|
{
|
|
method: "PUT",
|
|
body: JSON.stringify(patient),
|
|
},
|
|
);
|
|
}
|
|
|
|
// Suggest a unique-ish 5-digit file number for new charts. The server is the
|
|
// source of truth and rejects collisions with a 409.
|
|
export function generateFileNumber(): string {
|
|
return String(10000 + Math.floor(Math.random() * 89999));
|
|
}
|