Mobile Phase 8f: Pipeline card → dc anatomy (earmark/Priority/recency, scroll pills, dots)
Bring the mobile Pipeline surface to the PipelineApp.dc.html default anatomy: - Segmented control → horizontal-scroll pills with label + count badge; the active pill tints to its own stage color via --seg-* (aliased to --chip-*, so it flips in light). - Card → earmark corner + name + Priority pill / $amount · dot · recency / labeled ‹ Prev · Next › move footer (was name + contact·org sub + bare chevrons). Compact amount. - Stage-column header → StageChip + investor count + committed sum. - Page dots → tappable, active = 22px accent bar. Backend: the opportunities list injects two derived read-only fields (mirroring the Contacts-list pattern; opp writes use a field allowlist so neither round-trips): - existing_investor (contact_grid_signals committed>0) so the card earmark agrees with the detail's "Existing LP" pill. - last_contact_date (MAX communication_date on the deal's contact, deleted_at-filtered) → card recency line + the Staleness sort (replaces the updated_at proxy). Guarded by new soft-delete assertions in test_soft_delete_reads.py. 39/39 green.
This commit is contained in:
+13
-1
@@ -3014,7 +3014,9 @@ class CRMHandler(BaseHTTPRequestHandler):
|
||||
conn = get_db()
|
||||
query = """
|
||||
SELECT op.*, c.first_name, c.last_name, c.email as contact_email,
|
||||
o.name as organization_name, u.full_name as owner_name
|
||||
o.name as organization_name, u.full_name as owner_name,
|
||||
(SELECT MAX(communication_date) FROM communications
|
||||
WHERE contact_id = op.contact_id AND deleted_at IS NULL) as last_contact_date
|
||||
FROM opportunities op
|
||||
LEFT JOIN contacts c ON op.contact_id = c.id
|
||||
LEFT JOIN organizations o ON op.organization_id = o.id
|
||||
@@ -3047,6 +3049,16 @@ class CRMHandler(BaseHTTPRequestHandler):
|
||||
args.extend([limit, offset])
|
||||
|
||||
opps = rows_to_list(conn.execute(query, args).fetchall())
|
||||
# Read-only existing-LP signal for the mobile Pipeline card earmark (8f): the deal's linked
|
||||
# contact rolls up to a grid investor with committed capital. Same source as the Contacts
|
||||
# list's `committed` and the detail sheet's "Existing LP" pill (contact_grid_signals →
|
||||
# committed > 0), so card earmark and detail pill agree. Derived on GET, never persisted;
|
||||
# opportunity writes use a field allowlist (handle_update_opportunity) so it can't round-trip.
|
||||
# `last_contact_date` (subselect above) drives the card recency line + the Staleness sort.
|
||||
signals = contact_grid_signals(conn)
|
||||
for op in opps:
|
||||
sig = signals.get(str(op.get('contact_id') or ''))
|
||||
op['existing_investor'] = bool(sig and sig['committed'] > 0)
|
||||
conn.close()
|
||||
return self.send_json({"data": opps, "total": total, "limit": limit, "offset": offset})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user