feat: refactor clinic management into a dedicated settings tab
- Removed MyClinicPage and redirected its functionality to a new ClinicDoctorsPage. - Created ClinicDoctorsManager component for managing doctors and invitations within the settings layout. - Updated backend permissions to allow clinic owners to detach doctors, alongside admins. - Adjusted API documentation to reflect new permission structure. - Updated tests to cover new functionality and permissions. - Modified sidebar and settings menu to reflect the new structure and role-based visibility.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { SearchHeaderP } from '../../pages/subscriptionIcons';
|
||||
import { useAuthStore } from '../../stores/authStore';
|
||||
|
||||
/**
|
||||
* Settings sub-navigation for the subscription page — item list and order copied
|
||||
@@ -12,28 +13,32 @@ import { SearchHeaderP } from '../../pages/subscriptionIcons';
|
||||
* This list is intentionally separate from SETTINGS_MENU (SettingsLayout) so the
|
||||
* other settings pages are not affected.
|
||||
*/
|
||||
type NavItem = { key: string; label: string; to?: string };
|
||||
/** `roles`: when set, the item is only shown to those roles (omit = every role). */
|
||||
type NavItem = { key: string; label: string; to?: string; roles?: string[] };
|
||||
|
||||
const NAV_ITEMS: NavItem[] = [
|
||||
{ key: 'subscription', label: 'خرید اشتراک', to: '/admin/subscription' },
|
||||
{ key: 'payment', label: 'مدیریت پرداخت', to: '/admin/my-financial' },
|
||||
{ key: 'appointment', label: 'مدیریت نوبت دهی', to: '/admin/appointment-settings' },
|
||||
{ key: 'insurance', label: 'مدیریت بیمه', to: '/admin/insurance-pricing' },
|
||||
{ key: 'discounts', label: 'مدیریت تخفیفها', to: '/admin/discounts' },
|
||||
{ key: 'tags', label: 'تگ ها', to: '/admin/tags-settings' },
|
||||
{ key: 'sms', label: 'پیامک ها', to: '/admin/sms-wallet' },
|
||||
{ key: 'clinic', label: 'مدیریت مطب', to: '/admin/my-clinic' },
|
||||
{ key: 'secretary', label: 'مدیریت منشی', to: '/admin/my-secretaries' },
|
||||
{ key: 'staff', label: 'پرسنل', to: '/admin/staff' },
|
||||
{ key: 'account', label: 'حساب کاربری', to: '/admin/account-settings' },
|
||||
{ key: 'security', label: 'تنظیمات' },
|
||||
{ key: 'subscription', label: 'خرید اشتراک', to: '/admin/subscription' },
|
||||
{ key: 'payment', label: 'مدیریت پرداخت', to: '/admin/my-financial' },
|
||||
{ key: 'appointment', label: 'مدیریت نوبت دهی', to: '/admin/appointment-settings', roles: ['doctor'] },
|
||||
{ key: 'insurance', label: 'مدیریت بیمه', to: '/admin/insurance-pricing' },
|
||||
{ key: 'discounts', label: 'مدیریت تخفیفها', to: '/admin/discounts', roles: ['doctor', 'clinic'] },
|
||||
{ key: 'tags', label: 'تگ ها', to: '/admin/tags-settings' },
|
||||
{ key: 'sms', label: 'پیامک ها', to: '/admin/sms-wallet' },
|
||||
{ key: 'clinic-doctors', label: 'پزشکان کلینیک', to: '/admin/settings/clinic-doctors', roles: ['clinic'] },
|
||||
{ key: 'secretary', label: 'مدیریت منشی', to: '/admin/my-secretaries' },
|
||||
{ key: 'staff', label: 'پرسنل', to: '/admin/staff' },
|
||||
{ key: 'account', label: 'حساب کاربری', to: '/admin/account-settings' },
|
||||
{ key: 'security', label: 'تنظیمات' },
|
||||
];
|
||||
|
||||
export default function PurchaseSubscriptionSidebar({ active }: { active: string }) {
|
||||
const [query, setQuery] = useState('');
|
||||
const primaryRole = useAuthStore((s) => s.primaryRole);
|
||||
const items = useMemo(
|
||||
() => NAV_ITEMS.filter((i) => i.label.includes(query.trim())),
|
||||
[query],
|
||||
() => NAV_ITEMS
|
||||
.filter((i) => !i.roles || (primaryRole != null && i.roles.includes(primaryRole)))
|
||||
.filter((i) => i.label.includes(query.trim())),
|
||||
[query, primaryRole],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -34,10 +34,17 @@ describe('SettingsLayout', () => {
|
||||
expect(screen.queryByText('مدیریت پزشک')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the same menu to every role (ungated, mirroring tauri)', () => {
|
||||
// the desktop sidebar is not role-gated: مدیریت مطب shows even for a doctor
|
||||
it('role-gates the desktop sidebar: a doctor does not see the clinic-doctors tab', () => {
|
||||
renderWithProviders(<SettingsLayout active="subscription"><div /></SettingsLayout>);
|
||||
expect(screen.getByText('مدیریت مطب')).toBeInTheDocument();
|
||||
expect(screen.queryByText('پزشکان کلینیک')).not.toBeInTheDocument();
|
||||
// the removed 'مدیریت مطب' tab must be gone for everyone
|
||||
expect(screen.queryByText('مدیریت مطب')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the clinic-doctors tab to a clinic owner', () => {
|
||||
useAuthStore.setState({ primaryRole: 'clinic' });
|
||||
renderWithProviders(<SettingsLayout active="clinic-doctors"><div /></SettingsLayout>);
|
||||
expect(screen.getByText('پزشکان کلینیک').closest('a')).toHaveAttribute('href', '/admin/settings/clinic-doctors');
|
||||
});
|
||||
|
||||
it('filters the menu by the search query', () => {
|
||||
@@ -49,8 +56,10 @@ describe('SettingsLayout', () => {
|
||||
|
||||
it('menuForRole still role-gates the mobile settings list', () => {
|
||||
// SettingsMenuPage (mobile) keeps using menuForRole / SETTINGS_MENU
|
||||
expect(menuForRole('clinic').map((i) => i.key)).toContain('clinic');
|
||||
expect(menuForRole('clinic').map((i) => i.key)).toContain('clinic-doctors');
|
||||
expect(menuForRole('clinic').map((i) => i.key)).not.toContain('doctor');
|
||||
expect(menuForRole('clinic').map((i) => i.key)).not.toContain('appointment');
|
||||
// a plain doctor must not get the clinic-doctors management tab
|
||||
expect(menuForRole('doctor').map((i) => i.key)).not.toContain('clinic-doctors');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ export const SETTINGS_MENU: SettingsMenuItem[] = [
|
||||
{ key: 'subscription', label: 'خرید اشتراک', icon: CreditCardIcon, to: '/admin/subscription' },
|
||||
{ key: 'doctor', label: 'مدیریت پزشک', icon: UserIcon, to: '/admin/profile', roles: ['doctor'] },
|
||||
{ key: 'appointment', label: 'مدیریت نوبت دهی', icon: CalendarDaysIcon, to: '/admin/appointment-settings', roles: ['doctor'] },
|
||||
{ key: 'clinic', label: 'مدیریت مطب', icon: BuildingOffice2Icon, to: '/admin/my-clinic', roles: ['clinic'] },
|
||||
{ key: 'clinic-doctors', label: 'پزشکان کلینیک', icon: BuildingOffice2Icon, to: '/admin/settings/clinic-doctors', roles: ['clinic'] },
|
||||
{ key: 'payment', label: 'مدیریت پرداخت', icon: BanknotesIcon, to: '/admin/my-financial' },
|
||||
{ key: 'secretary', label: 'مدیریت منشی', icon: UsersIcon, to: '/admin/my-secretaries' },
|
||||
{ key: 'staff', label: 'پرسنل', icon: UserPlusIcon, to: '/admin/staff' },
|
||||
|
||||
@@ -203,7 +203,7 @@ function buildSections(
|
||||
if (primaryRole === "clinic") {
|
||||
const clinicTo = dbUuid
|
||||
? `/admin/clinics/${dbUuid}`
|
||||
: "/admin/my-clinic";
|
||||
: "/admin/settings/clinic-doctors";
|
||||
return [
|
||||
{
|
||||
label: "عمومی",
|
||||
|
||||
Reference in New Issue
Block a user