/** * Shared Role Permission Components * * Reusable components for displaying and editing staff role permissions. * Used in both StaffRolesSettings and the Invite Staff modal. */ import React from 'react'; import { useTranslation } from 'react-i18next'; import { PermissionDefinition } from '../../types'; export interface PermissionSectionProps { title: string; description: string; permissions: Record; values: Record; onChange: (key: string, value: boolean) => void; onSelectAll?: () => void; onClearAll?: () => void; variant?: 'default' | 'settings' | 'dangerous'; readOnly?: boolean; columns?: 1 | 2; lockedPermissions?: Record; // key -> reason (forced on) disabledPermissions?: Record; // key -> reason (grayed out until parent enabled) } /** * A section of permission checkboxes with header and select/clear all buttons */ export const PermissionSection: React.FC = ({ title, description, permissions, values, onChange, onSelectAll, onClearAll, variant = 'default', readOnly = false, columns = 2, lockedPermissions = {}, disabledPermissions = {}, }) => { const { t } = useTranslation(); const variantStyles = { default: { container: '', checkbox: 'text-brand-600 focus:ring-brand-500', hover: 'hover:bg-gray-50 dark:hover:bg-gray-700/50', }, settings: { container: 'p-3 bg-blue-50/50 dark:bg-blue-900/10 rounded-lg border border-blue-100 dark:border-blue-900/30', checkbox: 'text-blue-600 focus:ring-blue-500', hover: 'hover:bg-blue-100/50 dark:hover:bg-blue-900/20', }, dangerous: { container: 'p-3 bg-red-100 dark:bg-red-900/30 rounded-lg border border-red-200 dark:border-red-800/50', checkbox: 'text-red-600 focus:ring-red-500', hover: 'hover:bg-red-200/70 dark:hover:bg-red-900/40', }, }; const styles = variantStyles[variant]; const gridCols = columns === 1 ? 'grid-cols-1' : 'grid-cols-2'; return (

{title} {variant === 'dangerous' && ( {t('common.caution', 'Caution')} )}

{description}

{!readOnly && onSelectAll && onClearAll && (
|
)}
{Object.entries(permissions).map(([key, def]) => ( onChange(key, value)} checkboxClass={styles.checkbox} hoverClass={styles.hover} readOnly={readOnly} locked={!!lockedPermissions[key]} lockedReason={lockedPermissions[key]} disabled={!!disabledPermissions[key]} disabledReason={disabledPermissions[key]} /> ))}
); }; interface PermissionCheckboxProps { permissionKey: string; definition: PermissionDefinition; checked: boolean; onChange: (value: boolean) => void; checkboxClass?: string; hoverClass?: string; readOnly?: boolean; locked?: boolean; lockedReason?: string; disabled?: boolean; disabledReason?: string; } /** * Individual permission checkbox with label and description */ export const PermissionCheckbox: React.FC = ({ permissionKey, definition, checked, onChange, checkboxClass = 'text-brand-600 focus:ring-brand-500', hoverClass = 'hover:bg-gray-50 dark:hover:bg-gray-700/50', readOnly = false, locked = false, lockedReason, disabled = false, disabledReason, }) => { const isDisabled = readOnly || locked || disabled; const tooltipReason = locked ? lockedReason : disabled ? disabledReason : undefined; return ( ); }; interface RolePermissionsEditorProps { permissions: Record; onChange: (permissions: Record) => void; availablePermissions: { menu: Record; settings: Record; dangerous: Record; }; readOnly?: boolean; columns?: 1 | 2; } /** * Full role permissions editor with all three sections */ export const RolePermissionsEditor: React.FC = ({ permissions, onChange, availablePermissions, readOnly = false, columns = 2, }) => { const { t } = useTranslation(); const togglePermission = (key: string, value: boolean) => { const updates: Record = { [key]: value }; // If enabling any settings sub-permission, also enable the main settings access if (value && key.startsWith('can_access_settings_')) { updates['can_access_settings'] = true; } // If disabling the main settings access, disable all sub-permissions if (!value && key === 'can_access_settings') { Object.keys(availablePermissions.settings).forEach((settingKey) => { if (settingKey !== 'can_access_settings') { updates[settingKey] = false; } }); } // Schedule editing permissions are linked: // - If enabling "edit others' schedules", also enable "edit own schedule" // - "edit own schedule" can be disabled independently only if "edit others'" is off if (value && key === 'can_edit_others_schedules') { updates['can_edit_own_schedule'] = true; } // Prevent disabling "edit own schedule" if "edit others' schedules" is enabled if (!value && key === 'can_edit_own_schedule') { if (permissions['can_edit_others_schedules']) { // Keep it enabled - can't disable own schedule editing while others' is enabled updates['can_edit_own_schedule'] = true; } } onChange({ ...permissions, ...updates }); }; const toggleAllInCategory = (category: 'menu' | 'settings' | 'dangerous', enable: boolean) => { const categoryPerms = availablePermissions[category]; const updates: Record = {}; Object.keys(categoryPerms).forEach((key) => { updates[key] = enable; }); // If enabling settings permissions, ensure main settings access is also enabled if (category === 'settings' && enable) { updates['can_access_settings'] = true; } // If enabling menu permissions including edit_others, ensure edit_own is also enabled if (category === 'menu' && enable && updates['can_edit_others_schedules']) { updates['can_edit_own_schedule'] = true; } onChange({ ...permissions, ...updates }); }; // Calculate which permissions are locked (cannot be unchecked due to dependencies) const lockedPermissions: Record = {}; // "Edit Own Schedule" is locked when "Edit Others' Schedules" is enabled if (permissions['can_edit_others_schedules']) { lockedPermissions['can_edit_own_schedule'] = t( 'settings.staffRoles.lockedByEditOthers', 'Required when "Edit Others\' Schedules" is enabled' ); } // Calculate which settings permissions are disabled (require "Access Settings" to be enabled first) const disabledSettingsPermissions: Record = {}; if (!permissions['can_access_settings']) { // Disable all settings sub-permissions when main settings access is off Object.keys(availablePermissions.settings).forEach((key) => { if (key !== 'can_access_settings') { disabledSettingsPermissions[key] = t( 'settings.staffRoles.requiresAccessSettings', 'Enable "Access Settings" first' ); } }); } return (
{/* Menu Permissions */} toggleAllInCategory('menu', true)} onClearAll={() => toggleAllInCategory('menu', false)} variant="default" readOnly={readOnly} columns={columns} lockedPermissions={lockedPermissions} /> {/* Settings Permissions */} toggleAllInCategory('settings', true)} onClearAll={() => toggleAllInCategory('settings', false)} variant="settings" readOnly={readOnly} columns={columns} lockedPermissions={lockedPermissions} disabledPermissions={disabledSettingsPermissions} /> {/* Dangerous Permissions */} toggleAllInCategory('dangerous', true)} onClearAll={() => toggleAllInCategory('dangerous', false)} variant="dangerous" lockedPermissions={lockedPermissions} readOnly={readOnly} columns={columns} />
); }; export default RolePermissionsEditor;