Files
data-entry-app/frontend/src/lib/workspace-access.ts
T

208 lines
6.9 KiB
TypeScript
Raw Normal View History

2026-05-10 09:46:07 +12:00
import { hasModuleAccess, hasPermission, type AppSession } from '$lib/session';
export type WorkspaceRole = 'admin' | 'operations' | 'full' | 'client' | 'unknown';
type RouteAccessRule = {
path: string;
roles: WorkspaceRole[];
matches: (pathname: string) => boolean;
};
function hasPathPrefix(pathname: string, prefix: string) {
return pathname === prefix || pathname.startsWith(`${prefix}/`);
}
function canAccessWorkspaceArea(
session: AppSession | null | undefined,
moduleKey: string,
permissionKeys: string[],
minimumLevel: 'view' | 'edit' | 'manage' = 'view'
) {
if (!session) {
return false;
}
if (session.role === 'internal') {
return permissionKeys.some((permissionKey) => hasPermission(session, permissionKey));
}
return hasModuleAccess(session, moduleKey, minimumLevel);
}
export function getWorkspaceRole(session: AppSession | null | undefined): WorkspaceRole {
if (!session) {
return 'unknown';
}
if (session.role === 'admin') {
return 'admin';
}
if (session.role !== 'internal') {
return 'client';
}
if (session.role_name === 'Admin') {
return 'admin';
}
if (session.role_name === 'Operations') {
return 'operations';
}
if (session.role_name === 'Full Access') {
return 'full';
}
return 'unknown';
}
export function canOpenDashboard(session: AppSession | null | undefined) {
return canAccessWorkspaceArea(session, 'dashboard', ['view_dashboard']);
}
export function canOpenRawMaterials(session: AppSession | null | undefined) {
return canAccessWorkspaceArea(session, 'raw_materials', ['view_raw_materials', 'edit_raw_materials']);
}
export function canOpenMixMaster(session: AppSession | null | undefined) {
return canAccessWorkspaceArea(session, 'mix_master', ['view_mixes', 'edit_mixes']);
}
export function canCreateMixWorksheet(session: AppSession | null | undefined) {
return canAccessWorkspaceArea(session, 'mix_master', ['edit_mixes'], 'edit');
}
export function canOpenMixCalculator(session: AppSession | null | undefined) {
return canAccessWorkspaceArea(session, 'mix_calculator', ['view_mix_calculator', 'use_mix_calculator']);
}
export function canCreateMixSession(session: AppSession | null | undefined) {
return canAccessWorkspaceArea(session, 'mix_calculator', ['use_mix_calculator', 'save_mix_calculator_session'], 'edit');
}
export function canOpenProducts(session: AppSession | null | undefined) {
return canAccessWorkspaceArea(session, 'products', ['view_products', 'edit_products']);
}
export function canOpenScenarios(session: AppSession | null | undefined) {
return !!session && hasModuleAccess(session, 'scenarios');
}
2026-05-31 20:19:44 +12:00
export function canOpenThroughput(session: AppSession | null | undefined) {
return canAccessWorkspaceArea(session, 'operations_throughput', ['view_throughput', 'edit_throughput']);
}
export function canEditThroughput(session: AppSession | null | undefined) {
return canAccessWorkspaceArea(session, 'operations_throughput', ['edit_throughput'], 'edit');
}
2026-05-10 09:46:07 +12:00
export function canOpenReporting(session: AppSession | null | undefined) {
return canOpenProducts(session);
}
export function canOpenSettings(session: AppSession | null | undefined) {
if (!session) {
return false;
}
return session.role === 'internal'
? hasPermission(session, 'view_settings') || hasPermission(session, 'edit_settings')
: true;
}
export function canOpenClientAccess(session: AppSession | null | undefined) {
return !!session && hasModuleAccess(session, 'client_access', 'manage');
}
export const routeAccessRules: RouteAccessRule[] = [
{ path: '/', roles: ['admin', 'full', 'client'], matches: (pathname) => pathname === '/' },
{
path: '/mix-calculator',
roles: ['admin', 'operations', 'full', 'client'],
matches: (pathname) => hasPathPrefix(pathname, '/mix-calculator')
},
{
path: '/raw-materials',
roles: ['admin', 'full', 'client'],
matches: (pathname) => hasPathPrefix(pathname, '/raw-materials')
},
{ path: '/mixes', roles: ['admin', 'full', 'client'], matches: (pathname) => hasPathPrefix(pathname, '/mixes') },
{ path: '/products', roles: ['admin', 'full', 'client'], matches: (pathname) => hasPathPrefix(pathname, '/products') },
{ path: '/reporting', roles: ['admin', 'full', 'client'], matches: (pathname) => hasPathPrefix(pathname, '/reporting') },
{ path: '/scenarios', roles: ['admin', 'full', 'client'], matches: (pathname) => hasPathPrefix(pathname, '/scenarios') },
{
path: '/settings',
roles: ['admin', 'full', 'operations', 'client'],
matches: (pathname) => hasPathPrefix(pathname, '/settings')
},
{
path: '/client-access',
roles: ['admin', 'client'],
matches: (pathname) => hasPathPrefix(pathname, '/client-access')
2026-05-31 20:19:44 +12:00
},
{
path: '/throughput',
roles: ['admin', 'operations', 'full', 'client'],
matches: (pathname) => hasPathPrefix(pathname, '/throughput')
2026-05-10 09:46:07 +12:00
}
];
export function getDefaultRouteForRole(session: AppSession | null | undefined) {
const role = getWorkspaceRole(session);
if (role === 'operations') {
2026-05-31 20:19:44 +12:00
return '/mix-calculator';
2026-05-10 09:46:07 +12:00
}
if (role === 'admin' || role === 'full' || role === 'client') {
if (canOpenDashboard(session)) return '/';
2026-05-31 20:19:44 +12:00
if (canOpenMixCalculator(session)) return '/mix-calculator';
2026-05-10 09:46:07 +12:00
if (canOpenRawMaterials(session)) return '/raw-materials';
if (canOpenMixMaster(session)) return '/mixes';
if (canOpenProducts(session)) return '/products';
if (canOpenScenarios(session)) return '/scenarios';
if (canOpenSettings(session)) return '/settings';
}
return '/';
}
export function canAccessRoute(session: AppSession | null | undefined, pathname: string) {
const rule = routeAccessRules.find((candidate) => candidate.matches(pathname));
if (!rule) {
return true;
}
const role = getWorkspaceRole(session);
if (!rule.roles.includes(role)) {
return false;
}
if (pathname === '/') return canOpenDashboard(session);
if (pathname.startsWith('/mix-calculator')) return canOpenMixCalculator(session);
if (pathname.startsWith('/raw-materials')) return canOpenRawMaterials(session);
if (pathname.startsWith('/mixes')) return canOpenMixMaster(session);
if (pathname.startsWith('/products')) return canOpenProducts(session);
if (pathname.startsWith('/scenarios')) return canOpenScenarios(session);
if (pathname.startsWith('/reporting')) return canOpenReporting(session);
if (pathname.startsWith('/settings')) return canOpenSettings(session);
if (pathname.startsWith('/client-access')) return canOpenClientAccess(session);
2026-05-31 20:19:44 +12:00
if (pathname.startsWith('/throughput')) return canOpenThroughput(session);
2026-05-10 09:46:07 +12:00
return true;
}
export function canUseWorkspaceSearch(session: AppSession | null | undefined) {
return (
canOpenDashboard(session) ||
canOpenRawMaterials(session) ||
canOpenMixMaster(session) ||
canOpenMixCalculator(session) ||
canOpenProducts(session) ||
canOpenScenarios(session)
);
}
export const getWorkspaceHomeHref = getDefaultRouteForRole;
export const isWorkspaceRouteAllowed = canAccessRoute;