{"version":3,"sources":["../src/components/shared/ChartComponents.tsx"],"sourcesContent":["import { useState } from 'react';\nimport Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\nimport {\n  bgClassToCssVar,\n  polarToCartesian,\n  describeArc,\n} from '../../utils/chartUtils';\n\n/**\n * Bar item configuration used by chart components\n */\nexport interface BarItemConfig {\n  key: string;\n  label: string;\n  legendLabel: string;\n  value: number;\n  colorClass: string;\n}\n\n/**\n * Calculate Y-axis tick values based on max value.\n * Rounds up to the nearest multiple of 10 and generates 5 evenly spaced ticks.\n */\nexport const calculateYAxisTicks = (maxValue: number): number[] => {\n  if (maxValue <= 0) return [0];\n\n  const niceMax = Math.ceil(maxValue / 10) * 10;\n  const step = niceMax / 4;\n  return [\n    niceMax,\n    Math.round(step * 3),\n    Math.round(step * 2),\n    Math.round(step),\n    0,\n  ];\n};\n\n/**\n * Legend dot + label for chart legends\n */\nexport const LegendItem = ({\n  color,\n  label,\n}: {\n  color: string;\n  label: string;\n}) => (\n  <div className=\"flex flex-row items-center gap-2\">\n    <div className={cn('w-2 h-2 rounded-full', color)} />\n    <Text size=\"xs\" weight=\"medium\" className=\"text-text-600\">\n      {label}\n    </Text>\n  </div>\n);\n\n/**\n * Individual vertical bar component\n */\nexport const DataBar = ({\n  label,\n  value,\n  maxValue,\n  colorClass,\n  chartHeight,\n}: {\n  label: string;\n  value: number;\n  maxValue: number;\n  colorClass: string;\n  chartHeight: number;\n}) => {\n  const percentage = maxValue === 0 ? 0 : (value / maxValue) * 100;\n  const barHeight = (percentage / 100) * chartHeight;\n\n  return (\n    <div className=\"flex flex-col items-center gap-2 flex-1\">\n      <div\n        className=\"w-full flex items-end justify-center\"\n        style={{ height: chartHeight }}\n      >\n        <div\n          className={cn(\n            'w-16 rounded-lg transition-all duration-300',\n            colorClass\n          )}\n          style={{ height: `${barHeight}px` }}\n        />\n      </div>\n      <Text size=\"xs\" weight=\"medium\" className=\"text-text-600 text-center\">\n        {label}\n      </Text>\n    </div>\n  );\n};\n\n/**\n * Horizontal dashed grid lines for chart background\n */\nexport const GridLines = ({\n  ticks,\n  chartHeight,\n}: {\n  ticks: number[];\n  chartHeight: number;\n}) => (\n  <div\n    className=\"absolute inset-0 flex flex-col justify-between pointer-events-none\"\n    style={{ height: chartHeight }}\n  >\n    {ticks.map((tick, index) => (\n      <div\n        key={`${tick}-${index}`}\n        className=\"w-full border-t border-dashed border-border-200\"\n        style={{ marginTop: index === 0 ? 0 : undefined }}\n      />\n    ))}\n  </div>\n);\n\n/**\n * Y-Axis tick labels\n */\nexport const YAxis = ({\n  ticks,\n  chartHeight,\n}: {\n  ticks: number[];\n  chartHeight: number;\n}) => (\n  <div\n    className=\"flex flex-col justify-between items-end pr-3\"\n    style={{ height: chartHeight }}\n    aria-hidden=\"true\"\n  >\n    {ticks.map((tick, index) => (\n      <Text\n        key={`${tick}-${index}`}\n        size=\"xs\"\n        weight=\"medium\"\n        className=\"text-text-500\"\n      >\n        {tick}\n      </Text>\n    ))}\n  </div>\n);\n\n// ─── Pie chart ────────────────────────────────────────────────\n\n/**\n * A single slice of a pie chart.\n * Provide either `colorClass` (Tailwind bg-* class) or `color` (direct CSS value).\n * When `color` is set it takes precedence over `colorClass`.\n */\nexport interface PieSlice {\n  /** Unique key for the slice (uses label if not provided) */\n  key?: string;\n  label: string;\n  /** Drives the slice's angle. Always the number — never a formatted string. */\n  value: number;\n  colorClass: string;\n  /** Direct CSS color value — takes precedence over colorClass when provided */\n  color?: string;\n  /**\n   * What the legend shows, when it differs from `value`.\n   *\n   * The pie is sized by the percentage, but the legend often needs to read as\n   * something else — \"1h 20min\" rather than \"45\". Falls back to `value`.\n   */\n  displayValue?: string | number;\n}\n\n/** Default radius ratio (44% of size) */\nconst PIE_RADIUS_RATIO = 0.44;\n\n/** Default label radius ratio (62% from center) */\nconst PIE_LABEL_RADIUS_RATIO = 0.62;\n\n/** Default minimum percentage to show label */\nconst PIE_MIN_PERCENTAGE_FOR_LABEL = 5;\n\n/** Threshold to consider a slice as whole pie */\nconst PIE_WHOLE_THRESHOLD = 99.9999;\n\n/**\n * Props for SimplePieChart component\n */\nexport interface SimplePieChartProps {\n  /** Array of slices to render */\n  slices: PieSlice[];\n  /** Chart size in pixels (default: 130) */\n  size?: number;\n  /** Text to show when all values are zero */\n  emptyText?: string;\n  /** Minimum percentage to display label inside slice (default: 5) */\n  minPercentageForLabel?: number;\n  /** Label position as ratio of radius (default: 0.62) */\n  labelRadiusRatio?: number;\n  /** Label text color (default: var(--color-text-950)) */\n  labelColor?: string;\n  /** Label font weight (default: 500) */\n  labelFontWeight?: number;\n  /** Label text shadow (default: none) */\n  labelTextShadow?: string;\n  /** Hover overlay opacity (default: 0.4) */\n  hoverOpacity?: number;\n  /** External hover state (controlled mode) */\n  hoveredSlice?: string | null;\n  /** Callback when slice is hovered */\n  onSliceHover?: (key: string | null) => void;\n}\n\n/**\n * Interactive SVG pie chart built from PieSlice data.\n * Shows percentage labels for slices above minimum threshold.\n * Renders a grey circle when all values are zero.\n */\nexport const SimplePieChart = ({\n  slices,\n  size = 130,\n  emptyText,\n  minPercentageForLabel = PIE_MIN_PERCENTAGE_FOR_LABEL,\n  labelRadiusRatio = PIE_LABEL_RADIUS_RATIO,\n  labelColor = 'var(--color-text-950)',\n  labelFontWeight = 500,\n  labelTextShadow,\n  hoverOpacity = 0.4,\n  hoveredSlice: externalHovered,\n  onSliceHover,\n}: SimplePieChartProps) => {\n  const [internalHovered, setInternalHovered] = useState<string | null>(null);\n\n  // Use external hover state if provided (controlled mode)\n  const isControlled = externalHovered !== undefined;\n  const hovered = isControlled ? externalHovered : internalHovered;\n\n  const handleSliceHover = (label: string | null) => {\n    if (!isControlled) {\n      setInternalHovered(label);\n    }\n    onSliceHover?.(label);\n  };\n\n  const total = slices.reduce((sum, s) => sum + s.value, 0);\n  const radius = size * PIE_RADIUS_RATIO;\n  const center = size / 2;\n\n  if (total === 0) {\n    return (\n      <svg\n        width={size}\n        height={size}\n        viewBox={`0 0 ${size} ${size}`}\n        aria-hidden={!emptyText}\n        aria-label={emptyText || undefined}\n      >\n        <circle\n          cx={center}\n          cy={center}\n          r={radius}\n          className=\"fill-background-200\"\n        />\n        {emptyText && (\n          <text\n            x={center}\n            y={center}\n            textAnchor=\"middle\"\n            dominantBaseline=\"central\"\n            fill=\"var(--color-text-400)\"\n            style={{\n              fontSize: 12,\n              fontWeight: 500,\n              fontFamily: 'Roboto, sans-serif',\n            }}\n          >\n            {emptyText}\n          </text>\n        )}\n      </svg>\n    );\n  }\n\n  let cumAngle = 0;\n  const computed = slices\n    .map((s) => {\n      const pct = (s.value / total) * 100;\n      const startAngle = cumAngle;\n      cumAngle += (pct / 100) * 360;\n      const endAngle = cumAngle;\n      const midAngle = startAngle + (endAngle - startAngle) / 2;\n      return { ...s, pct, startAngle, endAngle, midAngle };\n    })\n    .filter((s) => s.pct > 0);\n\n  return (\n    <svg\n      width={size}\n      height={size}\n      viewBox={`0 0 ${size} ${size}`}\n      aria-hidden=\"true\"\n      onMouseLeave={() => handleSliceHover(null)}\n    >\n      {computed.map((slice) => {\n        const sliceKey = slice.key ?? slice.label;\n        const isHovered = hovered === sliceKey;\n        const isWhole = slice.pct >= PIE_WHOLE_THRESHOLD;\n        const arcPath = isWhole\n          ? undefined\n          : describeArc(\n              center,\n              center,\n              radius,\n              slice.startAngle,\n              slice.endAngle\n            );\n        const labelPos = polarToCartesian(\n          center,\n          center,\n          radius * labelRadiusRatio,\n          slice.midAngle\n        );\n        const fill = slice.color ?? bgClassToCssVar(slice.colorClass);\n\n        return (\n          <g\n            key={sliceKey}\n            onMouseEnter={() => handleSliceHover(sliceKey)}\n            className=\"cursor-pointer\"\n          >\n            {isWhole ? (\n              <circle cx={center} cy={center} r={radius} fill={fill} />\n            ) : (\n              <path d={arcPath} fill={fill} />\n            )}\n            {isHovered &&\n              (isWhole ? (\n                <circle\n                  cx={center}\n                  cy={center}\n                  r={radius}\n                  fill=\"white\"\n                  opacity={hoverOpacity}\n                  style={{ pointerEvents: 'none' }}\n                />\n              ) : (\n                <path\n                  d={arcPath}\n                  fill=\"white\"\n                  opacity={hoverOpacity}\n                  style={{ pointerEvents: 'none' }}\n                />\n              ))}\n            {slice.pct >= minPercentageForLabel && (\n              <text\n                x={labelPos.x}\n                y={labelPos.y}\n                textAnchor=\"middle\"\n                dominantBaseline=\"central\"\n                fill={labelColor}\n                style={{\n                  fontSize: 14,\n                  fontWeight: labelFontWeight,\n                  fontFamily: 'Roboto, sans-serif',\n                  lineHeight: 1,\n                  letterSpacing: 0,\n                  pointerEvents: 'none',\n                  textShadow: labelTextShadow,\n                }}\n              >\n                {`${Math.round(slice.pct)}%`}\n              </text>\n            )}\n          </g>\n        );\n      })}\n    </svg>\n  );\n};\n\n/**\n * A single legend row: colour dot + label + value.\n */\nexport const LegendRow = ({\n  colorClass,\n  color,\n  label,\n  value,\n  displayValue,\n}: {\n  colorClass: string;\n  color?: string;\n  label: string;\n  value: number;\n  displayValue?: string | number;\n}) => (\n  <div className=\"flex items-center gap-2\">\n    <div\n      className={cn('w-2 h-2 rounded-full shrink-0', !color && colorClass)}\n      style={color ? { backgroundColor: color } : undefined}\n    />\n    <Text size=\"sm\" weight=\"medium\" className=\"text-text-950 flex-1\">\n      {label}\n    </Text>\n    <Text size=\"sm\" weight=\"medium\" className=\"text-text-600 ml-3\">\n      {displayValue ?? value}\n    </Text>\n  </div>\n);\n\n/**\n * Card with a legend list and a pie chart side by side.\n */\nexport const LegendPieCard = ({ slices }: { slices: PieSlice[] }) => (\n  <div className=\"flex items-center justify-between gap-4 p-4 bg-background border border-border-50 rounded-xl\">\n    <div className=\"flex flex-col gap-2\">\n      {slices.map((s) => (\n        <LegendRow\n          key={s.key ?? s.label}\n          colorClass={s.colorClass}\n          color={s.color}\n          label={s.label}\n          value={s.value}\n          displayValue={s.displayValue}\n        />\n      ))}\n    </div>\n    <SimplePieChart slices={slices} />\n  </div>\n);\n"],"mappings":"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","names":[]}