diff --git a/app/ui/src/app/edit/components/layout/sidebar/SidebarResultsButton.tsx b/app/ui/src/app/edit/components/layout/sidebar/SidebarResultsButton.tsx index b3fb88cf..79c8fd49 100644 --- a/app/ui/src/app/edit/components/layout/sidebar/SidebarResultsButton.tsx +++ b/app/ui/src/app/edit/components/layout/sidebar/SidebarResultsButton.tsx @@ -1,7 +1,15 @@ +"use client"; + import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { useHasOptimizeResults } 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"; @@ -12,26 +20,48 @@ const SIDEBAR_RESULTS_ICON = ( height="24" viewBox="0 0 24 24" fill="none" + aria-hidden > ); export default function SidebarResultsButton() { + const pathname = usePathname(); + const isResultsPage = pathname === "/results"; + const hasStoredRoutes = useHasOptimizeResults(); + + if (isResultsPage) { + return ( + + + {SIDEBAR_RESULTS_ICON} + + Results + + ); + } + + if (hasStoredRoutes) { + return ( + + + {SIDEBAR_RESULTS_ICON} + + Results + + ); + } + return ( - - {" "} - {/* TODO: add results page link when at least one route exists */} + {SIDEBAR_RESULTS_ICON} Results - + ); } diff --git a/app/ui/src/app/edit/hooks/useOptimize.ts b/app/ui/src/app/edit/hooks/useOptimize.ts index 9c3998ca..8f81a0fa 100644 --- a/app/ui/src/app/edit/hooks/useOptimize.ts +++ b/app/ui/src/app/edit/hooks/useOptimize.ts @@ -13,6 +13,7 @@ import { vehicleRowToVehicleInput, } from "@/app/edit/utils/optimizeMapper"; import { SUPPORTED_STATES } from "@/app/edit/constants/supportedRegions"; +import { setOptimizeResults } from "@/app/edit/utils/hasOptimizeResults"; import { vroomToRoutes } from "@/app/edit/utils/vroomToRoutes"; import type { AddressCard, @@ -362,7 +363,7 @@ export function useOptimize( lockedVehicles, addresses, ); - sessionStorage.setItem("optimizeResults", JSON.stringify(routes)); + setOptimizeResults(routes); router.push("/results"); return; } diff --git a/app/ui/src/app/edit/utils/hasOptimizeResults.ts b/app/ui/src/app/edit/utils/hasOptimizeResults.ts new file mode 100644 index 00000000..57ae246f --- /dev/null +++ b/app/ui/src/app/edit/utils/hasOptimizeResults.ts @@ -0,0 +1,68 @@ +"use client"; + +import { useEffect, useState } from "react"; +import type { Route } from "@/app/results/types"; + +export const OPTIMIZE_RESULTS_STORAGE_KEY = "optimizeResults"; + +export const OPTIMIZE_RESULTS_UPDATED_EVENT = "optimize-results-updated"; + +function isValidRoute(value: unknown): value is Route { + if (!value || typeof value !== "object") return false; + const route = value as Record; + return typeof route.vehicleId === "string" && Array.isArray(route.stops); +} + +function parseStoredRoutes(stored: string): Route[] | null { + try { + const parsed: unknown = JSON.parse(stored); + if (!Array.isArray(parsed) || parsed.length === 0) return null; + if (!parsed.every(isValidRoute)) return null; + return parsed; + } catch { + return null; + } +} + +/** 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(OPTIMIZE_RESULTS_STORAGE_KEY); + if (!stored) return false; + + return parseStoredRoutes(stored) != null; +} + +export function notifyOptimizeResultsUpdated(): void { + if (typeof window === "undefined") return; + window.dispatchEvent(new Event(OPTIMIZE_RESULTS_UPDATED_EVENT)); +} + +export function setOptimizeResults(routes: Route[]): void { + sessionStorage.setItem(OPTIMIZE_RESULTS_STORAGE_KEY, JSON.stringify(routes)); + notifyOptimizeResultsUpdated(); +} + +export function clearOptimizeResults(): void { + sessionStorage.removeItem(OPTIMIZE_RESULTS_STORAGE_KEY); + notifyOptimizeResultsUpdated(); +} + +export function useHasOptimizeResults(): boolean { + const [hasResults, setHasResults] = useState(false); + + useEffect(() => { + const sync = () => setHasResults(readHasOptimizeResults()); + sync(); + + window.addEventListener(OPTIMIZE_RESULTS_UPDATED_EVENT, sync); + window.addEventListener("storage", sync); + return () => { + window.removeEventListener(OPTIMIZE_RESULTS_UPDATED_EVENT, sync); + window.removeEventListener("storage", sync); + }; + }, []); + + return hasResults; +} diff --git a/app/ui/src/app/results/components/Sidebar.tsx b/app/ui/src/app/results/components/Sidebar.tsx index 76a6bed9..19932032 100644 --- a/app/ui/src/app/results/components/Sidebar.tsx +++ b/app/ui/src/app/results/components/Sidebar.tsx @@ -91,7 +91,7 @@ export default function Sidebar({
  • -
    - - Delivery Optimizer - -

    Results

    -
    +

    DELIVERY OPTIMIZER

    @@ -159,14 +139,14 @@ export default function ResultsPage() { @@ -177,16 +157,19 @@ export default function ResultsPage() { disabled aria-disabled="true" title="Export coming soon" - className="rounded-full bg-emerald-500 px-3 py-1 text-xs font-medium text-white opacity-50 cursor-not-allowed" + className="h-9 px-6 rounded-[80px] bg-[var(--edit-teal-500)] font-medium text-[14px] leading-5 text-[var(--edit-foreground)] whitespace-nowrap opacity-50 cursor-not-allowed" > Export
    -
    + + + + + +