Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import { readHasOptimizeResults } from "../../../utils/hasOptimizeResults";
import {
SIDEBAR_NAV_ITEM_ACTIVE,
SIDEBAR_NAV_ITEM_DISABLED,
SIDEBAR_NAV_ITEM_INACTIVE,
SIDEBAR_NAV_LABEL_ACTIVE,
SIDEBAR_NAV_LABEL_INACTIVE,
SIDEBAR_NAV_PILL_ACTIVE,
SIDEBAR_NAV_PILL_INACTIVE,
} from "@/app/edit/formStyles.v2";

Expand All @@ -12,26 +20,48 @@ const SIDEBAR_RESULTS_ICON = (
height="24"
viewBox="0 0 24 24"
fill="none"
aria-hidden
>
<path
d="M4.35 20.7C4.01667 20.8333 3.70833 20.7958 3.425 20.5875C3.14167 20.3792 3 20.1 3 19.75V5.75C3 5.53333 3.0625 5.34167 3.1875 5.175C3.3125 5.00833 3.48333 4.88333 3.7 4.8L9 3L15 5.1L19.65 3.3C19.9833 3.16667 20.2917 3.20417 20.575 3.4125C20.8583 3.62083 21 3.9 21 4.25V12.675C20.75 12.2917 20.4542 11.9417 20.1125 11.625C19.7708 11.3083 19.4 11.0333 19 10.8V5.7L16 6.85V10C15.65 10 15.3083 10.0292 14.975 10.0875C14.6417 10.1458 14.3167 10.2333 14 10.35V6.85L10 5.45V18.525L4.35 20.7ZM5 18.3L8 17.15V5.45L5 6.45V18.3ZM17.4125 17.5C17.7875 17.1667 17.9833 16.6667 18 16C18.0167 15.4333 17.8292 14.9583 17.4375 14.575C17.0458 14.1917 16.5667 14 16 14C15.4333 14 14.9583 14.1917 14.575 14.575C14.1917 14.9583 14 15.4333 14 16C14 16.5667 14.1917 17.0417 14.575 17.425C14.9583 17.8083 15.4333 18 16 18C16.5667 18 17.0375 17.8333 17.4125 17.5ZM16 20C14.9 20 13.9583 19.6083 13.175 18.825C12.3917 18.0417 12 17.1 12 16C12 14.9 12.3917 13.9583 13.175 13.175C13.9583 12.3917 14.9 12 16 12C17.1 12 18.0417 12.3917 18.825 13.175C19.6083 13.9583 20 14.9 20 16C20 16.3833 19.9542 16.7458 19.8625 17.0875C19.7708 17.4292 19.6333 17.75 19.45 18.05L22 20.6L20.6 22L18.05 19.45C17.75 19.6333 17.4292 19.7708 17.0875 19.8625C16.7458 19.9542 16.3833 20 16 20Z"
fill="var(--edit-text-primary)"
fill="currentColor"
/>
</svg>
);

export default function SidebarResultsButton() {
const pathname = usePathname();
const isResultsPage = pathname === "/results";
const hasStoredRoutes = !isResultsPage && readHasOptimizeResults();

if (isResultsPage) {
return (
<span className={SIDEBAR_NAV_ITEM_ACTIVE} aria-current="page">
<span
className={`${SIDEBAR_NAV_PILL_ACTIVE} text-[var(--edit-foreground)]`}
>
{SIDEBAR_RESULTS_ICON}
</span>
<span className={SIDEBAR_NAV_LABEL_ACTIVE}>Results</span>
</span>
);
}

if (hasStoredRoutes) {
return (
<Link href="/results" className={SIDEBAR_NAV_ITEM_INACTIVE}>
<span className={SIDEBAR_NAV_PILL_INACTIVE}>
{SIDEBAR_RESULTS_ICON}
</span>
<span className={SIDEBAR_NAV_LABEL_INACTIVE}>Results</span>
</Link>
);
}

return (
<Link
href="#"
className={SIDEBAR_NAV_ITEM_DISABLED}
aria-disabled="true"
tabIndex={-1}
>
{" "}
{/* TODO: add results page link when at least one route exists */}
<span className={SIDEBAR_NAV_ITEM_DISABLED} aria-disabled="true">
<span className={SIDEBAR_NAV_PILL_INACTIVE}>{SIDEBAR_RESULTS_ICON}</span>
<span className={SIDEBAR_NAV_LABEL_INACTIVE}>Results</span>
</Link>
</span>
);
}
16 changes: 16 additions & 0 deletions app/ui/src/app/edit/utils/hasOptimizeResults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Route } from "@/app/results/types";

