import { createContext, useCallback, useContext, useMemo } from 'react'; import { StaffPermissionLevel, StaffPermissionSection, hasStaffPermission, resolveSchedulesScope, resolveStaffPermissionLevel, } from '../utils/staffAccess.ts'; import { useAuthServiceContext } from './AuthService.tsx'; export const useStaffPermissionService = () => { const { currentOrganization, practitionerRoles, selectedPractitionerRole, selectedWorkspace, userRole, USER_ROLES, } = useAuthServiceContext(); const activeWorkspace = useMemo( () => selectedWorkspace || userRole, [selectedWorkspace, userRole], ); const getPermissionLevel = useCallback(( section: StaffPermissionSection, ): StaffPermissionLevel => resolveStaffPermissionLevel({ workspace: activeWorkspace, organization: currentOrganization, practitionerRole: selectedPractitionerRole, practitionerRoles, section, }), [ activeWorkspace, currentOrganization, practitionerRoles, selectedPractitionerRole, ]); const canView = useCallback( (section: StaffPermissionSection) => hasStaffPermission( getPermissionLevel(section), 'view', ), [getPermissionLevel], ); const canManage = useCallback( (section: StaffPermissionSection) => hasStaffPermission( getPermissionLevel(section), 'manage', ), [getPermissionLevel], ); /** * - WHOSE schedules this caller may see. `own` means `GET /Schedule` comes back narrowed to * their own blocks, with a 200 and nothing to say so. * * A SURFACE ASKING ABOUT THE PRACTICE MUST CONSULT THIS BEFORE BELIEVING WHAT IT GOT. A narrowed * result is indistinguishable from an empty one, and "this provider has no schedule" is a claim about * a colleague's job. * It is not a gate - the server narrows whatever this says. It decides the SENTENCE, not the data. */ const schedulesScope = useMemo( () => resolveSchedulesScope({ organization: currentOrganization, practitionerRole: selectedPractitionerRole, practitionerRoles, }), [currentOrganization, selectedPractitionerRole, practitionerRoles], ); // The unsigned organization read carries no role permissions; only the caller's own read (the one // with a versionId) can say a section is closed. const permissionsSettled = Boolean(currentOrganization?.meta?.versionId); return { activeWorkspace, permissionsSettled, isGenericWorkspace: activeWorkspace === USER_ROLES.GENERIC, getPermissionLevel, canView, canManage, schedulesScope, }; }; const StaffPermissionServiceContext = createContext< ReturnType | undefined >(undefined); export const StaffPermissionServiceProvider = ({ children }: { children: Parameters[0]['children']; }) => { const service = useStaffPermissionService(); return ( {children} ); }; /** The same context, or undefined outside its provider - for a component that only reads it. */ export const useOptionalStaffPermissionServiceContext = () => useContext(StaffPermissionServiceContext) ?? undefined; export const useStaffPermissionServiceContext = () => { const context = useContext(StaffPermissionServiceContext); if (!context) { throw new Error( 'useStaffPermissionServiceContext must be used within StaffPermissionServiceProvider', ); } return context; };