feat: enhanced timeline hover tooltip with role, dates, status

The tooltip shown when hovering over project strips in the timeline
now includes additional information:
- Role name (e.g. "3D Artist", "Project Manager")
- Assignment date range (2026-03-01 → 2026-06-30)
- Status badge when not CONFIRMED (shows PROPOSED, DRAFT, etc.)
- Lead person and order type on the same line

Data comes from already-loaded timeline entries — no extra API calls.
Safe change: tooltip is pointer-events-none and read-only.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-03-23 07:30:08 +01:00
parent d46937300a
commit 3c0befb7db
2 changed files with 42 additions and 6 deletions
@@ -141,6 +141,10 @@ function TimelineResourcePanelInner({
orderType: string; orderType: string;
hoursPerDay: number; hoursPerDay: number;
responsiblePerson?: string | null; responsiblePerson?: string | null;
role?: string | null;
status?: string;
startDate?: string;
endDate?: string;
}[]; }[];
} | null>(null); } | null>(null);
@@ -295,6 +299,10 @@ function TimelineResourcePanelInner({
orderType: string; orderType: string;
hours: number; hours: number;
responsiblePerson?: string | null; responsiblePerson?: string | null;
role?: string | null;
status?: string;
startDate?: string;
endDate?: string;
} }
>(); >();
for (const alloc of a) { for (const alloc of a) {
@@ -314,6 +322,10 @@ function TimelineResourcePanelInner({
hours: alloc.hoursPerDay, hours: alloc.hoursPerDay,
responsiblePerson: responsiblePerson:
(alloc.project as { responsiblePerson?: string | null }).responsiblePerson ?? null, (alloc.project as { responsiblePerson?: string | null }).responsiblePerson ?? null,
role: alloc.role ?? alloc.roleEntity?.name ?? null,
status: alloc.status,
startDate: new Date(alloc.startDate).toISOString().slice(0, 10),
endDate: new Date(alloc.endDate).toISOString().slice(0, 10),
}); });
} }
} }
@@ -13,6 +13,10 @@ export type HeatmapHoverData = {
orderType: string; orderType: string;
hoursPerDay: number; hoursPerDay: number;
responsiblePerson?: string | null; responsiblePerson?: string | null;
role?: string | null;
status?: string;
startDate?: string;
endDate?: string;
}[]; }[];
}; };
@@ -81,10 +85,20 @@ export function TimelineTooltip({
{entry.projectName} {entry.projectName}
</div> </div>
<div className="truncate text-[11px] text-gray-400"> <div className="truncate text-[11px] text-gray-400">
{entry.responsiblePerson {[
? `Lead: ${entry.responsiblePerson}` entry.role,
: entry.orderType} entry.responsiblePerson ? `Lead: ${entry.responsiblePerson}` : null,
entry.orderType,
].filter(Boolean).join(" · ")}
</div> </div>
{entry.startDate && entry.endDate && (
<div className="text-[10px] text-gray-500">
{entry.startDate} {entry.endDate}
{entry.status && entry.status !== "CONFIRMED" && (
<span className="ml-1 uppercase text-amber-400">{entry.status}</span>
)}
</div>
)}
</div> </div>
<span className="whitespace-nowrap text-[11px] font-semibold text-gray-200"> <span className="whitespace-nowrap text-[11px] font-semibold text-gray-200">
{entry.hoursPerDay}h {entry.hoursPerDay}h
@@ -146,10 +160,20 @@ export function TimelineTooltip({
{entry.projectName} {entry.projectName}
</div> </div>
<div className="truncate text-[11px] text-gray-400"> <div className="truncate text-[11px] text-gray-400">
{entry.responsiblePerson {[
? `Lead: ${entry.responsiblePerson}` entry.role,
: entry.orderType} entry.responsiblePerson ? `Lead: ${entry.responsiblePerson}` : null,
entry.orderType,
].filter(Boolean).join(" · ")}
</div> </div>
{entry.startDate && entry.endDate && (
<div className="text-[10px] text-gray-500">
{entry.startDate} {entry.endDate}
{entry.status && entry.status !== "CONFIRMED" && (
<span className="ml-1 uppercase text-amber-400">{entry.status}</span>
)}
</div>
)}
</div> </div>
<span className="whitespace-nowrap text-[11px] font-semibold text-gray-200"> <span className="whitespace-nowrap text-[11px] font-semibold text-gray-200">
{entry.hoursPerDay}h {entry.hoursPerDay}h