"use client"; import { useState } from "react"; import type { RoleWithResourceCount } from "@capakraken/shared"; import { trpc } from "~/lib/trpc/client.js"; import { RoleModal } from "./RoleModal.js"; import { FilterChips } from "~/components/ui/FilterChips.js"; import { SortableColumnHeader } from "~/components/ui/SortableColumnHeader.js"; import { useTableSort } from "~/hooks/useTableSort.js"; import { useViewPrefs } from "~/hooks/useViewPrefs.js"; import { InfoTooltip } from "~/components/ui/InfoTooltip.js"; export function RolesClient() { const [search, setSearch] = useState(""); const [showInactive, setShowInactive] = useState(false); const [modalOpen, setModalOpen] = useState(false); const [editRole, setEditRole] = useState(null); const [confirmDelete, setConfirmDelete] = useState(null); const [actionError, setActionError] = useState(null); const utils = trpc.useUtils(); const { data: roles, isLoading } = trpc.role.list.useQuery( { isActive: showInactive ? undefined : true, search: search || undefined }, { placeholderData: (prev) => prev, staleTime: 60_000 }, ); const deactivateMutation = trpc.role.deactivate.useMutation({ onSuccess: async () => { await utils.role.list.invalidate(); }, onError: (err) => setActionError(err.message), }); const activateMutation = trpc.role.update.useMutation({ onSuccess: async () => { await utils.role.list.invalidate(); }, onError: (err) => setActionError(err.message), }); const deleteMutation = trpc.role.delete.useMutation({ onSuccess: async () => { await utils.role.list.invalidate(); setConfirmDelete(null); }, onError: (err) => { setActionError(err.message); setConfirmDelete(null); }, }); function openCreate() { setEditRole(null); setModalOpen(true); } function openEdit(role: RoleWithResourceCount) { setEditRole(role); setModalOpen(true); } const roleList = (roles ?? []) as unknown as RoleWithResourceCount[]; const rolesViewPrefs = useViewPrefs("roles"); const { sorted, sortField, sortDir, toggle } = useTableSort(roleList, { initialField: rolesViewPrefs.savedSort?.field ?? null, initialDir: rolesViewPrefs.savedSort?.dir ?? null, onSortChange: (field, dir) => { rolesViewPrefs.setSavedSort(field && dir ? { field, dir } : null); }, }); function clearAll() { setSearch(""); setShowInactive(false); } const chips = [ ...(search ? [{ label: `Search: "${search}"`, onRemove: () => setSearch("") }] : []), ...(showInactive ? [{ label: "Inactive included", onRemove: () => setShowInactive(false) }] : []), ]; return (

Roles

Manage role definitions and resource assignments

setSearch(e.target.value)} className="app-input w-64" />
{chips.length > 0 && (
)} {actionError && (
{actionError}
)}
toggle(f, (r) => r._count.resourceRoles)} align="center" tooltip="Number of resources that currently have this role assigned (active assignments only)." /> toggle(f, (r) => r._count.allocations)} align="center" tooltip="Total number of planning entries that use this role, including open-demand compatibility rows." /> toggle(f, (r) => (r.isActive ? 0 : 1))} align="center" tooltip="Active roles are available for assignment. Inactive roles are hidden from pickers but existing assignments remain." /> {isLoading && ( )} {!isLoading && sorted.length === 0 && ( )} {sorted.map((role) => ( ))}
Description Actions
Loading…
No roles found. Create one to get started.
{role.name}
{role.description ?? } {role._count.resourceRoles} {role._count.allocations} {role.isActive ? "Active" : "Inactive"}
{role.isActive && ( )} {!role.isActive && ( )}
{modalOpen && ( setModalOpen(false)} onSuccess={() => setModalOpen(false)} /> )} {confirmDelete && (

Delete Role

Are you sure you want to delete {confirmDelete.name}? {(confirmDelete._count.resourceRoles > 0 || confirmDelete._count.allocations > 0) && ( This role is assigned to {confirmDelete._count.resourceRoles} resource(s) and{" "} {confirmDelete._count.allocations} allocation(s). Deletion will be blocked. )}

)}
); }