console: All leads means all leads
The page called "All leads" dropped every closed lead from its query, so a lead that was lost, declined or onboarded vanished from the one place somebody goes when they cannot find something. That is the page whose entire job is to answer "where did it go?". Closed leads are shown now, dimmed, with the stage they ended in. A "Hide N closed" toggle is offered only when there is something to hide and is OFF by default — hiding is a choice, not the behaviour. The filter also moved off the query and onto the render. A view that throws rows away when it fetches them cannot be un-filtered by a toggle, and the header can now say how many are closed rather than silently showing fewer than the count claims. Dimmed rather than struck through or greyed to unreadability, and hover restores it: the row is muted, not disabled — it still opens, and the lead file behind a lost lead is often exactly what someone wants. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4b590474e4
commit
33bf65c5f5
@ -71,11 +71,18 @@ export default function Pipeline() {
|
||||
const { stageUid } = useParams()
|
||||
const navigate = useNavigate()
|
||||
const { client } = useZino()
|
||||
// "all" is not a stage — it is every open lead in one list. An operator
|
||||
// wanting to find a lead should not have to guess which queue it is in.
|
||||
// "all" is not a stage — it is every lead in one list, so an operator
|
||||
// wanting to find one does not have to guess which queue it is in.
|
||||
//
|
||||
// IT MEANS ALL. This view used to drop closed leads, so a lead that was lost
|
||||
// or onboarded disappeared from the page called "All leads" — the one place
|
||||
// somebody goes when they cannot find something. A lead that ended is still
|
||||
// a lead, and "where did it go?" is exactly the question this page exists to
|
||||
// answer. Closed rows are shown, and dimmed, and can be hidden by choice.
|
||||
const isAll = stageUid === 'all'
|
||||
const [hideClosed, setHideClosed] = useState(false)
|
||||
const stage = isAll
|
||||
? { uid: 'all', name: 'All open leads', kind: 'all', by: 'everyone' }
|
||||
? { uid: 'all', name: 'All leads', kind: 'all', by: 'everyone' }
|
||||
: STAGES.find((s) => s.uid === stageUid)
|
||||
|
||||
const [state, setState] = useState({ status: 'loading', rows: [], total: 0, error: null })
|
||||
@ -99,10 +106,9 @@ export default function Pipeline() {
|
||||
.then((res) => {
|
||||
if (cancelled) return
|
||||
let rows = res?.data ?? res?.rows ?? res?.records ?? []
|
||||
if (isAll) {
|
||||
const closed = new Set(STAGES.filter((x) => x.kind === 'end').map((x) => x.name))
|
||||
rows = rows.filter((r) => !closed.has(r.current_state_name))
|
||||
}
|
||||
// Nothing is dropped here any more. Whether closed leads are shown
|
||||
// is a choice made below, on data already fetched — a filter that
|
||||
// throws rows away at the query cannot be undone by a toggle.
|
||||
// total_count is the size of the QUEUE; rows is one page of at most
|
||||
// 100 of it. Counting the page would quietly under-report a busy stage.
|
||||
const total = isAll ? rows.length : (res?.pagination?.total_count ?? rows.length)
|
||||
@ -124,6 +130,14 @@ export default function Pipeline() {
|
||||
return () => { cancelled = true; clearInterval(id) }
|
||||
}, [client, stageUid, stage?.name, isAll])
|
||||
|
||||
// Split rather than filtered at the query, so the header can say how many
|
||||
// are closed instead of silently showing fewer than the page claims.
|
||||
const CLOSED = new Set(STAGES.filter((x) => x.kind === 'end').map((x) => x.name))
|
||||
const closedCount = isAll ? state.rows.filter((r) => CLOSED.has(r.current_state_name)).length : 0
|
||||
const shownRows = isAll && hideClosed
|
||||
? state.rows.filter((r) => !CLOSED.has(r.current_state_name))
|
||||
: state.rows
|
||||
|
||||
if (!stage) return <p className="empty">Unknown stage.</p>
|
||||
|
||||
return (
|
||||
@ -145,9 +159,20 @@ export default function Pipeline() {
|
||||
</p>
|
||||
</div>
|
||||
{state.status === 'ready' ? (
|
||||
<div className="page__count">
|
||||
<strong>{state.total}</strong>
|
||||
<span>{state.total === 1 ? 'lead' : 'leads'}</span>
|
||||
<div className="page__right">
|
||||
{/* Offered only where there is something to hide, and OFF by
|
||||
default: this page's job is that nothing disappears from it. */}
|
||||
{isAll && closedCount ? (
|
||||
<label className="toggle">
|
||||
<input type="checkbox" checked={hideClosed}
|
||||
onChange={(ev) => setHideClosed(ev.target.checked)} />
|
||||
Hide {closedCount} closed
|
||||
</label>
|
||||
) : null}
|
||||
<div className="page__count">
|
||||
<strong>{isAll ? shownRows.length : state.total}</strong>
|
||||
<span>{(isAll ? shownRows.length : state.total) === 1 ? 'lead' : 'leads'}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</header>
|
||||
@ -166,11 +191,11 @@ export default function Pipeline() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{state.status === 'ready' && state.rows.length === 0 ? (
|
||||
<p className="empty">No records in this stage.</p>
|
||||
{state.status === 'ready' && shownRows.length === 0 ? (
|
||||
<p className="empty">{isAll ? 'No leads yet.' : 'No records in this stage.'}</p>
|
||||
) : null}
|
||||
|
||||
{state.status === 'ready' && state.rows.length > 0 ? (
|
||||
{state.status === 'ready' && shownRows.length > 0 ? (
|
||||
<div className="gridwrap">
|
||||
<table className="grid">
|
||||
<thead>
|
||||
@ -183,7 +208,7 @@ export default function Pipeline() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{state.rows.map((r) => {
|
||||
{shownRows.map((r) => {
|
||||
const id = r.instance_id ?? r.id
|
||||
const ch = CHANNELS[r.source_channel] || { label: r.source_channel || '—' }
|
||||
const exp = expiry(r.renewal_due_date)
|
||||
@ -196,7 +221,9 @@ export default function Pipeline() {
|
||||
return (
|
||||
<tr
|
||||
key={id}
|
||||
className={'grid__row' + (prog ? ' is-' + prog.state : '')}
|
||||
className={'grid__row'
|
||||
+ (prog ? ' is-' + prog.state : '')
|
||||
+ (CLOSED.has(r.current_state_name) ? ' is-done' : '')}
|
||||
onClick={() => navigate(`/lead/${id}`)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); navigate(`/lead/${id}`) }
|
||||
|
||||
@ -1369,3 +1369,27 @@
|
||||
background: var(--zk-amber-tint);
|
||||
}
|
||||
.doing--stalled strong { color: var(--zk-amber-ink); }
|
||||
|
||||
/* ---- header right-hand cluster ---- */
|
||||
.page__right { display: flex; align-items: center; gap: 18px; flex: none; }
|
||||
|
||||
.toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
font-size: 0.78rem;
|
||||
color: var(--zk-muted);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.toggle input { accent-color: var(--zk-blue); cursor: pointer; }
|
||||
.toggle:hover { color: var(--zk-ink); }
|
||||
|
||||
/* A finished lead, still listed. Dimmed rather than removed: "where did it
|
||||
go?" is the question this page exists to answer, and a lead that ended is
|
||||
still a lead. Hover restores it — it is muted, not disabled. */
|
||||
.grid__row.is-done { color: var(--zk-grey); }
|
||||
.grid__row.is-done .grid__name { color: var(--zk-muted); font-weight: 400; }
|
||||
.grid__row.is-done:hover { color: var(--zk-ink); }
|
||||
.grid__row.is-done:hover .grid__name { color: var(--zk-blue-dark); }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user