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 const SETTINGS_GROUPS = [ { key: 'practice', label: 'مطب و کلینیک' }, { key: 'scheduling', label: 'نوبت‌دهی' }, { key: 'patients', label: 'بیماران' }, { key: 'finance', label: 'مالی' }, { key: 'account', label: 'حساب و اشتراک' }, ] as const; export type SettingsGroupKey = (typeof SETTINGS_GROUPS)[number]['key']; export type SettingsMenuGroup = { key: SettingsGroupKey; label: string; items: SettingsMenuItem[]; }; export type SettingsMenuItem = { key: string; label: string; icon: React.ElementType; group: SettingsGroupKey; 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: 'doctor', label: 'مدیریت پزشک', icon: UserIcon, group: 'practice', to: '/admin/profile', roles: ['doctor'] }, { key: 'clinic-doctors', label: 'پزشکان کلینیک', icon: BuildingOffice2Icon, group: 'practice', to: '/admin/settings/clinic-doctors', roles: ['clinic'], perm: ['clinic_doctors', 'view'] }, // مجوزش `clinic_info` است چون ذخیره‌اش روی PATCH /api/v1/clinic/{uuid} می‌نشیند. { key: 'practice-domain', label: 'حوزهٔ فعالیت', icon: SparklesIcon, group: 'practice', to: '/admin/settings/practice-domain', roles: ['clinic'], perm: ['clinic_info', 'view'] }, { key: 'secretary', label: 'مدیریت منشی', icon: UsersIcon, group: 'practice', to: '/admin/my-secretaries' }, { key: 'staff', label: 'پرسنل', icon: UserPlusIcon, group: 'practice', to: '/admin/staff', perm: ['staff', 'view'] }, // ── نوبت‌دهی — قواعدی که تقویم را می‌سازند ──────────────────────────────── // «منابع» به سایدبار اصلی («مدیریت») منتقل شد — کارِ روزمره است، نه تنظیمات. // گِیتش آنجا همین است: `appointment_settings.view` در هر چهار نقشی که اینجا می‌دیدندش. { key: 'appointment', label: 'مدیریت نوبت دهی', icon: CalendarDaysIcon, group: 'scheduling', to: '/admin/appointment-settings', roles: ['doctor'], perm: ['appointment_settings', 'view'] }, { key: 'appointment', label: 'مدیریت نوبت دهی', icon: CalendarDaysIcon, group: 'scheduling', to: '/admin/settings/appointment-settings', roles: ['clinic'], perm: ['appointment_settings', 'view'] }, { key: 'service-categories', label: 'دسته‌بندی‌ها', icon: RectangleStackIcon, group: 'scheduling', to: '/admin/service-categories', roles: ['doctor', 'clinic'], perm: ['appointment_settings', 'view'] }, { key: 'holidays', label: 'تعطیلات رسمی', icon: CalendarDaysIcon, group: 'scheduling', to: '/admin/holidays', roles: ['doctor', 'clinic'], perm: ['appointment_settings', 'view'] }, // ── بیماران — قواعدی که روی پروندهٔ بیمار می‌نشینند ──────────────────────── { key: 'record-number', label: 'شماره پرونده', icon: HashtagIcon, group: 'patients', to: '/admin/record-number-settings', roles: ['doctor', 'clinic'], perm: ['patients', 'view'] }, { key: 'tags', label: 'برچسب‌ها', icon: TagIcon, group: 'patients', to: '/admin/tags-settings', perm: ['tags', 'view'] }, // ── مالی — هر چیزی که به پول یا اعتبار وصل است ──────────────────────────── { key: 'payment', label: 'مدیریت پرداخت', icon: BanknotesIcon, group: 'finance', to: '/admin/my-financial', perm: ['payments', 'view'] }, { key: 'insurance', label: 'مدیریت بیمه', icon: ShieldCheckIcon, group: 'finance', to: '/admin/insurance-pricing', perm: ['insurances', 'view'] }, { key: 'discounts', label: 'مدیریت تخفیف‌ها', icon: ReceiptPercentIcon, group: 'finance', to: '/admin/discounts', roles: ['doctor', 'clinic'], perm: ['discounts', 'view'] }, { key: 'sms', label: 'پیامک‌ها', icon: ChatBubbleLeftRightIcon, group: 'finance', to: '/admin/sms-wallet', perm: ['sms', 'view'] }, // ── حساب و اشتراک — کم‌مصرف‌ترین‌ها، ته فهرست ───────────────────────────── { key: 'subscription', label: 'خرید اشتراک', icon: CreditCardIcon, group: 'account', to: '/admin/subscription', perm: ['subscription', 'view'] }, { key: 'account', label: 'حساب کاربری', icon: UserCircleIcon, group: 'account', 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)); }); } /** * همان خروجی `menuForRole`، دسته‌بندی‌شده و به ترتیبِ `SETTINGS_GROUPS`. * * دستهٔ خالی برنمی‌گردد: منشی‌ای که مجوز مالی ندارد نباید تیترِ «مالی» را ببیند و * زیرش هیچ. هر دو مصرف‌کنندهٔ منو از همین می‌خوانند تا ترتیب دسکتاپ و موبایل واگرا نشود. */ export function groupMenu(items: SettingsMenuItem[]): SettingsMenuGroup[] { return SETTINGS_GROUPS .map((group) => ({ key: group.key, label: group.label, items: items.filter((i) => i.group === group.key), })) .filter((group) => group.items.length > 0); } export function groupedMenuForRole( role: string | null | undefined, can?: (resource: string, action: string) => boolean, scope?: string | null, ): SettingsMenuGroup[] { return groupMenu(menuForRole(role, can, scope)); }