/** True when sessionStorage has at least one route ready for /results. */
export function readHasOptimizeResults(): boolean {
if (typeof window === "undefined") return false;

const stored = sessionStorage.getItem("optimizeResults");
if (!stored) return false;

try {
const parsed = JSON.parse(stored) as Route[];
return Array.isArray(parsed) && parsed.length > 0;
} catch {
return false;
}
}
58 changes: 56 additions & 2 deletions app/ui/src/app/results/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,43 @@ import type { PendingPinMove, Route } from "../types";
import { routeColorHex } from "../utils/routeColors";

const DAVIS_CENTER = { lat: 38.5449, lng: -121.7405 };
const MARKER_ICON_WIDTH = 28;
const MARKER_ICON_HEIGHT = 40;

let markerScaledSize: google.maps.Size | undefined;

function getMarkerScaledSize(): google.maps.Size | undefined {
if (typeof google === "undefined") return undefined;
markerScaledSize ??= new google.maps.Size(
MARKER_ICON_WIDTH,
MARKER_ICON_HEIGHT,
);
return markerScaledSize;
}

// fillColor is always a route palette hex from routeColorHex, never user input.
function markerSvgDataUrl(fillColor: string): string {
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${MARKER_ICON_WIDTH}" height="${MARKER_ICON_HEIGHT}" viewBox="0 0 28 40"><path d="M14 1C7.373 1 2 6.373 2 13c0 9.246 12 24 12 24s12-14.754 12-24C26 6.373 20.627 1 14 1z" fill="${fillColor}" stroke="#ffffff" stroke-width="2"/><circle cx="14" cy="13" r="4.25" fill="#ffffff"/></svg>`;
return `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(svg)}`;
}

function createRoutePinElement(fillColor: string): HTMLElement {
const wrapper = document.createElement("div");
wrapper.style.width = `${MARKER_ICON_WIDTH}px`;
wrapper.style.height = `${MARKER_ICON_HEIGHT}px`;
// Anchor bottom-center of the pin on the stop (tip is at y=40 in the SVG).
wrapper.style.transform = "translate(-50%, -100%)";

const img = document.createElement("img");
img.src = markerSvgDataUrl(fillColor);
img.width = MARKER_ICON_WIDTH;
img.height = MARKER_ICON_HEIGHT;
img.style.display = "block";
img.draggable = false;
img.alt = "";
wrapper.appendChild(img);
return wrapper;
}

function routePolylineOptions(
strokeColor: string,
Expand Down Expand Up @@ -307,7 +344,8 @@ function AdvancedMarkers({

if (cancelled) return;

routes.forEach((route) => {
routes.forEach((route, routeIndex) => {
const accentColor = routeColorHex(routeIndex);
const sorted = [...route.stops].sort(
(a, b) => a.sequence - b.sequence,
);
Expand All @@ -319,6 +357,7 @@ function AdvancedMarkers({
position,
title: stop.address,
gmpDraggable: isEditMode,
content: createRoutePinElement(accentColor),
});

m.addListener("dragend", () => {
Expand Down Expand Up @@ -466,7 +505,10 @@ export default function MapComponent({
/>
)}
{!mapId &&
routes.map((route) => {
routes.map((route, routeIndex) => {
const accentColor = routeColorHex(routeIndex);
const iconUrl = markerSvgDataUrl(accentColor);
const scaledSize = getMarkerScaledSize();
const sorted = [...route.stops].sort(
(a, b) => a.sequence - b.sequence,
);
Expand All @@ -485,6 +527,18 @@ export default function MapComponent({
key={stop.id}
position={position}
title={stop.address}
icon={
scaledSize
? {
url: iconUrl,
scaledSize,
anchor: new google.maps.Point(
MARKER_ICON_WIDTH / 2,
MARKER_ICON_HEIGHT,
),
}
: { url: iconUrl }
}
draggable={isEditMode}
onDragEnd={(e) => {
const latLng = e.latLng;
Expand Down
Loading
Loading