{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "leaflet-sales-territory-map",
  "title": "Leaflet Sales Territory Map",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/leaflet-sales-territory-map/leaflet-sales-territory-map.component.ts",
      "content": "import { Component, Input, OnInit, ViewChild, signal, ChangeDetectionStrategy } from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport {\n  UiLeafletMapComponent,\n  UiLeafletMarkerComponent,\n  UiLeafletPolygonComponent,\n} from '@/ui/leaflet-map/leaflet-map.component'\n\nexport type TerritoryStatus = 'attained' | 'ontrack' | 'attention'\n\nexport interface SalesTerritory {\n  id: string\n  name: string\n  code: string\n  rep: string\n  arr: string\n  attainment: number\n  status: TerritoryStatus\n  /** Territory centroid as [lng, lat]. */\n  centerLngLat: [number, number]\n  /** Territory boundary ring as [lng, lat][]. */\n  polygon: [number, number][]\n}\n\n// Hand-drawn plausible US region boundaries — four rep territories tiling CONUS.\nexport const DEFAULT_TERRITORIES: SalesTerritory[] = [\n  {\n    id: 'pacific-west',\n    name: 'Pacific West',\n    code: 'PAC',\n    rep: 'Sierra Delgado',\n    arr: '$2.8M',\n    attainment: 112,\n    status: 'attained',\n    centerLngLat: [-119.8, 39.5],\n    polygon: [\n      [-124.4, 32.5],\n      [-114.0, 32.5],\n      [-114.0, 35.0],\n      [-114.6, 39.0],\n      [-120.0, 42.0],\n      [-117.0, 45.9],\n      [-117.0, 49.0],\n      [-124.7, 48.4],\n      [-124.2, 40.0],\n      [-124.4, 32.5],\n    ],\n  },\n  {\n    id: 'mountain-central',\n    name: 'Mountain Central',\n    code: 'MTN',\n    rep: 'Colt Brannigan',\n    arr: '$2.2M',\n    attainment: 97,\n    status: 'ontrack',\n    centerLngLat: [-104.8, 39.9],\n    polygon: [\n      [-117.0, 49.0],\n      [-95.0, 49.0],\n      [-95.0, 43.5],\n      [-97.5, 40.0],\n      [-95.5, 36.5],\n      [-100.0, 34.5],\n      [-103.0, 32.0],\n      [-106.5, 31.8],\n      [-108.2, 31.3],\n      [-111.0, 31.3],\n      [-114.0, 32.5],\n      [-114.0, 35.0],\n      [-114.6, 39.0],\n      [-120.0, 42.0],\n      [-117.0, 45.9],\n      [-117.0, 49.0],\n    ],\n  },\n  {\n    id: 'southeast',\n    name: 'Southeast',\n    code: 'SE',\n    rep: 'Harper Quinn',\n    arr: '$1.9M',\n    attainment: 84,\n    status: 'attention',\n    centerLngLat: [-83.0, 33.5],\n    polygon: [\n      [-95.5, 36.5],\n      [-94.0, 33.0],\n      [-93.0, 30.0],\n      [-89.0, 29.0],\n      [-84.0, 29.9],\n      [-82.6, 28.0],\n      [-81.0, 25.8],\n      [-80.1, 25.1],\n      [-80.5, 26.8],\n      [-81.4, 30.4],\n      [-80.8, 32.2],\n      [-77.5, 34.0],\n      [-75.5, 35.5],\n      [-76.0, 36.9],\n      [-77.5, 39.0],\n      [-80.5, 40.8],\n      [-83.0, 39.5],\n      [-84.8, 38.0],\n      [-88.0, 37.0],\n      [-90.5, 36.0],\n      [-95.5, 36.5],\n    ],\n  },\n  {\n    id: 'northeast',\n    name: 'Northeast',\n    code: 'NE',\n    rep: 'Devon Whitaker',\n    arr: '$3.4M',\n    attainment: 121,\n    status: 'attained',\n    centerLngLat: [-74.5, 41.8],\n    polygon: [\n      [-95.0, 49.0],\n      [-95.0, 45.9],\n      [-92.5, 44.5],\n      [-89.5, 43.0],\n      [-87.0, 41.5],\n      [-83.5, 41.7],\n      [-79.8, 42.3],\n      [-76.8, 43.5],\n      [-74.0, 45.0],\n      [-71.0, 45.0],\n      [-70.0, 43.6],\n      [-70.9, 42.9],\n      [-74.0, 40.5],\n      [-75.0, 38.5],\n      [-77.5, 39.0],\n      [-80.5, 40.8],\n      [-83.0, 39.5],\n      [-84.8, 38.0],\n      [-88.0, 37.0],\n      [-90.5, 36.0],\n      [-95.5, 36.5],\n      [-97.5, 40.0],\n      [-95.0, 43.5],\n      [-95.0, 49.0],\n    ],\n  },\n]\n\n/** Plain-hex fills — Leaflet renders paths to SVG/canvas, so CSS vars do not resolve there. */\nexport const TERRITORY_STATUS_STYLES: Record<TerritoryStatus, { fill: string; stroke: string }> = {\n  attained: { fill: '#22c55e', stroke: '#4ade80' },\n  ontrack: { fill: '#38bdf8', stroke: '#7dd3fc' },\n  attention: { fill: '#f59e0b', stroke: '#fbbf24' },\n}\n\n// Geographic center of CONUS — stable reference so the map's center input never re-fires.\nconst US_CENTER: [number, number] = [-98.5795, 39.8283]\n\nconst PIN_SELECTED =\n  'border-primary bg-primary text-primary-foreground rounded-full border px-2 py-0.5 font-mono text-xs font-semibold shadow-xs'\nconst PIN_IDLE =\n  'border-border bg-card text-foreground rounded-full border px-2 py-0.5 font-mono text-xs font-semibold shadow-xs'\n\n/**\n * Sales territory map on free OpenStreetMap/Esri tiles — no API key. Region\n * boundary polygons and rep pins with quota attainment; selecting a territory\n * (polygon, pin or quota row) flies the camera to it.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-leaflet-sales-territory-map, [ui-leaflet-sales-territory-map]',\n  standalone: true,\n  imports: [\n    UiBadgeComponent,\n    UiCardComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiCardContentComponent,\n    UiLeafletMapComponent,\n    UiLeafletMarkerComponent,\n    UiLeafletPolygonComponent,\n  ],\n  host: {\n    'data-slot': 'leaflet-sales-territory-map',\n    '[class]': 'hostClass',\n  },\n  template: `\n    <div class=\"flex flex-wrap items-center justify-between gap-3\">\n      <h2 class=\"text-foreground text-lg font-semibold tracking-tight\">Territories</h2>\n      <p class=\"text-muted-foreground text-xs\">{{ territories.length }} regions</p>\n    </div>\n\n    <div class=\"grid min-h-0 flex-1 grid-cols-1 gap-4 lg:grid-cols-12\">\n      <ui-card class=\"border-border flex min-h-0 flex-col overflow-hidden shadow-xs lg:col-span-8\">\n        <div class=\"relative min-h-[360px] flex-1 overflow-hidden\">\n          <ui-leaflet-map variant=\"dark\" [center]=\"usCenter\" [zoom]=\"4\" class=\"absolute inset-0 size-full\">\n            @for (t of territories; track 'boundary-' + t.id) {\n              <ui-leaflet-polygon\n                [lngLatPath]=\"t.polygon\"\n                [color]=\"strokeColor(t)\"\n                [weight]=\"selectedId() === t.id ? 2.5 : 1.5\"\n                [opacity]=\"0.9\"\n                [fill]=\"true\"\n                [fillColor]=\"fillColor(t)\"\n                [fillOpacity]=\"selectedId() === t.id ? 0.3 : 0.14\"\n                (layerClick)=\"selectTerritory(t)\"\n              />\n            }\n            @for (t of territories; track t.id) {\n              <ui-leaflet-marker [lngLat]=\"t.centerLngLat\" anchor=\"center\">\n                <button type=\"button\" [class]=\"pinClass(t)\" (click)=\"selectTerritory(t)\">\n                  {{ t.code }} {{ t.attainment }}%\n                </button>\n              </ui-leaflet-marker>\n            }\n          </ui-leaflet-map>\n        </div>\n      </ui-card>\n\n      <ui-card class=\"border-border flex min-h-0 flex-col overflow-hidden shadow-xs lg:col-span-4\">\n        <ui-card-header class=\"border-border border-b p-4\">\n          <ui-card-title class=\"text-sm font-semibold\">Quota</ui-card-title>\n        </ui-card-header>\n        <ui-card-content class=\"divide-border min-h-0 flex-1 divide-y overflow-auto p-0\">\n          @for (t of territories; track t.id) {\n            <button type=\"button\" [class]=\"rowClass(t)\" (click)=\"selectTerritory(t)\">\n              <div class=\"min-w-0\">\n                <p class=\"text-foreground truncate text-sm font-medium\">{{ t.code }} · {{ t.name }}</p>\n                <p class=\"text-muted-foreground truncate font-mono text-xs tabular-nums\">{{ t.rep }} · {{ t.arr }}</p>\n              </div>\n              <ui-badge [variant]=\"statusVariant(t.status)\" class=\"shrink-0 text-xs tabular-nums\"\n                >{{ t.attainment }}%</ui-badge\n              >\n            </button>\n          }\n        </ui-card-content>\n      </ui-card>\n    </div>\n  `,\n})\nexport class UiLeafletSalesTerritoryMapComponent implements OnInit {\n  @Input() territories: SalesTerritory[] = DEFAULT_TERRITORIES\n  @Input('class') className?: string\n\n  @ViewChild(UiLeafletMapComponent) map?: UiLeafletMapComponent\n\n  protected readonly usCenter = US_CENTER\n  readonly selectedId = signal('')\n\n  get hostClass(): string {\n    return cn('flex h-full min-h-0 flex-col gap-4', this.className)\n  }\n\n  ngOnInit(): void {\n    this.selectedId.set(this.territories[0]?.id ?? '')\n  }\n\n  selectTerritory(territory: SalesTerritory): void {\n    this.selectedId.set(territory.id)\n    this.map?.flyTo({\n      center: territory.centerLngLat,\n      zoom: 5,\n      duration: 800,\n    })\n  }\n\n  statusVariant(status: TerritoryStatus): 'info' | 'secondary' | 'warning' {\n    if (status === 'attained') return 'info'\n    if (status === 'ontrack') return 'secondary'\n    return 'warning'\n  }\n\n  strokeColor(t: SalesTerritory): string {\n    return TERRITORY_STATUS_STYLES[t.status].stroke\n  }\n\n  fillColor(t: SalesTerritory): string {\n    return TERRITORY_STATUS_STYLES[t.status].fill\n  }\n\n  pinClass(t: SalesTerritory): string {\n    return this.selectedId() === t.id ? PIN_SELECTED : PIN_IDLE\n  }\n\n  rowClass(t: SalesTerritory): string {\n    return cn(\n      'hover:bg-accent/50 flex w-full items-center justify-between gap-3 px-4 py-3 text-left transition-colors',\n      this.selectedId() === t.id && 'bg-accent/60',\n    )\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/leaflet-sales-territory-map/leaflet-sales-territory-map.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/leaflet-sales-territory-map/index.ts",
      "content": "export {\n  UiLeafletSalesTerritoryMapComponent,\n  DEFAULT_TERRITORIES,\n  TERRITORY_STATUS_STYLES,\n  type SalesTerritory,\n  type TerritoryStatus,\n} from './leaflet-sales-territory-map.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/leaflet-sales-territory-map/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/leaflet-map.json"
  ],
  "description": "Sales territory map on free OpenStreetMap/Esri tiles — no API key. Region boundary polygons and rep pins with quota attainment; select a territory to fly the camera.",
  "categories": [
    "analytics",
    "dashboard"
  ]
}