feat: full role permissions system — new modules, API persistence, sidebar context
- Backend: migration 080_role_permissions table (hotel-scoped, upsert) - Backend: routes GET/PUT/DELETE /api/hotels/:slug/role-permissions - Frontend: RolePermissionsContext — loads saved perms from API, provides can() - Frontend: Sidebar uses context can() instead of hardcoded ROLE_PERMS - Frontend: fixed module ID→permKey mapping (room-service, olap-reports, website-builder) - Frontend: Documents page added to Управление nav (was missing) - Frontend: equipment/wifi/ttlock get own permission keys (not bundled under 'settings') - Frontend: floor_map gets own permission key (not bundled under 'rooms') - Frontend: new module group 'Технологии': wifi, equipment, ttlock, floor_map - Frontend: 'schedule' added to Администрирование module group - Updated INITIAL_ROLE_PERMISSIONS: receptionist+availability+reports+documents+website, accountant+documents, technician+floor_map+equipment+ttlock Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
104
src/contexts/RolePermissionsContext.tsx
Normal file
104
src/contexts/RolePermissionsContext.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react'
|
||||
import { useAuth } from './AuthContext'
|
||||
import { api } from '../lib/api'
|
||||
|
||||
// ── Default permissions for system roles (fallback if no DB data) ─────────────
|
||||
|
||||
export const DEFAULT_ROLE_PERMS: Record<string, string[]> = {
|
||||
hotel_admin: ['*'],
|
||||
manager: ['*'],
|
||||
receptionist: [
|
||||
'calendar', 'bookings', 'guests', 'rooms', 'availability',
|
||||
'housekeeping', 'room_service', 'rental',
|
||||
'pos', 'reviews', 'reports',
|
||||
'documents', 'website',
|
||||
],
|
||||
housekeeper: ['calendar', 'housekeeping', 'maintenance', 'rooms'],
|
||||
accountant: ['calendar', 'reports', 'pos', 'discounts', 'tariffs', 'pricing', 'loyalty', 'documents'],
|
||||
security: ['calendar', 'bookings'],
|
||||
technician: ['calendar', 'housekeeping', 'maintenance', 'rooms', 'floor_map', 'equipment', 'ttlock'],
|
||||
}
|
||||
|
||||
// ── Types ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface SavedRolePermission {
|
||||
id: string
|
||||
hotelId: string
|
||||
roleKey: string
|
||||
name: string
|
||||
color: string
|
||||
isSystem: boolean
|
||||
permissions: Record<string, boolean>
|
||||
}
|
||||
|
||||
interface RolePermissionsContextValue {
|
||||
/** Check if the current user has a given permission */
|
||||
can: (permission: string) => boolean
|
||||
/** Raw saved permissions for all roles (used by RolesTab editor) */
|
||||
savedPermissions: SavedRolePermission[]
|
||||
/** Re-fetch from API */
|
||||
reload: () => Promise<void>
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
// ── Context ───────────────────────────────────────────────────────────────────
|
||||
|
||||
const RolePermissionsContext = createContext<RolePermissionsContextValue>({
|
||||
can: () => false,
|
||||
savedPermissions: [],
|
||||
reload: async () => {},
|
||||
loading: false,
|
||||
})
|
||||
|
||||
export function RolePermissionsProvider({ children }: { children: ReactNode }) {
|
||||
const { user } = useAuth()
|
||||
const [savedPermissions, setSavedPermissions] = useState<SavedRolePermission[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const reload = useCallback(async () => {
|
||||
if (!user?.hotelSlug) return
|
||||
setLoading(true)
|
||||
try {
|
||||
const data = await api.rolePermissions.list(user.hotelSlug)
|
||||
setSavedPermissions(data)
|
||||
} catch {
|
||||
// silently fall back to defaults
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [user?.hotelSlug])
|
||||
|
||||
useEffect(() => {
|
||||
reload()
|
||||
}, [reload])
|
||||
|
||||
const can = useCallback((permission: string): boolean => {
|
||||
const role = user?.role ?? 'housekeeper'
|
||||
|
||||
// super_admin: full access everywhere
|
||||
if (role === 'super_admin') return true
|
||||
|
||||
// hotel_admin and manager: full access to all hotel modules
|
||||
if (role === 'hotel_admin' || role === 'manager') return true
|
||||
|
||||
// Check saved permissions from DB for this role
|
||||
const saved = savedPermissions.find(rp => rp.roleKey === role)
|
||||
if (saved) {
|
||||
return saved.permissions[permission] === true
|
||||
}
|
||||
|
||||
// Fall back to hardcoded defaults
|
||||
const perms = DEFAULT_ROLE_PERMS[role] ?? []
|
||||
return perms.includes('*') || perms.includes(permission)
|
||||
}, [user?.role, savedPermissions])
|
||||
|
||||
return (
|
||||
<RolePermissionsContext.Provider value={{ can, savedPermissions, reload, loading }}>
|
||||
{children}
|
||||
</RolePermissionsContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useRolePermissions() {
|
||||
return useContext(RolePermissionsContext)
|
||||
}
|
||||
Reference in New Issue
Block a user