Files
hotelsync/src/contexts/RolePermissionsContext.tsx
HotelSync 6d07632f01 feat: notification settings per role + review/unread-msg notifications
- Add notificationSettings JSONB to role_permissions (migration 089)
- sendPushForNotification() — pushes only to users with the notif type enabled
- reviews.ts — push + in-app on new direct and QR reviews
- room-service.ts — use sendPushForNotification('room_service_order')
- publicWidget.ts — push + in-app on new online booking
- jobs.ts — runUnreadMessagesJob() every 15 min, deduped by in-process set
- UsersPage: NOTIFICATION_GROUPS UI in roles tab with per-type toggles
- api.ts / RolePermissionsContext: notificationSettings in types and save()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 16:43:39 +03:00

136 lines
4.6 KiB
TypeScript

import { createContext, useContext, useState, useEffect, useCallback, useMemo, 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'],
}
// Default home pages per role (fallback)
export const DEFAULT_HOME_PAGES: Record<string, string> = {
hotel_admin: '/calendar',
manager: '/calendar',
receptionist: '/calendar',
housekeeper: '/housekeeping',
accountant: '/reports',
security: '/calendar',
technician: '/housekeeping',
}
// ── Types ─────────────────────────────────────────────────────────────────────
export interface SavedRolePermission {
id: string
hotelId: string
roleKey: string
name: string
color: string
isSystem: boolean
permissions: Record<string, boolean>
homePage?: string | null
notificationSettings?: Record<string, boolean>
}
interface RolePermissionsContextValue {
/** Check if the current user has a given permission */
can: (permission: string) => boolean
/** Home page path for the current user's role */
userHomePage: string
/** Raw saved permissions for all roles (used by RolesTab editor) */
savedPermissions: SavedRolePermission[]
/** Re-fetch from API */
reload: () => Promise<void>
/** True while initial permissions are being fetched */
loading: boolean
}
// ── Context ───────────────────────────────────────────────────────────────────
const RolePermissionsContext = createContext<RolePermissionsContextValue>({
can: () => false,
userHomePage: '/calendar',
savedPermissions: [],
reload: async () => {},
loading: true,
})
export function RolePermissionsProvider({ children }: { children: ReactNode }) {
const { user } = useAuth()
const [savedPermissions, setSavedPermissions] = useState<SavedRolePermission[]>([])
// Start as true — PermissionGuard waits before rendering, preventing permission flash
const [loading, setLoading] = useState(true)
const reload = useCallback(async () => {
if (!user?.hotelSlug) {
setLoading(false)
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'
if (role === 'super_admin') return true
if (role === 'hotel_admin' || role === 'manager') return true
// Check saved permissions from DB
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])
const userHomePage = useMemo(() => {
const role = user?.role ?? ''
if (role === 'super_admin') return '/admin'
if (role === 'hotel_admin' || role === 'manager') return '/calendar'
// Use saved home page from DB if set
const saved = savedPermissions.find(rp => rp.roleKey === role)
if (saved?.homePage) return saved.homePage
// Fall back to role default
return DEFAULT_HOME_PAGES[role] ?? '/calendar'
}, [user?.role, savedPermissions])
return (
<RolePermissionsContext.Provider value={{ can, userHomePage, savedPermissions, reload, loading }}>
{children}
</RolePermissionsContext.Provider>
)
}
export function useRolePermissions() {
return useContext(RolePermissionsContext)
}