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
@@ -30,15 +30,15 @@ export function TimelineHeader({
<>
{/* Month header */}
<div
className="sticky top-0 z-40 flex bg-white border-b border-gray-100"
className="sticky top-0 z-40 flex bg-white dark:bg-gray-900 border-b border-gray-100 dark:border-gray-800"
style={{ height: HEADER_MONTH_HEIGHT }}
>
<div className="flex-shrink-0 border-r border-gray-200" style={{ width: LABEL_WIDTH }} />
<div className="flex-shrink-0 border-r border-gray-200 dark:border-gray-700" style={{ width: LABEL_WIDTH }} />
<div className="flex">
{monthGroups.map((m, i) => (
<div
key={i}
className="text-xs font-semibold text-gray-500 border-r border-gray-200 px-2 flex items-center bg-gray-50"
className="text-xs font-semibold text-gray-500 dark:text-gray-400 border-r border-gray-200 dark:border-gray-700 px-2 flex items-center bg-gray-50 dark:bg-gray-800"
style={{ width: m.colCount * CELL_WIDTH }}
>
{m.label}
@@ -50,11 +50,11 @@ export function TimelineHeader({
{/* Day header — hidden at month zoom (cells too narrow for labels) */}
{zoom !== "month" && (
<div
className="sticky z-40 flex bg-gray-50 border-b border-gray-200 select-none"
className="sticky z-40 flex bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 select-none"
style={{ top: HEADER_MONTH_HEIGHT, height: HEADER_DAY_HEIGHT }}
>
<div
className="flex-shrink-0 border-r border-gray-200 flex items-center px-4 text-xs font-medium text-gray-400 uppercase tracking-wider"
className="flex-shrink-0 border-r border-gray-200 dark:border-gray-700 flex items-center px-4 text-xs font-medium text-gray-400 dark:text-gray-500 uppercase tracking-wider"
style={{ width: LABEL_WIDTH }}
>
{viewMode === "resource" ? "Resource" : "Project / Resource"}
@@ -72,10 +72,10 @@ export function TimelineHeader({
key={i}
className={clsx(
"flex-shrink-0 border-r flex flex-col items-center justify-center text-xs overflow-hidden",
isToday ? "bg-brand-50 border-brand-200" :
isSaturday ? "bg-amber-50/60 border-amber-200" :
isSunday ? "bg-gray-100/80 border-gray-200" :
isMonday ? "border-gray-200" : "border-gray-100",
isToday ? "bg-brand-50 dark:bg-brand-950/40 border-brand-200 dark:border-brand-800" :
isSaturday ? "bg-amber-50/60 dark:bg-amber-950/30 border-amber-200 dark:border-amber-800" :
isSunday ? "bg-gray-100/80 dark:bg-gray-800/50 border-gray-200 dark:border-gray-700" :
isMonday ? "border-gray-200 dark:border-gray-700" : "border-gray-100 dark:border-gray-800",
)}
style={{ width: CELL_WIDTH, height: HEADER_DAY_HEIGHT }}
>
@@ -83,7 +83,7 @@ export function TimelineHeader({
<>
<span className={clsx(
"font-medium leading-none",
isToday ? "text-brand-600" : isSaturday ? "text-amber-600" : "text-gray-600",
isToday ? "text-brand-600" : isSaturday ? "text-amber-600 dark:text-amber-400" : "text-gray-600 dark:text-gray-300",
)}>
{zoom === "week"
? `${date.getDate()} ${MONTHS_SHORT[date.getMonth()]}`
@@ -92,7 +92,7 @@ export function TimelineHeader({
{zoom === "day" && (
<span className={clsx(
"text-[9px] leading-none mt-0.5",
isSaturday ? "text-amber-400" : "text-gray-300",
isSaturday ? "text-amber-400 dark:text-amber-500" : "text-gray-300 dark:text-gray-600",
)}>
{["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"][date.getDay()]}
</span>