console: consolidate the lead page — say each thing once, weight by importance

The layout placed information by convenience, not importance, and repeated
itself. This is the consolidation pass, no new look.

- METRICS: seven at equal weight became four, money first — Premium, Commission,
  AI confidence, Renewal-countdown. Dropped lead age and time-in-stage (they
  overlapped each other, and the feed already carries timing), and product /
  vehicle (the rail's "The vehicle" holds them). Nothing is lost; every removed
  measure is still on the page once, where it belongs.
- STAGE stops appearing as a metric — the rail is where the lead's position
  lives, and the feed shows each transition.
- NEXT STEP no longer repeats the status. Its heading and "nothing is waiting on
  you" subtitle rendered even when nothing was owed, saying a third time what the
  status strip and the header already said. The heading shows only for a real
  step now; secondary actions (add a document, mark lost) still render, without
  the framing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yashas 2026-09-09 10:46:38 +05:30
parent 17561f3f73
commit 2965c47b71

View File

@ -16,13 +16,6 @@ import { actionsFor, rolesOf } from '../api/permissions.js'
import { describeError } from '../api/errors.js'
import './screens.css'
function fmtWhen(ts) {
if (!ts) return null
const d = new Date(ts)
if (isNaN(d)) return String(ts)
return d.toLocaleString('en-IN', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' })
}
/** The countdown, not just the date. On a renewal book this is the number
* that decides whether anyone should act today. */
function expiryOf(dateStr) {
@ -38,14 +31,6 @@ function expiryOf(dateStr) {
}
}
/** Whole days since a timestamp. Age and dwell time are the two numbers that
* say whether a lead is moving, and neither is on the record. */
function daysSince(ts) {
if (!ts) return null
const t = Date.parse(ts)
return isNaN(t) ? null : Math.floor((Date.now() - t) / 86400000)
}
function inrShort(v) {
const n = Number(v)
if (!Number.isFinite(n) || !n) return null
@ -380,7 +365,6 @@ export default function Lead() {
}).length
}, 0)
const isClosed = stage?.kind === 'end'
// An automated stage that has stopped for a stated reason. Checked before
// the "in progress" strip below, which would otherwise keep promising that
// something is happening for as long as the lead is left alone.
@ -462,46 +446,28 @@ export default function Lead() {
which no label/value pair on the page could. */}
{(() => {
const e = expiryOf(row.renewal_due_date)
const age = daysSince(row.created_at)
const dwell = daysSince(row.updated_at)
const premium = inrShort(row.quoted_premium)
const commission = inrShort(row.commission_amount)
const conf = Number(row.ai_recommendation_confidence)
// FOUR NUMBERS, NOT SEVEN. Money leads, because that is what a renewal
// is about; the AI's confidence in the cover sits beside it; the
// renewal countdown is the one deadline that matters. Dropped: lead age
// and time-in-stage (they overlapped each other and the feed carries
// timing), and product/vehicle (the rail's "The vehicle" holds them).
// Nothing is lost every removed measure is still on the page once,
// where it belongs.
return (
<div className="lmetrics">
<div className="lm">
<span className="lm__l">Renewal</span>
<strong className={'lm__v' + (e ? ' due--' + e.tone : '')}>{e ? e.label : '—'}</strong>
<span className="lm__s">{e ? e.on : 'no date on file'}</span>
</div>
<div className="lm">
<span className="lm__l">Lead age</span>
<strong className="lm__v">{age === null ? '—' : age === 0 ? 'Today' : age + 'd'}</strong>
<span className="lm__s">{row.created_at ? fmtWhen(row.created_at) : ''}</span>
</div>
<div className="lm">
<span className="lm__l">In this stage</span>
<strong className="lm__v">{dwell === null ? '—' : dwell === 0 ? '<1d' : dwell + 'd'}</strong>
<span className="lm__s">last activity {row.updated_at ? fmtWhen(row.updated_at) : '—'}</span>
</div>
<div className="lm lm--hero">
<span className="lm__l">Premium</span>
<strong className="lm__v">{premium ?? '—'}</strong>
<span className="lm__s">{premium ? 'quoted' : 'not yet rated'}</span>
<span className="lm__s">{premium ? 'quoted, incl. GST' : 'not yet rated'}</span>
</div>
<div className="lm lm--hero">
<span className="lm__l">Commission</span>
<strong className="lm__v">{commission ?? '—'}</strong>
<span className="lm__s">
{row.commission_rate_pct ? row.commission_rate_pct + '% of premium' : 'on placement'}
</span>
</div>
<div className="lm">
<span className="lm__l">Product</span>
<strong className="lm__v lm__v--sm">{PRODUCTS[row.product_line] ?? '—'}</strong>
<span className="lm__s">
{row.motor_reg_no || row.entity_name || ''}
{row.current_insurer ? ` · from ${row.current_insurer}` : ''}
{row.commission_rate_pct ? row.commission_rate_pct + '% · on issue' : 'on placement'}
</span>
</div>
{Number.isFinite(conf) && conf > 0 ? (
@ -511,6 +477,11 @@ export default function Lead() {
<span className="lm__s">on the cover advice</span>
</div>
) : null}
<div className="lm">
<span className="lm__l">Renewal</span>
<strong className={'lm__v' + (e ? ' due--' + e.tone : '')}>{e ? e.label : '—'}</strong>
<span className="lm__s">{e ? e.on : 'no date on file'}</span>
</div>
</div>
)
})()}
@ -653,25 +624,21 @@ export default function Lead() {
return (
<div className="panel" id="lead-next">
{/* THE HEADING IS FOR A REAL STEP ONLY. When there is no `do`
action, the "what is happening / nothing owed" story is
already told once by the status strip above and the
header so repeating it here as "Next step: nothing is
waiting on you" was the same sentence a third time. The
secondary actions (add a document, mark lost) still render
below, without the framing. */}
{step.length ? (
<div className="panel__head">
<div>
<h2 className="panel__title">Next step</h2>
<p className="panel__sub">
{step.length
? 'This is what this lead is waiting on.'
: isClosed
? 'This lead is closed.'
: blocked
? 'The chain has stopped. The fix is above.'
: accepted && stage?.uid === 'zk-state-quoted'
/* What is RUNNING is the strip's job now, so this
records only the fact otherwise the two lines
said the same thing twice, one above the other. */
? `Accepted by the customer${row.accepted_at ? ' on ' + row.accepted_at : ''}${row.acceptance_ref === 'whatsapp' ? ', over WhatsApp' : ''}. Nothing is waiting on you.`
: `Nothing is waiting on you — ${stage?.by ?? 'someone else'} has this one.`}
</p>
<p className="panel__sub">This is what this lead is waiting on.</p>
</div>
</div>
) : null}
{/* The step. One button, sized like a decision. Referred has
two because clear and decline are a pair, not a choice