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:
@@ -11,6 +11,8 @@ import { AiSummaryCard } from "./AiSummaryCard.js";
|
||||
import { SkillMatrixUpload } from "./SkillMatrixUpload.js";
|
||||
import { usePermissions } from "~/hooks/usePermissions.js";
|
||||
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
||||
import { ProgressRing } from "~/components/ui/ProgressRing.js";
|
||||
import { FadeIn } from "~/components/ui/FadeIn.js";
|
||||
|
||||
interface ResourceDetailProps {
|
||||
resourceId: string;
|
||||
@@ -47,11 +49,19 @@ const allocationStatusColor: Record<string, string> = {
|
||||
CANCELLED: "bg-red-100 text-red-500",
|
||||
};
|
||||
|
||||
function StatCard({ label, value, sub, tooltip }: { label: string; value: string | number; sub?: string; tooltip?: string }) {
|
||||
function StatCard({ label, value, sub, tooltip, ring }: { label: string; value: string | number; sub?: string; tooltip?: string; ring?: { value: number; color: string } }) {
|
||||
return (
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-4">
|
||||
<div className="bg-white dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-700 p-4">
|
||||
<div className="text-xs text-gray-500 mb-1 flex items-center">{label}{tooltip && <InfoTooltip content={tooltip} />}</div>
|
||||
<div className="text-xl font-bold text-gray-900">{value}</div>
|
||||
{ring ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<ProgressRing value={ring.value} size={48} strokeWidth={3.5} color={ring.color}>
|
||||
<span className="text-sm font-bold text-gray-900 dark:text-gray-100">{value}</span>
|
||||
</ProgressRing>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-xl font-bold text-gray-900 dark:text-gray-100">{value}</div>
|
||||
)}
|
||||
{sub && <div className="text-xs text-gray-400 mt-0.5">{sub}</div>}
|
||||
</div>
|
||||
);
|
||||
@@ -291,6 +301,10 @@ export function ResourceDetail({ resourceId }: ResourceDetailProps) {
|
||||
label="Chargeability Target"
|
||||
value={`${resource.chargeabilityTarget}%`}
|
||||
tooltip="The percentage of working time this resource is expected to spend on chargeable/billable work."
|
||||
ring={{
|
||||
value: resource.chargeabilityTarget,
|
||||
color: "var(--color-blue-500, #3b82f6)",
|
||||
}}
|
||||
/>
|
||||
{canViewCosts && (
|
||||
<StatCard
|
||||
@@ -302,6 +316,16 @@ export function ResourceDetail({ resourceId }: ResourceDetailProps) {
|
||||
? "Incl. proposed + imported TBD planning"
|
||||
: "Confirmed + active only"
|
||||
}
|
||||
{...(chargeStats != null ? {
|
||||
ring: {
|
||||
value: chargeStats.actualChargeability,
|
||||
color: chargeStats.actualChargeability >= resource.chargeabilityTarget
|
||||
? "var(--color-green-500, #22c55e)"
|
||||
: chargeStats.actualChargeability >= resource.chargeabilityTarget - 10
|
||||
? "var(--color-amber-500, #f59e0b)"
|
||||
: "var(--color-red-500, #ef4444)",
|
||||
},
|
||||
} : {})}
|
||||
/>
|
||||
)}
|
||||
{canViewCosts && (
|
||||
@@ -418,7 +442,9 @@ export function ResourceDetail({ resourceId }: ResourceDetailProps) {
|
||||
)}
|
||||
|
||||
{/* Skill Radar Chart */}
|
||||
<SkillRadarChart skills={skills} />
|
||||
<FadeIn delay={0.1} direction="up">
|
||||
<SkillRadarChart skills={skills} />
|
||||
</FadeIn>
|
||||
|
||||
{/* Roles */}
|
||||
{resourceRoles.length > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user