feat(patient): add search user functionality by mobile number
This commit is contained in:
@@ -2305,6 +2305,7 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
|
||||
onSuccess: () => {
|
||||
toast.success('آدرس حذف شد'); setDeletingAddrId(null);
|
||||
qc.invalidateQueries({ queryKey: ['doctor-detail', uuid] });
|
||||
qc.invalidateQueries({ queryKey: ['available-locations', uuid] });
|
||||
},
|
||||
onError: (e: Error) => toast.error(e.message),
|
||||
});
|
||||
@@ -2764,7 +2765,10 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
|
||||
onClose={() => { setAddrModalOpen(false); setEditingAddr(null); }}
|
||||
existing={editingAddr}
|
||||
doctorUuid={uuid}
|
||||
onSaved={() => qc.invalidateQueries({ queryKey: ['doctor-detail', uuid] })}
|
||||
onSaved={() => {
|
||||
qc.invalidateQueries({ queryKey: ['doctor-detail', uuid] });
|
||||
qc.invalidateQueries({ queryKey: ['available-locations', uuid] });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import React, { useState, useCallback, useRef } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
MagnifyingGlassIcon, PlusIcon, ChevronRightIcon, PencilIcon, PhoneIcon,
|
||||
FolderOpenIcon, UsersIcon,
|
||||
FolderOpenIcon, UsersIcon, UserPlusIcon, CheckCircleIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
@@ -66,6 +66,12 @@ function MyPatientsPageInner() {
|
||||
const [sectionUuid, setSectionUuid] = useState('');
|
||||
const [itemUuid, setItemUuid] = useState('');
|
||||
|
||||
const [createRecordOpen, setCreateRecordOpen] = useState(false);
|
||||
const [searchMobile, setSearchMobile] = useState('');
|
||||
const [foundUser, setFoundUser] = useState<{ uuid: string; name: string | null; mobile: string } | null>(null);
|
||||
const [searchError, setSearchError] = useState('');
|
||||
const mobileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const servicesTotal = selectedServices.reduce((sum, s) => sum + s.price_rials, 0);
|
||||
const finalPrice = calcFinalPrice(Number(watchVisit), Number(watchBase), Number(watchSupp), servicesTotal);
|
||||
|
||||
@@ -126,6 +132,40 @@ function MyPatientsPageInner() {
|
||||
onError: (e: any) => toast.error(e.message),
|
||||
});
|
||||
|
||||
const searchUserMut = useMutation({
|
||||
mutationFn: (mobile: string) => api.get(`/api/v1/patient/search-user?mobile=${encodeURIComponent(mobile)}`),
|
||||
onSuccess: (res: any) => { setFoundUser(res?.data); setSearchError(''); },
|
||||
onError: () => { setFoundUser(null); setSearchError('کاربری با این شماره در سیستم یافت نشد'); },
|
||||
});
|
||||
|
||||
const createRecordMut = useMutation({
|
||||
mutationFn: (userUuid: string) => api.post('/api/v1/patient', { user_uuid: userUuid }),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['patients'] });
|
||||
setCreateRecordOpen(false);
|
||||
setSearchMobile('');
|
||||
setFoundUser(null);
|
||||
setSearchError('');
|
||||
toast.success('پرونده بیمار ایجاد شد');
|
||||
},
|
||||
onError: (e: any) => toast.error(e.message),
|
||||
});
|
||||
|
||||
const handleSearchMobile = () => {
|
||||
const digits = searchMobile.replace(/\D/g, '');
|
||||
if (!/^09\d{9}$/.test(digits)) { setSearchError('شماره موبایل معتبر نیست'); return; }
|
||||
setSearchError('');
|
||||
setFoundUser(null);
|
||||
searchUserMut.mutate(digits);
|
||||
};
|
||||
|
||||
const handleCreateRecordClose = () => {
|
||||
setCreateRecordOpen(false);
|
||||
setSearchMobile('');
|
||||
setFoundUser(null);
|
||||
setSearchError('');
|
||||
};
|
||||
|
||||
const handleAddService = () => {
|
||||
if (!itemUuid) return;
|
||||
const found = itemsData?.data?.find((i) => i.uuid === itemUuid);
|
||||
@@ -192,7 +232,16 @@ function MyPatientsPageInner() {
|
||||
if (!selectedRecord) {
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="پرونده بیماران" description="مراجعهکنندگان ثبتشده شما" />
|
||||
<PageHeader
|
||||
title="پرونده بیماران"
|
||||
description="مراجعهکنندگان ثبتشده شما"
|
||||
action={
|
||||
<button className="btn primary sm" onClick={() => setCreateRecordOpen(true)}>
|
||||
<UserPlusIcon style={{ width: 16 }} />
|
||||
پرونده جدید
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div style={{
|
||||
@@ -234,6 +283,79 @@ function MyPatientsPageInner() {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Modal ایجاد پرونده دستی */}
|
||||
<Modal
|
||||
open={createRecordOpen}
|
||||
onClose={handleCreateRecordClose}
|
||||
title="ایجاد پرونده بیمار"
|
||||
size="sm"
|
||||
footer={
|
||||
<>
|
||||
<button className="btn ghost sm" onClick={handleCreateRecordClose}>انصراف</button>
|
||||
<button
|
||||
className="btn primary sm"
|
||||
disabled={!foundUser || createRecordMut.isPending}
|
||||
onClick={() => foundUser && createRecordMut.mutate(foundUser.uuid)}
|
||||
>
|
||||
{createRecordMut.isPending ? 'در حال ایجاد...' : 'ایجاد پرونده'}
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="field">
|
||||
<label>شماره موبایل بیمار</label>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<input
|
||||
ref={mobileInputRef}
|
||||
value={searchMobile}
|
||||
placeholder="09123456789"
|
||||
dir="ltr"
|
||||
inputMode="numeric"
|
||||
maxLength={11}
|
||||
autoFocus
|
||||
style={{ flex: 1, ...(searchError ? { borderColor: 'var(--danger)' } : {}) }}
|
||||
onChange={(e) => {
|
||||
const digits = e.target.value.replace(/\D/g, '');
|
||||
setSearchMobile(digits);
|
||||
setFoundUser(null);
|
||||
setSearchError('');
|
||||
}}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter') handleSearchMobile(); }}
|
||||
/>
|
||||
<button
|
||||
className="btn sm"
|
||||
onClick={handleSearchMobile}
|
||||
disabled={searchUserMut.isPending}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
{searchUserMut.isPending ? '...' : 'جستجو'}
|
||||
</button>
|
||||
</div>
|
||||
{searchError && <span className="field-error">{searchError}</span>}
|
||||
</div>
|
||||
|
||||
{foundUser && (
|
||||
<div style={{
|
||||
marginTop: 16,
|
||||
display: 'flex', alignItems: 'center', gap: 12,
|
||||
padding: '12px 14px',
|
||||
background: 'var(--success-subtle, oklch(0.97 0.03 150))',
|
||||
border: '1px solid oklch(0.88 0.07 150)',
|
||||
borderRadius: 'var(--r)',
|
||||
}}>
|
||||
<CheckCircleIcon style={{ width: 20, color: 'var(--success, #16a34a)', flexShrink: 0 }} />
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, fontSize: 14 }}>{foundUser.name ?? 'بدون نام'}</div>
|
||||
<div style={{ fontSize: 12.5, color: 'var(--text-3)', direction: 'ltr', marginTop: 2 }}>{foundUser.mobile}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p style={{ fontSize: 12.5, color: 'var(--text-3)', marginTop: 16, lineHeight: 1.7 }}>
|
||||
بیمار باید قبلاً در سیستم ثبتنام کرده باشد. با شماره موبایل جستجو کنید، سپس پرونده ایجاد کنید.
|
||||
</p>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user