{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "cost-breakdown",
  "title": "Cost Breakdown",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/cost-breakdown/cost-breakdown.component.ts",
      "content": "// Three-panel cost breakdown: a stacked-by-week bar over time on top, and two\n// side-by-side categorical pie charts below (cost share by lane + by carrier).\n// Port of the React block 1:1 — swap `weeklySeries`, `byLane`, and `byCarrier`\n// to repoint. Pies surface inline value labels so the panels read at a glance.\nimport { ChangeDetectionStrategy, Component, Input } from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport { UiBarChartComponent } from '@/ui/charts/bar-chart/bar-chart.component'\nimport { UiPieChartComponent } from '@/ui/charts/pie-chart/pie-chart.component'\n\nexport interface CostWeekRow {\n  week: string\n  detentionOrigin: number\n  combinedDndOrigin: number\n  demurrageOrigin: number\n  detentionDest: number\n  demurrageDest: number\n  combinedDndDest: number\n}\n\n// 17 weeks of cost data. Heavy spike in mid-Feb mirrors a closing\n// quarter cleanup or a port-disruption incident.\nexport const weeklySeries: CostWeekRow[] = [\n  {\n    week: 'Jan 26 - Feb 1',\n    detentionOrigin: 2800,\n    combinedDndOrigin: 0,\n    demurrageOrigin: 0,\n    detentionDest: 0,\n    demurrageDest: 0,\n    combinedDndDest: 0,\n  },\n  {\n    week: 'Feb 2 - Feb 8',\n    detentionOrigin: 1100,\n    combinedDndOrigin: 0,\n    demurrageOrigin: 0,\n    detentionDest: 0,\n    demurrageDest: 0,\n    combinedDndDest: 0,\n  },\n  {\n    week: 'Feb 9 - Feb 15',\n    detentionOrigin: 1800,\n    combinedDndOrigin: 3100,\n    demurrageOrigin: 800,\n    detentionDest: 1800,\n    demurrageDest: 4300,\n    combinedDndDest: 1100,\n  },\n  {\n    week: 'Feb 16 - Feb 22',\n    detentionOrigin: 0,\n    combinedDndOrigin: 22000,\n    demurrageOrigin: 12500,\n    detentionDest: 0,\n    demurrageDest: 1900,\n    combinedDndDest: 35500,\n  },\n  {\n    week: 'Feb 23 - Mar 1',\n    detentionOrigin: 0,\n    combinedDndOrigin: 0,\n    demurrageOrigin: 3300,\n    detentionDest: 800,\n    demurrageDest: 6800,\n    combinedDndDest: 4400,\n  },\n  {\n    week: 'Mar 2 - Mar 8',\n    detentionOrigin: 2400,\n    combinedDndOrigin: 0,\n    demurrageOrigin: 6400,\n    detentionDest: 1200,\n    demurrageDest: 2800,\n    combinedDndDest: 6600,\n  },\n  {\n    week: 'Mar 9 - Mar 15',\n    detentionOrigin: 0,\n    combinedDndOrigin: 0,\n    demurrageOrigin: 600,\n    detentionDest: 1300,\n    demurrageDest: 5300,\n    combinedDndDest: 5400,\n  },\n  {\n    week: 'Mar 16 - Mar 22',\n    detentionOrigin: 5400,\n    combinedDndOrigin: 0,\n    demurrageOrigin: 6000,\n    detentionDest: 600,\n    demurrageDest: 4200,\n    combinedDndDest: 1400,\n  },\n  {\n    week: 'Mar 23 - Mar 29',\n    detentionOrigin: 0,\n    combinedDndOrigin: 11000,\n    demurrageOrigin: 4900,\n    detentionDest: 0,\n    demurrageDest: 4300,\n    combinedDndDest: 2200,\n  },\n  {\n    week: 'Mar 30 - Apr 5',\n    detentionOrigin: 7700,\n    combinedDndOrigin: 14000,\n    demurrageOrigin: 5400,\n    detentionDest: 1700,\n    demurrageDest: 6300,\n    combinedDndDest: 7200,\n  },\n  {\n    week: 'Apr 6 - Apr 12',\n    detentionOrigin: 1200,\n    combinedDndOrigin: 3100,\n    demurrageOrigin: 1500,\n    detentionDest: 0,\n    demurrageDest: 4800,\n    combinedDndDest: 6300,\n  },\n  {\n    week: 'Apr 13 - Apr 19',\n    detentionOrigin: 0,\n    combinedDndOrigin: 3300,\n    demurrageOrigin: 3200,\n    detentionDest: 3500,\n    demurrageDest: 5000,\n    combinedDndDest: 5700,\n  },\n  {\n    week: 'Apr 20 - Apr 26',\n    detentionOrigin: 1700,\n    combinedDndOrigin: 5300,\n    demurrageOrigin: 2500,\n    detentionDest: 600,\n    demurrageDest: 3800,\n    combinedDndDest: 4800,\n  },\n  {\n    week: 'Apr 27 - May 3',\n    detentionOrigin: 6000,\n    combinedDndOrigin: 15000,\n    demurrageOrigin: 5500,\n    detentionDest: 600,\n    demurrageDest: 800,\n    combinedDndDest: 2400,\n  },\n  {\n    week: 'May 4 - May 10',\n    detentionOrigin: 1900,\n    combinedDndOrigin: 2200,\n    demurrageOrigin: 3300,\n    detentionDest: 3500,\n    demurrageDest: 3700,\n    combinedDndDest: 1100,\n  },\n  {\n    week: 'May 11 - May 17',\n    detentionOrigin: 1900,\n    combinedDndOrigin: 1200,\n    demurrageOrigin: 800,\n    detentionDest: 700,\n    demurrageDest: 600,\n    combinedDndDest: 1200,\n  },\n]\n\nexport const costStackKeys = [\n  { key: 'detentionOrigin', label: 'Detention at origin' },\n  { key: 'combinedDndOrigin', label: 'Combined at origin' },\n  { key: 'demurrageOrigin', label: 'Demurrage at origin' },\n  { key: 'detentionDest', label: 'Detention at destination' },\n  { key: 'demurrageDest', label: 'Demurrage at destination' },\n  { key: 'combinedDndDest', label: 'Combined at destination' },\n] as const\n\nexport const byLane = [\n  { name: 'LANE-01', value: 119800 },\n  { name: 'LANE-02', value: 39000 },\n  { name: 'LANE-03', value: 53100 },\n  { name: 'LANE-04', value: 32000 },\n  { name: 'LANE-05', value: 24700 },\n  { name: 'LANE-06', value: 23500 },\n  { name: 'LANE-07', value: 20300 },\n  { name: 'LANE-08', value: 18300 },\n  { name: 'LANE-09', value: 14700 },\n  { name: 'OTHER', value: 8100 },\n]\n\nexport const byCarrier = [\n  { name: 'C-ALPHA', value: 144200 },\n  { name: 'C-BETA', value: 69800 },\n  { name: 'C-GAMMA', value: 32700 },\n  { name: 'C-DELTA', value: 31800 },\n  { name: 'C-EPSILON', value: 27800 },\n  { name: 'C-ZETA', value: 22600 },\n  { name: 'OTHER', value: 10100 },\n]\n\nexport function totalCostAll(): number {\n  return weeklySeries.reduce(\n    (acc, w) =>\n      acc +\n      w.detentionOrigin +\n      w.combinedDndOrigin +\n      w.demurrageOrigin +\n      w.detentionDest +\n      w.demurrageDest +\n      w.combinedDndDest,\n    0,\n  )\n}\n\nexport function fmtUsd(n: number): string {\n  return `$${(n / 1000).toFixed(1)}k`\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-cost-breakdown, [ui-cost-breakdown]',\n  standalone: true,\n  // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [UiBarChartComponent, UiPieChartComponent],\n  template: `\n    <div data-slot=\"cost-breakdown\" [class]=\"rootClass\">\n      <!-- Weekly trend (top) -->\n      <section class=\"bg-card border-border rounded-lg border p-5\">\n        <div class=\"mb-3 flex items-baseline justify-between gap-4\">\n          <div>\n            <h3 class=\"text-sm font-semibold tracking-tight\">Cost per day</h3>\n            <p class=\"text-muted-foreground mt-0.5 text-xs\">17-week rollup. Hover a stack to inspect the breakdown.</p>\n          </div>\n          <div class=\"text-right\">\n            <div class=\"text-muted-foreground font-mono text-xs tracking-wider uppercase\">Total</div>\n            <div class=\"text-lg font-semibold tabular-nums\">{{ fmtUsd(totalAll) }}</div>\n          </div>\n        </div>\n        <ui-bar-chart\n          [data]=\"weeklySeries\"\n          xField=\"week\"\n          [yField]=\"stackFields\"\n          [option]=\"stackedOption\"\n          height=\"320\"\n        />\n      </section>\n\n      <!-- Dual pie breakdown (bottom) -->\n      <div class=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n        <section class=\"bg-card border-border rounded-lg border p-4 sm:p-5\">\n          <h3 class=\"text-sm font-semibold tracking-tight\">Cost share by lane</h3>\n          <p class=\"text-muted-foreground mt-0.5 mb-4 text-xs\">Top 9 lanes + a grouped 'Other' bucket.</p>\n          <ui-pie-chart [data]=\"byLane\" [option]=\"pieOption\" height=\"320\" />\n        </section>\n\n        <section class=\"bg-card border-border rounded-lg border p-4 sm:p-5\">\n          <h3 class=\"text-sm font-semibold tracking-tight\">Cost share by carrier</h3>\n          <p class=\"text-muted-foreground mt-0.5 mb-4 text-xs\">Top 6 carriers + a grouped 'Other' bucket.</p>\n          <ui-pie-chart [data]=\"byCarrier\" [option]=\"pieOption\" height=\"320\" />\n        </section>\n      </div>\n    </div>\n  `,\n})\nexport class UiCostBreakdownComponent {\n  protected readonly weeklySeries = weeklySeries\n  protected readonly byLane = byLane\n  protected readonly byCarrier = byCarrier\n  protected readonly totalAll = totalCostAll()\n\n  protected readonly stackFields = costStackKeys.map((s) => s.key)\n\n  protected readonly stackedOption = {\n    series: costStackKeys.map(() => ({ stack: 'total' as const })),\n    yAxis: {\n      type: 'value' as const,\n      axisLabel: { formatter: (v: number) => (v >= 1000 ? `$${Math.round(v / 1000)}K` : `$${v}`) },\n    },\n  }\n\n  // Inline slice labels mirror the React `pieOption`. The label colour is left to\n  // the pie wrapper default (registry chart text token) so the block does not take\n  // a direct `@/ui/charts/use-chart-theme` dependency.\n  protected readonly pieOption = {\n    legend: { show: false },\n    series: [\n      {\n        radius: ['0%', '70%'],\n        label: {\n          show: true,\n          formatter: (p: { value: number; percent: number }) => `$${(p.value / 1000).toFixed(1)}k\\n(${p.percent}%)`,\n          fontSize: 10,\n        },\n        labelLine: { show: false },\n      },\n    ],\n  }\n\n  @Input('class') className?: string\n\n  get rootClass(): string {\n    return cn('space-y-6', this.className)\n  }\n\n  fmtUsd(n: number): string {\n    return fmtUsd(n)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/cost-breakdown/cost-breakdown.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/cost-breakdown/index.ts",
      "content": "export {\n  UiCostBreakdownComponent,\n  weeklySeries,\n  costStackKeys,\n  byLane,\n  byCarrier,\n  totalCostAll,\n  fmtUsd,\n  type CostWeekRow,\n} from './cost-breakdown.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/cost-breakdown/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/bar-chart.json",
    "https://uipkge.dev/r/angular/pie-chart.json"
  ],
  "description": "Three-panel spend dashboard. Stacked weekly bar chart over time on top, plus two side-by-side categorical pies below (share by lane / share by carrier). Theme-aware via registry tokens.",
  "categories": [
    "finance",
    "dashboard",
    "analytics"
  ]
}