diff --git a/apps/web/src/components/allocations/AllocationModal.tsx b/apps/web/src/components/allocations/AllocationModal.tsx index fa5c3ce..0e9a015 100644 --- a/apps/web/src/components/allocations/AllocationModal.tsx +++ b/apps/web/src/components/allocations/AllocationModal.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef, useMemo } from "react"; import { useFocusTrap } from "~/hooks/useFocusTrap.js"; import { useInvalidatePlanningViews } from "~/hooks/useInvalidatePlanningViews.js"; import { AllocationStatus } from "@planarchy/shared"; @@ -73,6 +73,36 @@ export function AllocationModal({ allocation, onClose, onSuccess }: AllocationMo { staleTime: 60_000 }, ); + // Fetch existing allocations for the selected resource+project to detect overlaps + const shouldCheckOverlap = !isDemandEntry && !!resourceId && !!projectId; + const { data: existingAllocations } = trpc.allocation.listView.useQuery( + { projectId, resourceId }, + { enabled: shouldCheckOverlap, staleTime: 30_000 }, + ); + + const overlapWarning = useMemo(() => { + if (!shouldCheckOverlap || !existingAllocations || !startDate || !endDate) return null; + const formStart = new Date(startDate); + const formEnd = new Date(endDate); + if (isNaN(formStart.getTime()) || isNaN(formEnd.getTime())) return null; + + const allocList = (existingAllocations as { allocations?: Array<{ id: string; resourceId?: string | null; startDate: string | Date; endDate: string | Date }> }).allocations ?? []; + for (const existing of allocList) { + // Skip the allocation being edited + if (isEditing && allocation && existing.id === allocation.id) continue; + // Only check assignments for this resource + if (existing.resourceId !== resourceId) continue; + const exStart = new Date(existing.startDate); + const exEnd = new Date(existing.endDate); + // Check date overlap + if (formStart <= exEnd && formEnd >= exStart) { + const fmt = (d: Date) => d.toISOString().slice(0, 10); + return `This resource is already assigned to this project from ${fmt(exStart)} to ${fmt(exEnd)}. Consider updating the existing assignment instead.`; + } + } + return null; + }, [shouldCheckOverlap, existingAllocations, startDate, endDate, isEditing, allocation, resourceId]); + const invalidatePlanningViews = useInvalidatePlanningViews(); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -473,6 +503,13 @@ export function AllocationModal({ allocation, onClose, onSuccess }: AllocationMo )} + {/* Overlap warning */} + {overlapWarning && ( +
+ {"\u26A0"} {overlapWarning} +
+ )} + {/* Footer */}