From 2e3c42df5e46e587f68320b59cc3c7d91f7f5733 Mon Sep 17 00:00:00 2001 From: Yashas Date: Mon, 26 Oct 2026 16:40:14 +0530 Subject: [PATCH] Ops input: delivery slot is a date-time window; conversation shows one bubble per message Co-Authored-By: Claude Opus 5.5 --- src/screens/Enquiry.tsx | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/screens/Enquiry.tsx b/src/screens/Enquiry.tsx index 1af7c68..8a2f9c0 100644 --- a/src/screens/Enquiry.tsx +++ b/src/screens/Enquiry.tsx @@ -8,6 +8,21 @@ import type { Enquiry } from '../api/types'; import { Badge, Card, Empty, ErrorNote, KV, StateBadge, qtyText, timeAgo, usd } from '../components/ui'; const MODE_LABEL: Record = { barge: 'Barge', pipeline: 'Pipeline (ex-wharf)', truck: 'Truck' }; +/** "2026-11-12T06:00" -> "12 Nov 2026, 06:00". */ +function fmtDateTime(v: string): string { + const d = new Date(v); + if (!v || isNaN(d.getTime())) return ''; + return d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }) + ', ' + d.toTimeString().slice(0, 5); +} +/** A delivery window as the text the offer and the customer read: "12 Nov 2026, 06:00 - 12:00 IST". */ +function fmtWindow(from: string, to: string): string { + const a = fmtDateTime(from); + if (!a) return ''; + if (!to) return a + ' IST'; + const b = fmtDateTime(to); + return (from.slice(0, 10) === to.slice(0, 10) ? a + ' - ' + b.split(', ')[1] : a + ' - ' + b) + ' IST'; +} + const STOCK_LABEL: Record = { available: 'Available', partial: 'Partially available', not_available: 'Not available' }; export function EnquiryPage() { @@ -120,8 +135,8 @@ function Outcome({ e }: { e: Enquiry }) { /** conversation_log is "Customer: …" / "Desk: …" entries separated by blank lines. */ function Conversation({ log }: { log: string | null }) { - const entries = (log ?? '').split(/\n\n(?=(?:Customer|Desk)[^:\n]{0,24}: )/).filter(Boolean).map((chunk) => { - const m = chunk.match(/^(Customer|Desk)([^:\n]{0,24}): ([\s\S]*)$/); + const entries = (log ?? '').split(/\n+(?=(?:Customer|Desk)(?: \([^)\n]{1,24}\))?: )/).map((c) => c.trim()).filter(Boolean).map((chunk) => { + const m = chunk.match(/^(Customer|Desk)((?: \([^)\n]{1,24}\))?): ([\s\S]*)$/); return m ? { who: m[1], tag: m[2].trim().replace(/[()]/g, ''), text: m[3] } : { who: 'Desk', tag: '', text: chunk }; }); return ( @@ -194,13 +209,14 @@ function Field({ label, required, children }: { label: string; required?: boolea function OpsForm({ e, onDone }: { e: Enquiry; onDone: () => void }) { const { busy, error, ok, submit } = useSubmit(e, onDone); - const [f, setF] = useState({ stock: 'available', mode: 'barge', slot: '', qty: String(e.max_qty_mt ?? ''), remarks: '' }); + const [f, setF] = useState({ stock: 'available', mode: 'barge', from: '', to: '', qty: String(e.max_qty_mt ?? ''), remarks: '' }); + const windowError = f.from && f.to && f.to <= f.from ? 'The window must end after it starts' : null; const [regret, setRegret] = useState(false); if (regret) return setRegret(false)} />; return ( Your action}> -
{ ev.preventDefault(); submit(ACT.OPS_INPUT, { - stock_status__ops: f.stock, supply_mode__ops: f.mode, delivery_slot__ops: f.slot, + { ev.preventDefault(); if (windowError) return; submit(ACT.OPS_INPUT, { + stock_status__ops: f.stock, supply_mode__ops: f.mode, delivery_slot__ops: fmtWindow(f.from, f.to), supply_qty_mt__ops: f.qty === '' ? null : Number(f.qty), ops_remarks__ops: f.remarks, }); }}> @@ -213,8 +229,10 @@ function OpsForm({ e, onDone }: { e: Enquiry; onDone: () => void }) { - setF({ ...f, slot: x.target.value })} /> + setF({ ...f, from: x.target.value })} /> + setF({ ...f, to: x.target.value })} /> setF({ ...f, qty: x.target.value })} /> +
{windowError ? {windowError} : f.from ? <>Slot: {fmtWindow(f.from, f.to)} : 'Times are IST.'}