feat(doctors): add city and province filters for doctor listing
This commit is contained in:
@@ -107,6 +107,8 @@ export default function DoctorsPage() {
|
||||
const [status, setStatus] = useState('');
|
||||
const [ownerStatus, setOwnerStatus] = useState('');
|
||||
const [specialtyId, setSpecialty] = useState('');
|
||||
const [provinceId, setProvinceId] = useState('');
|
||||
const [cityId, setCityId] = useState('');
|
||||
const [view, setView] = useState<'table' | 'grid'>('table');
|
||||
const [deleteTarget, setDeleteTarget] = useState<AdminDoctor | null>(null);
|
||||
|
||||
@@ -115,7 +117,7 @@ export default function DoctorsPage() {
|
||||
return () => clearTimeout(t);
|
||||
}, [searchInput]);
|
||||
|
||||
useEffect(() => { setPage(1); }, [status, ownerStatus, specialtyId]);
|
||||
useEffect(() => { setPage(1); }, [status, ownerStatus, specialtyId, provinceId, cityId]);
|
||||
|
||||
// ── Queries ──
|
||||
|
||||
@@ -136,14 +138,29 @@ export default function DoctorsPage() {
|
||||
staleTime: 300_000,
|
||||
});
|
||||
|
||||
const provincesQ = useQuery({
|
||||
queryKey: ['provinces-list'],
|
||||
queryFn: () => api.get<ApiResponse<{ id: number; name: string }[]>>('/api/v1/provinces'),
|
||||
staleTime: 600_000,
|
||||
enabled: !isRepresentation,
|
||||
});
|
||||
const citiesQ = useQuery({
|
||||
queryKey: ['cities-list', provinceId],
|
||||
queryFn: () => api.get<ApiResponse<{ id: number; name: string }[]>>(`/api/v1/cities${provinceId ? `?province_id=${provinceId}` : ''}`),
|
||||
staleTime: 600_000,
|
||||
enabled: !isRepresentation && !!provinceId,
|
||||
});
|
||||
|
||||
const doctorsQ = useQuery({
|
||||
queryKey: ['admin-doctors', page, limit, search, status, ownerStatus, specialtyId],
|
||||
queryKey: ['admin-doctors', page, limit, search, status, ownerStatus, specialtyId, provinceId, cityId],
|
||||
queryFn: () => {
|
||||
const p = new URLSearchParams({ page: String(page), limit: String(limit) });
|
||||
if (search) p.set('search', search);
|
||||
if (status) p.set('status', status);
|
||||
if (ownerStatus && !isRepresentation) p.set('owner_status', ownerStatus);
|
||||
if (specialtyId) p.set('specialty_id', specialtyId);
|
||||
if (provinceId && !isRepresentation) p.set('state_id', provinceId);
|
||||
if (cityId && !isRepresentation) p.set('city_id', cityId);
|
||||
const base = isRepresentation ? '/api/v1/representation/doctors' : '/api/v1/admin/doctors';
|
||||
return api.get<PaginatedResponse<AdminDoctor>>(`${base}?${p}`);
|
||||
},
|
||||
@@ -159,6 +176,14 @@ export default function DoctorsPage() {
|
||||
() => (specialtiesQ.data?.data as any)?.data ?? specialtiesQ.data?.data ?? [],
|
||||
[specialtiesQ.data]
|
||||
);
|
||||
const provinceOptions = useMemo(
|
||||
() => ((provincesQ.data?.data as any)?.data ?? provincesQ.data?.data ?? []).map((p: { id: number; name: string }) => ({ value: String(p.id), label: p.name })),
|
||||
[provincesQ.data]
|
||||
);
|
||||
const cityOptions = useMemo(
|
||||
() => ((citiesQ.data?.data as any)?.data ?? citiesQ.data?.data ?? []).map((c: { id: number; name: string }) => ({ value: String(c.id), label: c.name })),
|
||||
[citiesQ.data]
|
||||
);
|
||||
|
||||
// ── Mutations ──
|
||||
|
||||
@@ -259,6 +284,30 @@ export default function DoctorsPage() {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!isRepresentation && (
|
||||
<div style={{ minWidth: 170 }}>
|
||||
<SearchableSelect
|
||||
options={provinceOptions}
|
||||
value={provinceId || null}
|
||||
onChange={(v) => { setProvinceId(v ? String(v) : ''); setCityId(''); }}
|
||||
placeholder="همه استانها"
|
||||
isClearable
|
||||
height={36}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!isRepresentation && provinceId && (
|
||||
<div style={{ minWidth: 170 }}>
|
||||
<SearchableSelect
|
||||
options={cityOptions}
|
||||
value={cityId || null}
|
||||
onChange={(v) => setCityId(v ? String(v) : '')}
|
||||
placeholder="همه شهرها"
|
||||
isClearable
|
||||
height={36}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="seg">
|
||||
<button className={!status ? 'on' : ''} onClick={() => setStatus('')}>همه</button>
|
||||
<button className={status === 'active' ? 'on' : ''} onClick={() => setStatus('active')}>فعال</button>
|
||||
|
||||
@@ -345,6 +345,8 @@ List all doctors with pagination.
|
||||
| `specialty_id` | integer | ❌ | Filter by specialty |
|
||||
| `owner_status` | string | ❌ | `claimed` \| `unclaimed` \| `pending_transfer` — پروفایلهای ایمپورت IRIMC (خروجی هم `owner_status` و `source` دارد) |
|
||||
| `unassigned` | string | ❌ | `1` → فقط پزشکانِ بدون نماینده (`representation_id IS NULL`) — برای انتخاب و اتصال به نماینده |
|
||||
| `city_id` | integer | ❌ | فیلتر بر اساس شهرِ آدرسِ خودِ پزشک (`doctor_addresses.city_id`) |
|
||||
| `state_id` | integer | ❌ | فیلتر بر اساس استانِ آدرسِ خودِ پزشک (`doctor_addresses.province_id`) |
|
||||
| `sort` | string | ❌ | Sort field |
|
||||
|
||||
### Response `200`
|
||||
|
||||
@@ -339,6 +339,8 @@ class AdminApiController extends BaseController
|
||||
$status = $request->query->get('status', '');
|
||||
$gender = trim((string) $request->query->get('gender', ''));
|
||||
$specId = (int) $request->query->get('specialty_id', 0);
|
||||
$cityId = (int) $request->query->get('city_id', 0);
|
||||
$stateId = (int) $request->query->get('state_id', 0);
|
||||
$sort = trim((string) $request->query->get('sort', 'newest'));
|
||||
|
||||
$conn = $this->em->getConnection();
|
||||
@@ -370,6 +372,15 @@ class AdminApiController extends BaseController
|
||||
if ($request->query->get('unassigned') === '1') {
|
||||
$where[] = 'd.representation_id IS NULL';
|
||||
}
|
||||
// فیلتر شهر/استان بر اساس آدرسِ خودِ پزشک (doctor_addresses).
|
||||
if ($cityId > 0) {
|
||||
$where[] = 'EXISTS (SELECT 1 FROM doctor_addresses da WHERE da.doctor_id = d.id AND da.city_id = :cityId)';
|
||||
$params['cityId'] = $cityId;
|
||||
}
|
||||
if ($stateId > 0) {
|
||||
$where[] = 'EXISTS (SELECT 1 FROM doctor_addresses das WHERE das.doctor_id = d.id AND das.province_id = :stateId)';
|
||||
$params['stateId'] = $stateId;
|
||||
}
|
||||
|
||||
$whereStr = implode(' AND ', $where);
|
||||
$orderBy = match ($sort) {
|
||||
|
||||
Reference in New Issue
Block a user