"use client"; import { clsx } from "clsx"; import type { RefObject } from "react"; import { useState } from "react"; import { createPortal } from "react-dom"; import { AllocationStatus } from "@capakraken/shared"; import { trpc } from "~/lib/trpc/client.js"; import { useInvalidatePlanningViews } from "~/hooks/useInvalidatePlanningViews.js"; import { useViewportPopover } from "~/hooks/useViewportPopover.js"; import { DateInput } from "~/components/ui/DateInput.js"; import { ProjectCombobox } from "~/components/ui/ProjectCombobox.js"; interface NewAllocationPopoverProps { resourceId: string; startDate: Date; endDate: Date; /** Pre-selected project (from project-view sub-row context) */ suggestedProjectId?: string | null; anchorX: number; anchorY: number; onClose: () => void; onCreated: () => void; ignoreScrollContainers?: RefObject[]; } function toDateInput(d: Date): string { const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${y}-${m}-${day}`; } export function NewAllocationPopover({ resourceId, startDate, endDate, suggestedProjectId, anchorX, anchorY, onClose, onCreated, ignoreScrollContainers, }: NewAllocationPopoverProps) { const { ref, style } = useViewportPopover({ anchor: { kind: "point", x: anchorX - 10, y: anchorY }, width: 320, estimatedHeight: 440, onClose, ignoreSelectors: ["[data-entity-combobox-overlay='true']"], ...(ignoreScrollContainers ? { ignoreScrollContainers } : {}), }); const invalidatePlanningViews = useInvalidatePlanningViews(); const [selectedProjectId, setSelectedProjectId] = useState( suggestedProjectId ?? null, ); const [role, setRole] = useState("Team Member"); const [hoursPerDay, setHoursPerDay] = useState(8); const [start, setStart] = useState(toDateInput(startDate)); const [end, setEnd] = useState(toDateInput(endDate)); const createMutation = trpc.timeline.quickAssign.useMutation({ onSuccess: () => { void invalidatePlanningViews(); onCreated(); onClose(); }, }); function handleCreate() { if (!selectedProjectId) return; createMutation.mutate({ resourceId, projectId: selectedProjectId, startDate: new Date(start), endDate: new Date(end), hoursPerDay, role, status: AllocationStatus.PROPOSED, }); } const canCreate = !!selectedProjectId && !!start && !!end && hoursPerDay > 0; const popover = (
{/* Header */}
Assign to Project
{/* Date range */}
{/* Project picker */}
{/* Role */}
setRole(e.target.value)} className="w-full 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-brand-400" />
{/* 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-brand-400" />
{[4, 6, 8].map((h) => ( ))}
{/* Overbooking notice */}

Overlapping allocations are allowed — resource may be overbooked.

{/* Error */} {createMutation.isError && (

{createMutation.error.message}

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