184 lines
5.1 KiB
TypeScript
184 lines
5.1 KiB
TypeScript
export const API_BASE = "/api/v1";
|
|
|
|
export type Entity = {
|
|
id: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
export type Customer = Entity & {
|
|
customer_type: "practice" | "clinic";
|
|
name: string;
|
|
city?: string | null;
|
|
phone?: string | null;
|
|
email?: string | null;
|
|
hygiene_officer?: string | null;
|
|
quality_manager?: string | null;
|
|
notes?: string | null;
|
|
};
|
|
|
|
export type Location = Entity & {
|
|
customer_id: string;
|
|
name: string;
|
|
street?: string | null;
|
|
postal_code?: string | null;
|
|
city?: string | null;
|
|
room?: string | null;
|
|
};
|
|
|
|
export type Contact = Entity & {
|
|
customer_id: string;
|
|
full_name: string;
|
|
function?: string | null;
|
|
email?: string | null;
|
|
phone?: string | null;
|
|
};
|
|
|
|
export type Device = Entity & {
|
|
customer_id: string;
|
|
location_id?: string | null;
|
|
manufacturer: string;
|
|
model: string;
|
|
serial_number: string;
|
|
device_type?: string | null;
|
|
year_built?: number | null;
|
|
commissioned_on?: string | null;
|
|
chamber_volume_liters?: number | null;
|
|
steam_generation?: string | null;
|
|
water_treatment?: string | null;
|
|
documentation?: string | null;
|
|
supplier?: string | null;
|
|
};
|
|
|
|
export type Equipment = Entity & {
|
|
kind: string;
|
|
manufacturer?: string | null;
|
|
model?: string | null;
|
|
serial_number: string;
|
|
calibrated_on?: string | null;
|
|
calibration_due_on?: string | null;
|
|
certificate_document_id?: string | null;
|
|
status: "green" | "yellow" | "red";
|
|
};
|
|
|
|
export type User = Entity & {
|
|
email: string;
|
|
first_name: string;
|
|
last_name: string;
|
|
role: "admin" | "pruefer" | "mitarbeiter" | "leser";
|
|
is_active: boolean;
|
|
must_change_password: boolean;
|
|
last_login_at?: string | null;
|
|
password_changed_at?: string | null;
|
|
};
|
|
|
|
export type ValidationItem = Entity & {
|
|
report_number: string;
|
|
customer_id: string;
|
|
location_id?: string | null;
|
|
contact_id?: string | null;
|
|
device_id?: string | null;
|
|
validation_type: string;
|
|
project?: string | null;
|
|
test_location?: string | null;
|
|
examiner_name?: string | null;
|
|
participants?: string | null;
|
|
operator_name?: string | null;
|
|
status: string;
|
|
result?: string | null;
|
|
scheduled_on?: string | null;
|
|
performed_on?: string | null;
|
|
next_validation_on?: string | null;
|
|
revalidation_interval_months: number;
|
|
next_validation_manually_overridden: boolean;
|
|
version: number;
|
|
previous_validation_id?: string | null;
|
|
equipment_ids: string[];
|
|
environment_conditions: Record<string, unknown>;
|
|
documentation_checklist: Record<string, unknown>[];
|
|
performance_checklist: Record<string, unknown>[];
|
|
programs: Record<string, unknown>[];
|
|
loading_patterns: Record<string, unknown>[];
|
|
measurement_data: Record<string, unknown>[];
|
|
drying: Record<string, unknown>;
|
|
recommendations: Record<string, unknown>[];
|
|
attachments: Record<string, unknown>[];
|
|
};
|
|
|
|
export function normalizeValidationPayload<T extends Record<string, unknown>>(values: T): T {
|
|
const optionalForeignKeys = ["contact_id", "examiner_id"];
|
|
return {
|
|
...values,
|
|
...Object.fromEntries(optionalForeignKeys.map((key) => [key, values[key] || null]))
|
|
};
|
|
}
|
|
|
|
export type Paginated<T> = {
|
|
items: T[];
|
|
total: number;
|
|
page: number;
|
|
page_size: number;
|
|
};
|
|
|
|
export async function login(email: string, password: string) {
|
|
const response = await fetch(`/api/login`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ email, password }),
|
|
credentials: "include"
|
|
});
|
|
if (!response.ok) {
|
|
const detail = await response.text();
|
|
const error = new Error(detail || `HTTP ${response.status}`) as Error & { status?: number };
|
|
error.status = response.status;
|
|
throw error;
|
|
}
|
|
return response.json() as Promise<{ user: User; expires_in: number }>;
|
|
}
|
|
|
|
export async function apiGet<T>(path: string, token: string): Promise<T> {
|
|
const response = await fetch(`${API_BASE}${path}`, {
|
|
cache: "no-store",
|
|
credentials: "include"
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error(`API request failed: ${response.status}`);
|
|
}
|
|
return response.json() as Promise<T>;
|
|
}
|
|
|
|
export async function apiSend<T>(path: string, token: string, method: "POST" | "PUT", body: unknown): Promise<T> {
|
|
const response = await fetch(`${API_BASE}${path}`, {
|
|
method,
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(body),
|
|
credentials: "include"
|
|
});
|
|
if (!response.ok) {
|
|
const detail = await response.text();
|
|
throw new Error(detail || `API request failed: ${response.status}`);
|
|
}
|
|
return response.json() as Promise<T>;
|
|
}
|
|
|
|
export async function apiDelete(path: string, token: string): Promise<void> {
|
|
const response = await fetch(`${API_BASE}${path}`, {
|
|
method: "DELETE",
|
|
credentials: "include"
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error(`API request failed: ${response.status}`);
|
|
}
|
|
}
|
|
|
|
export async function apiFetch<T>(path: string, token: string, init?: RequestInit): Promise<T> {
|
|
const response = await fetch(`${API_BASE}${path}`, {
|
|
...init,
|
|
headers: { "Content-Type": "application/json", ...(init?.headers ?? {}) },
|
|
credentials: "include"
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error(await response.text());
|
|
}
|
|
return response.json() as Promise<T>;
|
|
}
|