{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "leaflet-asset-tracking-map",
  "title": "Leaflet Asset Tracking Map",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/leaflet-asset-tracking-map/LeafletAssetTrackingMap.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { computed, ref } from 'vue'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'\nimport { LeafletMap, LeafletMarker, LeafletPolyline, type LeafletMapRef } from '@/components/ui/leaflet-map'\n\ntype LngLat = [number, number]\n\nexport interface ContainerAsset {\n  id: string\n  containerNo: string\n  type: 'Reefer' | 'Dry Cargo' | 'Hazardous'\n  location: string\n  coords: LngLat\n  temp?: string\n  humidity?: string\n  battery: string\n  geofence: 'Inside Zone' | 'In Transit' | 'Exited Perimeter'\n  status: 'Nominal' | 'Alert' | 'Secured'\n}\n\nexport interface LeafletAssetTrackingMapProps {\n  class?: HTMLAttributes['class']\n  assets?: ContainerAsset[]\n}\n\nconst DEFAULT_ASSETS: ContainerAsset[] = [\n  {\n    id: 'c-1',\n    containerNo: 'MSKU-948102',\n    type: 'Reefer',\n    location: 'Pasir Panjang Terminal, Berth P5',\n    coords: [103.792, 1.276],\n    temp: '-18.4°C',\n    humidity: '84%',\n    battery: '92%',\n    geofence: 'Inside Zone',\n    status: 'Nominal',\n  },\n  {\n    id: 'c-2',\n    containerNo: 'CMAU-310892',\n    type: 'Dry Cargo',\n    location: 'Singapore Strait, Eastbound Lane',\n    coords: [103.945, 1.205],\n    battery: '78%',\n    geofence: 'In Transit',\n    status: 'Nominal',\n  },\n  {\n    id: 'c-3',\n    containerNo: 'HLXU-772901',\n    type: 'Reefer',\n    location: 'Jurong Island Anchorage',\n    coords: [103.71, 1.245],\n    temp: '+4.2°C',\n    humidity: '62%',\n    battery: '64%',\n    geofence: 'Inside Zone',\n    status: 'Alert',\n  },\n  {\n    id: 'c-4',\n    containerNo: 'OOLU-550183',\n    type: 'Hazardous',\n    location: 'Tuas Port, Hazardous Bay T2',\n    coords: [103.635, 1.285],\n    battery: '95%',\n    geofence: 'Inside Zone',\n    status: 'Secured',\n  },\n]\n\n/** Eastbound / westbound shipping lanes through the Singapore Strait. */\nconst SHIPPING_LANES: LngLat[][] = [\n  [\n    [103.5, 1.16],\n    [103.7, 1.18],\n    [103.9, 1.2],\n    [104.1, 1.22],\n  ],\n  [\n    [103.5, 1.21],\n    [103.7, 1.24],\n    [103.9, 1.27],\n    [104.1, 1.29],\n  ],\n]\n\nconst props = defineProps<LeafletAssetTrackingMapProps>()\n\nconst assets = computed(() => props.assets ?? DEFAULT_ASSETS)\nconst activeId = ref(DEFAULT_ASSETS[0]?.id ?? '')\nconst activeAsset = computed(() => assets.value.find((a) => a.id === activeId.value) ?? assets.value[0])\n\nconst mapRef = ref<LeafletMapRef | null>(null)\n\nfunction selectAsset(a: ContainerAsset) {\n  activeId.value = a.id\n  mapRef.value?.flyTo({ center: a.coords, zoom: 11.5 })\n}\n</script>\n\n<template>\n  <div data-slot=\"leaflet-asset-tracking-map\" :class=\"cn('grid gap-4 lg:grid-cols-[1fr_320px]', props.class)\">\n    <div class=\"border-border bg-card relative h-[480px] overflow-hidden rounded-xl border\">\n      <LeafletMap\n        ref=\"mapRef\"\n        variant=\"satellite-streets\"\n        :center=\"[103.8519, 1.29027]\"\n        :zoom=\"11\"\n        class=\"h-full w-full\"\n      >\n        <!-- Strait shipping lanes -->\n        <LeafletPolyline\n          v-for=\"(lane, i) in SHIPPING_LANES\"\n          :key=\"i\"\n          :lng-lat-path=\"lane\"\n          color=\"#22d3ee\"\n          :weight=\"2\"\n          :opacity=\"0.55\"\n          dash-array=\"6 8\"\n        />\n\n        <LeafletMarker v-for=\"a in assets\" :key=\"a.id\" :lng-lat=\"a.coords\" anchor=\"bottom\">\n          <button\n            type=\"button\"\n            class=\"group relative flex cursor-pointer flex-col items-center\"\n            @click.stop=\"selectAsset(a)\"\n          >\n            <span\n              class=\"flex size-7 items-center justify-center rounded-md shadow-md ring-2\"\n              :class=\"\n                a.status === 'Alert'\n                  ? 'bg-warning text-white ring-amber-400'\n                  : 'bg-primary text-primary-foreground ring-primary/25'\n              \"\n            >\n              <svg\n                class=\"size-4\"\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                stroke-width=\"2\"\n                stroke-linecap=\"round\"\n                stroke-linejoin=\"round\"\n                aria-hidden=\"true\"\n              >\n                <path\n                  d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\"\n                />\n                <path d=\"m3.3 7 8.7 5 8.7-5\" />\n                <path d=\"M12 22V12\" />\n              </svg>\n            </span>\n            <span\n              class=\"border-border bg-background/90 mt-1 rounded border px-1 py-0.5 font-mono text-xs font-bold shadow-xs\"\n            >\n              {{ a.containerNo.split('-')[1] }}\n            </span>\n          </button>\n        </LeafletMarker>\n      </LeafletMap>\n    </div>\n\n    <!-- Asset Telemetry & Sensor Card -->\n    <Card class=\"flex flex-col justify-between\">\n      <CardHeader>\n        <div class=\"flex items-center justify-between\">\n          <Badge variant=\"outline\" class=\"font-mono text-xs\">{{ activeAsset.type }}</Badge>\n          <Badge\n            :class=\"\n              activeAsset.status === 'Alert'\n                ? 'border-warning/20 bg-warning/10 text-warning'\n                : 'border-success/20 bg-success/10 text-success'\n            \"\n          >\n            {{ activeAsset.status }}\n          </Badge>\n        </div>\n        <CardTitle class=\"mt-2 font-mono text-lg\">{{ activeAsset.containerNo }}</CardTitle>\n        <CardDescription class=\"text-xs\">{{ activeAsset.location }}</CardDescription>\n      </CardHeader>\n      <CardContent class=\"space-y-4\">\n        <div class=\"border-border bg-muted/40 grid grid-cols-2 gap-2 rounded-lg border p-2.5 font-mono text-xs\">\n          <div v-if=\"activeAsset.temp\">\n            <div class=\"text-muted-foreground text-xs uppercase\">Cold Chain Temp</div>\n            <div class=\"text-sm font-bold\" :class=\"activeAsset.status === 'Alert' ? 'text-warning' : 'text-foreground'\">\n              {{ activeAsset.temp }}\n            </div>\n          </div>\n          <div v-if=\"activeAsset.humidity\">\n            <div class=\"text-muted-foreground text-xs uppercase\">Humidity</div>\n            <div class=\"text-foreground text-sm font-bold\">{{ activeAsset.humidity }}</div>\n          </div>\n          <div>\n            <div class=\"text-muted-foreground text-xs uppercase\">IoT Battery</div>\n            <div class=\"text-foreground text-sm font-bold\">{{ activeAsset.battery }}</div>\n          </div>\n          <div>\n            <div class=\"text-muted-foreground text-xs uppercase\">Geofence</div>\n            <div class=\"text-foreground text-sm font-bold\">{{ activeAsset.geofence }}</div>\n          </div>\n        </div>\n\n        <div class=\"border-border space-y-1 border-t pt-3\">\n          <div class=\"text-muted-foreground mb-1 font-mono text-xs tracking-wider uppercase\">Tracked Cargo Units</div>\n          <button\n            v-for=\"a in assets\"\n            :key=\"a.id\"\n            type=\"button\"\n            class=\"flex w-full items-center justify-between rounded-md px-2 py-1 text-xs transition-colors\"\n            :class=\"\n              activeId === a.id\n                ? 'bg-accent text-accent-foreground font-semibold'\n                : 'hover:bg-muted text-muted-foreground'\n            \"\n            @click=\"selectAsset(a)\"\n          >\n            <span class=\"font-mono\">{{ a.containerNo }}</span>\n            <span class=\"font-mono text-xs\" :class=\"a.status === 'Alert' ? 'text-warning font-bold' : 'opacity-70'\">\n              {{ a.type }}\n            </span>\n          </button>\n        </div>\n\n        <Button variant=\"outline\" size=\"sm\" class=\"w-full font-mono text-xs\" @click=\"selectAsset(activeAsset)\">\n          Center on Container\n        </Button>\n      </CardContent>\n    </Card>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/LeafletAssetTrackingMap.vue"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/card.json",
    "https://uipkge.dev/r/vue/leaflet-map.json"
  ],
  "description": "Intermodal cargo container tracking on free Esri satellite tiles — no API key — with shipping lanes, cold-chain sensor status, and geofence state.",
  "categories": [
    "logistics",
    "supply-chain",
    "map"
  ]
}