Skip to content

Commit 6a6d0d6

Browse files
committed
feat(admin): add admin UI templates and static assets
1 parent 4d01e39 commit 6a6d0d6

10 files changed

Lines changed: 1129 additions & 0 deletions

File tree

web/static/js/admin.js

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
const mockVehicles = [
2+
{
3+
id: "SEA-101",
4+
label: "Bus 101",
5+
lat: 47.6101,
6+
lng: -122.3426,
7+
route: "Rapid E Line",
8+
status: "active",
9+
corridor: "Downtown to Ballard",
10+
stop: "3rd Ave & Pine St",
11+
},
12+
{
13+
id: "SEA-108",
14+
label: "Bus 108",
15+
lat: 47.6206,
16+
lng: -122.3201,
17+
route: "Route 8",
18+
status: "active",
19+
corridor: "Capitol Hill Crosstown",
20+
stop: "Denny Way & Broadway",
21+
},
22+
{
23+
id: "SEA-214",
24+
label: "Bus 214",
25+
lat: 47.5989,
26+
lng: -122.3347,
27+
route: "South Lake Loop",
28+
status: "active",
29+
corridor: "Pioneer Square Connector",
30+
stop: "Jackson St Transit Hub",
31+
},
32+
{
33+
id: "SEA-305",
34+
label: "Bus 305",
35+
lat: 47.6677,
36+
lng: -122.3826,
37+
route: "Rapid E Line",
38+
status: "idle",
39+
corridor: "Northwest Layover",
40+
stop: "Ballard Ave NW",
41+
},
42+
{
43+
id: "SEA-417",
44+
label: "Bus 417",
45+
lat: 47.6267,
46+
lng: -122.3561,
47+
route: "South Lake Loop",
48+
status: "active",
49+
corridor: "Seattle Center Spur",
50+
stop: "Queen Anne Ave N",
51+
},
52+
{
53+
id: "SEA-522",
54+
label: "Bus 522",
55+
lat: 47.5884,
56+
lng: -122.3023,
57+
route: "Route 8",
58+
status: "idle",
59+
corridor: "Mount Baker Relief",
60+
stop: "Rainier Ave S",
61+
},
62+
];
63+
64+
const mockCorridors = [
65+
{
66+
name: "Rapid E Line",
67+
color: "#0f766e",
68+
points: [
69+
[47.6101, -122.3426],
70+
[47.6205, -122.3492],
71+
[47.6362, -122.3563],
72+
[47.6516, -122.3752],
73+
[47.6677, -122.3826],
74+
],
75+
},
76+
{
77+
name: "Route 8",
78+
color: "#f59e0b",
79+
points: [
80+
[47.5884, -122.3023],
81+
[47.6002, -122.3119],
82+
[47.6117, -122.3174],
83+
[47.6206, -122.3201],
84+
[47.6312, -122.3225],
85+
],
86+
},
87+
{
88+
name: "South Lake Loop",
89+
color: "#0284c7",
90+
points: [
91+
[47.5989, -122.3347],
92+
[47.6072, -122.3324],
93+
[47.6202, -122.3384],
94+
[47.6267, -122.3561],
95+
],
96+
},
97+
];
98+
99+
function makeMarkerIcon(status) {
100+
const markerClass = status === "active" ? "bus-marker bus-marker--active" : "bus-marker bus-marker--idle";
101+
return L.divIcon({
102+
className: "",
103+
html: `<div class="${markerClass}">
104+
<span class="bus-marker__pulse"></span>
105+
<span class="bus-marker__icon">&#128652;</span>
106+
</div>`,
107+
iconSize: [42, 42],
108+
iconAnchor: [21, 21],
109+
popupAnchor: [0, -18],
110+
});
111+
}
112+
113+
function buildPopup(vehicle) {
114+
const badgeClass = vehicle.status === "active"
115+
? "map-popup__badge map-popup__badge--active"
116+
: "map-popup__badge map-popup__badge--idle";
117+
118+
return `<div class="map-popup">
119+
<div class="map-popup__header">
120+
<div>
121+
<div class="map-popup__title">&#128652; ${vehicle.label}</div>
122+
<div style="font-size:12px;color:#64748b;margin-top:2px;">${vehicle.id}</div>
123+
</div>
124+
<span class="${badgeClass}">${vehicle.status}</span>
125+
</div>
126+
<div class="map-popup__meta">
127+
<div><strong>Route</strong> ${vehicle.route}</div>
128+
<div><strong>Corridor</strong> ${vehicle.corridor}</div>
129+
<div><strong>Nearest stop</strong> ${vehicle.stop}</div>
130+
</div>
131+
</div>`;
132+
}
133+
134+
function initMap() {
135+
const el = document.getElementById("main-map");
136+
if (!el) return;
137+
const map = L.map("main-map", {
138+
zoomControl: false,
139+
scrollWheelZoom: true,
140+
}).setView([47.6062, -122.3321], 13);
141+
142+
L.tileLayer("https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png", {
143+
attribution: "&copy; OpenStreetMap contributors &copy; CARTO",
144+
maxZoom: 19,
145+
}).addTo(map);
146+
147+
L.control.zoom({ position: "bottomright" }).addTo(map);
148+
149+
mockCorridors.forEach(corridor => {
150+
L.polyline(corridor.points, {
151+
color: corridor.color,
152+
weight: 5,
153+
opacity: 0.82,
154+
lineCap: "round",
155+
})
156+
.addTo(map)
157+
.bindTooltip(corridor.name, {
158+
direction: "top",
159+
offset: [0, -4],
160+
opacity: 0.95,
161+
});
162+
});
163+
164+
mockVehicles.forEach(v => {
165+
L.marker([v.lat, v.lng], { icon: makeMarkerIcon(v.status) })
166+
.addTo(map)
167+
.bindPopup(buildPopup(v));
168+
});
169+
170+
const activeCount = mockVehicles.filter(vehicle => vehicle.status === "active").length;
171+
const activeCountEl = document.getElementById("fleet-active-count");
172+
const routeCountEl = document.getElementById("route-count");
173+
174+
if (activeCountEl) activeCountEl.textContent = String(activeCount);
175+
if (routeCountEl) routeCountEl.textContent = String(mockCorridors.length);
176+
}
177+
178+
// Auto-initialize the map if this page has the map container
179+
if (document.getElementById("main-map")) {
180+
initMap();
181+
}

0 commit comments

Comments
 (0)