diff --git a/app/ui/public/b2-logo.png b/app/ui/public/b2-logo.png new file mode 100644 index 00000000..7227f21a Binary files /dev/null and b/app/ui/public/b2-logo.png differ diff --git a/app/ui/src/app/edit/components/address/AddressCard.tsx b/app/ui/src/app/edit/components/address/AddressCard.tsx index fa43eca7..3933fbe5 100644 --- a/app/ui/src/app/edit/components/address/AddressCard.tsx +++ b/app/ui/src/app/edit/components/address/AddressCard.tsx @@ -6,6 +6,10 @@ import AddressOverlay, { } from "@/app/edit/components/address/AddressOverlay"; import { TIME_OPTIONS } from "@/app/edit/constants/timeOptions"; import type { AddressCard as AddressCardType } from "@/app/edit/types/delivery"; +import { + hasRecipientContact, + recipientSummary, +} from "@/app/edit/utils/recipientSummary"; import { ADDRESS_ROW_EDIT_ROOT, ADDRESS_ROW_DESKTOP_WRAPPER, @@ -257,15 +261,13 @@ export default function AddressCard({ <> {/* Recipient column — locked */}
- {(a.recipientName || a.phoneNumber) && ( + {hasRecipientContact(a) && ( )} ); } diff --git a/app/ui/src/app/edit/utils/csvParserUtils.ts b/app/ui/src/app/edit/utils/csvParserUtils.ts index edde8c05..9466def5 100644 --- a/app/ui/src/app/edit/utils/csvParserUtils.ts +++ b/app/ui/src/app/edit/utils/csvParserUtils.ts @@ -61,6 +61,7 @@ const COLUMN_ALIASES: Record = { ], phone_number: [ "phone_number", + "recipient_phone", "phone", "tel", "telephone", 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..b27ae35e --- /dev/null +++ b/app/ui/src/app/edit/utils/hasOptimizeResults.ts @@ -0,0 +1,14 @@ +/** 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: unknown = JSON.parse(stored); + return Array.isArray(parsed) && parsed.length > 0; + } catch { + return false; + } +} diff --git a/app/ui/src/app/edit/utils/recipientSummary.ts b/app/ui/src/app/edit/utils/recipientSummary.ts new file mode 100644 index 00000000..fbf5c341 --- /dev/null +++ b/app/ui/src/app/edit/utils/recipientSummary.ts @@ -0,0 +1,23 @@ +import type { AddressCard } from "../types/delivery"; + +type RecipientContactFields = Partial<{ + recipientName: string; + addresseeName: string; + phoneNumber: string; +}>; + +/** Display string for recipient name + phone (middle dot when both set). */ +export function recipientSummary(a: RecipientContactFields): string { + const n = (a.recipientName ?? a.addresseeName ?? "").trim(); + const p = (a.phoneNumber ?? "").trim(); + if (n && p) return `${n} · ${p}`; + if (n) return n; + if (p) return p; + return "—"; +} + +export function hasRecipientContact( + a: Pick, +): boolean { + return Boolean(a.recipientName.trim() || a.phoneNumber.trim()); +} diff --git a/app/ui/src/app/edit/utils/vroomToRoutes.ts b/app/ui/src/app/edit/utils/vroomToRoutes.ts index 8eee8170..7e4f5e39 100644 --- a/app/ui/src/app/edit/utils/vroomToRoutes.ts +++ b/app/ui/src/app/edit/utils/vroomToRoutes.ts @@ -56,6 +56,7 @@ export function vroomToRoutes( // arrival is in seconds; % 86400 extracts the within-day portion for display const arrivalTimeStr = secondsToTimeString(step.arrival % 86400); + const deliveryQuantity = address?.deliveryQuantity ?? 0; return { id: step.job_external_id!, @@ -64,7 +65,8 @@ export function vroomToRoutes( lat, lng, sequence: idx + 1, - capacityUsed: step.load?.[0] ?? 0, + capacityUsed: + deliveryQuantity > 0 ? deliveryQuantity : (step.load?.[0] ?? 0), timeWindow: { kind: inferTimeWindowKind( address?.deliveryTimeStart, @@ -75,6 +77,8 @@ export function vroomToRoutes( note: address?.notes ?? "", addresseeName: address?.recipientName || undefined, phoneNumber: address?.phoneNumber || undefined, + deliveryWindowStart: address?.deliveryTimeStart?.trim() || undefined, + deliveryWindowEnd: address?.deliveryTimeEnd?.trim() || undefined, }; }); diff --git a/app/ui/src/app/results/components/EditableStopItem.tsx b/app/ui/src/app/results/components/EditableStopItem.tsx index a1a6ce6d..83326fce 100644 --- a/app/ui/src/app/results/components/EditableStopItem.tsx +++ b/app/ui/src/app/results/components/EditableStopItem.tsx @@ -3,7 +3,8 @@ "use client"; import { useState } from "react"; -import type { Stop } from "../types"; +import { recipientSummary } from "@/app/edit/utils/recipientSummary"; +import type { Stop, TimeWindow } from "../types"; type EditableStopItemProps = { stop: Stop; @@ -12,6 +13,108 @@ type EditableStopItemProps = { onSaveNote: (note: string) => void; }; +function formatTime12h(raw: string): string { + const t = raw.trim(); + if (/am|pm/i.test(t)) return t; + const m = t.match(/^(\d{1,2}):(\d{2})$/); + if (!m) return t; + let h = parseInt(m[1]!, 10); + const min = m[2]; + const ap = h >= 12 ? "PM" : "AM"; + h = h % 12; + if (h === 0) h = 12; + return `${h}:${min} ${ap}`; +} + +function formatTimeWindowLine(tw: TimeWindow | undefined): string { + if (!tw?.time) return "—"; + const label = formatTime12h(tw.time); + if (tw.kind === "by") return `By ${label}`; + if (tw.kind === "at") return label; + if (tw.kind === "from") return `From ${label}`; + return label; +} + +function formatDeliveryWindow(stop: Stop): string { + const a = stop.deliveryWindowStart?.trim(); + const b = stop.deliveryWindowEnd?.trim(); + if (a && b) return `${formatTime12h(a)} – ${formatTime12h(b)}`; + return formatTimeWindowLine(stop.timeWindow); +} + +function PersonIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +function ClockIcon({ className }: { className?: string }) { + return ( + + + + + ); +} + +function NoteDocIcon({ className }: { className?: string }) { + return ( + + + + + ); +} + +function PackageIcon({ className }: { className?: string }) { + return ( + + + + + ); +} + export default function EditableStopItem({ stop, accentColor, @@ -19,81 +122,79 @@ export default function EditableStopItem({ onSaveNote, }: EditableStopItemProps) { const [draft, setDraft] = useState(stop.note ?? ""); + const contactText = recipientSummary(stop); + const timeText = formatDeliveryWindow(stop); return ( -
-
-
- - {stop.sequence} - - - {stop.address} - -
+
+
+

+ {stop.address} +

- Packages: - 📦 {stop.capacityUsed ?? "—"} + Boxes: + + {typeof stop.capacityUsed === "number" ? stop.capacityUsed : "—"}
-
-
- - Name of addressed to: - {" "} - {stop.addresseeName ?? "—"} + +
+
+ +
+ {contactText} +
-
- - Est time of arrival: - {" "} - {stop.timeWindow?.time ?? "—"} +
+ +
+ {timeText} +
{!isEditMode ? ( -
- Notes:{" "} - {stop.note?.trim() ? ( - stop.note - ) : ( - No notes - )} +
+ +
+ Note:{" "} + {stop.note?.trim() ? ( + {stop.note} + ) : ( + No notes + )} +
) : ( -
- -