feature: phase1 - org-type navigation, Cases permissions, staff filtering, and route guards.

This commit is contained in:
2026-06-28 14:59:06 +03:30
parent 64c7e5a257
commit dc965b2528
22 changed files with 376 additions and 76 deletions

View File

@@ -11,51 +11,73 @@ import {
FlaskConical,
FileText,
CreditCard,
Package,
} from 'lucide-react';
import type { OrgTypeName } from '@/components/shared/permissions';
import { useAuth } from '@/lib/hooks/useAuth';
import { usePendingConnectionsCount } from '@/lib/hooks/usePendingConnectionsCount';
import { canAccessAppointmentsSection, canViewTab } from '@/components/shared/permissions';
import {
canAccessAppointmentsSection,
canViewCases,
canViewTab,
} from '@/components/shared/permissions';
import {
counterpartOrganizationType,
organizationTypeIcon,
} from '@/components/shared/organizationTypeIcon';
type MenuItem = {
name: string;
path: string;
icon: typeof LayoutDashboard;
read: string;
orgTypes: OrgTypeName[];
};
function Sidebar() {
const t = useTranslations('nav');
const tCommon = useTranslations('common');
const pathname = usePathname();
const { currentOrganization } = useAuth();
const pendingConnectionsCount = usePendingConnectionsCount();
const orgType = currentOrganization?.type;
const menu = useMemo(
() => [
{ name: t('dashboard'), path: '/today', icon: LayoutDashboard, read: 'TAB_TODAY_READ' as const },
{ name: t('staff'), path: '/staff', icon: UserCog, read: 'TAB_STAFF_READ' as const },
const menu = useMemo((): MenuItem[] => {
const items: MenuItem[] = [
{ name: t('dashboard'), path: '/today', icon: LayoutDashboard, read: 'TAB_TODAY_READ', orgTypes: ['CLINIC', 'LAB'] },
{ name: t('staff'), path: '/staff', icon: UserCog, read: 'TAB_STAFF_READ', orgTypes: ['CLINIC', 'LAB'] },
{
name: currentOrganization?.type === 'LAB' ? t('clinics') : t('labs'),
name: orgType === 'LAB' ? t('clinics') : t('labs'),
path: '/organizations',
icon: organizationTypeIcon(counterpartOrganizationType(currentOrganization?.type)),
read: 'TAB_ORGANIZATIONS_READ' as const,
icon: organizationTypeIcon(counterpartOrganizationType(orgType)),
read: 'TAB_ORGANIZATIONS_READ',
orgTypes: ['CLINIC', 'LAB'],
},
{ name: t('patients'), path: '/patients', icon: Users, read: 'TAB_PATIENTS_READ' as const },
{ name: t('appointment'), path: '/appointments', icon: Calendar, read: 'TAB_APPOINTMENTS_READ' as const },
{ name: t('treatment'), path: '/treatment', icon: FlaskConical, read: 'TAB_TREATMENT_READ' as const },
{ name: t('billing'), path: '/billing', icon: CreditCard, read: 'TAB_BILLING_READ' as const },
{ name: t('reports'), path: '/reports', icon: FileText, read: 'TAB_REPORTS_READ' as const },
],
[currentOrganization?.type, t],
);
{ name: t('patients'), path: '/patients', icon: Users, read: 'TAB_PATIENTS_READ', orgTypes: ['CLINIC'] },
{ name: t('cases'), path: '/cases', icon: Package, read: 'TAB_CASES_READ', orgTypes: ['LAB'] },
{ name: t('appointment'), path: '/appointments', icon: Calendar, read: 'TAB_APPOINTMENTS_READ', orgTypes: ['CLINIC'] },
{ name: t('treatment'), path: '/treatment', icon: FlaskConical, read: 'TAB_TREATMENT_READ', orgTypes: ['CLINIC'] },
{ name: t('billing'), path: '/billing', icon: CreditCard, read: 'TAB_BILLING_READ', orgTypes: ['CLINIC', 'LAB'] },
{ name: t('reports'), path: '/reports', icon: FileText, read: 'TAB_REPORTS_READ', orgTypes: ['CLINIC', 'LAB'] },
];
return items;
}, [orgType, t]);
const visibleMenu = useMemo(
() =>
menu.filter((item) => {
if (!orgType || !item.orgTypes.includes(orgType)) {
return false;
}
if (item.path === '/appointments') {
return canAccessAppointmentsSection(currentOrganization);
}
if (item.path === '/cases') {
return canViewCases(currentOrganization);
}
return canViewTab(currentOrganization, item.read);
}),
[currentOrganization, menu],
[currentOrganization, menu, orgType],
);
return (