feat: create patient record without prior signup

Extend POST /api/v1/patient to resolve user by uuid or mobile, and
create a new ROLE_USER (name + optional national code, no password)
when no user exists. Admin modal shows a new-patient form on lookup miss.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-06-23 15:20:56 +03:30
co-authored by Claude Opus 4.8
parent 59fb590d65
commit 51d7e24e04
4 changed files with 231 additions and 23 deletions
+108 -8
View File
@@ -142,6 +142,8 @@ function MyPatientsPageInner() {
} | null>(null);
const [recordNationalCode, setRecordNationalCode] = useState("");
const [searchError, setSearchError] = useState("");
const [notFound, setNotFound] = useState(false);
const [newPatientName, setNewPatientName] = useState("");
const mobileInputRef = useRef<HTMLInputElement>(null);
const servicesTotal = selectedServices.reduce(
@@ -285,22 +287,28 @@ function MyPatientsPageInner() {
),
onSuccess: (res: any) => {
setFoundUser(res?.data);
setRecordNationalCode(res?.data?.national_code ?? "");
setNotFound(false);
setSearchError("");
},
onError: () => {
setFoundUser(null);
setSearchError("کاربری با این شماره در سیستم یافت نشد");
setNotFound(true);
setSearchError("");
},
});
const createRecordMut = useMutation({
mutationFn: (userUuid: string) =>
api.post("/api/v1/patient", { user_uuid: userUuid }),
mutationFn: (body: Record<string, unknown>) =>
api.post("/api/v1/patient", body),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ["patients"] });
setCreateRecordOpen(false);
setSearchMobile("");
setFoundUser(null);
setRecordNationalCode("");
setNotFound(false);
setNewPatientName("");
setSearchError("");
toast.success("پرونده بیمار ایجاد شد");
},
@@ -315,13 +323,32 @@ function MyPatientsPageInner() {
}
setSearchError("");
setFoundUser(null);
setNotFound(false);
searchUserMut.mutate(digits);
};
const handleCreateRecord = () => {
if (foundUser) {
createRecordMut.mutate({
user_uuid: foundUser.uuid,
national_code: recordNationalCode || undefined,
});
} else if (notFound) {
createRecordMut.mutate({
mobile: searchMobile.replace(/\D/g, ""),
name: newPatientName.trim(),
national_code: recordNationalCode || undefined,
});
}
};
const handleCreateRecordClose = () => {
setCreateRecordOpen(false);
setSearchMobile("");
setFoundUser(null);
setRecordNationalCode("");
setNotFound(false);
setNewPatientName("");
setSearchError("");
};
@@ -493,12 +520,11 @@ function MyPatientsPageInner() {
<button
className="btn primary sm"
disabled={
!foundUser || createRecordMut.isPending
}
onClick={() =>
foundUser &&
createRecordMut.mutate(foundUser.uuid)
createRecordMut.isPending ||
(!foundUser &&
!(notFound && newPatientName.trim()))
}
onClick={handleCreateRecord}
>
{createRecordMut.isPending
? "در حال ایجاد..."
@@ -685,6 +711,80 @@ function MyPatientsPageInner() {
</span>
</div>
)}
{notFound && (
<div
style={{
marginTop: 16,
padding: 14,
border: "1px solid var(--border)",
borderRadius: "var(--r)",
background: "var(--surface)",
display: "flex",
flexDirection: "column",
gap: 12,
}}
>
<div
style={{
fontSize: 12.5,
color: "var(--text-3)",
lineHeight: 1.7,
}}
>
کاربری با این شماره یافت نشد. برای ثبت بیمار جدید،
نام را وارد کنید.
</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 5,
}}
>
<label
style={{ fontSize: 12.5, fontWeight: 600 }}
>
نام و نام خانوادگی *
</label>
<input
className="input"
value={newPatientName}
onChange={(e) =>
setNewPatientName(e.target.value)
}
placeholder="مثال: محمد محمدی"
autoFocus
/>
</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 5,
}}
>
<label
style={{ fontSize: 12.5, fontWeight: 600 }}
>
کد ملی (اختیاری)
</label>
<input
className="input"
dir="ltr"
inputMode="numeric"
maxLength={10}
value={recordNationalCode}
onChange={(e) =>
setRecordNationalCode(
e.target.value.replace(/\D/g, ""),
)
}
placeholder="۱۰ رقم"
/>
</div>
</div>
)}
</Modal>
</>
);