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
103 changes: 28 additions & 75 deletions app/ui/src/app/driver_assist/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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
Expand Down Expand Up @@ -51,7 +50,6 @@ function openPhone(stop: DeliveryStop) {

export default function DriverAssistPwaPage() {
const router = useRouter();
const inputRef = useRef<HTMLInputElement>(null);
const topRef = useRef<HTMLDivElement>(null);
const remainingRef = useRef<HTMLDivElement>(null);
const deliveredRef = useRef<HTMLDivElement>(null);
Expand All @@ -63,45 +61,52 @@ export default function DriverAssistPwaPage() {
"Customer unavailable",
);
const [reportDetails, setReportDetails] = useState("");
const [error, setError] = useState<string | null>(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(() => {
Expand All @@ -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<DeliveryStop>) => {
// Keep stop updates narrow so notes, status, and failure reasons can share
// one path without rebuilding the whole route by hand.
Expand Down Expand Up @@ -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 <main style={styles.loadingScreen} aria-label="Loading route" />;
}

if (!route) {
return (
<main style={styles.safeArea}>
<section style={styles.uploadScreen}>
<h1 style={styles.appHeader}>driver_assist</h1>
<input
ref={inputRef}
type="file"
accept="application/json,.json"
style={styles.hiddenInput}
onChange={(event) => {
const file = event.target.files?.[0];
if (file) void importRoute(file);
}}
/>
<button
type="button"
style={styles.uploadButton}
onClick={() => inputRef.current?.click()}
disabled={isImporting}
>
{isImporting ? "Uploading..." : "Upload JSON"}
</button>
{error ? <p style={styles.errorText}>{error}</p> : null}
</section>
</main>
);
}

return (
<main style={styles.safeArea}>
<section ref={topRef} style={styles.container}>
Expand Down
29 changes: 0 additions & 29 deletions app/ui/src/app/driver_assist/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,12 @@ export const styles: Record<string, CSSProperties> = {
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",
Expand Down
24 changes: 6 additions & 18 deletions app/ui/src/app/driver_assist/summary/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -20,19 +19,15 @@ export default function DriverAssistSummaryPage() {
const [exportMessage, setExportMessage] = useState<string | null>(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(() => {
Expand Down Expand Up @@ -68,13 +63,6 @@ export default function DriverAssistSummaryPage() {
<section style={styles.container}>
<div style={styles.topBar}>
<h1 style={styles.appHeader}>Driver Assist</h1>
<button
type="button"
style={styles.warningButton}
aria-label="View remaining deliveries"
>
<WarningIcon />
</button>
</div>

<section style={styles.summarySection}>
Expand Down
9 changes: 0 additions & 9 deletions app/ui/src/app/driver_assist/summary/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,6 @@ export const summaryStyles: Record<string, CSSProperties> = {
lineHeight: 1.2,
margin: 0,
},
warningButton: {
alignItems: "center",
background: "transparent",
border: 0,
color: "#4b4b4b",
cursor: "pointer",
display: "inline-flex",
padding: 0,
},
summarySection: {
marginBottom: 24,
},
Expand Down
2 changes: 1 addition & 1 deletion app/ui/src/app/edit/hooks/useCSVImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "";
Expand Down
20 changes: 17 additions & 3 deletions app/ui/src/app/upload-route/page.tsx
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -20,6 +23,14 @@ export default function UploadRoutePage() {
const dragDepth = useRef(0);
const inputRef = useRef<HTMLInputElement>(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")) {
Expand Down Expand Up @@ -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 }),
Expand All @@ -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. */}
<style>{`
@import url('https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=DM+Sans:wght@400;500;600&display=swap');

Expand Down
Loading
Loading