"use client"; import { clsx } from "clsx"; import { useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { AllocationStatus } from "@capakraken/shared"; import { trpc } from "~/lib/trpc/client.js"; import { useInvalidateTimeline } from "~/hooks/useInvalidatePlanningViews.js"; import { ProjectCombobox } from "~/components/ui/ProjectCombobox.js"; const ENTITY_COMBOBOX_OVERLAY_SELECTOR = "[data-entity-combobox-overlay='true']"; interface BatchAssignPopoverProps { resourceIds: string[]; startDate: Date; endDate: Date; onClose: () => void; onCreated: () => void; } function toDateDisplay(d: Date): string { return d.toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric", }); } export function BatchAssignPopover({ resourceIds, startDate, endDate, onClose, onCreated, }: BatchAssignPopoverProps) { const ref = useRef(null); const invalidateTimeline = useInvalidateTimeline(); const [selectedProjectId, setSelectedProjectId] = useState( null, ); const [hoursPerDay, setHoursPerDay] = useState(8); const batchMutation = trpc.timeline.batchQuickAssign.useMutation({ onSuccess: () => { invalidateTimeline(); onCreated(); onClose(); }, }); // Close on outside click useEffect(() => { function handlePointerDown(event: PointerEvent) { const target = event.target; if (!(target instanceof Node)) { return; } if (ref.current?.contains(target)) { return; } if (target instanceof Element && target.closest(ENTITY_COMBOBOX_OVERLAY_SELECTOR)) { return; } if (ref.current) { onClose(); } } document.addEventListener("pointerdown", handlePointerDown, true); return () => document.removeEventListener("pointerdown", handlePointerDown, true); }, [onClose]); // Close on ESC useEffect(() => { function handleKey(e: KeyboardEvent) { if (e.key === "Escape") onClose(); } document.addEventListener("keydown", handleKey); return () => document.removeEventListener("keydown", handleKey); }, [onClose]); function handleAssign() { if (!selectedProjectId) return; batchMutation.mutate({ assignments: resourceIds.map((resourceId) => ({ resourceId, projectId: selectedProjectId, startDate, endDate, hoursPerDay, role: "Team Member", status: AllocationStatus.PROPOSED, })), }); } const canAssign = !!selectedProjectId && resourceIds.length > 0 && hoursPerDay > 0; const popover = (
{/* Header */}
Batch Assign
{/* Info line */}

Assigning to{" "} {resourceIds.length} {" "} resource{resourceIds.length !== 1 ? "s" : ""}

{toDateDisplay(startDate)} – {toDateDisplay(endDate)}

{/* Project picker */}
{/* Hours per day */}
setHoursPerDay(parseFloat(e.target.value))} className="w-24 border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-sky-400 dark:focus:ring-sky-500" />
{[4, 6, 8].map((h) => ( ))}
{/* Error */} {batchMutation.isError && (

{batchMutation.error.message}

)} {/* Actions */}
); return typeof document === "undefined" ? popover : createPortal(popover, document.body); }