feat(timeline): add inline allocation editor on double-click

Double-clicking an allocation bar opens an inline editor overlay
with start date, end date, and hours/day fields. Saves via
trpc.allocation.update, closes on Escape or click outside.
Only visible to users with manage permissions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 13:24:43 +02:00
parent fa54ef4cbd
commit e75d966b8d
3 changed files with 209 additions and 9 deletions
@@ -39,6 +39,7 @@ import type { TimelineVisualOverrides } from "./allocationVisualState.js";
import { SuccessToast } from "~/components/ui/SuccessToast.js";
import { useTimelineKeyboard } from "~/hooks/useTimelineKeyboard.js";
import { KeyboardShortcutOverlay } from "./KeyboardShortcutOverlay.js";
import { InlineAllocationEditor } from "./InlineAllocationEditor.js";
// ─── Entry point ────────────────────────────────────────────────────────────
// Two-layer mount: the outer shell creates drag state + project context,
@@ -397,6 +398,14 @@ function TimelineViewContent({
},
});
const [inlineEditTarget, setInlineEditTarget] = useState<{
allocationId: string;
startDate: Date;
endDate: Date;
hoursPerDay: number;
barRect: DOMRect;
} | null>(null);
const hasActivePointerOverlay =
dragState.isDragging || allocDragState.isActive || rangeState.isSelecting || multiSelectState.isMultiDragging;
@@ -799,6 +808,7 @@ function TimelineViewContent({
multiSelectState={multiSelectState}
optimisticAllocations={optimisticAllocations}
suppressHoverInteractions={hasActivePointerOverlay}
{...(!isSelfServiceTimeline ? { onInlineEdit: (id: string, vals: { startDate: Date; endDate: Date; hoursPerDay: number }, rect: DOMRect) => setInlineEditTarget({ allocationId: id, ...vals, barRect: rect }) } : {})}
CELL_WIDTH={CELL_WIDTH}
dates={dates}
totalCanvasWidth={totalCanvasWidth}
@@ -1096,6 +1106,19 @@ function TimelineViewContent({
/>
)}
{/* Inline allocation editor */}
{inlineEditTarget && (
<InlineAllocationEditor
allocationId={inlineEditTarget.allocationId}
initialStartDate={inlineEditTarget.startDate}
initialEndDate={inlineEditTarget.endDate}
initialHoursPerDay={inlineEditTarget.hoursPerDay}
barRect={inlineEditTarget.barRect}
onClose={() => setInlineEditTarget(null)}
onSaved={() => setInlineEditTarget(null)}
/>
)}
{/* Keyboard shortcut overlay */}
{showShortcuts && <KeyboardShortcutOverlay onClose={() => setShowShortcuts(false)} />}