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
@@ -145,6 +145,7 @@ export interface TimelineContextValue {
// ─ Display preferences
displayMode: TimelineDisplayMode;
heatmapScheme: HeatmapColorScheme;
blinkOverbookedDays: boolean;
// ─ Loading
isLoading: boolean;
@@ -287,6 +288,7 @@ export function TimelineProvider({
const { prefs: appPrefs } = useAppPreferences();
const displayMode = appPrefs.timelineDisplayMode;
const heatmapScheme = appPrefs.heatmapColorScheme;
const blinkOverbookedDays = appPrefs.blinkOverbookedDays;
// ─── Data queries ──────────────────────────────────────────────────────────
const { data: entriesView, isLoading } = trpc.timeline.getEntriesView.useQuery(
@@ -300,7 +302,7 @@ export function TimelineProvider({
...(filters.countryCodes.length > 0 ? { countryCodes: filters.countryCodes } : {}),
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
{ placeholderData: (prev: any) => prev },
{ placeholderData: (prev: any) => prev, refetchOnWindowFocus: false, staleTime: 90_000 },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as { data: TimelineEntriesView | undefined; isLoading: boolean };
@@ -309,7 +311,7 @@ export function TimelineProvider({
const { data: vacationEntries = [] } = trpc.vacation.list.useQuery(
{ startDate: viewStart, endDate: viewEnd, status: [VacationStatus.APPROVED, VacationStatus.PENDING], limit: 500 },
{ placeholderData: (prev) => prev },
{ placeholderData: (prev) => prev, refetchOnWindowFocus: false, staleTime: 90_000 },
);
const vacationsByResource = useMemo(() => {
@@ -593,6 +595,7 @@ export function TimelineProvider({
today,
displayMode,
heatmapScheme,
blinkOverbookedDays,
isLoading,
isInitialLoading,
totalAllocCount,
@@ -618,6 +621,7 @@ export function TimelineProvider({
today,
displayMode,
heatmapScheme,
blinkOverbookedDays,
isLoading,
isInitialLoading,
totalAllocCount,