Files
clinicpro/assets/admin/components/layout/settingsMenu.ts
T
hamedandClaude Opus 5 c7fdb92df4 feat(admin): practice domain settings, treatment case list and platform domain CRUD
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>
2026-08-06 21:51:21 +03:30

75 lines
5.7 KiB
TypeScript

import React from 'react';
import {
CreditCardIcon, UserIcon, CalendarDaysIcon, BuildingOffice2Icon,
BanknotesIcon, UsersIcon, ShieldCheckIcon,
TagIcon, ChatBubbleLeftRightIcon, UserCircleIcon, UserPlusIcon, ReceiptPercentIcon,
RectangleStackIcon, HashtagIcon, SparklesIcon,
} from '@heroicons/react/24/outline';
/**
* تنها منبعِ حقیقتِ منوی تنظیمات — هم فهرست موبایل (`SettingsMenuPage`) و هم سایدبار
* دسکتاپ (`PurchaseSubscriptionSidebar`) از همین می‌خوانند.
*
* قبلاً هرکدام فهرست خودش را داشت و آیتم تازه فقط در یکی ظاهر می‌شد؛ «منابع» و
* «دسته‌بندی‌ها» در موبایل بودند و در سایدبار نبودند.
*/
export type SettingsMenuItem = {
key: string;
label: string;
icon: React.ElementType;
to?: string;
/** when set, the item is only shown to these roles (omit = every role) */
roles?: string[];
/** [resource, action] — منشی فقط با داشتن این مجوز آیتم را می‌بیند. */
perm?: [string, string];
/** برای منشی همیشه نمایش داده می‌شود (حساب کاربری). */
alwaysOpen?: boolean;
/** مقصدی ندارد — در سایدبار خاکستری و غیرقابل کلیک نشان داده می‌شود. */
disabled?: boolean;
};
export const SETTINGS_MENU: SettingsMenuItem[] = [
{ key: 'subscription', label: 'خرید اشتراک', icon: CreditCardIcon, to: '/admin/subscription', perm: ['subscription', 'view'] },
{ key: 'doctor', label: 'مدیریت پزشک', icon: UserIcon, to: '/admin/profile', roles: ['doctor'] },
{ key: 'appointment', label: 'مدیریت نوبت دهی', icon: CalendarDaysIcon, to: '/admin/appointment-settings', roles: ['doctor'], perm: ['appointment_settings', 'view'] },
{ key: 'appointment', label: 'مدیریت نوبت دهی', icon: CalendarDaysIcon, to: '/admin/settings/appointment-settings', roles: ['clinic'], perm: ['appointment_settings', 'view'] },
{ key: 'clinic-doctors', label: 'پزشکان کلینیک', icon: BuildingOffice2Icon, to: '/admin/settings/clinic-doctors', roles: ['clinic'], perm: ['clinic_doctors', 'view'] },
// «منابع» به سایدبار اصلی («مدیریت») منتقل شد — کارِ روزمره است، نه تنظیمات.
// گِیتش آنجا همین است: `appointment_settings.view` در هر چهار نقشی که اینجا می‌دیدندش.
{ key: 'practice-domain', label: 'حوزهٔ فعالیت', icon: SparklesIcon, to: '/admin/settings/practice-domain', roles: ['clinic'], perm: ['appointment_settings', 'view'] },
{ key: 'service-categories', label: 'دسته‌بندی‌ها', icon: RectangleStackIcon, to: '/admin/service-categories', roles: ['doctor', 'clinic'], perm: ['appointment_settings', 'view'] },
{ key: 'holidays', label: 'تعطیلات رسمی', icon: CalendarDaysIcon, to: '/admin/holidays', roles: ['doctor', 'clinic'], perm: ['appointment_settings', 'view'] },
{ key: 'payment', label: 'مدیریت پرداخت', icon: BanknotesIcon, to: '/admin/my-financial', perm: ['payments', 'view'] },
{ key: 'secretary', label: 'مدیریت منشی', icon: UsersIcon, to: '/admin/my-secretaries' },
{ key: 'staff', label: 'پرسنل', icon: UserPlusIcon, to: '/admin/staff', perm: ['staff', 'view'] },
{ key: 'insurance', label: 'مدیریت بیمه', icon: ShieldCheckIcon, to: '/admin/insurance-pricing', perm: ['insurances', 'view'] },
{ key: 'discounts', label: 'مدیریت تخفیف‌ها', icon: ReceiptPercentIcon, to: '/admin/discounts', roles: ['doctor', 'clinic'], perm: ['discounts', 'view'] },
{ key: 'tags', label: 'برچسب‌ها', icon: TagIcon, to: '/admin/tags-settings', perm: ['tags', 'view'] },
{ key: 'record-number', label: 'شماره پرونده', icon: HashtagIcon, to: '/admin/record-number-settings', roles: ['doctor', 'clinic'], perm: ['patients', 'view'] },
{ key: 'sms', label: 'پیامک‌ها', icon: ChatBubbleLeftRightIcon, to: '/admin/sms-wallet', perm: ['sms', 'view'] },
{ key: 'account', label: 'حساب کاربری', icon: UserCircleIcon, to: '/admin/account-settings', alwaysOpen: true },
];
/**
* Menu items visible to the given role. برای منشی بر اساس مجوز فیلتر می‌شود
* (آیتمِ بدونِ perm/alwaysOpen پنهان است)؛ سایر نقش‌ها با roles.
*/
export function menuForRole(
role: string | null | undefined,
can?: (resource: string, action: string) => boolean,
scope?: string | null,
): SettingsMenuItem[] {
// منشی و پزشکِ عضوِ کلینیک (scope=clinic) محدود-به-مجوزند؛ بقیه با فیلترِ نقشی.
const permissionRestricted = role === 'secretary' || (role === 'doctor' && scope === 'clinic');
return SETTINGS_MENU.filter((i) => {
if (permissionRestricted) {
if (i.alwaysOpen) return true;
if (!i.perm || !can || !can(i.perm[0], i.perm[1])) return false;
// واریانتِ نقشی (نوبت‌دهی/پزشکان کلینیک) را با scope تطبیق بده.
if (i.roles) return i.roles.includes(scope === 'clinic' ? 'clinic' : 'doctor');
return true;
}
return !i.roles || (role != null && i.roles.includes(role));
});
}