Show the enquiry's channel: email thread vs WhatsApp, and the offer button says which

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Yashas 2026-10-26 17:26:19 +05:30
parent 470ddc98c4
commit c3dba17ba9
2 changed files with 12 additions and 10 deletions

View File

@ -1,7 +1,7 @@
import { useState } from 'react';
import type { ReactNode } from 'react';
import { Link, useParams } from 'react-router-dom';
import { ArrowLeft, Check, MessageCircle, Sparkles, X } from 'lucide-react';
import { ArrowLeft, Check, Mail, MessageCircle, Sparkles, X } from 'lucide-react';
import { useQuery, useZino } from '../api/provider';
import { ACT, RV_ENQUIRIES, TOUCH_POINTS, WF } from '../api/config';
import type { Enquiry } from '../api/types';
@ -49,7 +49,9 @@ export function EnquiryPage() {
<div className="flex flex-wrap items-center gap-3">
<h1 className="text-2xl font-bold">{e.enquiry_ref ?? `Enquiry #${e.instance_id}`}</h1>
<StateBadge state={e.current_state_name} />
{e.customer_contact && <span className="inline-flex items-center gap-1 text-sm text-muted"><MessageCircle size={14} /> +{e.customer_contact.replace(/^\+/, '')} · {e.channel ?? 'whatsapp'}</span>}
{e.customer_contact && (e.channel === 'email' || e.customer_contact.includes('@')
? <span className="inline-flex items-center gap-1 text-sm text-muted"><Mail size={14} /> {e.customer_contact} · email</span>
: <span className="inline-flex items-center gap-1 text-sm text-muted"><MessageCircle size={14} /> +{e.customer_contact.replace(/^\+/, '')} · WhatsApp</span>)}
</div>
{e.ai_summary && <p className="text-muted">{e.ai_summary}</p>}
@ -61,7 +63,7 @@ export function EnquiryPage() {
<OfferCard e={e} />
<Outcome e={e} />
</div>
<Conversation log={e.conversation_log} />
<Conversation log={e.conversation_log} channel={e.channel === 'email' || (e.customer_contact ?? '').includes('@') ? 'email' : 'whatsapp'} />
</div>
</div>
);
@ -140,13 +142,13 @@ function Outcome({ e }: { e: Enquiry }) {
}
/** conversation_log is "Customer: …" / "Desk: …" entries separated by blank lines. */
function Conversation({ log }: { log: string | null }) {
function Conversation({ log, channel }: { log: string | null; channel: string }) {
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 (
<Card title="Conversation" className="lg:sticky lg:top-20 lg:self-start">
<Card title={channel === 'email' ? 'Email thread' : 'WhatsApp conversation'} className="lg:sticky lg:top-20 lg:self-start">
<div className="max-h-[70vh] space-y-3 overflow-y-auto">
{entries.length === 0 && <Empty>No messages yet.</Empty>}
{entries.map((m, i) => (
@ -272,7 +274,7 @@ function OfferForm({ e, onDone, revise, onCancel }: { e: Enquiry; onDone: () =>
{error && <div className="sm:col-span-2"><ErrorNote>{error}</ErrorNote></div>}
{ok && <p className="text-sm text-positive sm:col-span-2">{ok}</p>}
<div className="flex flex-wrap gap-2 sm:col-span-2">
<button className="btn btn-cta" disabled={busy}>{busy ? 'Sending…' : 'Send offer on WhatsApp'}</button>
<button className="btn btn-cta" disabled={busy}>{busy ? 'Sending…' : (e.customer_contact ?? '').includes('@') ? 'Send offer by email' : 'Send offer on WhatsApp'}</button>
{onCancel && <button type="button" className="btn btn-secondary" onClick={onCancel}>Cancel</button>}
{!revise && <button type="button" className="btn btn-negative" onClick={() => setRegret(true)}>Regret</button>}
</div>
@ -290,7 +292,7 @@ function OfferSent({ e, onDone }: { e: Enquiry; onDone: () => void }) {
<Card title="Offer is with the customer" action={replied ? <Badge tone="notice">Customer replied</Badge> : undefined}>
<p className="mb-3 text-sm text-muted">
{replied ? 'The customer answered without a plain yes or no. Read the conversation, then revise the offer or close it.'
: 'A plain "confirm" from the customer books the stem automatically. A counter or a question lands here for you.'}
: 'A plain "confirm" from the customer, on WhatsApp or by email, books the stem automatically. A counter or a question lands here for you.'}
</p>
<div className="flex flex-wrap gap-2">
<button className="btn btn-cta" onClick={() => setMode('revise')}>Revise offer</button>

View File

@ -1,6 +1,6 @@
import { useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Search } from 'lucide-react';
import { Mail, MessageCircle, Search } from 'lucide-react';
import { useQuery, useZino } from '../api/provider';
import { RV_ENQUIRIES, STATES } from '../api/config';
import type { Enquiry } from '../api/types';
@ -44,7 +44,7 @@ export function Inbox() {
<div className="flex flex-wrap items-end justify-between gap-3">
<div>
<h1 className="text-2xl font-bold">Enquiries</h1>
<p className="text-sm text-muted">Every bunker enquiry received on WhatsApp and email, from first message to confirmed stem.</p>
<p className="text-sm text-muted">Every bunker enquiry received on WhatsApp or email, from first message to confirmed stem.</p>
</div>
<label className="relative w-full sm:w-72">
<Search size={16} className="pointer-events-none absolute left-2.5 top-2.5 text-muted" />
@ -75,7 +75,7 @@ export function Inbox() {
<tbody>
{shown.map((r) => (
<tr key={r.instance_id} onClick={() => nav(`/enquiries/${r.instance_id}`)} className="cursor-pointer border-b border-line last:border-0 hover:bg-surface-2">
<td className="px-3 py-2.5 font-semibold text-accent">{r.enquiry_ref ?? `#${r.instance_id}`}</td>
<td className="px-3 py-2.5 font-semibold text-accent"><span className="inline-flex items-center gap-1.5">{(r.customer_contact ?? '').includes('@') ? <Mail size={14} className="text-muted" /> : <MessageCircle size={14} className="text-muted" />}{r.enquiry_ref ?? `#${r.instance_id}`}</span></td>
<td className="px-3 py-2.5">{r.vessel_name ?? <span className="text-muted">-</span>}{r.imo_number && <div className="text-xs text-muted">IMO {r.imo_number}</div>}</td>
<td className="px-3 py-2.5">{r.port ?? '-'}</td>
<td className="px-3 py-2.5">{r.eta_laycan ?? '-'}</td>