feat(leaflet-markers): add driverLetterPin + unassignedPoolPin
This commit is contained in:
parent
d3c68d0703
commit
a26e7c6312
2 changed files with 110 additions and 0 deletions
65
src/components/leaflet-markers.test.ts
Normal file
65
src/components/leaflet-markers.test.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import L from "leaflet";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { driverLetterPin, unassignedPoolPin } from "./leaflet-markers";
|
||||
|
||||
describe("driverLetterPin", () => {
|
||||
it("returns an L.DivIcon sized 28x28", () => {
|
||||
const icon = driverLetterPin("A", 3);
|
||||
expect(icon).toBeInstanceOf(L.DivIcon);
|
||||
expect(icon.options.iconSize).toEqual([28, 28]);
|
||||
expect(icon.options.iconAnchor).toEqual([14, 14]);
|
||||
});
|
||||
|
||||
it("renders the letter inside the HTML", () => {
|
||||
const icon = driverLetterPin("B", 5);
|
||||
const html = icon.options.html as string;
|
||||
expect(html).toContain("B");
|
||||
expect(html).toContain("5");
|
||||
});
|
||||
|
||||
it("uses a custom color when provided", () => {
|
||||
const icon = driverLetterPin("A", 2, "#336699");
|
||||
const html = icon.options.html as string;
|
||||
expect(html).toContain("#336699");
|
||||
});
|
||||
|
||||
it("falls back to the default blue when no color is given", () => {
|
||||
const icon = driverLetterPin("A", 2);
|
||||
const html = icon.options.html as string;
|
||||
// Default blue from DRIVER_PALETTE[1] (#1B76BB) — assert presence
|
||||
// loosely via the substring "1B76BB" so a future palette tweak
|
||||
// doesn't immediately break the test.
|
||||
expect(html.toLowerCase()).toContain("1b76bb");
|
||||
});
|
||||
});
|
||||
|
||||
describe("unassignedPoolPin", () => {
|
||||
it("returns an L.DivIcon sized 28x28", () => {
|
||||
const icon = unassignedPoolPin("ready", 3);
|
||||
expect(icon).toBeInstanceOf(L.DivIcon);
|
||||
expect(icon.options.iconSize).toEqual([28, 28]);
|
||||
expect(icon.options.iconAnchor).toEqual([14, 14]);
|
||||
});
|
||||
|
||||
it("encodes ✓ when glyph is 'ready'", () => {
|
||||
const icon = unassignedPoolPin("ready", 3);
|
||||
const html = icon.options.html as string;
|
||||
expect(html).toContain("✓");
|
||||
expect(html).not.toContain("✕");
|
||||
expect(html).toContain("3");
|
||||
});
|
||||
|
||||
it("encodes ✕ when glyph is 'unready'", () => {
|
||||
const icon = unassignedPoolPin("unready", 2);
|
||||
const html = icon.options.html as string;
|
||||
expect(html).toContain("✕");
|
||||
expect(html).not.toContain("✓");
|
||||
expect(html).toContain("2");
|
||||
});
|
||||
|
||||
it("renders the amber fill colour", () => {
|
||||
const icon = unassignedPoolPin("ready", 1);
|
||||
const html = icon.options.html as string;
|
||||
expect(html.toLowerCase()).toContain("f59e0b");
|
||||
});
|
||||
});
|
||||
|
|
@ -94,4 +94,49 @@ export function clusterBubble(color: string, count: number) {
|
|||
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: `<div style="position:relative;width:${size}px;height:${size}px;"><div style="position:absolute;inset:0;display:grid;place-items:center;background:${color};color:white;border:2.5px solid white;border-radius:999px;box-shadow:0 6px 14px rgba(15,23,42,.28);font-size:13px;font-weight:800;letter-spacing:0.3px;text-shadow:0 1px 2px rgba(0,0,0,.45);">${letter}</div><div style="position:absolute;top:-4px;right:-4px;min-width:16px;height:16px;padding:0 4px;display:grid;place-items:center;background:white;color:#1F2937;border-radius:999px;border:1.5px solid ${color};font-size:10px;font-weight:800;box-shadow:0 2px 6px rgba(15,23,42,.22);">${count}</div></div>`,
|
||||
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: `<div style="position:relative;width:${size}px;height:${size}px;"><div style="position:absolute;inset:0;display:grid;place-items:center;background:${amber};color:white;border:2.5px solid white;border-radius:999px;box-shadow:0 6px 14px rgba(217,119,6,.32);font-size:14px;font-weight:900;line-height:1;text-shadow:0 1px 2px rgba(0,0,0,.45);">${symbol}</div><div style="position:absolute;top:-4px;right:-4px;min-width:16px;height:16px;padding:0 4px;display:grid;place-items:center;background:white;color:#1F2937;border-radius:999px;border:1.5px solid ${amber};font-size:10px;font-weight:800;box-shadow:0 2px 6px rgba(15,23,42,.22);">${count}</div></div>`,
|
||||
iconSize: [size, size],
|
||||
iconAnchor: [size / 2, size / 2],
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue