diff --git a/ui/src/pages/Admin/GroupsTab.tsx b/ui/src/pages/Admin/GroupsTab.tsx index 12fd05b5..8ea4a4e5 100644 --- a/ui/src/pages/Admin/GroupsTab.tsx +++ b/ui/src/pages/Admin/GroupsTab.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo } from 'react'; +import { useState, useMemo, useEffect } from 'react'; import { Avatar, Badge, @@ -35,7 +35,7 @@ import styles from './UserManagement.module.css'; const BUILTIN_ADMINS_ID = '00000000-0000-0000-0000-000000000010'; -export default function GroupsTab() { +export default function GroupsTab({ highlightId, onHighlightConsumed }: { highlightId?: string | null; onHighlightConsumed?: () => void }) { const { toast } = useToast(); const { data: groups = [], isLoading: groupsLoading } = useGroups(); const { data: users = [] } = useUsers(); @@ -47,6 +47,17 @@ export default function GroupsTab() { const [deleteTarget, setDeleteTarget] = useState(null); const [removeRoleTarget, setRemoveRoleTarget] = useState(null); + // Auto-select highlighted item from cmd-k navigation + useEffect(() => { + if (highlightId && groups) { + const match = groups.find((g) => g.id === highlightId); + if (match) { + setSelectedId(match.id); + onHighlightConsumed?.(); + } + } + }, [highlightId, groups]); // eslint-disable-line react-hooks/exhaustive-deps + // Create form state const [newName, setNewName] = useState(''); const [newParent, setNewParent] = useState(''); diff --git a/ui/src/pages/Admin/RbacPage.tsx b/ui/src/pages/Admin/RbacPage.tsx index c4159d7d..3c7868bc 100644 --- a/ui/src/pages/Admin/RbacPage.tsx +++ b/ui/src/pages/Admin/RbacPage.tsx @@ -1,4 +1,5 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; +import { useLocation, useNavigate } from 'react-router'; import { StatCard, Tabs } from '@cameleer/design-system'; import { useRbacStats } from '../../api/queries/admin/rbac'; import styles from './UserManagement.module.css'; @@ -12,9 +13,30 @@ const TABS = [ { label: 'Roles', value: 'roles' }, ]; +const VALID_TABS = new Set(TABS.map((t) => t.value)); + export default function RbacPage() { const { data: stats } = useRbacStats(); + const location = useLocation(); + const navigate = useNavigate(); const [tab, setTab] = useState('users'); + const [highlightId, setHighlightId] = useState(null); + + // Read tab + highlight from location state (set by cmd-k navigation) + useEffect(() => { + const state = location.state as { tab?: string; highlight?: string } | null; + if (!state) return; + + if (state.tab && VALID_TABS.has(state.tab)) { + setTab(state.tab); + } + if (state.highlight) { + setHighlightId(state.highlight); + } + + // Consume the state so back-navigation doesn't re-trigger + navigate(location.pathname, { replace: true, state: null }); + }, [location.state]); // eslint-disable-line react-hooks/exhaustive-deps return (
@@ -23,11 +45,11 @@ export default function RbacPage() {
- + { setTab(v); setHighlightId(null); }} />
- {tab === 'users' && } - {tab === 'groups' && } - {tab === 'roles' && } + {tab === 'users' && setHighlightId(null)} />} + {tab === 'groups' && setHighlightId(null)} />} + {tab === 'roles' && setHighlightId(null)} />}
); diff --git a/ui/src/pages/Admin/RolesTab.tsx b/ui/src/pages/Admin/RolesTab.tsx index 9be3cb81..fd9eff32 100644 --- a/ui/src/pages/Admin/RolesTab.tsx +++ b/ui/src/pages/Admin/RolesTab.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo } from 'react'; +import { useState, useMemo, useEffect } from 'react'; import { Avatar, Badge, @@ -22,7 +22,7 @@ import { import type { RoleDetail } from '../../api/queries/admin/rbac'; import styles from './UserManagement.module.css'; -export default function RolesTab() { +export default function RolesTab({ highlightId, onHighlightConsumed }: { highlightId?: string | null; onHighlightConsumed?: () => void }) { const { toast } = useToast(); const { data: roles, isLoading } = useRoles(); @@ -31,6 +31,17 @@ export default function RolesTab() { const [creating, setCreating] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); + // Auto-select highlighted item from cmd-k navigation + useEffect(() => { + if (highlightId && roles) { + const match = roles.find((r) => r.id === highlightId); + if (match) { + setSelectedId(match.id); + onHighlightConsumed?.(); + } + } + }, [highlightId, roles]); // eslint-disable-line react-hooks/exhaustive-deps + // Create form state const [newName, setNewName] = useState(''); const [newDesc, setNewDesc] = useState(''); diff --git a/ui/src/pages/Admin/UsersTab.tsx b/ui/src/pages/Admin/UsersTab.tsx index 585904b2..8a049237 100644 --- a/ui/src/pages/Admin/UsersTab.tsx +++ b/ui/src/pages/Admin/UsersTab.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo } from 'react'; +import { useState, useMemo, useEffect } from 'react'; import { Avatar, Badge, @@ -36,7 +36,7 @@ import type { UserDetail } from '../../api/queries/admin/rbac'; import { useAuthStore } from '../../auth/auth-store'; import styles from './UserManagement.module.css'; -export default function UsersTab() { +export default function UsersTab({ highlightId, onHighlightConsumed }: { highlightId?: string | null; onHighlightConsumed?: () => void }) { const { toast } = useToast(); const { data: users, isLoading } = useUsers(); const { data: allGroups } = useGroups(); @@ -49,6 +49,17 @@ export default function UsersTab() { const [deleteTarget, setDeleteTarget] = useState(null); const [removeGroupTarget, setRemoveGroupTarget] = useState(null); + // Auto-select highlighted item from cmd-k navigation + useEffect(() => { + if (highlightId && users) { + const match = users.find((u) => u.userId === highlightId); + if (match) { + setSelectedId(match.userId); + onHighlightConsumed?.(); + } + } + }, [highlightId, users]); // eslint-disable-line react-hooks/exhaustive-deps + // Create form state const [newUsername, setNewUsername] = useState(''); const [newDisplay, setNewDisplay] = useState('');