fd2e02086e
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>
231 lines
10 KiB
TypeScript
231 lines
10 KiB
TypeScript
// ============================================================
|
|
// LynkedUp Pro — Leads mock data.
|
|
// A storm-restoration roofing pipeline: door-knocked leads in
|
|
// the Plano, TX hail zone. Each lead carries a rich detail
|
|
// record (contact, property, job, insurance, assignment) that
|
|
// powers the lead-detail popup. All client-side so the screen
|
|
// is fully interactive without a backend.
|
|
// ============================================================
|
|
|
|
export type LeadStatus = "new" | "contacted" | "appointed" | "closed";
|
|
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 (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;
|
|
priority: LeadPriority;
|
|
status: LeadStatus;
|
|
tag: string; // "Storm Zone"
|
|
updated: string; // "3d ago"
|
|
setter: string; // compact chip name
|
|
|
|
// storm banner
|
|
storm: { zone: string; date: string; detail: string };
|
|
|
|
// contact
|
|
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 };
|
|
|
|
// job details
|
|
job: {
|
|
source: string; leadType: string; workType: string; tradeType: string;
|
|
urgency: string; canvasser: string; notes: string;
|
|
};
|
|
|
|
// insurance
|
|
insurance: {
|
|
company: string; claimStatus: string; claimNumber: string;
|
|
policyNumber: string; adjusterName: string; adjusterPhone: string;
|
|
};
|
|
|
|
// assignment
|
|
assignment: {
|
|
assignedTo: string; priority: string; followUp: string;
|
|
createdBy: string; createdAt: string;
|
|
};
|
|
};
|
|
|
|
const G = {
|
|
orange: "linear-gradient(135deg,#fda913,#fd6d13)",
|
|
blue: "linear-gradient(135deg,#4f8cff,#2c5cff)",
|
|
purple: "linear-gradient(135deg,#b07bf2,#7b53e0)",
|
|
green: "linear-gradient(135deg,#33c98a,#1fa46c)",
|
|
cyan: "linear-gradient(135deg,#34c9d6,#1f9aa4)",
|
|
};
|
|
|
|
export const LEADS: Lead[] = [
|
|
{
|
|
id: "SAL-001", initials: "JM", name: "John Martinez", gradient: G.orange,
|
|
priority: "high", status: "contacted", tag: "Storm Zone", updated: "3d ago", setter: "Cody",
|
|
storm: { zone: "E Plano / Spring Creek Pkwy", date: "2026-04-28", detail: '2.5" hail (severe)' },
|
|
phones: [
|
|
{ number: "(469) 500-1000", type: "Mobile", primary: true },
|
|
{ number: "(214) 600-2000", type: "Home" },
|
|
],
|
|
emails: [{ address: "john.martinez@gmail.com", primary: true }],
|
|
property: { address: "4821 Spring Creek Pkwy", city: "Plano", state: "TX", zip: "75023", type: "Single Family" },
|
|
job: {
|
|
source: "Door Knock", leadType: "Insurance", workType: "Roof Replacement", tradeType: "Roofing",
|
|
urgency: "High", canvasser: "Cody Tatum",
|
|
notes: "Homeowner showed significant granule loss on south-facing slopes; agreed to inspection.",
|
|
},
|
|
insurance: {
|
|
company: "State Farm", claimStatus: "Filed", claimNumber: "CLM-2026-1000",
|
|
policyNumber: "POL-080000", adjusterName: "Marcus Powell", adjusterPhone: "(972) 700-3000",
|
|
},
|
|
assignment: {
|
|
assignedTo: "Jesus Gonzales", priority: "High", followUp: "Jun 4, 2026",
|
|
createdBy: "Cody Tatum", createdAt: "May 28, 2026",
|
|
},
|
|
},
|
|
{
|
|
id: "SAL-002", initials: "SK", name: "Sarah Kim", gradient: G.purple,
|
|
priority: "high", status: "appointed", tag: "Storm Zone", updated: "2d ago", setter: "Shelby",
|
|
storm: { zone: "E Plano / Custer Rd", date: "2026-04-28", detail: '2.5" hail (severe)' },
|
|
phones: [
|
|
{ number: "(972) 501-1037", type: "Mobile", primary: true },
|
|
{ number: "(214) 601-2044", type: "Home" },
|
|
],
|
|
emails: [{ address: "sarah.kim@outlook.com", primary: true }],
|
|
property: { address: "4905 Custer Rd", city: "Plano", state: "TX", zip: "75023", type: "Single Family" },
|
|
job: {
|
|
source: "Door Knock", leadType: "Insurance", workType: "Roof Replacement", tradeType: "Roofing",
|
|
urgency: "High", canvasser: "Hannah Reyes",
|
|
notes: "Visible mat exposure on rear elevation. Appointment set for adjuster meet.",
|
|
},
|
|
insurance: {
|
|
company: "Allstate", claimStatus: "Approved", claimNumber: "CLM-2026-1037",
|
|
policyNumber: "POL-081037", adjusterName: "Dana Whitfield", adjusterPhone: "(972) 700-3037",
|
|
},
|
|
assignment: {
|
|
assignedTo: "Hannah Reyes", priority: "High", followUp: "Jun 6, 2026",
|
|
createdBy: "Shelby Greer", createdAt: "May 29, 2026",
|
|
},
|
|
},
|
|
{
|
|
id: "SAL-003", initials: "RC", name: "Robert Chen", gradient: G.green,
|
|
priority: "high", status: "closed", tag: "Storm Zone", updated: "2d ago", setter: "Dalton",
|
|
storm: { zone: "E Plano / Independence Pkwy", date: "2026-04-28", detail: '2.5" hail (severe)' },
|
|
phones: [
|
|
{ number: "(469) 502-1074", type: "Mobile", primary: true },
|
|
],
|
|
emails: [{ address: "robert.chen@gmail.com", primary: true }],
|
|
property: { address: "5012 Independence Pkwy", city: "Plano", state: "TX", zip: "75023", type: "Single Family" },
|
|
job: {
|
|
source: "Door Knock", leadType: "Insurance", workType: "Roof Replacement", tradeType: "Roofing",
|
|
urgency: "High", canvasser: "Travis Boone",
|
|
notes: "Full replacement approved and installed. Final invoice cleared.",
|
|
},
|
|
insurance: {
|
|
company: "Farmers", claimStatus: "Paid", claimNumber: "CLM-2026-1074",
|
|
policyNumber: "POL-081074", adjusterName: "Leah Ortiz", adjusterPhone: "(972) 700-3074",
|
|
},
|
|
assignment: {
|
|
assignedTo: "Travis Boone", priority: "High", followUp: "—",
|
|
createdBy: "Dalton Pruitt", createdAt: "May 20, 2026",
|
|
},
|
|
},
|
|
{
|
|
id: "SAL-004", initials: "MG", name: "Maria Garcia", gradient: G.cyan,
|
|
priority: "high", status: "closed", tag: "Storm Zone", updated: "2d ago", setter: "Hannah",
|
|
storm: { zone: "E Plano / Alma Dr", date: "2026-04-28", detail: '2.5" hail (severe)' },
|
|
phones: [
|
|
{ number: "(972) 503-1111", type: "Mobile", primary: true },
|
|
],
|
|
emails: [{ address: "maria.garcia@gmail.com", primary: true }],
|
|
property: { address: "4720 Alma Dr", city: "Plano", state: "TX", zip: "75023", type: "Single Family" },
|
|
job: {
|
|
source: "Door Knock", leadType: "Insurance", workType: "Roof Replacement", tradeType: "Roofing",
|
|
urgency: "High", canvasser: "Shelby Greer",
|
|
notes: "Signed contract; build complete. Awaiting review request.",
|
|
},
|
|
insurance: {
|
|
company: "USAA", claimStatus: "Paid", claimNumber: "CLM-2026-1111",
|
|
policyNumber: "POL-081111", adjusterName: "Grant Mueller", adjusterPhone: "(972) 700-3111",
|
|
},
|
|
assignment: {
|
|
assignedTo: "Shelby Greer", priority: "High", followUp: "—",
|
|
createdBy: "Hannah Reyes", createdAt: "May 18, 2026",
|
|
},
|
|
},
|
|
{
|
|
id: "SAL-005", initials: "DT", name: "David Thompson", gradient: G.blue,
|
|
priority: "high", status: "appointed", tag: "Storm Zone", updated: "1d ago", setter: "Travis",
|
|
storm: { zone: "E Plano / Spring Creek Pkwy", date: "2026-04-28", detail: '2.5" hail (severe)' },
|
|
phones: [
|
|
{ number: "(469) 504-1148", type: "Mobile", primary: true },
|
|
{ number: "(214) 604-2148", type: "Work" },
|
|
],
|
|
emails: [{ address: "david.thompson@gmail.com", primary: true }],
|
|
property: { address: "5130 Spring Creek Pkwy", city: "Plano", state: "TX", zip: "75023", type: "Single Family" },
|
|
job: {
|
|
source: "Door Knock", leadType: "Insurance", workType: "Roof Replacement", tradeType: "Roofing",
|
|
urgency: "High", canvasser: "Dalton Pruitt",
|
|
notes: "Adjuster appointment confirmed for next week. Bring hail map + photos.",
|
|
},
|
|
insurance: {
|
|
company: "Liberty Mutual", claimStatus: "Filed", claimNumber: "CLM-2026-1148",
|
|
policyNumber: "POL-081148", adjusterName: "Priya Nair", adjusterPhone: "(972) 700-3148",
|
|
},
|
|
assignment: {
|
|
assignedTo: "Dalton Pruitt", priority: "High", followUp: "Jun 9, 2026",
|
|
createdBy: "Travis Boone", createdAt: "May 30, 2026",
|
|
},
|
|
},
|
|
];
|
|
|
|
// Header stat — the full book is larger than the loaded page.
|
|
export const TOTAL_LEADS = 35;
|
|
|
|
export const STATUS_META: Record<LeadStatus, { label: string; tone: string }> = {
|
|
new: { label: "New", tone: "blue" },
|
|
contacted: { label: "Contacted", tone: "orange" },
|
|
appointed: { label: "Appointed", tone: "purple" },
|
|
closed: { label: "Closed", tone: "green" },
|
|
};
|
|
|
|
export const PRIORITY_META: Record<LeadPriority, { label: string; tone: string }> = {
|
|
high: { label: "High", tone: "red" },
|
|
medium: { label: "Medium", tone: "orange" },
|
|
low: { label: "Low", tone: "muted" },
|
|
};
|
|
|
|
/* ---------------------------------------------------------- */
|
|
/* Reps + option lists — power the New Lead form */
|
|
/* ---------------------------------------------------------- */
|
|
|
|
export type Rep = { id: string; initials: string; name: string; email: string };
|
|
|
|
export const REPS: Rep[] = [
|
|
{ id: "LUP-1040", initials: "CT", name: "Cody Tatum", email: "cody.tatum@lynkeduppro.com" },
|
|
{ id: "LUP-1041", initials: "HR", name: "Hannah Reyes", email: "hannah.reyes@lynkeduppro.com" },
|
|
{ id: "LUP-1042", initials: "TB", name: "Travis Boone", email: "travis.boone@lynkeduppro.com" },
|
|
{ id: "LUP-1043", initials: "SG", name: "Shelby Greer", email: "shelby.greer@lynkeduppro.com" },
|
|
{ id: "LUP-1044", initials: "DP", name: "Dalton Pruitt", email: "dalton.pruitt@lynkeduppro.com" },
|
|
];
|
|
|
|
export const LEAD_SOURCES = ["Door Knock", "Referral", "Storm Chase", "Mailer / Postcard", "Sign Call", "Insurance Agent Referral", "Repeat Customer", "Social Media", "Other"];
|
|
export const LEAD_TYPES = ["Insurance", "Retail"];
|
|
export const WORK_TYPES = ["Roof Replacement", "Roof Repair", "Inspection", "Gutter Install"];
|
|
export const TRADE_TYPES = ["Roofing", "Gutters", "Siding", "Windows"];
|
|
export const PROPERTY_TYPES = ["Single Family", "Multi Family", "Commercial"];
|
|
export const CLAIM_STATUSES = ["Not Filed", "Filed", "Approved", "Paid", "Denied"];
|