fix(web): portal timeline overlays above stacked panels

This commit is contained in:
2026-03-30 13:18:08 +02:00
parent 58824545fc
commit e20bf64eef
5 changed files with 22 additions and 6 deletions
@@ -2,6 +2,7 @@
import { clsx } from "clsx"; import { clsx } from "clsx";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import type { AllocationLike, AllocationReadModel, Assignment } from "@capakraken/shared"; import type { AllocationLike, AllocationReadModel, Assignment } from "@capakraken/shared";
import { trpc } from "~/lib/trpc/client.js"; import { trpc } from "~/lib/trpc/client.js";
import { useInvalidateTimeline } from "~/hooks/useInvalidatePlanningViews.js"; import { useInvalidateTimeline } from "~/hooks/useInvalidatePlanningViews.js";
@@ -89,16 +90,17 @@ export function AllocationPopover({
} }
if (isLoading || !allocation) { if (isLoading || !allocation) {
return ( const loadingPopover = (
<div ref={ref} style={style} className="bg-white border border-gray-200 rounded-xl shadow-xl p-4 text-sm text-gray-500"> <div ref={ref} style={style} className="bg-white border border-gray-200 rounded-xl shadow-xl p-4 text-sm text-gray-500">
Loading... Loading...
</div> </div>
); );
return typeof document === "undefined" ? loadingPopover : createPortal(loadingPopover, document.body);
} }
const dailyCostEUR = ((hoursPerDay ?? allocation.hoursPerDay) * (allocation.resource?.lcrCents ?? 0) / 100).toFixed(2); const dailyCostEUR = ((hoursPerDay ?? allocation.hoursPerDay) * (allocation.resource?.lcrCents ?? 0) / 100).toFixed(2);
return ( const popover = (
<div <div
ref={ref} ref={ref}
style={style} style={style}
@@ -211,4 +213,6 @@ export function AllocationPopover({
</div> </div>
</div> </div>
); );
return typeof document === "undefined" ? popover : createPortal(popover, document.body);
} }
@@ -2,6 +2,7 @@
import { clsx } from "clsx"; import { clsx } from "clsx";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { AllocationStatus } from "@capakraken/shared"; import { AllocationStatus } from "@capakraken/shared";
import { trpc } from "~/lib/trpc/client.js"; import { trpc } from "~/lib/trpc/client.js";
import { useInvalidateTimeline } from "~/hooks/useInvalidatePlanningViews.js"; import { useInvalidateTimeline } from "~/hooks/useInvalidatePlanningViews.js";
@@ -96,7 +97,7 @@ export function BatchAssignPopover({
const canAssign = const canAssign =
!!selectedProjectId && resourceIds.length > 0 && hoursPerDay > 0; !!selectedProjectId && resourceIds.length > 0 && hoursPerDay > 0;
return ( const popover = (
<div <div
ref={ref} ref={ref}
className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-[60] w-[360px] bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl shadow-2xl dark:shadow-black/40 overflow-hidden" className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-[60] w-[360px] bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl shadow-2xl dark:shadow-black/40 overflow-hidden"
@@ -254,4 +255,6 @@ export function BatchAssignPopover({
</div> </div>
</div> </div>
); );
return typeof document === "undefined" ? popover : createPortal(popover, document.body);
} }
@@ -1,5 +1,6 @@
"use client"; "use client";
import { createPortal } from "react-dom";
import type { TimelineDemandEntry } from "./TimelineContext.js"; import type { TimelineDemandEntry } from "./TimelineContext.js";
import { formatCents, formatDateLong } from "~/lib/format.js"; import { formatCents, formatDateLong } from "~/lib/format.js";
import { useViewportPopover } from "~/hooks/useViewportPopover.js"; import { useViewportPopover } from "~/hooks/useViewportPopover.js";
@@ -36,7 +37,7 @@ export function DemandPopover({
const totalHours = demand.hoursPerDay * days; const totalHours = demand.hoursPerDay * days;
const budgetCents = demand.dailyCostCents * days; const budgetCents = demand.dailyCostCents * days;
return ( const popover = (
<div <div
ref={ref} ref={ref}
style={style} style={style}
@@ -171,4 +172,6 @@ export function DemandPopover({
</div> </div>
</div> </div>
); );
return typeof document === "undefined" ? popover : createPortal(popover, document.body);
} }
@@ -2,6 +2,7 @@
import { clsx } from "clsx"; import { clsx } from "clsx";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { AllocationStatus } from "@capakraken/shared"; import { AllocationStatus } from "@capakraken/shared";
import { trpc } from "~/lib/trpc/client.js"; import { trpc } from "~/lib/trpc/client.js";
import { useInvalidateTimeline } from "~/hooks/useInvalidatePlanningViews.js"; import { useInvalidateTimeline } from "~/hooks/useInvalidatePlanningViews.js";
@@ -88,7 +89,7 @@ export function NewAllocationPopover({
const canCreate = !!selectedProjectId && !!start && !!end && hoursPerDay > 0; const canCreate = !!selectedProjectId && !!start && !!end && hoursPerDay > 0;
return ( const popover = (
<div <div
ref={ref} ref={ref}
style={style} style={style}
@@ -238,4 +239,6 @@ export function NewAllocationPopover({
</div> </div>
</div> </div>
); );
return typeof document === "undefined" ? popover : createPortal(popover, document.body);
} }
@@ -1,5 +1,6 @@
"use client"; "use client";
import { createPortal } from "react-dom";
import { trpc } from "~/lib/trpc/client.js"; import { trpc } from "~/lib/trpc/client.js";
import { formatCents } from "~/lib/format.js"; import { formatCents } from "~/lib/format.js";
import type { SkillEntry } from "@capakraken/shared"; import type { SkillEntry } from "@capakraken/shared";
@@ -33,7 +34,7 @@ export function ResourceHoverCard({ resourceId, anchorEl, onClose }: ResourceHov
.sort((a, b) => b.proficiency - a.proficiency) .sort((a, b) => b.proficiency - a.proficiency)
.slice(0, 6); .slice(0, 6);
return ( const hoverCard = (
<div <div
ref={ref} ref={ref}
data-resource-hover-card="true" data-resource-hover-card="true"
@@ -160,4 +161,6 @@ export function ResourceHoverCard({ resourceId, anchorEl, onClose }: ResourceHov
)} )}
</div> </div>
); );
return typeof document === "undefined" ? hoverCard : createPortal(hoverCard, document.body);
} }