/** * Shared Leaflet marker factories. * * Used by `SupplierMap` and `CollectionMap`. Kept in a sibling file * (rather than co-located in `geo.ts`) so importing this pulls in the * Leaflet runtime — only do that from a `"use client"` page. */ import L from "leaflet"; /** * Small circular pin. Used for both supplier coordinates and assigned * stops. `label` renders inside the circle (empty string for blank). */ export function pinMarker(color: string, label: string) { return L.divIcon({ className: "", html: `
${label}
`, iconSize: [22, 22], iconAnchor: [11, 11], }); } /** * Status-aware pin — same coordinate, four distinct shapes/colours so * the operator can read the bucket state at a glance when the All filter * is active. * * assigned → 22px filled circle in the driver's palette colour, white border. * unassigned → 28px rotated-square "diamond", hollow centre with a dashed * white inset ring, amber fill, amber outer shadow. The * diamond is rotated via CSS transform so Leaflet's iconAnchor * stays centered. * collected → 24px filled emerald circle, white border (closed-state — * the supplier handed goods over successfully). * failed → 24px filled emerald circle, white border, plus a 3px red * ring on the outside so the sub-state stays visible without * overriding the green "collected" semantic. */ export function pinMarkerKind( kind: "assigned" | "unassigned" | "collected" | "failed", color: string, label: string, count?: number ) { if (kind === "unassigned") { const size = 28; return L.divIcon({ className: "", html: `
${label}
`, iconSize: [size, size], iconAnchor: [size / 2, size / 2], }); } if (kind === "collected" || kind === "failed") { const size = 24; const outerRing = kind === "failed" ? `
` : ""; return L.divIcon({ className: "", html: `
${outerRing}
${label}
`, iconSize: [size + (kind === "failed" ? 6 : 0), size + (kind === "failed" ? 6 : 0)], iconAnchor: [(size + (kind === "failed" ? 6 : 0)) / 2, (size + (kind === "failed" ? 6 : 0)) / 2], }); } // assigned — preserve the original shape for back-compat return pinMarker(color, label); } /** * Square pin with the brand-yellow ring. Used exclusively for drivers * to distinguish them from supplier/stop pins at a glance. The yellow * ring is the PixelForge brand accent and reads on both light and dark * tile layers. */ export function driverMarker(color: string, initials: string) { return L.divIcon({ className: "", html: `
${initials}
`, iconSize: [28, 28], iconAnchor: [14, 14], }); } /** * Round cluster bubble with a count. Scales up for larger clusters * so 25+ doesn't render at the same size as 2. */ export function clusterBubble(color: string, count: number) { const size = count >= 25 ? 52 : count >= 10 ? 46 : 38; return L.divIcon({ className: "", html: `
${count}
`, iconSize: [size, size], iconAnchor: [size / 2, size / 2], }); } /** * Blue circle with the driver letter (A-Z, then 1-9) inside. Used by * the new collection-map "one pin per driver per supplier" model. The * count renders as a small round superscript in the top-right corner. * * Default colour is `DRIVER_PALETTE[1]` (#1B76BB) — the existing deep * blue from src/lib/geo.ts — so the helper stays consistent with the * rest of the app's driver signal even though the page no longer uses * the palette for differentiating drivers (the letter does that now). */ export function driverLetterPin( letter: string, count: number, color: string = "#1B76BB" ) { const size = 28; return L.divIcon({ className: "", html: `
${letter}
${count}
`, iconSize: [size, size], iconAnchor: [size / 2, size / 2], }); } /** * Amber circle with ✓ or ✕ inside, for unassigned buckets on the * collection map. Same size as the driver letter pin so they read as * sibling shapes. The count renders as a white-bordered superscript * in the top-right, mirroring the driver pin. */ export function unassignedPoolPin( glyph: "ready" | "unready", count: number ) { const size = 28; const amber = "#F59E0B"; const symbol = glyph === "ready" ? "✓" : "✕"; return L.divIcon({ className: "", html: `
${symbol}
${count}
`, iconSize: [size, size], iconAnchor: [size / 2, size / 2], }); }