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
+19 -7
View File
@@ -220,7 +220,7 @@ export const notificationRouter = createTRPCRouter({
});
}),
/** Get task counts for the current user */
/** Get task counts for the current user — single groupBy instead of 5 counts */
taskCounts: protectedProcedure.query(async ({ ctx }) => {
const userId = await resolveUserId(ctx);
const now = new Date();
@@ -230,11 +230,12 @@ export const notificationRouter = createTRPCRouter({
category: { in: ["TASK" as const, "APPROVAL" as const] },
};
const [open, inProgress, done, dismissed, overdue] = await Promise.all([
ctx.db.notification.count({ where: { ...where, taskStatus: "OPEN" } }),
ctx.db.notification.count({ where: { ...where, taskStatus: "IN_PROGRESS" } }),
ctx.db.notification.count({ where: { ...where, taskStatus: "DONE" } }),
ctx.db.notification.count({ where: { ...where, taskStatus: "DISMISSED" } }),
const [grouped, overdue] = await Promise.all([
ctx.db.notification.groupBy({
by: ["taskStatus"],
where,
_count: true,
}),
ctx.db.notification.count({
where: {
...where,
@@ -244,7 +245,18 @@ export const notificationRouter = createTRPCRouter({
}),
]);
return { open, inProgress, done, dismissed, overdue };
const counts: Record<string, number> = {};
for (const g of grouped) {
if (g.taskStatus) counts[g.taskStatus] = g._count;
}
return {
open: counts["OPEN"] ?? 0,
inProgress: counts["IN_PROGRESS"] ?? 0,
done: counts["DONE"] ?? 0,
dismissed: counts["DISMISSED"] ?? 0,
overdue,
};
}),
/** Update task status */