"use client"; import { useState } from "react"; import { DOMAIN_COLORS, DOMAIN_LABELS, } from "~/components/analytics/computation-graph/domain-colors"; import { useComputationGraphData } from "~/components/analytics/computation-graph/useComputationGraphData"; import ComputationGraph2D from "~/components/analytics/ComputationGraph2D"; import ComputationGraph3D from "~/components/analytics/ComputationGraph3D"; type Dimension = "2d" | "3d"; interface ResourceHolidayMeta { date: string; name: string; scope: string; calendarName: string | null; } interface ResourceFactorMeta { weeklyAvailability?: Record; baseWorkingDays?: number; effectiveWorkingDays?: number; baseAvailableHours?: number; effectiveAvailableHours?: number; publicHolidayCount?: number; publicHolidayWorkdayCount?: number; publicHolidayHoursDeduction?: number; absenceDayCount?: number; absenceHoursDeduction?: number; chargeableHours?: number; utilizationPct?: number; } interface ResourceGraphMeta { resourceName?: string; resourceEid?: string; month?: string; countryCode?: string | null; countryName?: string | null; federalState?: string | null; metroCityName?: string | null; resolvedHolidays?: ResourceHolidayMeta[]; factors?: ResourceFactorMeta; } function formatNumber(value: number | undefined, digits = 1): string { if (typeof value !== "number" || Number.isNaN(value)) { return "—"; } return new Intl.NumberFormat("de-DE", { minimumFractionDigits: digits, maximumFractionDigits: digits, }).format(value); } export default function ComputationGraphClient() { const state = useComputationGraphData(); const [dimension, setDimension] = useState("2d"); const { viewMode, setViewMode, resourceId, setResourceId, month, setMonth, projectId, setProjectId, resources, projects, isLoading, activeDomains, graphData, rawData, highlightedNodes, setHighlightedNodes, domainFilter, toggleDomain, } = state; const resourceMeta = viewMode === "resource" ? (rawData?.meta as ResourceGraphMeta | undefined) : undefined; const resourceFactors = resourceMeta?.factors; const weeklyAvailabilityEntries: Array<[string, number | undefined]> = resourceFactors?.weeklyAvailability ? [ ["Mo", resourceFactors.weeklyAvailability.monday], ["Di", resourceFactors.weeklyAvailability.tuesday], ["Mi", resourceFactors.weeklyAvailability.wednesday], ["Do", resourceFactors.weeklyAvailability.thursday], ["Fr", resourceFactors.weeklyAvailability.friday], ["Sa", resourceFactors.weeklyAvailability.saturday], ["So", resourceFactors.weeklyAvailability.sunday], ] : []; const weeklyAvailability = resourceFactors?.weeklyAvailability ? weeklyAvailabilityEntries .filter((entry): entry is [string, number] => typeof entry[1] === "number" && entry[1] > 0) .map(([label, hours]) => `${label} ${formatNumber(hours, 1)}h`) .join(" · ") : "—"; const topHolidays = resourceMeta?.resolvedHolidays?.slice(0, 6) ?? []; return (
{/* ── Header Bar ── */}
{/* 2D / 3D Toggle */}
{/* View Mode Toggle */}
{/* Selectors */} {viewMode === "resource" ? ( <> setMonth(e.target.value)} className="rounded-md border border-zinc-300 bg-white px-3 py-1.5 text-sm dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-200" /> ) : ( )} {/* Meta info */} {graphData.nodes.length > 0 && ( {graphData.nodes.length} nodes, {graphData.links.length} links )} {/* Clear highlight */} {highlightedNodes && ( )}
{/* ── Main Area ── */}
{/* Domain Filter Sidebar */}
Domains {activeDomains.map((domain) => ( ))}
{/* Graph View */}
{isLoading ? (
Loading computation graph...
) : dimension === "2d" ? ( ) : ( )}
{viewMode === "resource" && resourceMeta && ( )}
); }