feat(leads+verification): wire Leads & Lead Verification to be-crm data door

Integrates the Leads and Lead Verification screens with the live be-crm
backend (crm.lead.* / crm.leadVerification.* / crm.media.*) behind a
mode-agnostic data layer that still falls back to the local mock when the
Shell isn't configured.

Data layer (new)
- src/lib/leads-api.ts    — crm.lead.search/get/stats/create/update/
  updateStatus/assign/sendForVerification + media (presign upload/download)
- src/lib/verify-api.ts   — crm.leadVerification.search/get/stats/verify/
  markUnverified/assign/reassign/moveToPending

Backend → FE wiring
- Assignee / creator names: read the resolved DTO fields and, as a safety
  net, resolve member ids → real names against crm.team.member.search
  (be-crm currently echoes the raw principalId as the name).
- Created By: read the createdBy object the backend returns (was blank).
- Site photos: upload via crm.media.presignUpload → PUT, persist through
  crm.lead.attachment.add (per photo, after create), render in the detail
  popup via crm.media.presignDownload.
- Duplicate guard: surface the backend's real error (detail / duplicate_lead)
  instead of the SDK's opaque "data command failed: 400".

UX
- Leads detail: Property Photos gallery + edit-mode photo add/remove.
- Verification: "Change Assignee" now opens a searchable, scrollable popup
  instead of a long inline dropdown.
- Removed the static "Storm Zone" tag from lead cards/detail; storm banner
  only renders when the lead carries storm data.

Reference / follow-ups
- leads.http, leads.postman_collection.json — be-crm data-door requests.
- LEADS_BACKEND_CHANGES.md — required be-crm changes (name resolution,
  assignee carry-over on sendForVerification) verified against :4010.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mayur Shinde
2026-07-24 15:20:29 +05:30
parent f380c7d465
commit fd2e02086e
10 changed files with 2539 additions and 171 deletions
+11 -1
View File
@@ -12,9 +12,16 @@ export type LeadPriority = "high" | "medium" | "low";
export type Phone = { number: string; type: "Mobile" | "Home" | "Work"; primary?: boolean };
export type Email = { address: string; type?: string; primary?: boolean };
/** A site photo (or other file) stored via the media presign flow (§11.5).
* `contentRef` is the IIOS object key — render it through a signed download URL. */
export type Attachment = { id?: string; contentRef: string; mimeType: string; sizeBytes: number; filename: string };
export type Lead = {
id: string; // SAL-001
id: string; // SAL-001 (display code)
refId?: string; // opaque server id (live mode); commands target this — falls back to `id`
/** Set once the lead has been pushed to the verification queue — blocks a duplicate send.
* Populated from the backend (e.g. LeadDTO.verificationId) when available. */
verificationId?: string;
initials: string;
name: string;
gradient: string;
@@ -31,6 +38,9 @@ export type Lead = {
phones: Phone[];
emails: Email[];
// site photos (uploaded via the media presign flow; shown in the detail popup)
attachments?: Attachment[];
// property
property: { address: string; city: string; state: string; zip: string; type: string };