const RoleModal = ({ role, onSave, onClose, saving }) => { const slugifyRoleName = (displayName) => { return (displayName || '') .trim() .toLowerCase() .replace(/\s+/g, '_') .replace(/[^a-z0-9_]/g, '_') .replace(/_+/g, '_') .replace(/^_+|_+$/g, ''); }; // Permission items: { key } for single, { keys: [] } for grouped (one checkbox sets all). const getItemKeys = (item) => (item.keys || [item.key]).filter(Boolean); // parentKey: first item is the gate; all other items (unless independent) require it. const PERMISSION_DEFS = [ { group: "Property Management", parentKey: "can_manage_properties", items: [ { key: "can_manage_properties", label: "Manage Properties", desc: "Required for access to property management; enables settings button." }, { key: "can_edit_property_details", label: "Editing Property Details", desc: "Edit property info (code, name, address, abbreviation, phone, GM)." }, { key: "can_add_properties", label: "Add Properties", desc: "Create new properties in Property Management." }, { key: "can_manage_vendors", label: "Managing Vendors", desc: "Configure property vendors (contacts, services, contracts)." }, { keys: ["can_manage_venues", "can_edit_dining_concepts"], label: "Managing Amenities, Dining, & Restaurants", desc: "Manage venues, dining concepts, and amenities at the property." } ] }, { group: "Directory Visibility", items: [ { key: "can_be_global_directory_contact", label: "Be Global Directory Contact", desc: "Everyone in this role is visible in the directory for all users (not limited to their property)." }, { key: "can_be_assigned_as_gm", label: "Can Be Assigned as General Manager", desc: "Users with this role can be selected as the General Manager for a property." } ] }, { group: "Directory", parentKey: "can_view_directory", items: [ { key: "can_view_directory", label: "View Directory", desc: "Required for access to Directory tab and the following." }, { key: "can_view_all_properties", label: "View All Properties", desc: "See all properties, not just assigned ones." }, { key: "can_manage_users", label: "Manage Users & Roles", desc: "Add, modify users and assign roles (limited to lower-ranked roles)." }, { key: "can_view_disabled_users", label: "View Disabled Users", desc: "View disabled users for properties they have access to." } ] }, { group: "Knowledge Base", parentKey: "can_view_knowledge_base", items: [ { key: "can_view_knowledge_base", label: "View Knowledge Base", desc: "Required for KB access. Browse and read articles." }, { keys: ["can_manage_knowledge_base", "can_edit_knowledge_base", "can_submit_knowledge_base"], label: "Manage Knowledge Base", desc: "Create folders, edit, submit, and delete articles." }, { key: "can_approve_knowledge_base", label: "Approve KB Articles", desc: "Approve or reject articles submitted by others." } ] }, { group: "JitBit", items: [ { key: "can_access_it_support", label: "Access Tickets Associated with Property", desc: "Access IT support tickets for properties they have access to." } ] }, { group: "AI", parentKey: "can_access_general_ai", items: [ { key: "can_access_general_ai", label: "Access AI Assistant", desc: "Required for AI access. Use the general AI assistant for property info, lookups, and questions." }, { key: "can_access_it_support", label: "Access IT Support Agent", desc: "Use the IT Support agent for troubleshooting and ticket submission." }, { key: "can_access_capex_support", label: "Access CapEx Agent", desc: "Use the CapEx agent for capital expenditure requests." }, { type: "divider", sectionLabel: "Ticket visibility in AI" }, { key: "can_view_property_tickets", label: "View Property Tickets (AI)", desc: "In the AI, view tickets for properties the user has access to. Separate from property access." }, { key: "can_view_other_users_tickets", label: "View Other Users' Tickets (AI)", desc: "In the AI, view tickets opened by another user (e.g. tickets opened by [name])." } ] }, { group: "System Settings", items: [ { key: "can_manage_settings", label: "Manage Settings", desc: "Administration: Roles & Permissions and system settings." }, { key: "can_view_audit_logs", label: "View Audit", desc: "Access system audit logs." } ] } ]; const initialDisplayName = role?.display_name || ''; const [displayName, setDisplayName] = useState(initialDisplayName); const [description, setDescription] = useState(role?.description || ''); // Initialize permissions from role — only UI permissions (matches backend PERMISSION_FIELDS). const p = (key) => !!(role?.[key] ?? role?.permissions?.[key] ?? false); const [permissions, setPermissions] = useState({ can_manage_properties: p('can_manage_properties'), can_edit_property_details: p('can_edit_property_details'), can_add_properties: p('can_add_properties'), can_manage_vendors: p('can_manage_vendors'), can_manage_venues: p('can_manage_venues'), can_edit_dining_concepts: p('can_edit_dining_concepts'), can_be_global_directory_contact: p('can_be_global_directory_contact'), can_be_assigned_as_gm: p('can_be_assigned_as_gm'), can_view_directory: p('can_view_directory'), can_view_all_properties: p('can_view_all_properties'), can_manage_users: p('can_manage_users'), can_view_disabled_users: p('can_view_disabled_users'), can_view_knowledge_base: p('can_view_knowledge_base'), can_manage_knowledge_base: p('can_manage_knowledge_base'), can_approve_knowledge_base: p('can_approve_knowledge_base'), can_submit_knowledge_base: p('can_submit_knowledge_base'), can_edit_knowledge_base: p('can_edit_knowledge_base'), can_access_it_support: p('can_access_it_support'), can_access_general_ai: p('can_access_general_ai'), can_access_capex_support: p('can_access_capex_support'), can_view_property_tickets: p('can_view_property_tickets'), can_view_other_users_tickets: p('can_view_other_users_tickets'), can_manage_settings: p('can_manage_settings'), can_view_audit_logs: p('can_view_audit_logs'), }); const isEdit = !!role; const internalName = isEdit ? (role?.name || '') : slugifyRoleName(displayName); const setGroup = (groupItems, value) => { const next = { ...permissions }; groupItems.forEach((item) => { getItemKeys(item).forEach((k) => { next[k] = value; }); }); setPermissions(next); }; const handleSubmit = (e) => { e.preventDefault(); const payload = { // Backend requires "name" on create. On edit, backend ignores "name" but we preserve it anyway. name: internalName, display_name: displayName, description, ...permissions }; onSave(payload); }; return (

{isEdit ? 'Edit Role' : 'Create Role'}

setDisplayName(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="e.g., Front Desk Manager" required /> {!isEdit && (
This will auto-generate the internal role key: {internalName || '...'}
)}