diff --git a/src/screens/Overview.jsx b/src/screens/Overview.jsx index 18a066f..8a4ff1a 100644 --- a/src/screens/Overview.jsx +++ b/src/screens/Overview.jsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from 'react' -import { Link } from 'react-router-dom' +import { Link, useNavigate } from 'react-router-dom' import { useZino } from '../api/provider.jsx' import { RV_LEADS, STAGES } from '../api/config.js' import { rolesOf, visibleStages } from '../api/permissions.js' @@ -55,6 +55,7 @@ function expiryTone(d) { export default function Overview() { const { client, user } = useZino() + const navigate = useNavigate() const roles = rolesOf(user) const [state, setState] = useState({ status: 'loading', rows: [], at: null, error: null }) @@ -236,21 +237,32 @@ export default function Overview() {
| Lead | Expiry | Stage | Premium | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| Lead | Expiry | Stage | Premium | |||||||
| {r.customer_name || r.lead_ref || `#${id}`}
- {r.lead_ref || ''} |
+ ||||||||||
|
+ {r.customer_name || r.lead_ref || `#${id}`}
+ {r.lead_ref || ''}
+ |
{r._d < 0 ? Math.abs(r._d) + 'd overdue' : r._d === 0 ? 'today' : r._d + 'd'} | {r.current_state_name} | {r.quoted_premium ? inr(num(r.quoted_premium)) : '—'} | -Open |
| Customer | Renewal due | Product | -Premium | Waiting | + | Customer | +{isAll ? 'Stage' : 'Status'} | +Renewal due | +Premium | +Waiting |
|---|---|---|---|---|---|---|---|---|---|---|
|
- {r.customer_name || '—'}
+ {r.customer_name || '—'}
{r.lead_ref || `#${id}`}
- {r.entity_name ? ` · ${r.entity_name}` : ''}
+ {r.product_line ? ` · ${r.product_line === 'motor' ? 'Motor' : 'SME'}` : ''}
{ch.label ? ` · ${ch.label}` : ''}
|
+ {/* Who holds it, and — where an agent does — whether it is
+ moving. On the all-leads view the stage name is the
+ useful column; inside a queue every row shares it, so
+ the useful thing is progress. */}
+ + {prog ? ( + + + {prog.label} + + ) : isAll ? ( + {r.current_state_name || '—'} + ) : ( + + {rowStage?.by ? `with ${rowStage.by}` : '—'} + + )} + |
{exp
? <>{exp.label}
{exp.on} >
: —}
|
- {r.product_line === 'motor' ? 'Motor' : 'SME Package'} | {r.quoted_premium ? '₹' + Number(r.quoted_premium).toLocaleString('en-IN') : '—'} | {add.rel || add.abs} | -- Open - | ||||
| collapses the column alignment the queue depends on. */ +.grid__row { cursor: pointer; transition: background .11s, box-shadow .11s; } +.grid__row:hover { background: var(--zk-tint); } +.grid__row:hover .grid__name { color: var(--zk-blue-dark); } +.grid__row:focus-visible { + outline: 2px solid var(--zk-blue); + outline-offset: -2px; + background: var(--zk-tint); +} +.grid__name { font-weight: 500; color: var(--zk-ink); } + +/* A left edge that says who holds the lead, readable before any text is. + Only the states worth acting on are marked — colouring every row would + make the marking mean nothing. */ +.grid__row.is-stalled td:first-child { box-shadow: inset 3px 0 0 var(--zk-danger); } +.grid__row.is-working td:first-child { box-shadow: inset 3px 0 0 var(--zk-blue-mid); } + +/* ── What the agent is doing ────────────────────────────────────────────── + Agent-status disclosure: an automated stage is either moving or stuck, and + after an hour those look identical without this. `working` pulses because + something is happening right now; `waiting` is steady because it is normal; + `stalled` is red because a lead three hours into a two-minute step is the + thing this console exists to catch. */ +.run { display: inline-flex; align-items: center; gap: 7px; font-size: 0.8rem; line-height: 1.3; } +.run__dot { width: 7px; height: 7px; border-radius: 50%; flex: none; } + +.run--working { color: var(--zk-blue-dark); } +.run--working .run__dot { background: var(--zk-blue); animation: runPulse 1.6s ease-in-out infinite; } + +.run--waiting { color: var(--zk-muted); } +.run--waiting .run__dot { background: var(--zk-blue-light); } + +.run--stalled { color: var(--zk-danger-ink); font-weight: 500; } +.run--stalled .run__dot { background: var(--zk-danger); } + +@keyframes runPulse { + 0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(33,103,174,.45); } + 50% { opacity: .55; box-shadow: 0 0 0 5px rgba(33,103,174,0); } +} +/* Respect a reader who has asked the interface to stop moving. */ +@media (prefers-reduced-motion: reduce) { + .run--working .run__dot { animation: none; } +} |