feat: Sprint 2 — data storytelling and visual richness
Timeline project color system: - 16-color deterministic palette (same project = same color always) - Resource panel: allocation blocks colored by project instead of uniform green - Project panel: colored left border + dot on project headers - ProjectColorLegend: floating strip showing color-to-project mapping - Utilization intensity tint: subtle background gradient on resource rows Table visual enhancements: - Resources: inline 3px utilization bar below chargeability percentage - Resources: 32px avatar circles with initials + role-derived colors - Projects: animated budget bars, styled resource count badges - Allocations: 3px left border colored by status (green/amber/blue/gray/red) KPI progress rings: - Budget utilization: ProgressRing wrapping AnimatedNumber on dashboard - Chargeability report: ring on average chargeability summary card - Resource detail: rings on chargeability target + actual metrics - Vacation balance: ring showing remaining days with color thresholds - Demand widget: mini rings on FTE fill rate per project - Resource detail: FadeIn on SkillRadarChart Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
PROJECT_HEADER_HEIGHT,
|
||||
ORDER_TYPE_COLORS,
|
||||
} from "./timelineConstants.js";
|
||||
import { getProjectColor } from "~/lib/project-colors.js";
|
||||
import type { DragState, AllocDragState, RangeState, MultiSelectState } from "~/hooks/useTimelineDrag.js";
|
||||
import type { AllocMouseDownInfo, RowMouseDownInfo } from "./TimelineResourcePanel.js";
|
||||
import {
|
||||
@@ -609,6 +610,7 @@ function TimelineProjectPanelInner({
|
||||
(() => {
|
||||
const { project } = row;
|
||||
const customColor = project.color;
|
||||
const projectColor = getProjectColor(project.id);
|
||||
const colors = ORDER_TYPE_COLORS[project.orderType] ?? {
|
||||
bg: "bg-gray-400",
|
||||
text: "text-white",
|
||||
@@ -631,7 +633,7 @@ function TimelineProjectPanelInner({
|
||||
<div
|
||||
data-project-group="true"
|
||||
className={clsx("flex border-b border-gray-200 dark:border-gray-700 group/proj", colors.light)}
|
||||
style={{ height: PROJECT_HEADER_HEIGHT }}
|
||||
style={{ height: PROJECT_HEADER_HEIGHT, borderLeft: `4px solid ${customColor ?? projectColor.hex}` }}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
@@ -641,6 +643,10 @@ function TimelineProjectPanelInner({
|
||||
style={{ width: LABEL_WIDTH }}
|
||||
onClick={() => onOpenPanel(project.id)}
|
||||
>
|
||||
<div
|
||||
className="w-3 h-3 rounded-full flex-shrink-0"
|
||||
style={{ backgroundColor: customColor ?? projectColor.hex }}
|
||||
/>
|
||||
<div className="min-w-0">
|
||||
<div className="text-sm font-semibold text-gray-800 dark:text-gray-100 truncate">
|
||||
{project.name}
|
||||
@@ -659,19 +665,17 @@ function TimelineProjectPanelInner({
|
||||
{projWidth > 0 && projLeft < totalCanvasWidth && (
|
||||
<div
|
||||
className={clsx(
|
||||
"absolute rounded flex items-center px-2 gap-1.5 transition-all duration-75",
|
||||
"absolute rounded flex items-center px-2 gap-1.5 transition-all duration-75 text-white",
|
||||
isThisProjectShifting
|
||||
? "opacity-90 shadow-lg ring-2 ring-white ring-offset-1 cursor-grabbing z-20 scale-[1.01]"
|
||||
: "cursor-grab hover:opacity-90 hover:ring-2 hover:ring-white hover:ring-offset-1",
|
||||
!customColor && colors.bg,
|
||||
customColor ? "text-white" : colors.text,
|
||||
)}
|
||||
style={{
|
||||
left: projLeft + 2,
|
||||
width: projWidth - 4,
|
||||
top: 8,
|
||||
height: 24,
|
||||
...(customColor ? { backgroundColor: customColor } : {}),
|
||||
backgroundColor: customColor ?? projectColor.hex + "CC",
|
||||
}}
|
||||
onClick={() => {
|
||||
if (!dragState.isDragging) onOpenPanel(project.id);
|
||||
|
||||
Reference in New Issue
Block a user