- Integrated TourButton component into SettingsMenuPage, SkillsPage, SmsWalletPage, StaffPage, StaffSessionDetailPage, StaffTreatmentSessionsPage, SubscriptionPage, TagsSettingsPage, TreatmentCasesPage to enhance user onboarding experience. - Created new tour definitions for appointments, clinics, staff management, financial management, and patient management, ensuring comprehensive guidance for users navigating the admin panel. - Updated documentation to reflect the addition of tours and their implementation details.
116 lines
5.5 KiB
TypeScript
116 lines
5.5 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 SettingsLayout from '../components/layout/SettingsLayout';
|
|
import SearchableSelect from '../components/ui/SearchableSelect';
|
|
import { useAuthStore } from '../stores/authStore';
|
|
import { usePermissions } from '../hooks/usePermissions';
|
|
import type { PracticeDomain } from '../types';
|
|
import TourButton from '../components/ui/TourButton';
|
|
|
|
/**
|
|
* حوزهٔ فعالیت کلینیک.
|
|
*
|
|
* تخصصِ پزشکی نیست — آن برچسبی است برای سایت عمومی. این یک کلید پیکربندی است که
|
|
* تعیین میکند کدام workflow درمان صدا زده شود، پس کدش پایدار میماند و فقط ادمین
|
|
* پلتفرم میتواند حوزهٔ تازه بسازد.
|
|
*/
|
|
export default function PracticeDomainSettingsPage() {
|
|
const qc = useQueryClient();
|
|
// محیط جاری با `db_uuid` شناخته میشود؛ همان uuid کلینیک است.
|
|
const clinicUuid = useAuthStore((s) => s.context?.db_uuid ?? null);
|
|
const { can } = usePermissions();
|
|
const canUpdate = can('clinic_info', 'update');
|
|
|
|
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 (
|
|
// پوستهٔ تنظیمات، مثل بقیهٔ صفحات همین منو. بدون آن، این صفحه تنها صفحهای بود که
|
|
// منوی تنظیمات را نشان نمیداد و کاربر برای رفتن به بخش بعدی باید «بازگشت» میزد.
|
|
<SettingsLayout active="practice-domain">
|
|
<div className="card card-pad" style={{ display: 'grid', gap: 14, maxWidth: 560 }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }} data-tour="page-title"><h1 className="section-title" style={{ margin: 0 }}>حوزهٔ فعالیت</h1><TourButton tourId="practice-domain" ready /></div>
|
|
|
|
<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>
|
|
)}
|
|
|
|
{/* پزشکِ عضو ممکن است «مشاهده» داشته باشد و «ویرایش» نه؛ دکمهٔ فعالی که
|
|
حتماً ۴۰۳ میگیرد، خطای کاربر نیست — خطای رابط است. */}
|
|
{!canUpdate && (
|
|
<span className="muted" style={{ fontSize: 12 }}>
|
|
برای تغییر حوزهٔ فعالیت، دسترسی «ویرایش اطلاعات کلینیک» لازم است.
|
|
</span>
|
|
)}
|
|
|
|
<button
|
|
type="button"
|
|
className="btn primary"
|
|
disabled={save.isPending || !clinicUuid || !canUpdate}
|
|
onClick={() => save.mutate(selected)}
|
|
style={{ justifySelf: 'start' }}
|
|
>
|
|
{save.isPending ? 'در حال ذخیره...' : 'ذخیره'}
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
</SettingsLayout>
|
|
);
|
|
}
|