"use client"; import { useState } from "react"; import { InfoTooltip } from "~/components/ui/InfoTooltip.js"; import { trpc } from "~/lib/trpc/client.js"; type LevelRow = { id: string; name: string; groupId: string }; type GroupRow = { id: string; name: string; targetPercentage: number; sortOrder: number; levels: LevelRow[]; }; type EditingGroup = { id?: string; name: string; targetPercentage: number; sortOrder: number; }; type EditingLevel = { id?: string; name: string; groupId: string; }; export function ManagementLevelsClient() { const [editingGroup, setEditingGroup] = useState(null); const [editingLevel, setEditingLevel] = useState(null); const [error, setError] = useState(null); const utils = trpc.useUtils(); const { data: groups, isLoading } = trpc.managementLevel.listGroups.useQuery(); const createGroupMut = trpc.managementLevel.createGroup.useMutation({ onSuccess: () => { void utils.managementLevel.listGroups.invalidate(); setEditingGroup(null); }, onError: (e) => setError(e.message), }); const updateGroupMut = trpc.managementLevel.updateGroup.useMutation({ onSuccess: () => { void utils.managementLevel.listGroups.invalidate(); setEditingGroup(null); }, onError: (e) => setError(e.message), }); const createLevelMut = trpc.managementLevel.createLevel.useMutation({ onSuccess: () => { void utils.managementLevel.listGroups.invalidate(); setEditingLevel(null); }, onError: (e) => setError(e.message), }); const updateLevelMut = trpc.managementLevel.updateLevel.useMutation({ onSuccess: () => { void utils.managementLevel.listGroups.invalidate(); setEditingLevel(null); }, onError: (e) => setError(e.message), }); const deleteLevelMut = trpc.managementLevel.deleteLevel.useMutation({ onSuccess: () => void utils.managementLevel.listGroups.invalidate(), onError: (e) => setError(e.message), }); function openCreateGroup() { const maxOrder = Math.max(0, ...(groups ?? []).map((g) => (g as unknown as GroupRow).sortOrder)); setEditingGroup({ name: "", targetPercentage: 0, sortOrder: maxOrder + 1 }); setError(null); } function openEditGroup(g: GroupRow) { setEditingGroup({ id: g.id, name: g.name, targetPercentage: g.targetPercentage, sortOrder: g.sortOrder }); setError(null); } function handleSaveGroup() { if (!editingGroup) return; setError(null); if (editingGroup.id) { updateGroupMut.mutate({ id: editingGroup.id, data: { name: editingGroup.name, targetPercentage: editingGroup.targetPercentage, sortOrder: editingGroup.sortOrder }, }); } else { createGroupMut.mutate({ name: editingGroup.name, targetPercentage: editingGroup.targetPercentage, sortOrder: editingGroup.sortOrder, }); } } function openCreateLevel(groupId: string) { setEditingLevel({ name: "", groupId }); setError(null); } function openEditLevel(l: LevelRow) { setEditingLevel({ id: l.id, name: l.name, groupId: l.groupId }); setError(null); } function handleSaveLevel() { if (!editingLevel) return; setError(null); if (editingLevel.id) { updateLevelMut.mutate({ id: editingLevel.id, data: { name: editingLevel.name, groupId: editingLevel.groupId }, }); } else { createLevelMut.mutate({ name: editingLevel.name, groupId: editingLevel.groupId }); } } const isGroupPending = createGroupMut.isPending || updateGroupMut.isPending; const isLevelPending = createLevelMut.isPending || updateLevelMut.isPending; const rows = (groups ?? []) as unknown as GroupRow[]; return (

Management Levels

Level groups with chargeability targets and individual levels

{error && (
{error}
)} {isLoading &&
Loading...
}
{rows.map((group) => (
{/* Group header */}

{group.name}

Target: {Math.round(group.targetPercentage * 100)}%
{/* Levels */}
{group.levels.length === 0 && (
No levels in this group yet.
)} {group.levels.map((level) => (
{level.name}
))}
))} {!isLoading && rows.length === 0 && (
No management level groups yet.
)}
{/* Group Modal */} {editingGroup && (

{editingGroup.id ? "Edit Group" : "Add Group"}

setEditingGroup({ ...editingGroup, name: e.target.value })} placeholder="e.g. Senior Management" className="border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2 text-sm w-full bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-brand-400" />
setEditingGroup({ ...editingGroup, targetPercentage: (parseInt(e.target.value) || 0) / 100 })} min={0} max={100} className="border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2 text-sm w-full bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-brand-400" />
setEditingGroup({ ...editingGroup, sortOrder: parseInt(e.target.value) || 0 })} className="border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2 text-sm w-full bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-brand-400" />
)} {/* Level Modal */} {editingLevel && (

{editingLevel.id ? "Edit Level" : "Add Level"}

setEditingLevel({ ...editingLevel, name: e.target.value })} placeholder="e.g. Managing Director" className="border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2 text-sm w-full bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-brand-400" />
)}
); }