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:
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Deterministic project color assignment.
|
||||
* Maps a project ID to one of 16 visually distinct colors.
|
||||
* Colors are chosen for readability on both light and dark backgrounds.
|
||||
*/
|
||||
|
||||
const PROJECT_PALETTE = [
|
||||
{ bg: "bg-sky-500/70", dark: "bg-sky-400/60", border: "border-sky-600", hex: "#0ea5e9" },
|
||||
{ bg: "bg-violet-500/70", dark: "bg-violet-400/60", border: "border-violet-600", hex: "#8b5cf6" },
|
||||
{ bg: "bg-amber-500/70", dark: "bg-amber-400/60", border: "border-amber-600", hex: "#f59e0b" },
|
||||
{ bg: "bg-rose-500/70", dark: "bg-rose-400/60", border: "border-rose-600", hex: "#f43f5e" },
|
||||
{ bg: "bg-emerald-500/70", dark: "bg-emerald-400/60", border: "border-emerald-600", hex: "#10b981" },
|
||||
{ bg: "bg-indigo-500/70", dark: "bg-indigo-400/60", border: "border-indigo-600", hex: "#6366f1" },
|
||||
{ bg: "bg-orange-500/70", dark: "bg-orange-400/60", border: "border-orange-600", hex: "#f97316" },
|
||||
{ bg: "bg-teal-500/70", dark: "bg-teal-400/60", border: "border-teal-600", hex: "#14b8a6" },
|
||||
{ bg: "bg-pink-500/70", dark: "bg-pink-400/60", border: "border-pink-600", hex: "#ec4899" },
|
||||
{ bg: "bg-cyan-500/70", dark: "bg-cyan-400/60", border: "border-cyan-600", hex: "#06b6d4" },
|
||||
{ bg: "bg-lime-500/70", dark: "bg-lime-400/60", border: "border-lime-600", hex: "#84cc16" },
|
||||
{ bg: "bg-fuchsia-500/70", dark: "bg-fuchsia-400/60", border: "border-fuchsia-600", hex: "#d946ef" },
|
||||
{ bg: "bg-yellow-500/70", dark: "bg-yellow-400/60", border: "border-yellow-600", hex: "#eab308" },
|
||||
{ bg: "bg-red-500/70", dark: "bg-red-400/60", border: "border-red-600", hex: "#ef4444" },
|
||||
{ bg: "bg-blue-500/70", dark: "bg-blue-400/60", border: "border-blue-600", hex: "#3b82f6" },
|
||||
{ bg: "bg-green-500/70", dark: "bg-green-400/60", border: "border-green-600", hex: "#22c55e" },
|
||||
] as const;
|
||||
|
||||
export type ProjectColor = (typeof PROJECT_PALETTE)[number];
|
||||
|
||||
/**
|
||||
* Returns a deterministic color for a project ID using a simple hash.
|
||||
* Same project ID always gets the same color.
|
||||
*/
|
||||
export function getProjectColor(projectId: string): ProjectColor {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < projectId.length; i++) {
|
||||
hash = ((hash << 5) - hash + projectId.charCodeAt(i)) | 0;
|
||||
}
|
||||
return PROJECT_PALETTE[Math.abs(hash) % PROJECT_PALETTE.length]!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hex color with an alpha suffix for inline styles.
|
||||
* Useful when the block uses inline `backgroundColor` instead of Tailwind classes.
|
||||
*/
|
||||
export function getProjectHex(projectId: string, alpha = "B3"): string {
|
||||
return getProjectColor(projectId).hex + alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a lookup map for a set of project IDs.
|
||||
* Call once per render with visible project IDs.
|
||||
*/
|
||||
export function buildProjectColorMap(projectIds: string[]): Map<string, ProjectColor> {
|
||||
const map = new Map<string, ProjectColor>();
|
||||
for (const id of projectIds) {
|
||||
map.set(id, getProjectColor(id));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
Reference in New Issue
Block a user