import { FillOpenDemandModal } from "~/components/allocations/FillOpenDemandModal.js"; import { AllocationPopover } from "./AllocationPopover.js"; import { BatchAssignPopover } from "./BatchAssignPopover.js"; import { DemandPopover } from "./DemandPopover.js"; import { InlineAllocationEditor } from "./InlineAllocationEditor.js"; import { KeyboardShortcutOverlay } from "./KeyboardShortcutOverlay.js"; import { NewAllocationPopover } from "./NewAllocationPopover.js"; import { ProjectPanel } from "./ProjectPanel.js"; import { ResourceHoverCard } from "./ResourceHoverCard.js"; import type { TimelineDemandEntry, TimelineAssignmentEntry } from "./TimelineContext.js"; import type { OpenDemandAssignment } from "./TimelineProjectPanel.js"; import type { useTimelineDrag } from "~/hooks/useTimelineDrag.js"; interface TimelinePopoversProps { isSelfServiceTimeline: boolean; hasActivePointerOverlay: boolean; popover: { allocationId: string; projectId: string; allocation?: TimelineAssignmentEntry | null; x: number; y: number; contextDate?: Date; } | null; setPopover: React.Dispatch>; demandPopover: { demand: TimelineDemandEntry; x: number; y: number } | null; setDemandPopover: React.Dispatch>; newAllocPopover: { resourceId: string; startDate: Date; endDate: Date; suggestedProjectId: string | null; anchorX: number; anchorY: number; selectionResourceId: string; selectionStart: Date; selectionEnd: Date; } | null; setNewAllocPopover: React.Dispatch< React.SetStateAction >; enrichedSuggestedProjectId: string | null; openPanelProjectId: string | null; setOpenPanelProjectId: React.Dispatch>; openDemandToAssign: OpenDemandAssignment | null; setOpenDemandToAssign: React.Dispatch>; openDemandsByProject: Map; scrollContainerRef: React.RefObject; multiSelectState: ReturnType["multiSelectState"]; clearMultiSelect: ReturnType["clearMultiSelect"]; handleBatchDelete: () => void; handleShowBatchAssign: () => void; isDeleting: boolean; showBatchAssign: boolean; setShowBatchAssign: React.Dispatch>; resourceHover: { resourceId: string; anchorEl: HTMLElement } | null; setResourceHover: React.Dispatch>; inlineEditTarget: { allocationId: string; startDate: Date; endDate: Date; hoursPerDay: number; barRect: DOMRect; } | null; setInlineEditTarget: React.Dispatch< React.SetStateAction >; showShortcuts: boolean; setShowShortcuts: React.Dispatch>; } function buildDemandAssignment(d: TimelineDemandEntry): OpenDemandAssignment { return { id: d.id, projectId: d.projectId, roleId: d.roleId, role: d.role, headcount: d.requestedHeadcount, startDate: new Date(d.startDate), endDate: new Date(d.endDate), hoursPerDay: d.hoursPerDay, ...(d.roleEntity !== undefined ? { roleEntity: d.roleEntity } : {}), ...(d.project !== undefined ? { project: d.project } : {}), }; } export function TimelinePopovers({ isSelfServiceTimeline, hasActivePointerOverlay, popover, setPopover, demandPopover, setDemandPopover, newAllocPopover, setNewAllocPopover, enrichedSuggestedProjectId, openPanelProjectId, setOpenPanelProjectId, openDemandToAssign, setOpenDemandToAssign, openDemandsByProject, scrollContainerRef, multiSelectState, clearMultiSelect, handleBatchDelete, handleShowBatchAssign, isDeleting, showBatchAssign, setShowBatchAssign, resourceHover, setResourceHover, inlineEditTarget, setInlineEditTarget, showShortcuts, setShowShortcuts, }: TimelinePopoversProps) { return ( <> {/* Allocation / Demand popover (click path) */} {!isSelfServiceTimeline && !hasActivePointerOverlay && popover && (() => { const clickedDemand = openDemandsByProject .get(popover.projectId) ?.find((d) => d.id === popover.allocationId); if (clickedDemand) { return ( setPopover(null)} onOpenPanel={(pid) => { setPopover(null); setOpenPanelProjectId(pid); }} onFillDemand={(d) => { setPopover(null); setOpenDemandToAssign(buildDemandAssignment(d)); }} anchorX={popover.x} anchorY={popover.y} ignoreScrollContainers={[scrollContainerRef]} /> ); } return ( setPopover(null)} onOpenPanel={(pid) => { setPopover(null); setOpenPanelProjectId(pid); }} anchorX={popover.x} anchorY={popover.y} ignoreScrollContainers={[scrollContainerRef]} {...(popover.contextDate ? { contextDate: popover.contextDate } : {})} /> ); })()} {/* Demand popover (context menu path) */} {!isSelfServiceTimeline && !hasActivePointerOverlay && demandPopover && ( setDemandPopover(null)} onOpenPanel={(pid) => { setDemandPopover(null); setOpenPanelProjectId(pid); }} onFillDemand={(d) => { setDemandPopover(null); setOpenDemandToAssign(buildDemandAssignment(d)); }} anchorX={demandPopover.x} anchorY={demandPopover.y} ignoreScrollContainers={[scrollContainerRef]} /> )} {/* New allocation popover */} {!isSelfServiceTimeline && newAllocPopover && ( setNewAllocPopover(null)} onCreated={() => setNewAllocPopover(null)} ignoreScrollContainers={[scrollContainerRef]} /> )} {/* Project side panel */} {!isSelfServiceTimeline && openPanelProjectId && ( setOpenPanelProjectId(null)} /> )} {/* Open-demand assignment modal */} {!isSelfServiceTimeline && openDemandToAssign && ( setOpenDemandToAssign(null)} onSuccess={() => setOpenDemandToAssign(null)} /> )} {/* Multi-select floating action bar + batch assign */} {showBatchAssign && multiSelectState.dateRange && ( setShowBatchAssign(false)} onCreated={() => { setShowBatchAssign(false); clearMultiSelect(); }} /> )} {/* Resource hover card */} {!hasActivePointerOverlay && resourceHover && ( setResourceHover(null)} /> )} {/* Inline allocation editor */} {inlineEditTarget && ( setInlineEditTarget(null)} onSaved={() => setInlineEditTarget(null)} /> )} {/* Keyboard shortcut overlay */} {showShortcuts && setShowShortcuts(false)} />} {/* Keyboard shortcut hint button */} ); }