diff --git a/src/screens/Pipeline.jsx b/src/screens/Pipeline.jsx index faa063d..d9fd03b 100644 --- a/src/screens/Pipeline.jsx +++ b/src/screens/Pipeline.jsx @@ -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

Unknown stage.

return ( @@ -145,9 +159,20 @@ export default function Pipeline() {

{state.status === 'ready' ? ( -
- {state.total} - {state.total === 1 ? 'lead' : 'leads'} +
+ {/* Offered only where there is something to hide, and OFF by + default: this page's job is that nothing disappears from it. */} + {isAll && closedCount ? ( + + ) : null} +
+ {isAll ? shownRows.length : state.total} + {(isAll ? shownRows.length : state.total) === 1 ? 'lead' : 'leads'} +
) : null} @@ -166,11 +191,11 @@ export default function Pipeline() {
) : null} - {state.status === 'ready' && state.rows.length === 0 ? ( -

No records in this stage.

+ {state.status === 'ready' && shownRows.length === 0 ? ( +

{isAll ? 'No leads yet.' : 'No records in this stage.'}

) : null} - {state.status === 'ready' && state.rows.length > 0 ? ( + {state.status === 'ready' && shownRows.length > 0 ? (
@@ -183,7 +208,7 @@ export default function Pipeline() { - {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 ( navigate(`/lead/${id}`)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); navigate(`/lead/${id}`) } diff --git a/src/screens/screens.css b/src/screens/screens.css index 11a45ff..3560b09 100644 --- a/src/screens/screens.css +++ b/src/screens/screens.css @@ -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); }