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:
parent
17561f3f73
commit
2965c47b71
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user