feat: home page per role — selector in RolesTab, saved to DB and applied on login

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-20 10:42:05 +03:00
parent d26ad30433
commit 80429cd52a
2 changed files with 73 additions and 2 deletions

View File

@@ -309,7 +309,7 @@ export const api = {
'GET', `/api/hotels/${slug}/role-permissions`),
save: (slug: string, roleKey: string, data: {
name: string; color: string; isSystem: boolean; permissions: Record<string, boolean>
name: string; color: string; isSystem: boolean; permissions: Record<string, boolean>; homePage?: string | null
}) =>
req<import('../contexts/RolePermissionsContext').SavedRolePermission>(
'PUT', `/api/hotels/${slug}/role-permissions/${roleKey}`, data),

View File

@@ -41,6 +41,7 @@ interface RolePermissions {
color: string
isSystem: boolean
permissions: Record<string, boolean>
homePage?: string | null
}
// ── Constants ──────────────────────────────────────────────────────────────────
@@ -134,6 +135,36 @@ const ALL_MODULE_KEYS = MODULE_GROUPS.flatMap(g => g.modules.map(m => m.key))
const allPerms = (v: boolean) =>
Object.fromEntries(ALL_MODULE_KEYS.map(k => [k, v]))
// Permission key → route path (for home page selector)
const PERM_TO_ROUTE: Record<string, string> = {
calendar: '/calendar',
bookings: '/bookings',
guests: '/guests',
rooms: '/rooms',
availability: '/availability',
housekeeping: '/housekeeping',
maintenance: '/maintenance',
room_service: '/room-service',
rental: '/rental',
pos: '/pos',
tariffs: '/tariffs',
pricing: '/dynamic-pricing',
discounts: '/discounts',
loyalty: '/loyalty',
reports: '/reports',
channels: '/channels',
reviews: '/reviews',
website: '/website',
floor_map: '/floor-map',
equipment: '/equipment',
wifi: '/wifi',
ttlock: '/ttlock',
users: '/users',
schedule: '/schedule',
documents: '/documents',
settings: '/settings',
}
const INITIAL_ROLE_PERMISSIONS: RolePermissions[] = [
{
id: 'rp_hotel_admin',
@@ -492,7 +523,7 @@ function RolesTab() {
if (!s) return def
// Ensure all module keys are present (new modules default to false)
const fullPerms = { ...allPerms(false), ...s.permissions }
return { ...def, permissions: fullPerms }
return { ...def, permissions: fullPerms, homePage: s.homePage ?? def.homePage }
})
// Custom roles (not in system list)
@@ -505,6 +536,7 @@ function RolesTab() {
color: r.color,
isSystem: false,
permissions: { ...allPerms(false), ...r.permissions },
homePage: r.homePage ?? null,
}))
setRoles([...merged, ...custom])
@@ -534,6 +566,12 @@ function RolesTab() {
))
}
const setHomePage = (page: string | null) => {
setRoles(prev => prev.map(r =>
r.id === selectedRoleId ? { ...r, homePage: page } : r
))
}
// ── Save to API ────────────────────────────────────────────────────────────
const handleSave = async () => {
if (!slug || saving) return
@@ -546,6 +584,7 @@ function RolesTab() {
color: role.color,
isSystem: role.isSystem,
permissions: role.permissions,
homePage: role.homePage ?? null,
})
}))
setSaveOk(true)
@@ -786,6 +825,38 @@ function RolesTab() {
))}
</div>
{/* Home page selector */}
<div className="px-4 pb-4 border-t border-slate-100 dark:border-slate-700 pt-4">
<div className="flex items-center gap-3">
<div className="flex-1">
<p className="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wide mb-1">
Стартовая страница
</p>
<p className="text-xs text-slate-400 dark:text-slate-500 mb-2">
Страница, которая открывается при входе в систему
</p>
</div>
</div>
<select
value={selectedRole.homePage ?? ''}
onChange={e => setHomePage(e.target.value || null)}
className="input text-sm w-full sm:w-72"
>
<option value=""> По умолчанию для роли </option>
{ALL_MODULE_KEYS
.filter(k => selectedRole.permissions[k] && PERM_TO_ROUTE[k])
.map(k => {
const mod = MODULE_GROUPS.flatMap(g => g.modules).find(m => m.key === k)
return (
<option key={k} value={PERM_TO_ROUTE[k]}>
{mod?.label ?? k}
</option>
)
})
}
</select>
</div>
{selectedRole.isSystem && (
<div className="px-4 pb-4">
<div className="flex items-center gap-2 p-3 rounded-xl bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800">