Files
mapcn/public/r/logistics-network.json
2026-03-16 17:20:51 +05:30

55 lines
22 KiB
JSON

{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "logistics-network",
"title": "Logistics Network",
"description": "Domestic logistics map with a sidebar of stats.",
"dependencies": [
"lucide-react"
],
"registryDependencies": [
"map",
"card",
"badge",
"button"
],
"files": [
{
"path": "src/registry/blocks/logistics-network/page.tsx",
"content": "\"use client\";\n\nimport { SidebarInset, SidebarProvider } from \"@/components/ui/sidebar\";\nimport { hubs, routes } from \"./data\";\nimport { FilterSidebar } from \"./components/filter-sidebar\";\nimport { NetworkMap } from \"./components/network-map\";\n\nexport function LogisticsNetworkBlock() {\n return (\n <SidebarProvider>\n <FilterSidebar hubs={hubs} routes={routes} />\n <SidebarInset>\n <NetworkMap hubs={hubs} routes={routes} />\n </SidebarInset>\n </SidebarProvider>\n );\n}\n",
"type": "registry:page",
"target": "app/logistics/page.tsx"
},
{
"path": "src/registry/blocks/logistics-network/data.ts",
"content": "export interface Hub {\n id: string;\n city: string;\n lng: number;\n lat: number;\n type: \"primary\" | \"secondary\";\n shipments: number;\n region: \"west\" | \"midwest\" | \"south\" | \"northeast\";\n}\n\nexport interface Route {\n from: string;\n to: string;\n mode: \"air\" | \"ground\";\n shipments: number;\n status: \"active\" | \"delayed\";\n}\n\nexport const hubs: Hub[] = [\n {\n id: \"ord\",\n city: \"Chicago\",\n lng: -87.6298,\n lat: 41.8781,\n type: \"primary\",\n shipments: 1247,\n region: \"midwest\",\n },\n {\n id: \"lax\",\n city: \"Los Angeles\",\n lng: -118.2437,\n lat: 34.0522,\n type: \"primary\",\n shipments: 1102,\n region: \"west\",\n },\n {\n id: \"jfk\",\n city: \"New York\",\n lng: -73.9352,\n lat: 40.6413,\n type: \"primary\",\n shipments: 983,\n region: \"northeast\",\n },\n {\n id: \"dfw\",\n city: \"Dallas\",\n lng: -96.797,\n lat: 32.8968,\n type: \"primary\",\n shipments: 856,\n region: \"south\",\n },\n {\n id: \"atl\",\n city: \"Atlanta\",\n lng: -84.4281,\n lat: 33.6407,\n type: \"primary\",\n shipments: 914,\n region: \"south\",\n },\n {\n id: \"den\",\n city: \"Denver\",\n lng: -104.6731,\n lat: 39.8617,\n type: \"secondary\",\n shipments: 634,\n region: \"west\",\n },\n {\n id: \"sea\",\n city: \"Seattle\",\n lng: -122.3321,\n lat: 47.6062,\n type: \"secondary\",\n shipments: 723,\n region: \"west\",\n },\n {\n id: \"mia\",\n city: \"Miami\",\n lng: -80.1918,\n lat: 25.7617,\n type: \"secondary\",\n shipments: 478,\n region: \"south\",\n },\n {\n id: \"phx\",\n city: \"Phoenix\",\n lng: -112.074,\n lat: 33.4484,\n type: \"secondary\",\n shipments: 512,\n region: \"west\",\n },\n {\n id: \"iah\",\n city: \"Houston\",\n lng: -95.3698,\n lat: 29.9844,\n type: \"secondary\",\n shipments: 698,\n region: \"south\",\n },\n {\n id: \"bos\",\n city: \"Boston\",\n lng: -71.0054,\n lat: 42.3643,\n type: \"secondary\",\n shipments: 534,\n region: \"northeast\",\n },\n {\n id: \"sfo\",\n city: \"San Francisco\",\n lng: -122.4194,\n lat: 37.7749,\n type: \"secondary\",\n shipments: 789,\n region: \"west\",\n },\n {\n id: \"msp\",\n city: \"Minneapolis\",\n lng: -93.2219,\n lat: 44.8848,\n type: \"secondary\",\n shipments: 423,\n region: \"midwest\",\n },\n {\n id: \"dtw\",\n city: \"Detroit\",\n lng: -83.0458,\n lat: 42.2162,\n type: \"secondary\",\n shipments: 456,\n region: \"midwest\",\n },\n {\n id: \"slc\",\n city: \"Salt Lake City\",\n lng: -111.978,\n lat: 40.758,\n type: \"secondary\",\n shipments: 342,\n region: \"west\",\n },\n];\n\nexport const routes: Route[] = [\n { from: \"ord\", to: \"lax\", mode: \"air\", shipments: 234, status: \"active\" },\n { from: \"ord\", to: \"jfk\", mode: \"ground\", shipments: 312, status: \"active\" },\n { from: \"ord\", to: \"dfw\", mode: \"air\", shipments: 189, status: \"active\" },\n { from: \"ord\", to: \"atl\", mode: \"air\", shipments: 213, status: \"active\" },\n { from: \"ord\", to: \"den\", mode: \"ground\", shipments: 156, status: \"active\" },\n { from: \"ord\", to: \"msp\", mode: \"ground\", shipments: 198, status: \"active\" },\n { from: \"ord\", to: \"dtw\", mode: \"ground\", shipments: 167, status: \"delayed\" },\n { from: \"lax\", to: \"sfo\", mode: \"ground\", shipments: 245, status: \"active\" },\n { from: \"lax\", to: \"sea\", mode: \"air\", shipments: 178, status: \"active\" },\n { from: \"lax\", to: \"den\", mode: \"air\", shipments: 198, status: \"active\" },\n { from: \"lax\", to: \"phx\", mode: \"ground\", shipments: 212, status: \"active\" },\n { from: \"lax\", to: \"dfw\", mode: \"air\", shipments: 223, status: \"active\" },\n { from: \"jfk\", to: \"atl\", mode: \"air\", shipments: 267, status: \"active\" },\n { from: \"jfk\", to: \"mia\", mode: \"air\", shipments: 234, status: \"active\" },\n { from: \"jfk\", to: \"bos\", mode: \"ground\", shipments: 189, status: \"active\" },\n { from: \"jfk\", to: \"ord\", mode: \"air\", shipments: 278, status: \"active\" },\n { from: \"dfw\", to: \"atl\", mode: \"air\", shipments: 198, status: \"active\" },\n { from: \"dfw\", to: \"iah\", mode: \"ground\", shipments: 245, status: \"active\" },\n { from: \"dfw\", to: \"den\", mode: \"air\", shipments: 167, status: \"active\" },\n { from: \"dfw\", to: \"phx\", mode: \"ground\", shipments: 156, status: \"delayed\" },\n { from: \"atl\", to: \"mia\", mode: \"ground\", shipments: 234, status: \"active\" },\n { from: \"atl\", to: \"iah\", mode: \"air\", shipments: 189, status: \"active\" },\n { from: \"den\", to: \"sea\", mode: \"air\", shipments: 178, status: \"active\" },\n { from: \"den\", to: \"slc\", mode: \"ground\", shipments: 198, status: \"active\" },\n { from: \"den\", to: \"phx\", mode: \"air\", shipments: 167, status: \"active\" },\n { from: \"sea\", to: \"sfo\", mode: \"air\", shipments: 212, status: \"active\" },\n { from: \"sfo\", to: \"phx\", mode: \"air\", shipments: 156, status: \"active\" },\n { from: \"mia\", to: \"iah\", mode: \"air\", shipments: 198, status: \"active\" },\n { from: \"msp\", to: \"ord\", mode: \"ground\", shipments: 189, status: \"active\" },\n { from: \"msp\", to: \"den\", mode: \"air\", shipments: 145, status: \"active\" },\n { from: \"dtw\", to: \"ord\", mode: \"ground\", shipments: 167, status: \"active\" },\n { from: \"dtw\", to: \"jfk\", mode: \"air\", shipments: 178, status: \"active\" },\n { from: \"bos\", to: \"jfk\", mode: \"ground\", shipments: 156, status: \"active\" },\n { from: \"slc\", to: \"den\", mode: \"ground\", shipments: 134, status: \"active\" },\n { from: \"slc\", to: \"phx\", mode: \"air\", shipments: 123, status: \"delayed\" },\n { from: \"ord\", to: \"sfo\", mode: \"air\", shipments: 198, status: \"active\" },\n { from: \"ord\", to: \"mia\", mode: \"air\", shipments: 245, status: \"active\" },\n { from: \"lax\", to: \"atl\", mode: \"air\", shipments: 289, status: \"active\" },\n { from: \"jfk\", to: \"dfw\", mode: \"air\", shipments: 234, status: \"active\" },\n { from: \"atl\", to: \"ord\", mode: \"air\", shipments: 267, status: \"active\" },\n { from: \"sfo\", to: \"den\", mode: \"air\", shipments: 189, status: \"active\" },\n];\n\nexport const modeConfig = {\n air: { color: \"#3b82f6\", label: \"Air\" },\n ground: { color: \"#22c55e\", label: \"Ground\" },\n} as const;\n\nexport const statusConfig = {\n active: { color: \"#10b981\", label: \"Active\" },\n delayed: { color: \"#f59e0b\", label: \"Delayed\" },\n} as const;\n\nexport const regionLabels: Record<Hub[\"region\"], string> = {\n west: \"West\",\n midwest: \"Midwest\",\n south: \"South\",\n northeast: \"Northeast\",\n};\n",
"type": "registry:component",
"target": "app/logistics/data.ts"
},
{
"path": "src/registry/blocks/logistics-network/components/map-arcs.tsx",
"content": "\"use client\";\n\nimport { useCallback, useEffect, useMemo } from \"react\";\nimport { useMap } from \"@/registry/map\";\nimport type MapLibreGL from \"maplibre-gl\";\nimport { hubs, modeConfig, statusConfig, type Route } from \"../data\";\n\nconst SOURCE_ID = \"logistics-arcs-source\";\nconst LAYER_ID = \"logistics-arcs-layer\";\n\nfunction generateArc(\n start: [number, number],\n end: [number, number],\n segments = 50,\n): number[][] {\n const [x1, y1] = start;\n const [x2, y2] = end;\n\n const mx = (x1 + x2) / 2;\n const my = (y1 + y2) / 2;\n\n const dx = x2 - x1;\n const dy = y2 - y1;\n const dist = Math.sqrt(dx * dx + dy * dy);\n\n const nx = -dy / dist;\n const ny = dx / dist;\n const height = dist * 0.3;\n\n const cx = mx + nx * height;\n const cy = my + ny * height;\n\n const coords: number[][] = [];\n for (let i = 0; i <= segments; i++) {\n const t = i / segments;\n const px = (1 - t) * (1 - t) * x1 + 2 * (1 - t) * t * cx + t * t * x2;\n const py = (1 - t) * (1 - t) * y1 + 2 * (1 - t) * t * cy + t * t * y2;\n coords.push([px, py]);\n }\n return coords;\n}\n\nfunction getHubById(id: string) {\n return hubs.find((h) => h.id === id)!;\n}\n\ninterface MapArcsProps {\n routes: Route[];\n}\n\nexport function MapArcs({ routes: arcRoutes }: MapArcsProps) {\n const { map, isLoaded } = useMap();\n\n const geoJSON = useMemo<GeoJSON.FeatureCollection>(() => {\n const features: GeoJSON.Feature[] = arcRoutes.map((route) => {\n const fromHub = getHubById(route.from);\n const toHub = getHubById(route.to);\n const coordinates = generateArc(\n [fromHub.lng, fromHub.lat],\n [toHub.lng, toHub.lat],\n );\n return {\n type: \"Feature\" as const,\n properties: {\n id: `${route.from}-${route.to}`,\n mode: route.mode,\n status: route.status,\n color:\n route.status === \"delayed\"\n ? statusConfig.delayed.color\n : modeConfig[route.mode].color,\n },\n geometry: {\n type: \"LineString\" as const,\n coordinates,\n },\n };\n });\n return { type: \"FeatureCollection\", features };\n }, [arcRoutes]);\n\n const addLayer = useCallback(() => {\n if (!map) return;\n\n if (!map.getSource(SOURCE_ID)) {\n map.addSource(SOURCE_ID, { type: \"geojson\", data: geoJSON });\n map.addLayer({\n id: LAYER_ID,\n type: \"line\",\n source: SOURCE_ID,\n layout: { \"line-join\": \"round\", \"line-cap\": \"round\" },\n paint: {\n \"line-color\": [\"get\", \"color\"],\n \"line-width\": 2,\n \"line-opacity\": 0.65,\n },\n });\n } else {\n (map.getSource(SOURCE_ID) as MapLibreGL.GeoJSONSource).setData(geoJSON);\n }\n }, [map, geoJSON]);\n\n useEffect(() => {\n if (!map || !isLoaded) return;\n\n addLayer();\n\n return () => {\n try {\n if (map.getLayer(LAYER_ID)) map.removeLayer(LAYER_ID);\n if (map.getSource(SOURCE_ID)) map.removeSource(SOURCE_ID);\n } catch {\n // ignore\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [map, isLoaded]);\n\n useEffect(() => {\n if (!map || !isLoaded) return;\n const source = map.getSource(SOURCE_ID) as MapLibreGL.GeoJSONSource;\n if (source) source.setData(geoJSON);\n }, [map, isLoaded, geoJSON]);\n\n return null;\n}\n",
"type": "registry:component",
"target": "app/logistics/components/map-arcs.tsx"
},
{
"path": "src/registry/blocks/logistics-network/components/filter-sidebar.tsx",
"content": "\"use client\";\n\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarSeparator,\n} from \"@/components/ui/sidebar\";\nimport { Network, Plane, Truck } from \"lucide-react\";\nimport { regionLabels, statusConfig, type Hub, type Route } from \"../data\";\n\nconst regionIcons: Record<Hub[\"region\"], string> = {\n west: \"W\",\n midwest: \"MW\",\n south: \"S\",\n northeast: \"NE\",\n};\n\ninterface FilterSidebarProps {\n hubs: Hub[];\n routes: Route[];\n}\n\nexport function FilterSidebar({ hubs, routes }: FilterSidebarProps) {\n const totalShipments = routes.reduce((s, r) => s + r.shipments, 0);\n const activeCount = routes.filter((r) => r.status === \"active\").length;\n const delayedCount = routes.filter((r) => r.status === \"delayed\").length;\n const airRouteCount = routes.filter((r) => r.mode === \"air\").length;\n const groundRouteCount = routes.filter((r) => r.mode === \"ground\").length;\n\n return (\n <Sidebar collapsible=\"offcanvas\">\n <SidebarHeader className=\"p-4\">\n <div className=\"flex items-center gap-2\">\n <div className=\"bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg\">\n <Network className=\"size-4\" />\n </div>\n <div className=\"flex flex-col gap-0.5 leading-none\">\n <span className=\"text-sm font-medium\">Logistics Network</span>\n <span className=\"text-muted-foreground text-xs\">\n Domestic Routes\n </span>\n </div>\n </div>\n <div className=\"mt-3 grid grid-cols-3 gap-2\">\n <div className=\"bg-background rounded-md border px-2.5 py-2 text-center\">\n <p className=\"text-lg leading-none font-bold tabular-nums\">\n {hubs.length}\n </p>\n <p className=\"text-muted-foreground mt-1 text-[10px]\">Hubs</p>\n </div>\n <div className=\"bg-background rounded-md border px-2.5 py-2 text-center\">\n <p className=\"text-lg leading-none font-bold tabular-nums\">\n {activeCount}\n </p>\n <p className=\"text-muted-foreground mt-1 text-[10px]\">Active</p>\n </div>\n <div className=\"bg-background rounded-md border px-2.5 py-2 text-center\">\n <p className=\"text-lg leading-none font-bold tabular-nums\">\n {delayedCount}\n </p>\n <p className=\"text-muted-foreground mt-1 text-[10px]\">Delayed</p>\n </div>\n </div>\n </SidebarHeader>\n\n <SidebarSeparator className=\"mx-0\" />\n\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel>Transport Mode</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton\n tooltip=\"Air\"\n className=\"pointer-events-none\"\n >\n <Plane className=\"size-4\" />\n <span>Air Freight</span>\n </SidebarMenuButton>\n <SidebarMenuBadge>{airRouteCount}</SidebarMenuBadge>\n </SidebarMenuItem>\n <SidebarMenuItem>\n <SidebarMenuButton\n tooltip=\"Ground\"\n className=\"pointer-events-none\"\n >\n <Truck className=\"size-4\" />\n <span>Ground</span>\n </SidebarMenuButton>\n <SidebarMenuBadge>{groundRouteCount}</SidebarMenuBadge>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n\n <SidebarGroup>\n <SidebarGroupLabel>Status</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton\n tooltip={statusConfig.active.label}\n className=\"pointer-events-none\"\n >\n <span className=\"flex size-4 items-center justify-center\">\n <span\n className=\"size-2 rounded-full\"\n style={{ backgroundColor: statusConfig.active.color }}\n />\n </span>\n <span>{statusConfig.active.label}</span>\n </SidebarMenuButton>\n <SidebarMenuBadge>{activeCount}</SidebarMenuBadge>\n </SidebarMenuItem>\n <SidebarMenuItem>\n <SidebarMenuButton\n tooltip={statusConfig.delayed.label}\n className=\"pointer-events-none\"\n >\n <span className=\"flex size-4 items-center justify-center\">\n <span\n className=\"size-2 rounded-full\"\n style={{ backgroundColor: statusConfig.delayed.color }}\n />\n </span>\n <span>{statusConfig.delayed.label}</span>\n </SidebarMenuButton>\n <SidebarMenuBadge>{delayedCount}</SidebarMenuBadge>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n\n <SidebarGroup>\n <SidebarGroupLabel>Region</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n {([\"west\", \"midwest\", \"south\", \"northeast\"] as const).map(\n (region) => {\n const hubsInRegion = hubs.filter((h) => h.region === region);\n return (\n <SidebarMenuItem key={region}>\n <SidebarMenuButton\n tooltip={regionLabels[region]}\n className=\"pointer-events-none\"\n >\n <span className=\"bg-muted text-muted-foreground flex size-4 items-center justify-center rounded text-[9px] font-bold\">\n {regionIcons[region]}\n </span>\n <span>{regionLabels[region]}</span>\n </SidebarMenuButton>\n <SidebarMenuBadge>{hubsInRegion.length}</SidebarMenuBadge>\n </SidebarMenuItem>\n );\n },\n )}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n\n <SidebarFooter className=\"p-4 pt-0\">\n <p className=\"text-muted-foreground mb-2 text-[11px] font-medium tracking-wider uppercase\">\n Summary\n </p>\n <div className=\"bg-background space-y-1.5 rounded-md border px-3 py-2 text-xs\">\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Shipments</span>\n <span className=\"text-primary font-medium tabular-nums\">\n {totalShipments.toLocaleString()}\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Routes</span>\n <span className=\"text-primary font-medium tabular-nums\">\n {routes.length}\n </span>\n </div>\n </div>\n </SidebarFooter>\n </Sidebar>\n );\n}\n",
"type": "registry:component",
"target": "app/logistics/components/filter-sidebar.tsx"
},
{
"path": "src/registry/blocks/logistics-network/components/network-map.tsx",
"content": "\"use client\";\n\nimport {\n Map,\n MapControls,\n MapMarker,\n MarkerContent,\n MarkerTooltip,\n} from \"@/registry/map\";\nimport { SidebarTrigger } from \"@/components/ui/sidebar\";\nimport {\n modeConfig,\n regionLabels,\n statusConfig,\n type Hub,\n type Route,\n} from \"../data\";\nimport { MapArcs } from \"./map-arcs\";\nimport { Separator } from \"@/components/ui/separator\";\n\ninterface NetworkMapProps {\n hubs: Hub[];\n routes: Route[];\n}\n\nfunction MapControlsCard() {\n return (\n <div className=\"border-border/40 bg-background/70 absolute top-4 left-4 z-20 flex items-center gap-3 rounded-lg border px-2.5 py-1.5 backdrop-blur-sm\">\n <SidebarTrigger />\n <Separator orientation=\"vertical\" className=\"h-4!\" />\n <div className=\"flex items-center gap-3 text-xs\">\n <div className=\"flex items-center gap-1.5\">\n <span\n className=\"h-0.5 w-4 shrink-0 rounded-full\"\n style={{ backgroundColor: modeConfig.air.color }}\n />\n <span>{modeConfig.air.label}</span>\n </div>\n <div className=\"flex items-center gap-1.5\">\n <span\n className=\"h-0.5 w-4 shrink-0 rounded-full\"\n style={{ backgroundColor: modeConfig.ground.color }}\n />\n <span>{modeConfig.ground.label}</span>\n </div>\n <div className=\"flex items-center gap-1.5\">\n <span\n className=\"h-0.5 w-4 shrink-0 rounded-full\"\n style={{ backgroundColor: statusConfig.delayed.color }}\n />\n <span>{statusConfig.delayed.label}</span>\n </div>\n <div className=\"bg-border h-4 w-px\" />\n <div className=\"flex items-center gap-1.5\">\n <div className=\"size-2.5 shrink-0 rounded-full border border-white bg-blue-500 shadow-sm\" />\n <span>Hub</span>\n </div>\n </div>\n </div>\n );\n}\n\nexport function NetworkMap({ hubs, routes }: NetworkMapProps) {\n return (\n <div className=\"relative h-full\">\n <MapControlsCard />\n\n <Map center={[-98, 39]} zoom={4} projection={{ type: \"globe\" }}>\n <MapControls />\n <MapArcs routes={routes} />\n\n {hubs.map((hub) => (\n <MapMarker key={hub.id} longitude={hub.lng} latitude={hub.lat}>\n <MarkerContent>\n <div className=\"size-3 rounded-full border-2 border-white bg-blue-500 shadow-md\" />\n </MarkerContent>\n <MarkerTooltip\n offset={16}\n className=\"bg-background text-foreground border px-2.5 py-1.5\"\n >\n <p className=\"font-medium\">{hub.city}</p>\n <p className=\"text-muted-foreground mt-1\">\n {hub.shipments.toLocaleString()} shipments\n <span className=\"mx-1.5\">·</span>\n {regionLabels[hub.region]}\n </p>\n </MarkerTooltip>\n </MapMarker>\n ))}\n </Map>\n </div>\n );\n}\n",
"type": "registry:component",
"target": "app/logistics/components/network-map.tsx"
}
],
"meta": {
"iframeHeight": "800px"
},
"categories": [
"logistics",
"network"
],
"type": "registry:block"
}