diff --git a/app/ui/src/app/driver_assist/page.tsx b/app/ui/src/app/driver_assist/page.tsx index 7b5228c12..b76042564 100644 --- a/app/ui/src/app/driver_assist/page.tsx +++ b/app/ui/src/app/driver_assist/page.tsx @@ -3,11 +3,6 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useRouter } from "next/navigation"; -import { - loadSessionFromFile, - loadSessionFromText, -} from "@/lib/driver-route/importSession"; -import { transformSessionToDriverRoute } from "@/lib/driver-route/transformSession"; import type { DeliveryStop, DriverRoute } from "@/lib/driver-route/types"; import DriverFooter from "./components/DriverFooter"; @@ -24,6 +19,10 @@ import { readUploadedRouteFile, } from "./storage"; import { styles } from "./styles"; +import { + parseRouteUploadFile, + ROUTE_UPLOAD_ERROR_KEY, +} from "@/app/upload-route/routeUploadValidation"; function openNavigation(stop: DeliveryStop) { // Prefer exact coordinates from the route file; fall back to the address if @@ -51,7 +50,6 @@ function openPhone(stop: DeliveryStop) { export default function DriverAssistPwaPage() { const router = useRouter(); - const inputRef = useRef(null); const topRef = useRef(null); const remainingRef = useRef(null); const deliveredRef = useRef(null); @@ -63,45 +61,52 @@ export default function DriverAssistPwaPage() { "Customer unavailable", ); const [reportDetails, setReportDetails] = useState(""); - const [error, setError] = useState(null); - const [isImporting, setIsImporting] = useState(false); const [hasCheckedRoute, setHasCheckedRoute] = useState(false); useEffect(() => { // The upload page hands off the raw JSON through sessionStorage so this // page can import once, save the driver shape, and avoid bouncing back. const uploadedRoute = readUploadedRouteFile(); - let importFailed = false; if (uploadedRoute) { try { - const session = loadSessionFromText(uploadedRoute.content); - const nextRoute = transformSessionToDriverRoute(session); + const nextRoute = parseRouteUploadFile( + uploadedRoute.name, + uploadedRoute.content, + ); persistRoute(nextRoute); clearUploadedRouteFile(); - setRoute(nextRoute); - setOpenId(nextRoute.stops[0]?.id || null); - setHasCheckedRoute(true); + queueMicrotask(() => { + setRoute(nextRoute); + setOpenId(nextRoute.stops[0]?.id || null); + setHasCheckedRoute(true); + }); return; } catch (importError) { - importFailed = true; - setError( + const message = importError instanceof Error ? importError.message - : "Please upload a valid JSON file.", - ); + : "Please upload a valid JSON file."; + clearUploadedRouteFile(); + sessionStorage.setItem(ROUTE_UPLOAD_ERROR_KEY, message); + router.replace("/upload-route"); + return; } } // Reloading the PWA should keep the driver exactly where they left off. const savedRoute = readSavedRoute(); - setRoute(savedRoute); - setOpenId(savedRoute?.stops[0]?.id || null); - setHasCheckedRoute(true); - if (!savedRoute && !importFailed) { + if (!savedRoute) { router.replace("/upload-route"); + return; } + + queueMicrotask(() => { + setRoute(savedRoute); + setOpenId(savedRoute.stops[0]?.id || null); + setHasCheckedRoute(true); + }); }, [router]); useEffect(() => { @@ -127,29 +132,6 @@ export default function DriverAssistPwaPage() { }; }, [route]); - const importRoute = async (file: File) => { - setError(null); - setIsImporting(true); - - try { - // Direct upload is kept here too, so /driver_assist works even if a - // driver lands on it without going through /upload-route first. - const session = await loadSessionFromFile(file); - const nextRoute = transformSessionToDriverRoute(session); - persistRoute(nextRoute); - setRoute(nextRoute); - setOpenId(nextRoute.stops[0]?.id || null); - } catch (importError) { - setError( - importError instanceof Error - ? importError.message - : "Please upload a valid JSON file.", - ); - } finally { - setIsImporting(false); - } - }; - const updateStop = (stopId: string, changes: Partial) => { // Keep stop updates narrow so notes, status, and failure reasons can share // one path without rebuilding the whole route by hand. @@ -209,41 +191,12 @@ export default function DriverAssistPwaPage() { const reportedStops = route?.stops.filter((stop) => stop.status === "failed") || []; - if (!hasCheckedRoute || (!route && !error)) { + if (!hasCheckedRoute || !route) { // Empty shell prevents the black-and-white upload screen from flashing // while local/session storage is being checked. return
; } - if (!route) { - return ( -
-
-

driver_assist

- { - const file = event.target.files?.[0]; - if (file) void importRoute(file); - }} - /> - - {error ?

{error}

: null} -
-
- ); - } - return (
diff --git a/app/ui/src/app/driver_assist/styles.ts b/app/ui/src/app/driver_assist/styles.ts index a16f4c78a..1d821f7d2 100644 --- a/app/ui/src/app/driver_assist/styles.ts +++ b/app/ui/src/app/driver_assist/styles.ts @@ -12,41 +12,12 @@ export const styles: Record = { minHeight: "100dvh", backgroundColor: "#ffffff", }, - uploadScreen: { - minHeight: "100dvh", - display: "flex", - flexDirection: "column", - justifyContent: "center", - alignItems: "center", - padding: 24, - backgroundColor: "#ffffff", - }, - hiddenInput: { - display: "none", - }, appHeader: { fontSize: 14, fontWeight: 700, color: "#202020", margin: 0, }, - uploadButton: { - backgroundColor: "#111827", - border: 0, - borderRadius: 8, - color: "#ffffff", - cursor: "pointer", - fontSize: 16, - fontWeight: 600, - padding: "14px 20px", - }, - errorText: { - color: "#b91c1c", - fontSize: 14, - marginTop: 14, - maxWidth: 300, - textAlign: "center", - }, container: { width: "100%", maxWidth: "none", diff --git a/app/ui/src/app/driver_assist/summary/page.tsx b/app/ui/src/app/driver_assist/summary/page.tsx index 449bdf678..70f64ab59 100644 --- a/app/ui/src/app/driver_assist/summary/page.tsx +++ b/app/ui/src/app/driver_assist/summary/page.tsx @@ -7,7 +7,6 @@ import { downloadRouteSummary } from "@/lib/driver-route/exportSummary"; import type { DriverRoute } from "@/lib/driver-route/types"; import DriverFooter from "../components/DriverFooter"; -import { WarningIcon } from "../components/icons"; import { readSavedRoute } from "../storage"; import SummaryStatBlock from "./components/SummaryStatBlock"; import SummaryStopCard from "./components/SummaryStopCard"; @@ -20,19 +19,15 @@ export default function DriverAssistSummaryPage() { const [exportMessage, setExportMessage] = useState(null); useEffect(() => { - // Let the active route page finish its final localStorage write before the - // summary reads it back after the Finish tap. - const timeoutId = window.setTimeout(() => { - const savedRoute = readSavedRoute(); + const savedRoute = readSavedRoute(); + queueMicrotask(() => { setRoute(savedRoute); setHasCheckedRoute(true); + }); - if (!savedRoute) { - router.replace("/upload-route"); - } - }, 0); - - return () => window.clearTimeout(timeoutId); + if (!savedRoute) { + router.replace("/upload-route"); + } }, [router]); const totals = useMemo(() => { @@ -68,13 +63,6 @@ export default function DriverAssistSummaryPage() {

Driver Assist

-
diff --git a/app/ui/src/app/driver_assist/summary/styles.ts b/app/ui/src/app/driver_assist/summary/styles.ts index bb0a701fa..84ef9fdc8 100644 --- a/app/ui/src/app/driver_assist/summary/styles.ts +++ b/app/ui/src/app/driver_assist/summary/styles.ts @@ -32,15 +32,6 @@ export const summaryStyles: Record = { lineHeight: 1.2, margin: 0, }, - warningButton: { - alignItems: "center", - background: "transparent", - border: 0, - color: "#4b4b4b", - cursor: "pointer", - display: "inline-flex", - padding: 0, - }, summarySection: { marginBottom: 24, }, diff --git a/app/ui/src/app/edit/hooks/useCSVImport.ts b/app/ui/src/app/edit/hooks/useCSVImport.ts index 4d24a42fb..ce893b070 100644 --- a/app/ui/src/app/edit/hooks/useCSVImport.ts +++ b/app/ui/src/app/edit/hooks/useCSVImport.ts @@ -156,7 +156,7 @@ function parseJsonToRows(text: string): string[][] { * Minimal RFC-4180 CSV parser — handles quoted fields, embedded commas, * CRLF and LF line endings. */ -function parseCsvToRows(text: string): string[][] { +export function parseCsvToRows(text: string): string[][] { const rows: string[][] = []; let currentRow: string[] = []; let currentField = ""; diff --git a/app/ui/src/app/upload-route/page.tsx b/app/ui/src/app/upload-route/page.tsx index cb5f1f02f..29535724e 100644 --- a/app/ui/src/app/upload-route/page.tsx +++ b/app/ui/src/app/upload-route/page.tsx @@ -1,13 +1,16 @@ // app/upload-route/page.tsx "use client"; -export const dynamic = "force-dynamic"; - -import { useCallback, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import ShellNavbar from "@/app/components/ShellNavbar"; import { formatSize } from "@/app/utils/routeUtils"; +import { + parseRouteUploadFile, + ROUTE_UPLOAD_ERROR_KEY, +} from "./routeUploadValidation"; + const MAX_FILE_MB = 10; const MAX_FILE_BYTES = MAX_FILE_MB * 1024 * 1024; @@ -20,6 +23,14 @@ export default function UploadRoutePage() { const dragDepth = useRef(0); const inputRef = useRef(null); + useEffect(() => { + const uploadError = sessionStorage.getItem(ROUTE_UPLOAD_ERROR_KEY); + if (!uploadError) return; + + sessionStorage.removeItem(ROUTE_UPLOAD_ERROR_KEY); + queueMicrotask(() => setError(uploadError)); + }, []); + const handleFile = (f: File) => { setError(null); if (!f.name.endsWith(".json") && !f.name.endsWith(".csv")) { @@ -60,6 +71,7 @@ export default function UploadRoutePage() { try { const text = await file.text(); + parseRouteUploadFile(file.name, text); sessionStorage.setItem( "routeFile", JSON.stringify({ name: file.name, content: text }), @@ -77,6 +89,8 @@ export default function UploadRoutePage() { return ( <> + {/* This page keeps its scoped CSS inline because it predates the + driver_assist CSSProperties split and uses class-based drag states. */}