Files
duocthu/apps/web/app/_components/ProfileForm.tsx
T

155 lines
5.4 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import type { PatientProfile } from "@duoc-thu/shared-types";
import { getPatientProfile, savePatientProfile } from "@duoc-thu/api-client";
const EMPTY: PatientProfile = {
ageText: null,
weightKg: null,
renalFunction: null,
hepaticFunction: null,
knownAllergies: null,
};
/** Optional per-account clinical context. Nothing here is required — an
* empty profile behaves exactly like no profile: the chatbot keeps asking
* age/weight per turn, same as an anonymous session. Only a logged-in user
* ever sees this page (`AccountMenu` is the only link to it). */
export function ProfileForm() {
const [profile, setProfile] = useState<PatientProfile>(EMPTY);
const [loaded, setLoaded] = useState(false);
const [saved, setSaved] = useState(false);
const [error, setError] = useState<string | null>(null);
const [pending, setPending] = useState(false);
useEffect(() => {
getPatientProfile()
.then((p) => p && setProfile(p))
.finally(() => setLoaded(true));
}, []);
async function onSubmit(event: React.FormEvent) {
event.preventDefault();
setError(null);
setSaved(false);
setPending(true);
try {
const result = await savePatientProfile(profile);
setProfile(result);
setSaved(true);
} catch {
setError("Không lưu được lúc này. Vui lòng thử lại.");
} finally {
setPending(false);
}
}
if (!loaded) return null;
return (
<div className="flex flex-1 items-center justify-center p-6">
<form
onSubmit={onSubmit}
className="w-full max-w-sm space-y-4 rounded-2xl border border-border-subtle bg-surface p-6 shadow-sm"
>
<div>
<h1 className="text-lg font-bold text-txt-primary">
Hồ bệnh nhân
</h1>
<p className="mt-1 text-xs text-txt-secondary">
Điền sẵn đ không phải nhắc lại tuổi/cân nặng mỗi lượt hỏi. Bỏ
trống mục nào thì chatbot vẫn hỏi lại mục đó như bình thường.
</p>
</div>
<label className="block text-xs font-semibold text-txt-secondary">
Tuổi
<input
type="text"
value={profile.ageText ?? ""}
onChange={(e) => setProfile({ ...profile, ageText: e.target.value })}
placeholder="VD: 8 tuổi, sơ sinh 2 tháng"
className="mt-1 w-full rounded-lg border border-border-subtle bg-surface-hover px-3 py-2 text-sm text-txt-primary"
/>
</label>
<label className="block text-xs font-semibold text-txt-secondary">
Cân nặng (kg)
<input
type="number"
min={0}
max={500}
value={profile.weightKg ?? ""}
onChange={(e) =>
setProfile({
...profile,
weightKg: e.target.value === "" ? null : Number(e.target.value),
})
}
className="mt-1 w-full rounded-lg border border-border-subtle bg-surface-hover px-3 py-2 text-sm text-txt-primary"
/>
</label>
<label className="block text-xs font-semibold text-txt-secondary">
Chức năng thận
<input
type="text"
value={profile.renalFunction ?? ""}
onChange={(e) =>
setProfile({ ...profile, renalFunction: e.target.value })
}
placeholder="VD: suy thận độ 2, Clcr 45 ml/phút"
className="mt-1 w-full rounded-lg border border-border-subtle bg-surface-hover px-3 py-2 text-sm text-txt-primary"
/>
</label>
<label className="block text-xs font-semibold text-txt-secondary">
Chức năng gan
<input
type="text"
value={profile.hepaticFunction ?? ""}
onChange={(e) =>
setProfile({ ...profile, hepaticFunction: e.target.value })
}
placeholder="VD: suy gan nhẹ"
className="mt-1 w-full rounded-lg border border-border-subtle bg-surface-hover px-3 py-2 text-sm text-txt-primary"
/>
</label>
<label className="block text-xs font-semibold text-txt-secondary">
Dị ng đã biết
<input
type="text"
value={profile.knownAllergies ?? ""}
onChange={(e) =>
setProfile({ ...profile, knownAllergies: e.target.value })
}
placeholder="VD: dị ứng penicillin"
className="mt-1 w-full rounded-lg border border-border-subtle bg-surface-hover px-3 py-2 text-sm text-txt-primary"
/>
</label>
{error && <p className="text-xs text-red-500">{error}</p>}
{saved && !error && (
<p className="text-xs text-emerald-600">Đã lưu.</p>
)}
<div className="flex items-center gap-3">
<button
type="submit"
disabled={pending}
className="rounded-lg bg-accent-primary px-4 py-2 text-sm font-semibold text-txt-inverse disabled:opacity-60"
>
{pending ? "Đang lưu..." : "Lưu"}
</button>
<Link href="/" className="text-xs text-txt-secondary hover:underline">
Về trang tra cứu
</Link>
</div>
</form>
</div>
);
}