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
@@ -3,6 +3,7 @@
import { useState } from "react";
import { trpc } from "~/lib/trpc/client.js";
import { BroadcastModal } from "./BroadcastModal.js";
import { CreateTaskModal } from "./CreateTaskModal.js";
function formatDate(date: string | Date): string {
const d = typeof date === "string" ? new Date(date) : date;
@@ -25,6 +26,7 @@ const TARGET_LABELS: Record<string, string> = {
export function BroadcastManagementClient() {
const [showModal, setShowModal] = useState(false);
const [showTaskModal, setShowTaskModal] = useState(false);
const { data: broadcasts = [], isLoading } = trpc.notification.listBroadcasts.useQuery(
{ limit: 50 },
@@ -42,16 +44,28 @@ export function BroadcastManagementClient() {
{/* Header */}
<div className="flex items-center justify-between mb-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-50">Broadcast Management</h1>
<button
type="button"
onClick={() => setShowModal(true)}
className="inline-flex items-center gap-1.5 rounded-lg bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700 transition-colors"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
Send Broadcast
</button>
<div className="flex items-center gap-3">
<button
type="button"
onClick={() => setShowTaskModal(true)}
className="inline-flex items-center gap-1.5 rounded-lg border border-brand-600 px-4 py-2 text-sm font-medium text-brand-600 hover:bg-brand-50 dark:hover:bg-brand-900/20 transition-colors"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
</svg>
Create Task
</button>
<button
type="button"
onClick={() => setShowModal(true)}
className="inline-flex items-center gap-1.5 rounded-lg bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700 transition-colors"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
Send Broadcast
</button>
</div>
</div>
{/* Loading */}
@@ -139,6 +153,14 @@ export function BroadcastManagementClient() {
onSuccess={handleSuccess}
/>
)}
{/* Create Task Modal */}
{showTaskModal && (
<CreateTaskModal
onClose={() => setShowTaskModal(false)}
onSuccess={handleSuccess}
/>
)}
</div>
);
}