feature: phase1 - org-type navigation, Cases permissions, staff filtering, and route guards.
This commit is contained in:
@@ -1,16 +1,34 @@
|
||||
import type { Organization } from '@/types/organization';
|
||||
|
||||
const ROUTE_TAB_READ: { prefix: string; permission: string }[] = [
|
||||
{ prefix: '/today', permission: 'TAB_TODAY_READ' },
|
||||
{ prefix: '/staff', permission: 'TAB_STAFF_READ' },
|
||||
{ prefix: '/organizations', permission: 'TAB_ORGANIZATIONS_READ' },
|
||||
{ prefix: '/patients', permission: 'TAB_PATIENTS_READ' },
|
||||
{ prefix: '/appointments', permission: 'TAB_APPOINTMENTS_READ' },
|
||||
{ prefix: '/treatment', permission: 'TAB_TREATMENT_READ' },
|
||||
{ prefix: '/billing', permission: 'TAB_BILLING_READ' },
|
||||
{ prefix: '/reports', permission: 'TAB_REPORTS_READ' },
|
||||
export type OrgTypeName = 'CLINIC' | 'LAB';
|
||||
|
||||
export type DashboardRouteConfig = {
|
||||
prefix: string;
|
||||
permission: string;
|
||||
orgTypes: OrgTypeName[];
|
||||
};
|
||||
|
||||
export const DASHBOARD_ROUTES: DashboardRouteConfig[] = [
|
||||
{ prefix: '/today', permission: 'TAB_TODAY_READ', orgTypes: ['CLINIC', 'LAB'] },
|
||||
{ prefix: '/staff', permission: 'TAB_STAFF_READ', orgTypes: ['CLINIC', 'LAB'] },
|
||||
{ prefix: '/organizations', permission: 'TAB_ORGANIZATIONS_READ', orgTypes: ['CLINIC', 'LAB'] },
|
||||
{ prefix: '/patients', permission: 'TAB_PATIENTS_READ', orgTypes: ['CLINIC'] },
|
||||
{ prefix: '/appointments', permission: 'TAB_APPOINTMENTS_READ', orgTypes: ['CLINIC'] },
|
||||
{ prefix: '/treatment', permission: 'TAB_TREATMENT_READ', orgTypes: ['CLINIC'] },
|
||||
{ prefix: '/cases', permission: 'TAB_CASES_READ', orgTypes: ['LAB'] },
|
||||
{ prefix: '/billing', permission: 'TAB_BILLING_READ', orgTypes: ['CLINIC', 'LAB'] },
|
||||
{ prefix: '/reports', permission: 'TAB_REPORTS_READ', orgTypes: ['CLINIC', 'LAB'] },
|
||||
];
|
||||
|
||||
export function isRouteAllowedForOrgType(pathname: string, orgType: OrgTypeName | undefined): boolean {
|
||||
if (!orgType) return false;
|
||||
const route = DASHBOARD_ROUTES.find(
|
||||
(r) => pathname === r.prefix || pathname.startsWith(`${r.prefix}/`),
|
||||
);
|
||||
if (!route) return true;
|
||||
return route.orgTypes.includes(orgType);
|
||||
}
|
||||
|
||||
export function hasPermission(org: Organization | null, permission: string): boolean {
|
||||
if (!org) return false;
|
||||
return Boolean(org.permissions?.includes(permission));
|
||||
@@ -26,21 +44,49 @@ export function canViewTab(org: Organization | null, readPermission: string): bo
|
||||
return hasPermission(org, readPermission);
|
||||
}
|
||||
|
||||
export function getRequiredReadPermissionForPath(pathname: string): string | null {
|
||||
for (const { prefix, permission } of ROUTE_TAB_READ) {
|
||||
if (pathname === prefix || pathname.startsWith(`${prefix}/`)) {
|
||||
return permission;
|
||||
export function getRouteConfigForPath(pathname: string): DashboardRouteConfig | null {
|
||||
for (const route of DASHBOARD_ROUTES) {
|
||||
if (pathname === route.prefix || pathname.startsWith(`${route.prefix}/`)) {
|
||||
return route;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getRequiredReadPermissionForPath(pathname: string): string | null {
|
||||
return getRouteConfigForPath(pathname)?.permission ?? null;
|
||||
}
|
||||
|
||||
export function canAccessDashboardRoute(org: Organization | null, pathname: string): boolean {
|
||||
if (!org) return false;
|
||||
|
||||
const route = getRouteConfigForPath(pathname);
|
||||
if (!route) return true;
|
||||
|
||||
if (!isRouteAllowedForOrgType(pathname, org.type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (route.prefix === '/appointments') {
|
||||
return canAccessAppointmentsSection(org);
|
||||
}
|
||||
|
||||
return hasPermission(org, route.permission);
|
||||
}
|
||||
|
||||
/** First dashboard route the user may open (ordered). Fallback: account settings. */
|
||||
export function firstAccessibleDashboardPath(org: Organization | null): string {
|
||||
if (!org) return '/today';
|
||||
for (const { prefix, permission } of ROUTE_TAB_READ) {
|
||||
if (hasPermission(org, permission)) return prefix;
|
||||
|
||||
for (const route of DASHBOARD_ROUTES) {
|
||||
if (!route.orgTypes.includes(org.type)) continue;
|
||||
if (route.prefix === '/appointments') {
|
||||
if (canAccessAppointmentsSection(org)) return route.prefix;
|
||||
continue;
|
||||
}
|
||||
if (hasPermission(org, route.permission)) return route.prefix;
|
||||
}
|
||||
|
||||
return '/settings/account';
|
||||
}
|
||||
|
||||
@@ -62,6 +108,9 @@ export function canEditAppointments(org: Organization | null): boolean {
|
||||
if (!org) {
|
||||
return false;
|
||||
}
|
||||
if (org.type !== 'CLINIC') {
|
||||
return false;
|
||||
}
|
||||
if (org.isOwner) {
|
||||
return true;
|
||||
}
|
||||
@@ -76,6 +125,9 @@ export function canAccessAppointmentsSection(org: Organization | null): boolean
|
||||
if (!org) {
|
||||
return false;
|
||||
}
|
||||
if (org.type !== 'CLINIC') {
|
||||
return false;
|
||||
}
|
||||
if (org.isOwner) {
|
||||
return true;
|
||||
}
|
||||
@@ -90,6 +142,7 @@ export function canAccessAppointmentsSection(org: Organization | null): boolean
|
||||
/** Treatment composer, scheduling columns, and saving clinical workflows */
|
||||
export function canEditTreatment(org: Organization | null): boolean {
|
||||
if (!org) return false;
|
||||
if (org.type !== 'CLINIC') return false;
|
||||
if (org.isOwner) return true;
|
||||
return hasPermission(org, 'TAB_TREATMENT_EDIT');
|
||||
}
|
||||
@@ -97,9 +150,28 @@ export function canEditTreatment(org: Organization | null): boolean {
|
||||
/** View treatment workspace (read-only or edit) */
|
||||
export function canViewTreatment(org: Organization | null): boolean {
|
||||
if (!org) return false;
|
||||
if (org.type !== 'CLINIC') return false;
|
||||
if (org.isOwner) return true;
|
||||
return (
|
||||
hasPermission(org, 'TAB_TREATMENT_READ') ||
|
||||
hasPermission(org, 'TAB_TREATMENT_EDIT')
|
||||
);
|
||||
}
|
||||
|
||||
/** Lab cases inbox */
|
||||
export function canViewCases(org: Organization | null): boolean {
|
||||
if (!org) return false;
|
||||
if (org.type !== 'LAB') return false;
|
||||
if (org.isOwner) return true;
|
||||
return (
|
||||
hasPermission(org, 'TAB_CASES_READ') ||
|
||||
hasPermission(org, 'TAB_CASES_EDIT')
|
||||
);
|
||||
}
|
||||
|
||||
export function canEditCases(org: Organization | null): boolean {
|
||||
if (!org) return false;
|
||||
if (org.type !== 'LAB') return false;
|
||||
if (org.isOwner) return true;
|
||||
return hasPermission(org, 'TAB_CASES_EDIT');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user