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 */}