feat: timeline multi-select, demand popover, resource hover card, merged tooltips, dark mode fixes

Major timeline enhancements:
- Right-click drag multi-selection with floating action bar (batch delete/assign)
- DemandPopover for demand strip details (replaces broken "Loading" modal)
- ResourceHoverCard on name hover showing skills, rates, role, chapter
- Merged heatmap+vacation tooltips into unified TimelineTooltip component
- Fixed overbooking blink animation (date normalization, z-index ordering)
- Fixed dark mode sticky column bleed-through in project view
- System roles admin page, notification task management, performance review docs

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-03-18 23:43:51 +01:00
parent d0f04f13f8
commit ddec3a927a
67 changed files with 4930 additions and 1166 deletions
@@ -0,0 +1,88 @@
"use client";
import { clsx } from "clsx";
interface FloatingActionBarProps {
selectedAllocationCount: number;
selectedResourceCount: number;
onDelete: () => void;
onAssign: () => void;
onClear: () => void;
isDeleting: boolean;
}
export function FloatingActionBar({
selectedAllocationCount,
selectedResourceCount,
onDelete,
onAssign,
onClear,
isDeleting,
}: FloatingActionBarProps) {
const totalCount = selectedAllocationCount + selectedResourceCount;
if (totalCount === 0) return null;
const label =
selectedAllocationCount > 0 && selectedResourceCount > 0
? `${selectedAllocationCount} allocation${selectedAllocationCount !== 1 ? "s" : ""} + ${selectedResourceCount} resource${selectedResourceCount !== 1 ? "s" : ""} selected`
: selectedAllocationCount > 0
? `${selectedAllocationCount} allocation${selectedAllocationCount !== 1 ? "s" : ""} selected`
: `${selectedResourceCount} resource${selectedResourceCount !== 1 ? "s" : ""} selected`;
return (
<div
className={clsx(
"fixed bottom-6 left-1/2 -translate-x-1/2 z-50",
"flex items-center gap-3 rounded-full px-5 py-2.5",
"bg-white dark:bg-gray-800",
"border border-gray-200 dark:border-gray-700",
"shadow-xl dark:shadow-black/40",
)}
>
<span className="text-sm font-medium text-gray-700 dark:text-gray-200">
{label}
</span>
{selectedAllocationCount > 0 && (
<button
type="button"
onClick={onDelete}
disabled={isDeleting}
className={clsx(
"text-xs font-medium px-3 py-1.5 rounded-full transition-colors",
"bg-red-600 hover:bg-red-700 text-white",
"disabled:opacity-40 disabled:cursor-not-allowed",
)}
>
{isDeleting ? "Deleting\u2026" : "Delete"}
</button>
)}
{selectedResourceCount > 0 && (
<button
type="button"
onClick={onAssign}
className={clsx(
"text-xs font-medium px-3 py-1.5 rounded-full transition-colors",
"bg-sky-600 hover:bg-sky-700 dark:bg-sky-600 dark:hover:bg-sky-700 text-white",
)}
>
Assign
</button>
)}
<button
type="button"
onClick={onClear}
className={clsx(
"text-xs font-medium px-2 py-1.5 transition-colors",
"text-gray-500 dark:text-gray-400",
"hover:text-gray-700 dark:hover:text-gray-300",
)}
>
Clear{" "}
<span className="text-gray-400 dark:text-gray-500">(ESC)</span>
</button>
</div>
);
}