feat(appointments): enhance doctor and secretary views with scheduling context and resource management

This commit is contained in:
hamed
2026-08-04 10:42:20 +03:30
parent 1db1ee4cd8
commit 429d7ed813
2 changed files with 142 additions and 13 deletions
+56 -12
View File
@@ -215,16 +215,18 @@ export default function AppointmentsPage() {
// ── Clinic doctors (authoritative list for tabs)
const clinicDoctorsQuery = useQuery<ApiResponse<{ data: { uuid: string; name: string; has_schedule?: boolean }[] }>>({
queryKey: ['clinic-doctors', dbUuid, isSecretary, isClinic],
// منشی و کلینیک از اندپوینتِ احرازشده می‌گیرند: هم فقط پزشکانِ مجاز را می‌دهد و هم
// `has_schedule` را، که مبنای ساختن تب است. ادمین از لیستِ کلینیکِ انتخاب‌شده
// می‌خواند (آن اندپوینت نقش ادمین را پوشش نمی‌دهد) و آنجا فلگ نمی‌آید.
queryKey: ['clinic-doctors', dbUuid, isSecretary, isClinic, isDoctor],
// منشی، کلینیک و خودِ پزشک از اندپوینتِ احرازشده می‌گیرند: هم فقط پزشکانِ مجاز را
// می‌دهد و هم `has_schedule`ِ همین محیط را. پزشک هم لازمش دارد — بدون آن،
// پروفایل خودش نمی‌داند که «ساعت کاری تنظیم نشده» و فقط تایم‌لاین خالی می‌بیند.
// ادمین از لیستِ کلینیکِ انتخاب‌شده می‌خواند (آن اندپوینت نقش ادمین را پوشش
// نمی‌دهد) و آنجا فلگ نمی‌آید.
queryFn: () => api.get(
isSecretary || isClinic
isSecretary || isClinic || isDoctor
? '/api/v1/my/clinic-doctors'
: `/api/v1/clinic/doctor-list/${dbUuid}`,
),
enabled: isSecretary || (isClinic && !!dbUuid),
enabled: isSecretary || isDoctor || (isClinic && !!dbUuid),
});
const clinicDoctorsList = clinicDoctorsQuery.data?.data?.data ?? [];
@@ -247,6 +249,34 @@ export default function AppointmentsPage() {
/** پزشکِ انتخاب‌شده برنامهٔ هفتگی ندارد ⇒ تایم‌لاین جای خود را به راهنما می‌دهد. */
const doctorHasNoSchedule = selectedDoctor?.hasSchedule === false;
/**
* صفحهٔ تنظیماتِ نوبت‌دهی که همین کاربر واقعاً می‌تواند بازش کند.
*
* مالک کلینیک و منشیِ محیط کلینیک از تنظیمات کلینیک وارد می‌شوند (با تبِ همان
* پزشک)؛ پزشک و منشیِ مطب شخصی از صفحهٔ خودِ پزشک که انتخابگر محیط دارد. بدون
* مجوز `appointment_settings.view` لینک اصلاً نمی‌آید — همان گیتی که `RoleRoute`
* روی هر دو مسیر می‌گذارد، وگرنه کلیک به داشبورد پرت می‌شد.
*/
/**
* محیط‌های کلینیکیِ دیگرِ همین کاربر، وقتی خودش در مطب شخصی ایستاده.
*
* پزشکِ مهمانِ یک کلینیک، به‌طور پیش‌فرض در محیط شخصی وارد پنل می‌شود و آنجا نه
* برنامهٔ کلینیک را می‌بیند نه منابعش (هر دو tenant-scoped‌اند) — بدون این راهنما،
* «چیزی نیست» با «جای دیگری است» اشتباه گرفته می‌شد.
*/
const availableContexts = useAuthStore(s => s.availableContexts);
const otherClinicNames = clinicUuid === null
? availableContexts.filter(c => c.type === 'clinic').map(c => c.name)
: [];
const apptSettingsHref = !can('appointment_settings', 'view')
? null
: isClinic || isClinicScopedSecretary
? `/admin/settings/appointment-settings?scope=doctors&doctor=${selectedDoctorUuid}`
: isDoctor || isSecretary
? '/admin/appointment-settings'
: null;
// سرویس‌های موجود در نوبت‌های امروز (برای فیلتر «سرویس مورد نظر...»).
const serviceOptions = React.useMemo(() => {
const map = new Map<string, string>();
@@ -547,11 +577,8 @@ export default function AppointmentsPage() {
/* بدون برنامهٔ هفتگی، خودِ پزشک اسلاتی ندارد؛ اما منابعِ تحت نظرش
تقویم مستقل دارند و همچنان قابل نوبت‌دهی‌اند. */
<NoScheduleNotice
settingsHref={
isClinic || isClinicScopedSecretary
? `/admin/settings/appointment-settings?scope=doctors&doctor=${selectedDoctorUuid}`
: isDoctor || isSecretary ? '/admin/appointment-settings' : null
}
settingsHref={apptSettingsHref}
otherClinicNames={otherClinicNames}
resources={supervisedResources}
canCreate={!isRepresentation && canCreateAppt}
onBookResource={setBookingResource}
@@ -642,8 +669,10 @@ export default function AppointmentsPage() {
* منبع، زیرمجموعهٔ پزشک است ولی تقویمش مستقل؛ پس نبودِ ساعت کاریِ پزشک نوبت‌دهیِ
* منابعش را متوقف نمی‌کند و همین‌جا میان‌بُرِ رزروشان می‌آید.
*/
function NoScheduleNotice({ settingsHref, resources, canCreate, onBookResource, onOpenResource }: {
function NoScheduleNotice({ settingsHref, otherClinicNames, resources, canCreate, onBookResource, onOpenResource }: {
settingsHref: string | null;
/** کلینیک‌هایی که کاربر عضوشان است ولی الان در محیطشان نیست. */
otherClinicNames: string[];
resources: ClinicResource[];
canCreate: boolean;
onBookResource: (r: ClinicResource) => void;
@@ -661,6 +690,21 @@ function NoScheduleNotice({ settingsHref, resources, canCreate, onBookResource,
</Link>
)}
{resources.length === 0 && otherClinicNames.length > 0 && (
<div style={{
marginTop: 18, paddingTop: 16, borderTop: '1px solid var(--border)',
fontSize: 12.5, color: 'var(--text-2)', lineHeight: 2,
}}>
شما الان در محیط «مطب شخصی» هستید. برنامهٔ کاری و منابعِ
{' '}{otherClinicNames.map(n => `«${n}»`).join('، ')}{' '}
در محیط همان کلینیک تعریف میشوند.
<br />
<Link to="/admin/select-context" className="btn secondary sm" style={{ display: 'inline-flex', marginTop: 8 }}>
تغییر محیط کاری
</Link>
</div>
)}
{resources.length > 0 && (
<div style={{
marginTop: 22, paddingTop: 18, borderTop: '1px solid var(--border)',