feat: add mobile number change functionality for doctors and clinics

- Implemented PATCH endpoints for changing the login mobile number of doctors and clinics.
- Added ChangeLoginMobileModal component for handling mobile number updates in the UI.
- Updated ClinicsPage and DoctorsPage to include buttons for changing mobile numbers.
- Enhanced AdminApiController to manage mobile number changes with validation.
- Created tests to ensure proper functionality and validation for mobile number changes.
- Updated API documentation to reflect new endpoints and their usage.
This commit is contained in:
hamed
2026-07-25 21:40:38 +03:30
parent 3cc4a59459
commit 50ba7e44ff
13 changed files with 520 additions and 38 deletions
+17
View File
@@ -7,6 +7,7 @@ import {
MagnifyingGlassIcon,
PlusIcon,
BuildingOffice2Icon,
DevicePhoneMobileIcon,
} from '@heroicons/react/24/outline';
import { toast } from 'sonner';
import { useForm } from 'react-hook-form';
@@ -21,6 +22,7 @@ import Portal from '../components/ui/Portal';
import { formatDate, formatNumber, iranMobileSchema } from '../lib/utils';
import Pagination from '../components/ui/Pagination';
import ConfirmDialog from '../components/ui/ConfirmDialog';
import ChangeLoginMobileModal from '../components/ChangeLoginMobileModal';
import { latinDigitsField } from '../lib/forms';
const HUES_LIST = [256, 205, 162, 295, 272];
@@ -41,6 +43,7 @@ export default function ClinicsPage() {
const [search, setSearch] = useState('');
const [statusFilter, setStatusFilter] = useState('');
const [deleteTarget, setDeleteTarget] = useState<Clinic | null>(null);
const [mobileTarget, setMobileTarget] = useState<{ uuid: string; name: string; mobile_number?: string | null } | null>(null);
const [addOpen, setAddOpen] = useState(false);
const limit = 15;
@@ -217,6 +220,13 @@ export default function ClinicsPage() {
>
<EyeIcon style={{ width: 15, height: 15 }} />
</button>
<button
className="mini-btn"
title="تغییر شماره ورود"
onClick={() => setMobileTarget({ uuid: c.uuid, name: c.name, mobile_number: c.owner_mobile })}
>
<DevicePhoneMobileIcon style={{ width: 15, height: 15 }} />
</button>
<button
className={`mini-btn${c.is_active ? '' : ' active'}`}
title={c.is_active ? 'غیرفعال کردن' : 'فعال کردن'}
@@ -303,6 +313,13 @@ export default function ClinicsPage() {
onConfirm={() => deleteTarget && deleteMutation.mutate(deleteTarget)}
onCancel={() => setDeleteTarget(null)}
/>
<ChangeLoginMobileModal
target={mobileTarget}
resource="clinic"
queryKey={['clinics']}
onClose={() => setMobileTarget(null)}
/>
</div>
);
}
+14 -1
View File
@@ -3,7 +3,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
import {
MagnifyingGlassIcon, PlusIcon, EyeIcon, TrashIcon, ArrowPathIcon,
CheckCircleIcon, XCircleIcon, TableCellsIcon, Squares2X2Icon,
CheckCircleIcon, XCircleIcon, TableCellsIcon, Squares2X2Icon, DevicePhoneMobileIcon,
} from '@heroicons/react/24/outline';
import { XMarkIcon } from '@heroicons/react/24/outline';
import { toast } from 'sonner';
@@ -11,6 +11,7 @@ import { api } from '../lib/api';
import type { ApiResponse, PaginatedResponse } from '../lib/api';
import { formatDate, formatNumber, displayDoctorName } from '../lib/utils';
import ConfirmDialog from '../components/ui/ConfirmDialog';
import ChangeLoginMobileModal from '../components/ChangeLoginMobileModal';
import Pagination from '../components/ui/Pagination';
import SearchableSelect from '../components/ui/SearchableSelect';
import { useAuthStore } from '../stores/authStore';
@@ -111,6 +112,7 @@ export default function DoctorsPage() {
const [cityId, setCityId] = useState('');
const [view, setView] = useState<'table' | 'grid'>('table');
const [deleteTarget, setDeleteTarget] = useState<AdminDoctor | null>(null);
const [mobileTarget, setMobileTarget] = useState<{ uuid: string; name: string; mobile_number?: string | null } | null>(null);
useEffect(() => {
const t = setTimeout(() => { setSearch(searchInput); setPage(1); }, 350);
@@ -430,6 +432,10 @@ export default function DoctorsPage() {
onClick={() => navigate(`/admin/doctors/${doc.uuid}`)}>
<EyeIcon style={{ width: 16, height: 16 }} />
</button>
<button className="mini-btn" title="تغییر شماره ورود"
onClick={() => setMobileTarget({ uuid: doc.uuid, name: doc.name, mobile_number: doc.mobile })}>
<DevicePhoneMobileIcon style={{ width: 16, height: 16 }} />
</button>
<button className="mini-btn" title={doc.is_active ? 'غیرفعال کردن' : 'فعال‌سازی'}
onClick={() => toggleMut.mutate(doc.uuid)} disabled={toggleMut.isPending}>
{doc.is_active
@@ -517,6 +523,13 @@ export default function DoctorsPage() {
onCancel={() => setDeleteTarget(null)}
/>
<ChangeLoginMobileModal
target={mobileTarget}
resource="doctors"
queryKey={['doctors']}
onClose={() => setMobileTarget(null)}
/>
</div>
);
}