chore(frontend): format files with prettier
This commit is contained in:
parent
0e7a39836f
commit
067e460dd2
20 changed files with 1615 additions and 1509 deletions
|
|
@ -5,85 +5,85 @@
|
|||
/* ── CSS variable definitions (light / dark) ─────────────────────────────── */
|
||||
|
||||
:root {
|
||||
--background: hsl(0 0% 100%);
|
||||
--foreground: hsl(240 10% 3.9%);
|
||||
--card: hsl(0 0% 100%);
|
||||
--card-foreground: hsl(240 10% 3.9%);
|
||||
--popover: hsl(0 0% 100%);
|
||||
--popover-foreground: hsl(240 10% 3.9%);
|
||||
--primary: hsl(240 5.9% 10%);
|
||||
--primary-foreground: hsl(0 0% 98%);
|
||||
--secondary: hsl(240 4.8% 95.9%);
|
||||
--secondary-foreground: hsl(240 5.9% 10%);
|
||||
--muted: hsl(240 4.8% 95.9%);
|
||||
--muted-foreground: hsl(240 3.8% 46.1%);
|
||||
--accent: hsl(240 4.8% 95.9%);
|
||||
--accent-foreground: hsl(240 5.9% 10%);
|
||||
--destructive: hsl(0 84.2% 60.2%);
|
||||
--destructive-foreground: hsl(0 0% 98%);
|
||||
--border: hsl(240 5.9% 90%);
|
||||
--input: hsl(240 5.9% 90%);
|
||||
--ring: hsl(240 5.9% 10%);
|
||||
--radius: 0.5rem;
|
||||
--background: hsl(0 0% 100%);
|
||||
--foreground: hsl(240 10% 3.9%);
|
||||
--card: hsl(0 0% 100%);
|
||||
--card-foreground: hsl(240 10% 3.9%);
|
||||
--popover: hsl(0 0% 100%);
|
||||
--popover-foreground: hsl(240 10% 3.9%);
|
||||
--primary: hsl(240 5.9% 10%);
|
||||
--primary-foreground: hsl(0 0% 98%);
|
||||
--secondary: hsl(240 4.8% 95.9%);
|
||||
--secondary-foreground: hsl(240 5.9% 10%);
|
||||
--muted: hsl(240 4.8% 95.9%);
|
||||
--muted-foreground: hsl(240 3.8% 46.1%);
|
||||
--accent: hsl(240 4.8% 95.9%);
|
||||
--accent-foreground: hsl(240 5.9% 10%);
|
||||
--destructive: hsl(0 84.2% 60.2%);
|
||||
--destructive-foreground: hsl(0 0% 98%);
|
||||
--border: hsl(240 5.9% 90%);
|
||||
--input: hsl(240 5.9% 90%);
|
||||
--ring: hsl(240 5.9% 10%);
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: hsl(240 10% 3.9%);
|
||||
--foreground: hsl(0 0% 98%);
|
||||
--card: hsl(240 10% 3.9%);
|
||||
--card-foreground: hsl(0 0% 98%);
|
||||
--popover: hsl(240 10% 3.9%);
|
||||
--popover-foreground: hsl(0 0% 98%);
|
||||
--primary: hsl(0 0% 98%);
|
||||
--primary-foreground: hsl(240 5.9% 10%);
|
||||
--secondary: hsl(240 3.7% 15.9%);
|
||||
--secondary-foreground: hsl(0 0% 98%);
|
||||
--muted: hsl(240 3.7% 15.9%);
|
||||
--muted-foreground: hsl(240 5% 64.9%);
|
||||
--accent: hsl(240 3.7% 15.9%);
|
||||
--accent-foreground: hsl(0 0% 98%);
|
||||
--destructive: hsl(0 62.8% 30.6%);
|
||||
--destructive-foreground: hsl(0 0% 98%);
|
||||
--border: hsl(240 3.7% 15.9%);
|
||||
--input: hsl(240 3.7% 15.9%);
|
||||
--ring: hsl(240 4.9% 83.9%);
|
||||
--background: hsl(240 10% 3.9%);
|
||||
--foreground: hsl(0 0% 98%);
|
||||
--card: hsl(240 10% 3.9%);
|
||||
--card-foreground: hsl(0 0% 98%);
|
||||
--popover: hsl(240 10% 3.9%);
|
||||
--popover-foreground: hsl(0 0% 98%);
|
||||
--primary: hsl(0 0% 98%);
|
||||
--primary-foreground: hsl(240 5.9% 10%);
|
||||
--secondary: hsl(240 3.7% 15.9%);
|
||||
--secondary-foreground: hsl(0 0% 98%);
|
||||
--muted: hsl(240 3.7% 15.9%);
|
||||
--muted-foreground: hsl(240 5% 64.9%);
|
||||
--accent: hsl(240 3.7% 15.9%);
|
||||
--accent-foreground: hsl(0 0% 98%);
|
||||
--destructive: hsl(0 62.8% 30.6%);
|
||||
--destructive-foreground: hsl(0 0% 98%);
|
||||
--border: hsl(240 3.7% 15.9%);
|
||||
--input: hsl(240 3.7% 15.9%);
|
||||
--ring: hsl(240 4.9% 83.9%);
|
||||
}
|
||||
|
||||
/* ── Map CSS vars → Tailwind v4 design tokens ────────────────────────────── */
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
}
|
||||
|
||||
/* ── Base resets ─────────────────────────────────────────────────────────── */
|
||||
|
||||
* {
|
||||
border-color: var(--border);
|
||||
border-color: var(--border);
|
||||
}
|
||||
body {
|
||||
background-color: var(--background);
|
||||
color: var(--foreground);
|
||||
background-color: var(--background);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
|
|
|||
14
frontend/src/app.d.ts
vendored
14
frontend/src/app.d.ts
vendored
|
|
@ -1,13 +1,13 @@
|
|||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { paraglideMiddleware } from '$lib/paraglide/server.js';
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
import { paraglideMiddleware } from "$lib/paraglide/server.js";
|
||||
import type { Handle } from "@sveltejs/kit";
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
return paraglideMiddleware(event.request, () => resolve(event));
|
||||
return paraglideMiddleware(event.request, () => resolve(event));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,280 +1,349 @@
|
|||
import { browser } from '$app/environment';
|
||||
import { PUBLIC_API_BASE } from '$env/static/public';
|
||||
import { browser } from "$app/environment";
|
||||
import { PUBLIC_API_BASE } from "$env/static/public";
|
||||
import type {
|
||||
ActiveIngredient,
|
||||
BatchSuggestion,
|
||||
GroomingSchedule,
|
||||
LabResult,
|
||||
MedicationEntry,
|
||||
MedicationUsage,
|
||||
PartOfDay,
|
||||
Product,
|
||||
ProductContext,
|
||||
ProductEffectProfile,
|
||||
ProductInteraction,
|
||||
ProductInventory,
|
||||
Routine,
|
||||
RoutineSuggestion,
|
||||
RoutineStep,
|
||||
SkinConditionSnapshot
|
||||
} from './types';
|
||||
ActiveIngredient,
|
||||
BatchSuggestion,
|
||||
GroomingSchedule,
|
||||
LabResult,
|
||||
MedicationEntry,
|
||||
MedicationUsage,
|
||||
PartOfDay,
|
||||
Product,
|
||||
ProductContext,
|
||||
ProductEffectProfile,
|
||||
ProductInteraction,
|
||||
ProductInventory,
|
||||
Routine,
|
||||
RoutineSuggestion,
|
||||
RoutineStep,
|
||||
SkinConditionSnapshot,
|
||||
} from "./types";
|
||||
|
||||
// ─── Core fetch helpers ──────────────────────────────────────────────────────
|
||||
|
||||
async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
|
||||
// Server-side uses PUBLIC_API_BASE (e.g. http://localhost:8000).
|
||||
// Browser-side uses /api so nginx proxies the request on the correct host.
|
||||
const base = browser ? '/api' : PUBLIC_API_BASE;
|
||||
const url = `${base}${path}`;
|
||||
const res = await fetch(url, {
|
||||
headers: { 'Content-Type': 'application/json', ...init.headers },
|
||||
...init
|
||||
});
|
||||
if (!res.ok) {
|
||||
const detail = await res.json().catch(() => ({ detail: res.statusText }));
|
||||
throw new Error(detail?.detail ?? res.statusText);
|
||||
}
|
||||
if (res.status === 204) return undefined as T;
|
||||
return res.json();
|
||||
// Server-side uses PUBLIC_API_BASE (e.g. http://localhost:8000).
|
||||
// Browser-side uses /api so nginx proxies the request on the correct host.
|
||||
const base = browser ? "/api" : PUBLIC_API_BASE;
|
||||
const url = `${base}${path}`;
|
||||
const res = await fetch(url, {
|
||||
headers: { "Content-Type": "application/json", ...init.headers },
|
||||
...init,
|
||||
});
|
||||
if (!res.ok) {
|
||||
const detail = await res.json().catch(() => ({ detail: res.statusText }));
|
||||
throw new Error(detail?.detail ?? res.statusText);
|
||||
}
|
||||
if (res.status === 204) return undefined as T;
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export const api = {
|
||||
get: <T>(path: string) => request<T>(path),
|
||||
post: <T>(path: string, body: unknown) =>
|
||||
request<T>(path, { method: 'POST', body: JSON.stringify(body) }),
|
||||
patch: <T>(path: string, body: unknown) =>
|
||||
request<T>(path, { method: 'PATCH', body: JSON.stringify(body) }),
|
||||
del: (path: string) => request<void>(path, { method: 'DELETE' })
|
||||
get: <T>(path: string) => request<T>(path),
|
||||
post: <T>(path: string, body: unknown) =>
|
||||
request<T>(path, { method: "POST", body: JSON.stringify(body) }),
|
||||
patch: <T>(path: string, body: unknown) =>
|
||||
request<T>(path, { method: "PATCH", body: JSON.stringify(body) }),
|
||||
del: (path: string) => request<void>(path, { method: "DELETE" }),
|
||||
};
|
||||
|
||||
// ─── Products ────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface ProductListParams {
|
||||
category?: string;
|
||||
brand?: string;
|
||||
targets?: string[];
|
||||
is_medication?: boolean;
|
||||
is_tool?: boolean;
|
||||
category?: string;
|
||||
brand?: string;
|
||||
targets?: string[];
|
||||
is_medication?: boolean;
|
||||
is_tool?: boolean;
|
||||
}
|
||||
|
||||
export function getProducts(params: ProductListParams = {}): Promise<Product[]> {
|
||||
const q = new URLSearchParams();
|
||||
if (params.category) q.set('category', params.category);
|
||||
if (params.brand) q.set('brand', params.brand);
|
||||
if (params.targets) params.targets.forEach((t) => q.append('targets', t));
|
||||
if (params.is_medication != null) q.set('is_medication', String(params.is_medication));
|
||||
if (params.is_tool != null) q.set('is_tool', String(params.is_tool));
|
||||
const qs = q.toString();
|
||||
return api.get(`/products${qs ? `?${qs}` : ''}`);
|
||||
export function getProducts(
|
||||
params: ProductListParams = {},
|
||||
): Promise<Product[]> {
|
||||
const q = new URLSearchParams();
|
||||
if (params.category) q.set("category", params.category);
|
||||
if (params.brand) q.set("brand", params.brand);
|
||||
if (params.targets) params.targets.forEach((t) => q.append("targets", t));
|
||||
if (params.is_medication != null)
|
||||
q.set("is_medication", String(params.is_medication));
|
||||
if (params.is_tool != null) q.set("is_tool", String(params.is_tool));
|
||||
const qs = q.toString();
|
||||
return api.get(`/products${qs ? `?${qs}` : ""}`);
|
||||
}
|
||||
|
||||
export const getProduct = (id: string): Promise<Product> => api.get(`/products/${id}`);
|
||||
export const createProduct = (body: Record<string, unknown>): Promise<Product> =>
|
||||
api.post('/products', body);
|
||||
export const updateProduct = (id: string, body: Record<string, unknown>): Promise<Product> =>
|
||||
api.patch(`/products/${id}`, body);
|
||||
export const deleteProduct = (id: string): Promise<void> => api.del(`/products/${id}`);
|
||||
export const getProduct = (id: string): Promise<Product> =>
|
||||
api.get(`/products/${id}`);
|
||||
export const createProduct = (
|
||||
body: Record<string, unknown>,
|
||||
): Promise<Product> => api.post("/products", body);
|
||||
export const updateProduct = (
|
||||
id: string,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<Product> => api.patch(`/products/${id}`, body);
|
||||
export const deleteProduct = (id: string): Promise<void> =>
|
||||
api.del(`/products/${id}`);
|
||||
|
||||
export const getInventory = (productId: string): Promise<ProductInventory[]> =>
|
||||
api.get(`/products/${productId}/inventory`);
|
||||
api.get(`/products/${productId}/inventory`);
|
||||
export const createInventory = (
|
||||
productId: string,
|
||||
body: Record<string, unknown>
|
||||
): Promise<ProductInventory> => api.post(`/products/${productId}/inventory`, body);
|
||||
export const updateInventory = (id: string, body: Record<string, unknown>): Promise<ProductInventory> =>
|
||||
api.patch(`/inventory/${id}`, body);
|
||||
export const deleteInventory = (id: string): Promise<void> => api.del(`/inventory/${id}`);
|
||||
productId: string,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<ProductInventory> =>
|
||||
api.post(`/products/${productId}/inventory`, body);
|
||||
export const updateInventory = (
|
||||
id: string,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<ProductInventory> => api.patch(`/inventory/${id}`, body);
|
||||
export const deleteInventory = (id: string): Promise<void> =>
|
||||
api.del(`/inventory/${id}`);
|
||||
|
||||
export interface ProductParseResponse {
|
||||
name?: string; brand?: string; line_name?: string; sku?: string; url?: string; barcode?: string;
|
||||
category?: string; recommended_time?: string; texture?: string; absorption_speed?: string;
|
||||
leave_on?: boolean; price_tier?: string;
|
||||
size_ml?: number; full_weight_g?: number; empty_weight_g?: number; pao_months?: number;
|
||||
inci?: string[]; actives?: ActiveIngredient[];
|
||||
recommended_for?: string[]; targets?: string[];
|
||||
contraindications?: string[]; usage_notes?: string;
|
||||
fragrance_free?: boolean; essential_oils_free?: boolean;
|
||||
alcohol_denat_free?: boolean; pregnancy_safe?: boolean;
|
||||
product_effect_profile?: ProductEffectProfile;
|
||||
ph_min?: number; ph_max?: number;
|
||||
incompatible_with?: ProductInteraction[]; synergizes_with?: string[];
|
||||
context_rules?: ProductContext;
|
||||
min_interval_hours?: number; max_frequency_per_week?: number;
|
||||
is_medication?: boolean; is_tool?: boolean; needle_length_mm?: number;
|
||||
name?: string;
|
||||
brand?: string;
|
||||
line_name?: string;
|
||||
sku?: string;
|
||||
url?: string;
|
||||
barcode?: string;
|
||||
category?: string;
|
||||
recommended_time?: string;
|
||||
texture?: string;
|
||||
absorption_speed?: string;
|
||||
leave_on?: boolean;
|
||||
price_tier?: string;
|
||||
size_ml?: number;
|
||||
full_weight_g?: number;
|
||||
empty_weight_g?: number;
|
||||
pao_months?: number;
|
||||
inci?: string[];
|
||||
actives?: ActiveIngredient[];
|
||||
recommended_for?: string[];
|
||||
targets?: string[];
|
||||
contraindications?: string[];
|
||||
usage_notes?: string;
|
||||
fragrance_free?: boolean;
|
||||
essential_oils_free?: boolean;
|
||||
alcohol_denat_free?: boolean;
|
||||
pregnancy_safe?: boolean;
|
||||
product_effect_profile?: ProductEffectProfile;
|
||||
ph_min?: number;
|
||||
ph_max?: number;
|
||||
incompatible_with?: ProductInteraction[];
|
||||
synergizes_with?: string[];
|
||||
context_rules?: ProductContext;
|
||||
min_interval_hours?: number;
|
||||
max_frequency_per_week?: number;
|
||||
is_medication?: boolean;
|
||||
is_tool?: boolean;
|
||||
needle_length_mm?: number;
|
||||
}
|
||||
|
||||
export const parseProductText = (text: string): Promise<ProductParseResponse> =>
|
||||
api.post('/products/parse-text', { text });
|
||||
api.post("/products/parse-text", { text });
|
||||
|
||||
// ─── Routines ────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface RoutineListParams {
|
||||
from_date?: string;
|
||||
to_date?: string;
|
||||
part_of_day?: string;
|
||||
from_date?: string;
|
||||
to_date?: string;
|
||||
part_of_day?: string;
|
||||
}
|
||||
|
||||
export function getRoutines(params: RoutineListParams = {}): Promise<Routine[]> {
|
||||
const q = new URLSearchParams();
|
||||
if (params.from_date) q.set('from_date', params.from_date);
|
||||
if (params.to_date) q.set('to_date', params.to_date);
|
||||
if (params.part_of_day) q.set('part_of_day', params.part_of_day);
|
||||
const qs = q.toString();
|
||||
return api.get(`/routines${qs ? `?${qs}` : ''}`);
|
||||
export function getRoutines(
|
||||
params: RoutineListParams = {},
|
||||
): Promise<Routine[]> {
|
||||
const q = new URLSearchParams();
|
||||
if (params.from_date) q.set("from_date", params.from_date);
|
||||
if (params.to_date) q.set("to_date", params.to_date);
|
||||
if (params.part_of_day) q.set("part_of_day", params.part_of_day);
|
||||
const qs = q.toString();
|
||||
return api.get(`/routines${qs ? `?${qs}` : ""}`);
|
||||
}
|
||||
|
||||
export const getRoutine = (id: string): Promise<Routine> => api.get(`/routines/${id}`);
|
||||
export const createRoutine = (body: Record<string, unknown>): Promise<Routine> =>
|
||||
api.post('/routines', body);
|
||||
export const updateRoutine = (id: string, body: Record<string, unknown>): Promise<Routine> =>
|
||||
api.patch(`/routines/${id}`, body);
|
||||
export const deleteRoutine = (id: string): Promise<void> => api.del(`/routines/${id}`);
|
||||
export const getRoutine = (id: string): Promise<Routine> =>
|
||||
api.get(`/routines/${id}`);
|
||||
export const createRoutine = (
|
||||
body: Record<string, unknown>,
|
||||
): Promise<Routine> => api.post("/routines", body);
|
||||
export const updateRoutine = (
|
||||
id: string,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<Routine> => api.patch(`/routines/${id}`, body);
|
||||
export const deleteRoutine = (id: string): Promise<void> =>
|
||||
api.del(`/routines/${id}`);
|
||||
|
||||
export const addRoutineStep = (routineId: string, body: Record<string, unknown>): Promise<RoutineStep> =>
|
||||
api.post(`/routines/${routineId}/steps`, body);
|
||||
export const updateRoutineStep = (stepId: string, body: Record<string, unknown>): Promise<RoutineStep> =>
|
||||
api.patch(`/routines/steps/${stepId}`, body);
|
||||
export const addRoutineStep = (
|
||||
routineId: string,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<RoutineStep> => api.post(`/routines/${routineId}/steps`, body);
|
||||
export const updateRoutineStep = (
|
||||
stepId: string,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<RoutineStep> => api.patch(`/routines/steps/${stepId}`, body);
|
||||
export const deleteRoutineStep = (stepId: string): Promise<void> =>
|
||||
api.del(`/routines/steps/${stepId}`);
|
||||
api.del(`/routines/steps/${stepId}`);
|
||||
|
||||
export const suggestRoutine = (body: {
|
||||
routine_date: string;
|
||||
part_of_day: PartOfDay;
|
||||
notes?: string;
|
||||
include_minoxidil_beard?: boolean;
|
||||
leaving_home?: boolean;
|
||||
}): Promise<RoutineSuggestion> => api.post('/routines/suggest', body);
|
||||
routine_date: string;
|
||||
part_of_day: PartOfDay;
|
||||
notes?: string;
|
||||
include_minoxidil_beard?: boolean;
|
||||
leaving_home?: boolean;
|
||||
}): Promise<RoutineSuggestion> => api.post("/routines/suggest", body);
|
||||
|
||||
export const suggestBatch = (body: {
|
||||
from_date: string;
|
||||
to_date: string;
|
||||
notes?: string;
|
||||
include_minoxidil_beard?: boolean;
|
||||
minimize_products?: boolean;
|
||||
}): Promise<BatchSuggestion> => api.post('/routines/suggest-batch', body);
|
||||
from_date: string;
|
||||
to_date: string;
|
||||
notes?: string;
|
||||
include_minoxidil_beard?: boolean;
|
||||
minimize_products?: boolean;
|
||||
}): Promise<BatchSuggestion> => api.post("/routines/suggest-batch", body);
|
||||
|
||||
export const getGroomingSchedule = (): Promise<GroomingSchedule[]> =>
|
||||
api.get('/routines/grooming-schedule');
|
||||
export const createGroomingScheduleEntry = (body: Record<string, unknown>): Promise<GroomingSchedule> =>
|
||||
api.post('/routines/grooming-schedule', body);
|
||||
export const updateGroomingScheduleEntry = (id: string, body: Record<string, unknown>): Promise<GroomingSchedule> =>
|
||||
api.patch(`/routines/grooming-schedule/${id}`, body);
|
||||
api.get("/routines/grooming-schedule");
|
||||
export const createGroomingScheduleEntry = (
|
||||
body: Record<string, unknown>,
|
||||
): Promise<GroomingSchedule> => api.post("/routines/grooming-schedule", body);
|
||||
export const updateGroomingScheduleEntry = (
|
||||
id: string,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<GroomingSchedule> =>
|
||||
api.patch(`/routines/grooming-schedule/${id}`, body);
|
||||
export const deleteGroomingScheduleEntry = (id: string): Promise<void> =>
|
||||
api.del(`/routines/grooming-schedule/${id}`);
|
||||
api.del(`/routines/grooming-schedule/${id}`);
|
||||
|
||||
// ─── Health – Medications ────────────────────────────────────────────────────
|
||||
|
||||
export interface MedicationListParams {
|
||||
kind?: string;
|
||||
product_name?: string;
|
||||
kind?: string;
|
||||
product_name?: string;
|
||||
}
|
||||
|
||||
export function getMedications(params: MedicationListParams = {}): Promise<MedicationEntry[]> {
|
||||
const q = new URLSearchParams();
|
||||
if (params.kind) q.set('kind', params.kind);
|
||||
if (params.product_name) q.set('product_name', params.product_name);
|
||||
const qs = q.toString();
|
||||
return api.get(`/health/medications${qs ? `?${qs}` : ''}`);
|
||||
export function getMedications(
|
||||
params: MedicationListParams = {},
|
||||
): Promise<MedicationEntry[]> {
|
||||
const q = new URLSearchParams();
|
||||
if (params.kind) q.set("kind", params.kind);
|
||||
if (params.product_name) q.set("product_name", params.product_name);
|
||||
const qs = q.toString();
|
||||
return api.get(`/health/medications${qs ? `?${qs}` : ""}`);
|
||||
}
|
||||
|
||||
export const getMedication = (id: string): Promise<MedicationEntry> =>
|
||||
api.get(`/health/medications/${id}`);
|
||||
export const createMedication = (body: Record<string, unknown>): Promise<MedicationEntry> =>
|
||||
api.post('/health/medications', body);
|
||||
api.get(`/health/medications/${id}`);
|
||||
export const createMedication = (
|
||||
body: Record<string, unknown>,
|
||||
): Promise<MedicationEntry> => api.post("/health/medications", body);
|
||||
export const updateMedication = (
|
||||
id: string,
|
||||
body: Record<string, unknown>
|
||||
id: string,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<MedicationEntry> => api.patch(`/health/medications/${id}`, body);
|
||||
export const deleteMedication = (id: string): Promise<void> =>
|
||||
api.del(`/health/medications/${id}`);
|
||||
api.del(`/health/medications/${id}`);
|
||||
|
||||
export const getMedicationUsages = (medicationId: string): Promise<MedicationUsage[]> =>
|
||||
api.get(`/health/medications/${medicationId}/usages`);
|
||||
export const getMedicationUsages = (
|
||||
medicationId: string,
|
||||
): Promise<MedicationUsage[]> =>
|
||||
api.get(`/health/medications/${medicationId}/usages`);
|
||||
export const createMedicationUsage = (
|
||||
medicationId: string,
|
||||
body: Record<string, unknown>
|
||||
): Promise<MedicationUsage> => api.post(`/health/medications/${medicationId}/usages`, body);
|
||||
medicationId: string,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<MedicationUsage> =>
|
||||
api.post(`/health/medications/${medicationId}/usages`, body);
|
||||
|
||||
// ─── Health – Lab results ────────────────────────────────────────────────────
|
||||
|
||||
export interface LabResultListParams {
|
||||
test_code?: string;
|
||||
flag?: string;
|
||||
lab?: string;
|
||||
from_date?: string;
|
||||
to_date?: string;
|
||||
test_code?: string;
|
||||
flag?: string;
|
||||
lab?: string;
|
||||
from_date?: string;
|
||||
to_date?: string;
|
||||
}
|
||||
|
||||
export function getLabResults(params: LabResultListParams = {}): Promise<LabResult[]> {
|
||||
const q = new URLSearchParams();
|
||||
if (params.test_code) q.set('test_code', params.test_code);
|
||||
if (params.flag) q.set('flag', params.flag);
|
||||
if (params.lab) q.set('lab', params.lab);
|
||||
if (params.from_date) q.set('from_date', params.from_date);
|
||||
if (params.to_date) q.set('to_date', params.to_date);
|
||||
const qs = q.toString();
|
||||
return api.get(`/health/lab-results${qs ? `?${qs}` : ''}`);
|
||||
export function getLabResults(
|
||||
params: LabResultListParams = {},
|
||||
): Promise<LabResult[]> {
|
||||
const q = new URLSearchParams();
|
||||
if (params.test_code) q.set("test_code", params.test_code);
|
||||
if (params.flag) q.set("flag", params.flag);
|
||||
if (params.lab) q.set("lab", params.lab);
|
||||
if (params.from_date) q.set("from_date", params.from_date);
|
||||
if (params.to_date) q.set("to_date", params.to_date);
|
||||
const qs = q.toString();
|
||||
return api.get(`/health/lab-results${qs ? `?${qs}` : ""}`);
|
||||
}
|
||||
|
||||
export const getLabResult = (id: string): Promise<LabResult> =>
|
||||
api.get(`/health/lab-results/${id}`);
|
||||
export const createLabResult = (body: Record<string, unknown>): Promise<LabResult> =>
|
||||
api.post('/health/lab-results', body);
|
||||
export const updateLabResult = (id: string, body: Record<string, unknown>): Promise<LabResult> =>
|
||||
api.patch(`/health/lab-results/${id}`, body);
|
||||
api.get(`/health/lab-results/${id}`);
|
||||
export const createLabResult = (
|
||||
body: Record<string, unknown>,
|
||||
): Promise<LabResult> => api.post("/health/lab-results", body);
|
||||
export const updateLabResult = (
|
||||
id: string,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<LabResult> => api.patch(`/health/lab-results/${id}`, body);
|
||||
export const deleteLabResult = (id: string): Promise<void> =>
|
||||
api.del(`/health/lab-results/${id}`);
|
||||
api.del(`/health/lab-results/${id}`);
|
||||
|
||||
// ─── Skin ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface SnapshotListParams {
|
||||
from_date?: string;
|
||||
to_date?: string;
|
||||
overall_state?: string;
|
||||
from_date?: string;
|
||||
to_date?: string;
|
||||
overall_state?: string;
|
||||
}
|
||||
|
||||
export function getSkinSnapshots(params: SnapshotListParams = {}): Promise<SkinConditionSnapshot[]> {
|
||||
const q = new URLSearchParams();
|
||||
if (params.from_date) q.set('from_date', params.from_date);
|
||||
if (params.to_date) q.set('to_date', params.to_date);
|
||||
if (params.overall_state) q.set('overall_state', params.overall_state);
|
||||
const qs = q.toString();
|
||||
return api.get(`/skincare${qs ? `?${qs}` : ''}`);
|
||||
export function getSkinSnapshots(
|
||||
params: SnapshotListParams = {},
|
||||
): Promise<SkinConditionSnapshot[]> {
|
||||
const q = new URLSearchParams();
|
||||
if (params.from_date) q.set("from_date", params.from_date);
|
||||
if (params.to_date) q.set("to_date", params.to_date);
|
||||
if (params.overall_state) q.set("overall_state", params.overall_state);
|
||||
const qs = q.toString();
|
||||
return api.get(`/skincare${qs ? `?${qs}` : ""}`);
|
||||
}
|
||||
|
||||
export const getSkinSnapshot = (id: string): Promise<SkinConditionSnapshot> =>
|
||||
api.get(`/skincare/${id}`);
|
||||
export const createSkinSnapshot = (body: Record<string, unknown>): Promise<SkinConditionSnapshot> =>
|
||||
api.post('/skincare', body);
|
||||
api.get(`/skincare/${id}`);
|
||||
export const createSkinSnapshot = (
|
||||
body: Record<string, unknown>,
|
||||
): Promise<SkinConditionSnapshot> => api.post("/skincare", body);
|
||||
export const updateSkinSnapshot = (
|
||||
id: string,
|
||||
body: Record<string, unknown>
|
||||
id: string,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<SkinConditionSnapshot> => api.patch(`/skincare/${id}`, body);
|
||||
export const deleteSkinSnapshot = (id: string): Promise<void> => api.del(`/skincare/${id}`);
|
||||
export const deleteSkinSnapshot = (id: string): Promise<void> =>
|
||||
api.del(`/skincare/${id}`);
|
||||
|
||||
export interface SkinPhotoAnalysisResponse {
|
||||
overall_state?: string;
|
||||
texture?: string;
|
||||
skin_type?: string;
|
||||
hydration_level?: number;
|
||||
sebum_tzone?: number;
|
||||
sebum_cheeks?: number;
|
||||
sensitivity_level?: number;
|
||||
barrier_state?: string;
|
||||
active_concerns?: string[];
|
||||
risks?: string[];
|
||||
priorities?: string[];
|
||||
notes?: string;
|
||||
overall_state?: string;
|
||||
texture?: string;
|
||||
skin_type?: string;
|
||||
hydration_level?: number;
|
||||
sebum_tzone?: number;
|
||||
sebum_cheeks?: number;
|
||||
sensitivity_level?: number;
|
||||
barrier_state?: string;
|
||||
active_concerns?: string[];
|
||||
risks?: string[];
|
||||
priorities?: string[];
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export async function analyzeSkinPhotos(files: File[]): Promise<SkinPhotoAnalysisResponse> {
|
||||
const body = new FormData();
|
||||
for (const file of files) body.append('photos', file);
|
||||
const base = browser ? '/api' : PUBLIC_API_BASE;
|
||||
const res = await fetch(`${base}/skincare/analyze-photos`, { method: 'POST', body });
|
||||
if (!res.ok) {
|
||||
const detail = await res.json().catch(() => ({ detail: res.statusText }));
|
||||
throw new Error(detail?.detail ?? res.statusText);
|
||||
}
|
||||
return res.json();
|
||||
export async function analyzeSkinPhotos(
|
||||
files: File[],
|
||||
): Promise<SkinPhotoAnalysisResponse> {
|
||||
const body = new FormData();
|
||||
for (const file of files) body.append("photos", file);
|
||||
const base = browser ? "/api" : PUBLIC_API_BASE;
|
||||
const res = await fetch(`${base}/skincare/analyze-photos`, {
|
||||
method: "POST",
|
||||
body,
|
||||
});
|
||||
if (!res.ok) {
|
||||
const detail = await res.json().catch(() => ({ detail: res.statusText }));
|
||||
throw new Error(detail?.detail ?? res.statusText);
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import Root, {
|
||||
type ButtonProps,
|
||||
type ButtonSize,
|
||||
type ButtonVariant,
|
||||
buttonVariants,
|
||||
type ButtonProps,
|
||||
type ButtonSize,
|
||||
type ButtonVariant,
|
||||
buttonVariants,
|
||||
} from "./button.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
type ButtonProps as Props,
|
||||
//
|
||||
Root as Button,
|
||||
buttonVariants,
|
||||
type ButtonProps,
|
||||
type ButtonSize,
|
||||
type ButtonVariant,
|
||||
Root,
|
||||
type ButtonProps as Props,
|
||||
//
|
||||
Root as Button,
|
||||
buttonVariants,
|
||||
type ButtonProps,
|
||||
type ButtonSize,
|
||||
type ButtonVariant,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,19 +7,19 @@ import Title from "./card-title.svelte";
|
|||
import Action from "./card-action.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Description,
|
||||
Footer,
|
||||
Header,
|
||||
Title,
|
||||
Action,
|
||||
//
|
||||
Root as Card,
|
||||
Content as CardContent,
|
||||
Description as CardDescription,
|
||||
Footer as CardFooter,
|
||||
Header as CardHeader,
|
||||
Title as CardTitle,
|
||||
Action as CardAction,
|
||||
Root,
|
||||
Content,
|
||||
Description,
|
||||
Footer,
|
||||
Header,
|
||||
Title,
|
||||
Action,
|
||||
//
|
||||
Root as Card,
|
||||
Content as CardContent,
|
||||
Description as CardDescription,
|
||||
Footer as CardFooter,
|
||||
Header as CardHeader,
|
||||
Title as CardTitle,
|
||||
Action as CardAction,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import Root from "./input.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Input,
|
||||
Root,
|
||||
//
|
||||
Root as Input,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import Root from "./label.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Label,
|
||||
Root,
|
||||
//
|
||||
Root as Label,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,27 +11,27 @@ import GroupHeading from "./select-group-heading.svelte";
|
|||
import Portal from "./select-portal.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Group,
|
||||
Label,
|
||||
Item,
|
||||
Content,
|
||||
Trigger,
|
||||
Separator,
|
||||
ScrollDownButton,
|
||||
ScrollUpButton,
|
||||
GroupHeading,
|
||||
Portal,
|
||||
//
|
||||
Root as Select,
|
||||
Group as SelectGroup,
|
||||
Label as SelectLabel,
|
||||
Item as SelectItem,
|
||||
Content as SelectContent,
|
||||
Trigger as SelectTrigger,
|
||||
Separator as SelectSeparator,
|
||||
ScrollDownButton as SelectScrollDownButton,
|
||||
ScrollUpButton as SelectScrollUpButton,
|
||||
GroupHeading as SelectGroupHeading,
|
||||
Portal as SelectPortal,
|
||||
Root,
|
||||
Group,
|
||||
Label,
|
||||
Item,
|
||||
Content,
|
||||
Trigger,
|
||||
Separator,
|
||||
ScrollDownButton,
|
||||
ScrollUpButton,
|
||||
GroupHeading,
|
||||
Portal,
|
||||
//
|
||||
Root as Select,
|
||||
Group as SelectGroup,
|
||||
Label as SelectLabel,
|
||||
Item as SelectItem,
|
||||
Content as SelectContent,
|
||||
Trigger as SelectTrigger,
|
||||
Separator as SelectSeparator,
|
||||
ScrollDownButton as SelectScrollDownButton,
|
||||
ScrollUpButton as SelectScrollUpButton,
|
||||
GroupHeading as SelectGroupHeading,
|
||||
Portal as SelectPortal,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import Root from "./separator.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Separator,
|
||||
Root,
|
||||
//
|
||||
Root as Separator,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,21 +8,21 @@ import Header from "./table-header.svelte";
|
|||
import Row from "./table-row.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Body,
|
||||
Caption,
|
||||
Cell,
|
||||
Footer,
|
||||
Head,
|
||||
Header,
|
||||
Row,
|
||||
//
|
||||
Root as Table,
|
||||
Body as TableBody,
|
||||
Caption as TableCaption,
|
||||
Cell as TableCell,
|
||||
Footer as TableFooter,
|
||||
Head as TableHead,
|
||||
Header as TableHeader,
|
||||
Row as TableRow,
|
||||
Root,
|
||||
Body,
|
||||
Caption,
|
||||
Cell,
|
||||
Footer,
|
||||
Head,
|
||||
Header,
|
||||
Row,
|
||||
//
|
||||
Root as Table,
|
||||
Body as TableBody,
|
||||
Caption as TableCaption,
|
||||
Cell as TableCell,
|
||||
Footer as TableFooter,
|
||||
Head as TableHead,
|
||||
Header as TableHeader,
|
||||
Row as TableRow,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import List from "./tabs-list.svelte";
|
|||
import Trigger from "./tabs-trigger.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
List,
|
||||
Trigger,
|
||||
//
|
||||
Root as Tabs,
|
||||
Content as TabsContent,
|
||||
List as TabsList,
|
||||
Trigger as TabsTrigger,
|
||||
Root,
|
||||
Content,
|
||||
List,
|
||||
Trigger,
|
||||
//
|
||||
Root as Tabs,
|
||||
Content as TabsContent,
|
||||
List as TabsList,
|
||||
Trigger as TabsTrigger,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,308 +1,335 @@
|
|||
// ─── Enums ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export type AbsorptionSpeed = 'very_fast' | 'fast' | 'moderate' | 'slow' | 'very_slow';
|
||||
export type BarrierState = 'intact' | 'mildly_compromised' | 'compromised';
|
||||
export type DayTime = 'am' | 'pm' | 'both';
|
||||
export type GroomingAction = 'shaving_razor' | 'shaving_oneblade' | 'dermarolling';
|
||||
export type AbsorptionSpeed =
|
||||
| "very_fast"
|
||||
| "fast"
|
||||
| "moderate"
|
||||
| "slow"
|
||||
| "very_slow";
|
||||
export type BarrierState = "intact" | "mildly_compromised" | "compromised";
|
||||
export type DayTime = "am" | "pm" | "both";
|
||||
export type GroomingAction =
|
||||
| "shaving_razor"
|
||||
| "shaving_oneblade"
|
||||
| "dermarolling";
|
||||
export type IngredientFunction =
|
||||
| 'humectant'
|
||||
| 'emollient'
|
||||
| 'occlusive'
|
||||
| 'exfoliant_aha'
|
||||
| 'exfoliant_bha'
|
||||
| 'exfoliant_pha'
|
||||
| 'retinoid'
|
||||
| 'antioxidant'
|
||||
| 'soothing'
|
||||
| 'barrier_support'
|
||||
| 'brightening'
|
||||
| 'anti_acne'
|
||||
| 'ceramide'
|
||||
| 'niacinamide'
|
||||
| 'sunscreen'
|
||||
| 'peptide'
|
||||
| 'hair_growth_stimulant'
|
||||
| 'prebiotic'
|
||||
| 'vitamin_c'
|
||||
| 'anti_aging';
|
||||
export type InteractionScope = 'same_step' | 'same_day' | 'same_period';
|
||||
export type MedicationKind = 'prescription' | 'otc' | 'supplement' | 'herbal' | 'other';
|
||||
export type OverallSkinState = 'excellent' | 'good' | 'fair' | 'poor';
|
||||
export type PartOfDay = 'am' | 'pm';
|
||||
export type PriceTier = 'budget' | 'mid' | 'premium' | 'luxury';
|
||||
| "humectant"
|
||||
| "emollient"
|
||||
| "occlusive"
|
||||
| "exfoliant_aha"
|
||||
| "exfoliant_bha"
|
||||
| "exfoliant_pha"
|
||||
| "retinoid"
|
||||
| "antioxidant"
|
||||
| "soothing"
|
||||
| "barrier_support"
|
||||
| "brightening"
|
||||
| "anti_acne"
|
||||
| "ceramide"
|
||||
| "niacinamide"
|
||||
| "sunscreen"
|
||||
| "peptide"
|
||||
| "hair_growth_stimulant"
|
||||
| "prebiotic"
|
||||
| "vitamin_c"
|
||||
| "anti_aging";
|
||||
export type InteractionScope = "same_step" | "same_day" | "same_period";
|
||||
export type MedicationKind =
|
||||
| "prescription"
|
||||
| "otc"
|
||||
| "supplement"
|
||||
| "herbal"
|
||||
| "other";
|
||||
export type OverallSkinState = "excellent" | "good" | "fair" | "poor";
|
||||
export type PartOfDay = "am" | "pm";
|
||||
export type PriceTier = "budget" | "mid" | "premium" | "luxury";
|
||||
export type ProductCategory =
|
||||
| 'cleanser'
|
||||
| 'toner'
|
||||
| 'essence'
|
||||
| 'serum'
|
||||
| 'moisturizer'
|
||||
| 'spf'
|
||||
| 'mask'
|
||||
| 'exfoliant'
|
||||
| 'hair_treatment'
|
||||
| 'tool'
|
||||
| 'spot_treatment'
|
||||
| 'oil';
|
||||
export type ResultFlag = 'N' | 'ABN' | 'POS' | 'NEG' | 'L' | 'H';
|
||||
| "cleanser"
|
||||
| "toner"
|
||||
| "essence"
|
||||
| "serum"
|
||||
| "moisturizer"
|
||||
| "spf"
|
||||
| "mask"
|
||||
| "exfoliant"
|
||||
| "hair_treatment"
|
||||
| "tool"
|
||||
| "spot_treatment"
|
||||
| "oil";
|
||||
export type ResultFlag = "N" | "ABN" | "POS" | "NEG" | "L" | "H";
|
||||
export type SkinConcern =
|
||||
| 'acne'
|
||||
| 'rosacea'
|
||||
| 'hyperpigmentation'
|
||||
| 'aging'
|
||||
| 'dehydration'
|
||||
| 'redness'
|
||||
| 'damaged_barrier'
|
||||
| 'pore_visibility'
|
||||
| 'uneven_texture'
|
||||
| 'hair_growth'
|
||||
| 'sebum_excess';
|
||||
export type SkinTexture = 'smooth' | 'rough' | 'flaky' | 'bumpy';
|
||||
export type SkinType = 'dry' | 'oily' | 'combination' | 'sensitive' | 'normal' | 'acne_prone';
|
||||
| "acne"
|
||||
| "rosacea"
|
||||
| "hyperpigmentation"
|
||||
| "aging"
|
||||
| "dehydration"
|
||||
| "redness"
|
||||
| "damaged_barrier"
|
||||
| "pore_visibility"
|
||||
| "uneven_texture"
|
||||
| "hair_growth"
|
||||
| "sebum_excess";
|
||||
export type SkinTexture = "smooth" | "rough" | "flaky" | "bumpy";
|
||||
export type SkinType =
|
||||
| "dry"
|
||||
| "oily"
|
||||
| "combination"
|
||||
| "sensitive"
|
||||
| "normal"
|
||||
| "acne_prone";
|
||||
export type StrengthLevel = 1 | 2 | 3;
|
||||
export type TextureType = 'watery' | 'gel' | 'emulsion' | 'cream' | 'oil' | 'balm' | 'foam' | 'fluid';
|
||||
export type TextureType =
|
||||
| "watery"
|
||||
| "gel"
|
||||
| "emulsion"
|
||||
| "cream"
|
||||
| "oil"
|
||||
| "balm"
|
||||
| "foam"
|
||||
| "fluid";
|
||||
// ─── Product types ───────────────────────────────────────────────────────────
|
||||
|
||||
export interface ActiveIngredient {
|
||||
name: string;
|
||||
percent?: number;
|
||||
functions: IngredientFunction[];
|
||||
strength_level?: StrengthLevel;
|
||||
irritation_potential?: StrengthLevel;
|
||||
name: string;
|
||||
percent?: number;
|
||||
functions: IngredientFunction[];
|
||||
strength_level?: StrengthLevel;
|
||||
irritation_potential?: StrengthLevel;
|
||||
}
|
||||
|
||||
export interface ProductEffectProfile {
|
||||
hydration_immediate: number;
|
||||
hydration_long_term: number;
|
||||
barrier_repair_strength: number;
|
||||
soothing_strength: number;
|
||||
exfoliation_strength: number;
|
||||
retinoid_strength: number;
|
||||
irritation_risk: number;
|
||||
comedogenic_risk: number;
|
||||
barrier_disruption_risk: number;
|
||||
dryness_risk: number;
|
||||
brightening_strength: number;
|
||||
anti_acne_strength: number;
|
||||
anti_aging_strength: number;
|
||||
hydration_immediate: number;
|
||||
hydration_long_term: number;
|
||||
barrier_repair_strength: number;
|
||||
soothing_strength: number;
|
||||
exfoliation_strength: number;
|
||||
retinoid_strength: number;
|
||||
irritation_risk: number;
|
||||
comedogenic_risk: number;
|
||||
barrier_disruption_risk: number;
|
||||
dryness_risk: number;
|
||||
brightening_strength: number;
|
||||
anti_acne_strength: number;
|
||||
anti_aging_strength: number;
|
||||
}
|
||||
|
||||
export interface ProductInteraction {
|
||||
target: string;
|
||||
scope: InteractionScope;
|
||||
reason?: string;
|
||||
target: string;
|
||||
scope: InteractionScope;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export interface ProductContext {
|
||||
safe_after_shaving?: boolean;
|
||||
safe_after_acids?: boolean;
|
||||
safe_after_retinoids?: boolean;
|
||||
safe_with_compromised_barrier?: boolean;
|
||||
low_uv_only?: boolean;
|
||||
safe_after_shaving?: boolean;
|
||||
safe_after_acids?: boolean;
|
||||
safe_after_retinoids?: boolean;
|
||||
safe_with_compromised_barrier?: boolean;
|
||||
low_uv_only?: boolean;
|
||||
}
|
||||
|
||||
export interface ProductInventory {
|
||||
id: string;
|
||||
product_id: string;
|
||||
is_opened: boolean;
|
||||
opened_at?: string;
|
||||
finished_at?: string;
|
||||
expiry_date?: string;
|
||||
current_weight_g?: number;
|
||||
last_weighed_at?: string;
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
product?: Product;
|
||||
id: string;
|
||||
product_id: string;
|
||||
is_opened: boolean;
|
||||
opened_at?: string;
|
||||
finished_at?: string;
|
||||
expiry_date?: string;
|
||||
current_weight_g?: number;
|
||||
last_weighed_at?: string;
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
product?: Product;
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
id: string;
|
||||
name: string;
|
||||
brand: string;
|
||||
line_name?: string;
|
||||
sku?: string;
|
||||
url?: string;
|
||||
barcode?: string;
|
||||
category: ProductCategory;
|
||||
recommended_time: DayTime;
|
||||
texture?: TextureType;
|
||||
absorption_speed?: AbsorptionSpeed;
|
||||
leave_on: boolean;
|
||||
price_tier?: PriceTier;
|
||||
size_ml?: number;
|
||||
full_weight_g?: number;
|
||||
empty_weight_g?: number;
|
||||
pao_months?: number;
|
||||
inci: string[];
|
||||
actives?: ActiveIngredient[];
|
||||
recommended_for: SkinType[];
|
||||
targets: SkinConcern[];
|
||||
contraindications: string[];
|
||||
usage_notes?: string;
|
||||
fragrance_free?: boolean;
|
||||
essential_oils_free?: boolean;
|
||||
alcohol_denat_free?: boolean;
|
||||
pregnancy_safe?: boolean;
|
||||
product_effect_profile: ProductEffectProfile;
|
||||
ph_min?: number;
|
||||
ph_max?: number;
|
||||
incompatible_with?: ProductInteraction[];
|
||||
synergizes_with?: string[];
|
||||
context_rules?: ProductContext;
|
||||
min_interval_hours?: number;
|
||||
max_frequency_per_week?: number;
|
||||
is_medication: boolean;
|
||||
is_tool: boolean;
|
||||
needle_length_mm?: number;
|
||||
personal_tolerance_notes?: string;
|
||||
personal_repurchase_intent?: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
inventory: ProductInventory[];
|
||||
id: string;
|
||||
name: string;
|
||||
brand: string;
|
||||
line_name?: string;
|
||||
sku?: string;
|
||||
url?: string;
|
||||
barcode?: string;
|
||||
category: ProductCategory;
|
||||
recommended_time: DayTime;
|
||||
texture?: TextureType;
|
||||
absorption_speed?: AbsorptionSpeed;
|
||||
leave_on: boolean;
|
||||
price_tier?: PriceTier;
|
||||
size_ml?: number;
|
||||
full_weight_g?: number;
|
||||
empty_weight_g?: number;
|
||||
pao_months?: number;
|
||||
inci: string[];
|
||||
actives?: ActiveIngredient[];
|
||||
recommended_for: SkinType[];
|
||||
targets: SkinConcern[];
|
||||
contraindications: string[];
|
||||
usage_notes?: string;
|
||||
fragrance_free?: boolean;
|
||||
essential_oils_free?: boolean;
|
||||
alcohol_denat_free?: boolean;
|
||||
pregnancy_safe?: boolean;
|
||||
product_effect_profile: ProductEffectProfile;
|
||||
ph_min?: number;
|
||||
ph_max?: number;
|
||||
incompatible_with?: ProductInteraction[];
|
||||
synergizes_with?: string[];
|
||||
context_rules?: ProductContext;
|
||||
min_interval_hours?: number;
|
||||
max_frequency_per_week?: number;
|
||||
is_medication: boolean;
|
||||
is_tool: boolean;
|
||||
needle_length_mm?: number;
|
||||
personal_tolerance_notes?: string;
|
||||
personal_repurchase_intent?: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
inventory: ProductInventory[];
|
||||
}
|
||||
|
||||
// ─── Routine types ───────────────────────────────────────────────────────────
|
||||
|
||||
export interface RoutineStep {
|
||||
id: string;
|
||||
routine_id: string;
|
||||
product_id?: string;
|
||||
order_index: number;
|
||||
action_type?: GroomingAction;
|
||||
action_notes?: string;
|
||||
dose?: string;
|
||||
region?: string;
|
||||
product?: Product;
|
||||
id: string;
|
||||
routine_id: string;
|
||||
product_id?: string;
|
||||
order_index: number;
|
||||
action_type?: GroomingAction;
|
||||
action_notes?: string;
|
||||
dose?: string;
|
||||
region?: string;
|
||||
product?: Product;
|
||||
}
|
||||
|
||||
export interface Routine {
|
||||
id: string;
|
||||
routine_date: string;
|
||||
part_of_day: PartOfDay;
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
steps?: RoutineStep[];
|
||||
id: string;
|
||||
routine_date: string;
|
||||
part_of_day: PartOfDay;
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
steps?: RoutineStep[];
|
||||
}
|
||||
|
||||
export interface GroomingSchedule {
|
||||
id: string;
|
||||
day_of_week: number;
|
||||
action: GroomingAction;
|
||||
notes?: string;
|
||||
id: string;
|
||||
day_of_week: number;
|
||||
action: GroomingAction;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface SuggestedStep {
|
||||
product_id?: string;
|
||||
action_type?: GroomingAction;
|
||||
action_notes?: string;
|
||||
dose?: string;
|
||||
region?: string;
|
||||
product_id?: string;
|
||||
action_type?: GroomingAction;
|
||||
action_notes?: string;
|
||||
dose?: string;
|
||||
region?: string;
|
||||
}
|
||||
|
||||
export interface RoutineSuggestion {
|
||||
steps: SuggestedStep[];
|
||||
reasoning: string;
|
||||
steps: SuggestedStep[];
|
||||
reasoning: string;
|
||||
}
|
||||
|
||||
export interface DayPlan {
|
||||
date: string;
|
||||
am_steps: SuggestedStep[];
|
||||
pm_steps: SuggestedStep[];
|
||||
reasoning: string;
|
||||
date: string;
|
||||
am_steps: SuggestedStep[];
|
||||
pm_steps: SuggestedStep[];
|
||||
reasoning: string;
|
||||
}
|
||||
|
||||
export interface BatchSuggestion {
|
||||
days: DayPlan[];
|
||||
overall_reasoning: string;
|
||||
days: DayPlan[];
|
||||
overall_reasoning: string;
|
||||
}
|
||||
|
||||
// ─── Shopping suggestion types ───────────────────────────────────────────────
|
||||
|
||||
export interface ProductSuggestion {
|
||||
category: string;
|
||||
product_type: string;
|
||||
key_ingredients: string[];
|
||||
target_concerns: string[];
|
||||
why_needed: string;
|
||||
recommended_time: string;
|
||||
frequency: string;
|
||||
category: string;
|
||||
product_type: string;
|
||||
key_ingredients: string[];
|
||||
target_concerns: string[];
|
||||
why_needed: string;
|
||||
recommended_time: string;
|
||||
frequency: string;
|
||||
}
|
||||
|
||||
export interface ShoppingSuggestionResponse {
|
||||
suggestions: ProductSuggestion[];
|
||||
reasoning: string;
|
||||
suggestions: ProductSuggestion[];
|
||||
reasoning: string;
|
||||
}
|
||||
|
||||
// ─── Health types ────────────────────────────────────────────────────────────
|
||||
|
||||
export interface MedicationUsage {
|
||||
record_id: string;
|
||||
medication_record_id: string;
|
||||
dose_value?: number;
|
||||
dose_unit?: string;
|
||||
frequency?: string;
|
||||
schedule_text?: string;
|
||||
as_needed: boolean;
|
||||
valid_from: string;
|
||||
valid_to?: string;
|
||||
source_file?: string;
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
record_id: string;
|
||||
medication_record_id: string;
|
||||
dose_value?: number;
|
||||
dose_unit?: string;
|
||||
frequency?: string;
|
||||
schedule_text?: string;
|
||||
as_needed: boolean;
|
||||
valid_from: string;
|
||||
valid_to?: string;
|
||||
source_file?: string;
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface MedicationEntry {
|
||||
record_id: string;
|
||||
kind: MedicationKind;
|
||||
product_name: string;
|
||||
active_substance?: string;
|
||||
formulation?: string;
|
||||
route?: string;
|
||||
source_file?: string;
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
usage_history: MedicationUsage[];
|
||||
record_id: string;
|
||||
kind: MedicationKind;
|
||||
product_name: string;
|
||||
active_substance?: string;
|
||||
formulation?: string;
|
||||
route?: string;
|
||||
source_file?: string;
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
usage_history: MedicationUsage[];
|
||||
}
|
||||
|
||||
export interface LabResult {
|
||||
record_id: string;
|
||||
collected_at: string;
|
||||
test_code: string;
|
||||
test_name_original?: string;
|
||||
test_name_loinc?: string;
|
||||
value_num?: number;
|
||||
value_text?: string;
|
||||
value_bool?: boolean;
|
||||
unit_original?: string;
|
||||
unit_ucum?: string;
|
||||
ref_low?: number;
|
||||
ref_high?: number;
|
||||
ref_text?: string;
|
||||
flag?: ResultFlag;
|
||||
lab?: string;
|
||||
source_file?: string;
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
record_id: string;
|
||||
collected_at: string;
|
||||
test_code: string;
|
||||
test_name_original?: string;
|
||||
test_name_loinc?: string;
|
||||
value_num?: number;
|
||||
value_text?: string;
|
||||
value_bool?: boolean;
|
||||
unit_original?: string;
|
||||
unit_ucum?: string;
|
||||
ref_low?: number;
|
||||
ref_high?: number;
|
||||
ref_text?: string;
|
||||
flag?: ResultFlag;
|
||||
lab?: string;
|
||||
source_file?: string;
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
// ─── Skin types ──────────────────────────────────────────────────────────────
|
||||
|
||||
export interface SkinConditionSnapshot {
|
||||
id: string;
|
||||
snapshot_date: string;
|
||||
overall_state?: OverallSkinState;
|
||||
skin_type?: SkinType;
|
||||
texture?: SkinTexture;
|
||||
hydration_level?: number;
|
||||
sebum_tzone?: number;
|
||||
sebum_cheeks?: number;
|
||||
sensitivity_level?: number;
|
||||
barrier_state?: BarrierState;
|
||||
active_concerns: SkinConcern[];
|
||||
risks: string[];
|
||||
priorities: string[];
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
id: string;
|
||||
snapshot_date: string;
|
||||
overall_state?: OverallSkinState;
|
||||
skin_type?: SkinType;
|
||||
texture?: SkinTexture;
|
||||
hydration_level?: number;
|
||||
sebum_tzone?: number;
|
||||
sebum_cheeks?: number;
|
||||
sensitivity_level?: number;
|
||||
barrier_state?: BarrierState;
|
||||
active_concerns: SkinConcern[];
|
||||
risks: string[];
|
||||
priorities: string[];
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue