Completes the panel side. A clinic picks its practice domain in settings, where the copy says plainly that this is not the specialty label the public site shows; picking nothing stays valid and changes nothing. Cases and the unbooked queue share one page rather than two, because they answer the same question — which patient is where in their course and what is still owed. The queue explains why booking is not automatic instead of leaving the reader to wonder. Platform admins get domain CRUD with a column showing whether a domain has a dedicated workflow or falls back to the default, so the gap is visible rather than guessed at; the code field is locked after creation because workflows bind to it. Also puts the staff session screens in the sidebar — they were reachable only by typing the URL. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
102 lines
4.3 KiB
TypeScript
102 lines
4.3 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { toast } from 'sonner';
|
|
import { api, ApiError, type ApiResponse } from '../lib/api';
|
|
import PageHeader from '../components/ui/PageHeader';
|
|
import SearchableSelect from '../components/ui/SearchableSelect';
|
|
import { useAuthStore } from '../stores/authStore';
|
|
import type { PracticeDomain } from '../types';
|
|
|
|
/**
|
|
* حوزهٔ فعالیت کلینیک.
|
|
*
|
|
* تخصصِ پزشکی نیست — آن برچسبی است برای سایت عمومی. این یک کلید پیکربندی است که
|
|
* تعیین میکند کدام workflow درمان صدا زده شود، پس کدش پایدار میماند و فقط ادمین
|
|
* پلتفرم میتواند حوزهٔ تازه بسازد.
|
|
*/
|
|
export default function PracticeDomainSettingsPage() {
|
|
const qc = useQueryClient();
|
|
// محیط جاری با `db_uuid` شناخته میشود؛ همان uuid کلینیک است.
|
|
const clinicUuid = useAuthStore((s) => s.context?.db_uuid ?? null);
|
|
|
|
const { data: domainsData, isLoading } = useQuery({
|
|
queryKey: ['practice-domains'],
|
|
queryFn: () => api.get<ApiResponse<PracticeDomain[]>>('/api/v1/practice-domains'),
|
|
staleTime: 300_000,
|
|
});
|
|
|
|
const { data: clinicData } = useQuery({
|
|
queryKey: ['clinic-practice-domain', clinicUuid],
|
|
queryFn: () => api.get<ApiResponse<{ data: { practice_domain: PracticeDomain | null } }>>(
|
|
`/api/v1/clinic/${clinicUuid}`,
|
|
),
|
|
enabled: !!clinicUuid,
|
|
});
|
|
|
|
const current = clinicData?.data?.data?.practice_domain ?? null;
|
|
const [selected, setSelected] = useState<string | null>(null);
|
|
|
|
useEffect(() => setSelected(current?.uuid ?? null), [current]);
|
|
|
|
const save = useMutation({
|
|
mutationFn: (uuid: string | null) => api.patch<ApiResponse<unknown>>(
|
|
`/api/v1/clinic/${clinicUuid}`,
|
|
{ practice_domain_uuid: uuid ?? '' },
|
|
),
|
|
onSuccess: () => {
|
|
toast.success('حوزهٔ فعالیت ذخیره شد');
|
|
qc.invalidateQueries({ queryKey: ['clinic-practice-domain', clinicUuid] });
|
|
},
|
|
onError: (e) => toast.error(e instanceof ApiError ? e.message : 'ذخیرهٔ حوزهٔ فعالیت ناموفق بود'),
|
|
});
|
|
|
|
const domains = domainsData?.data ?? [];
|
|
const options = domains.map((d) => ({ value: d.uuid, label: d.name }));
|
|
const chosen = domains.find((d) => d.uuid === selected) ?? null;
|
|
|
|
return (
|
|
<>
|
|
<PageHeader title="حوزهٔ فعالیت" backTo="/admin/settings" />
|
|
|
|
<div className="card card-pad" style={{ display: 'grid', gap: 14, maxWidth: 560 }}>
|
|
<p style={{ margin: 0, fontSize: 12.5, color: 'var(--text-3)', lineHeight: 1.9 }}>
|
|
حوزهٔ فعالیت تعیین میکند سیستم چه فرآیند درمانی برای کلینیک شما اجرا کند. این با
|
|
«تخصص» فرق دارد: تخصص برچسبی است که در سایت عمومی دیده میشود، این یک تنظیم است.
|
|
انتخابنکردنش خطا نیست و کلینیک مثل امروز کار میکند.
|
|
</p>
|
|
|
|
{isLoading ? (
|
|
<span style={{ fontSize: 13, color: 'var(--text-3)' }}>در حال بارگذاری...</span>
|
|
) : (
|
|
<>
|
|
<SearchableSelect
|
|
options={options}
|
|
value={selected}
|
|
onChange={(v) => setSelected(v === null ? null : String(v))}
|
|
placeholder="حوزهای انتخاب نشده"
|
|
isClearable
|
|
ariaLabel="حوزهٔ فعالیت کلینیک"
|
|
/>
|
|
|
|
{chosen && !chosen.has_workflow && (
|
|
<span style={{ fontSize: 12.5, color: 'var(--warning)' }}>
|
|
برای این حوزه هنوز فرآیند اختصاصی تعریف نشده است؛ رفتار پیشفرض اعمال میشود.
|
|
</span>
|
|
)}
|
|
|
|
<button
|
|
type="button"
|
|
className="btn primary"
|
|
disabled={save.isPending || !clinicUuid}
|
|
onClick={() => save.mutate(selected)}
|
|
style={{ justifySelf: 'start' }}
|
|
>
|
|
{save.isPending ? 'در حال ذخیره...' : 'ذخیره'}
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|