"use client"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { useMutation } from "@tanstack/react-query"; import { useState } from "react"; import { Heart, Stethoscope, ClipboardList, Loader2, RotateCcw, Activity, } from "lucide-react"; import { useT } from "@/i18n/context"; import { api, type PredictRequest, type PredictResponse } from "@/lib/api"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Ticket } from "@/components/ticket/ticket"; const numberOrEmpty = z .union([z.string(), z.number()]) .transform((v) => { if (typeof v === "number") return v; if (v === "" || v == null) return undefined; const n = Number(v); return Number.isFinite(n) ? n : undefined; }) .optional(); const requiredNumber = z .union([z.string(), z.number()]) .transform((v) => Number(v)) .refine((n) => Number.isFinite(n), { message: "Invalid number" }); const schema = z.object({ patient_id: z.string().max(64).optional(), age: requiredNumber.refine((n) => n >= 1 && n <= 120, "Age out of range"), sex: z.enum(["M", "F"]), resting_bp: numberOrEmpty, cholesterol: numberOrEmpty, max_hr: requiredNumber.refine((n) => n >= 40 && n <= 240, "HR out of range"), fasting_bs: z.enum(["0", "1"]), exercise_angina: z.enum(["Y", "N"]), chest_pain_type: z.enum(["ASY", "ATA", "NAP", "TA"]), resting_ecg: z.enum(["Normal", "ST", "LVH"]).optional(), st_slope: z.enum(["Up", "Flat", "Down"]), oldpeak: numberOrEmpty, }); type FormValues = z.input; type FormOutput = z.output; const DEFAULTS: Partial = { patient_id: "", sex: "M", fasting_bs: "0", exercise_angina: "N", chest_pain_type: "ASY", resting_ecg: "Normal", st_slope: "Flat", }; export function EvaluationForm() { const { dict } = useT(); const [result, setResult] = useState<{ response: PredictResponse; snapshot: FormOutput; } | null>(null); const { register, handleSubmit, reset, setValue, watch, formState: { errors }, } = useForm({ resolver: zodResolver(schema), defaultValues: DEFAULTS, }); const mutation = useMutation({ mutationFn: (values: PredictRequest) => api.predict(values), }); const onSubmit = handleSubmit(async (values) => { const payload: PredictRequest = { patient_id: values.patient_id || null, age: values.age, sex: values.sex, chest_pain_type: values.chest_pain_type, max_hr: values.max_hr, fasting_bs: Number(values.fasting_bs) as 0 | 1, exercise_angina: values.exercise_angina, st_slope: values.st_slope, resting_bp: values.resting_bp ?? null, cholesterol: values.cholesterol ?? null, resting_ecg: values.resting_ecg ?? null, oldpeak: values.oldpeak ?? null, }; const response = await mutation.mutateAsync(payload); setResult({ response, snapshot: values }); }); if (result) { return ( { setResult(null); reset(DEFAULTS); }} /> ); } return (
} label={dict.form.sections.demographics} />
} label={dict.form.sections.vitals} />
} label={dict.form.sections.symptoms} />
{mutation.error && (
{dict.result.error}
)}
); } function SectionTitle({ icon, label, }: { icon: React.ReactNode; label: string; }) { return (
{icon}
{label}
); } function Field({ label, error, children, }: { label: string; error?: string; children: React.ReactNode; }) { return (
{children} {error &&

{error}

}
); }