forked from Goutam/lynkeduppro-crm
149 lines
8.3 KiB
TypeScript
149 lines
8.3 KiB
TypeScript
// ============================================================
|
|
// LynkedUp Pro — Projects mock data (Construction jobs + Pipeline
|
|
// leads). Used only when the Shell isn't configured, so the demo
|
|
// stays fully interactive without a backend — mirrors team-data.ts.
|
|
// ============================================================
|
|
|
|
export type ConstructionStatus = "active" | "complete" | "stuck" | "followup";
|
|
|
|
export type ConstructionLead = {
|
|
id: string;
|
|
name: string;
|
|
address: string;
|
|
status: ConstructionStatus;
|
|
stage: string;
|
|
jobType: string;
|
|
agent: string;
|
|
progress: number; // 0-100
|
|
health: number; // 0-100
|
|
value: number; // dollars
|
|
};
|
|
|
|
// The first 8 rows mirror the reference design exactly (name, address, job
|
|
// type, stage, status, progress and health). The rest extend the set to 15
|
|
// construction jobs with a realistic status/stage spread so the quick
|
|
// filters (Active/Complete/Stuck/Follow-up) all have something to show.
|
|
export const constructionLeads: ConstructionLead[] = [
|
|
{ id: "c1", name: "Derek Holloway", address: "2814 Ravenswood Dr, Plano TX 75023", status: "active", stage: "New Lead", jobType: "Roof Replacement", agent: "Cody Tatum", progress: 14, health: 65, value: 28500 },
|
|
{ id: "c2", name: "Brenda Castillo", address: "5501 Shady Brook Ln, Plano TX 75093", status: "active", stage: "New Lead", jobType: "Roof Inspection", agent: "Cody Tatum", progress: 14, health: 65, value: 15400 },
|
|
{ id: "c3", name: "Antonio Reyes", address: "1122 Custer Rd, Plano TX 75075", status: "active", stage: "New Lead", jobType: "Gutter Replacement", agent: "Cody Tatum", progress: 14, health: 65, value: 19200 },
|
|
{ id: "c4", name: "Sylvia Nguyen", address: "3308 Roundrock Trl, Plano TX 75023", status: "active", stage: "New Lead", jobType: "Siding Repair", agent: "Cody Tatum", progress: 14, health: 65, value: 21900 },
|
|
{ id: "c5", name: "Raymond Osei", address: "2814 Ravenswood Dr, Plano TX 75023", status: "active", stage: "New Lead", jobType: "Roof Replacement", agent: "Cody Tatum", progress: 14, health: 65, value: 31200 },
|
|
{ id: "c6", name: "Carolyn Estrada", address: "2814 Ravenswood Dr, Plano TX 75023", status: "active", stage: "New Lead", jobType: "Window Replacement", agent: "Cody Tatum", progress: 14, health: 65, value: 22400 },
|
|
{ id: "c7", name: "Marcus Tillman", address: "2814 Ravenswood Dr, Plano TX 75023", status: "active", stage: "New Lead", jobType: "Roof Replacement", agent: "Cody Tatum", progress: 14, health: 65, value: 26800 },
|
|
{ id: "c8", name: "Diane Kowalski", address: "815 Independence Pkwy, Plano TX 75023", status: "active", stage: "New Lead", jobType: "Roof Replacement", agent: "Cody Tatum", progress: 14, health: 65, value: 29500 },
|
|
{ id: "c9", name: "Felicia Grant", address: "9021 Legacy Dr, Plano TX 75024", status: "complete", stage: "Completed", jobType: "Gutter Replacement", agent: "Emma Wilson", progress: 100, health: 92, value: 18600 },
|
|
{ id: "c10", name: "Harold Jennings", address: "4477 Coit Rd, Plano TX 75075", status: "stuck", stage: "Permit Hold", jobType: "Siding Repair", agent: "Liam Foster", progress: 42, health: 38, value: 24800 },
|
|
{ id: "c11", name: "Yolanda Brooks", address: "6650 Parker Rd, Plano TX 75093", status: "followup", stage: "Awaiting Customer", jobType: "Window Replacement", agent: "Sophie Turner", progress: 55, health: 51, value: 20700 },
|
|
{ id: "c12", name: "Preston Wallace", address: "3312 Alma Dr, Plano TX 75075", status: "active", stage: "Scheduled", jobType: "Roof Repair", agent: "Cody Tatum", progress: 30, health: 70, value: 27500 },
|
|
{ id: "c13", name: "Nadia Ferreira", address: "8890 Independence Pkwy, Plano TX 75025", status: "complete", stage: "Completed", jobType: "Roof Inspection", agent: "Emma Wilson", progress: 100, health: 95, value: 16200 },
|
|
{ id: "c14", name: "Louis Abernathy", address: "2200 K Ave, Plano TX 75074", status: "stuck", stage: "Material Delay", jobType: "Roof Replacement", agent: "Liam Foster", progress: 60, health: 44, value: 24900 },
|
|
{ id: "c15", name: "Grace Delgado", address: "7301 Ohio Dr, Plano TX 75093", status: "active", stage: "In Progress", jobType: "Roof Replacement", agent: "Cody Tatum", progress: 68, health: 78, value: 51600 },
|
|
];
|
|
|
|
export type CollectionStatus = "paid" | "pending" | "overdue";
|
|
|
|
export type CollectionRecord = {
|
|
id: string;
|
|
name: string;
|
|
address: string;
|
|
referenceId: string;
|
|
date: string; // YYYY-MM-DD
|
|
type: string; // "Deposit — 30%", "Progress Payment — 40%", "Final Payment — 30%"
|
|
status: CollectionStatus;
|
|
amount: number; // dollars
|
|
};
|
|
|
|
// Deterministic 3-installment payment schedule per construction job (deposit /
|
|
// progress / final), so "Total Collected" has something real to show without a
|
|
// billing backend. Which installments are PAID vs PENDING/OVERDUE follows the
|
|
// job's own status — a completed job is fully paid, a stuck job has an overdue
|
|
// progress payment, etc.
|
|
const INSTALLMENTS = [
|
|
{ label: "Deposit — 30%", pct: 0.3 },
|
|
{ label: "Progress Payment — 40%", pct: 0.4 },
|
|
{ label: "Final Payment — 30%", pct: 0.3 },
|
|
] as const;
|
|
|
|
function statusFor(jobStatus: ConstructionStatus, i: number): CollectionStatus {
|
|
if (jobStatus === "complete") return "paid";
|
|
if (i === 0) return "paid"; // deposit is always collected up front
|
|
if (jobStatus === "stuck" && i === 1) return "overdue";
|
|
return "pending";
|
|
}
|
|
|
|
export function collectionsFor(lead: ConstructionLead, leadIndex: number): CollectionRecord[] {
|
|
const deposit = Math.round(lead.value * INSTALLMENTS[0].pct);
|
|
const progress = Math.round(lead.value * INSTALLMENTS[1].pct);
|
|
const final = lead.value - deposit - progress; // remainder avoids rounding drift
|
|
const amounts = [deposit, progress, final];
|
|
const ref = `PRJ-2026-${String(leadIndex + 1).padStart(3, "0")}`;
|
|
return INSTALLMENTS.map((inst, i) => {
|
|
const month = ((leadIndex * 2 + i * 2) % 12) + 1;
|
|
const day = ((leadIndex * 5 + i * 7) % 27) + 1;
|
|
return {
|
|
id: `${lead.id}-r${i + 1}`,
|
|
name: lead.name,
|
|
address: lead.address,
|
|
referenceId: `${ref}-R${i + 1}`,
|
|
date: `2026-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`,
|
|
type: inst.label,
|
|
status: statusFor(lead.status, i),
|
|
amount: amounts[i],
|
|
};
|
|
});
|
|
}
|
|
|
|
export type PipelineLead = {
|
|
id: string;
|
|
name: string;
|
|
address: string;
|
|
jobType: string;
|
|
stage: string;
|
|
agent: string;
|
|
createdAgo: string;
|
|
};
|
|
|
|
const FIRST_NAMES = [
|
|
"Wesley", "Ivy", "Corey", "Renata", "Miles", "Paula", "Jasper", "Dana",
|
|
"Terrence", "Alina", "Grant", "Bethany", "Owen", "Marisol", "Kurt",
|
|
"Tanya", "Reggie", "Selena", "Blake", "Vivian", "Colton", "Priya",
|
|
"Gerald", "Fiona",
|
|
];
|
|
const LAST_NAMES = [
|
|
"Whitfield", "Caldwell", "Rourke", "Sanborn", "Delacroix", "Winters",
|
|
"Blackwood", "Herrera", "Sweeney", "Okafor", "Lindgren", "Pruitt",
|
|
"Castellano", "Marsh", "Yaeger", "Doyle", "Kowalczyk", "Beaumont",
|
|
"Ashworth", "Nakamura", "Villanueva", "Prescott", "Hutchins",
|
|
"Loomis", "Stanhope", "Everly", "Boone",
|
|
];
|
|
const STREETS = [
|
|
"Independence Pkwy", "Legacy Dr", "Coit Rd", "Parker Rd", "Alma Dr",
|
|
"K Ave", "Ohio Dr", "Preston Rd", "Spring Creek Pkwy", "Custer Rd",
|
|
"Shady Brook Ln", "Roundrock Trl", "Ridgeview Dr", "Chisholm Trl",
|
|
"Los Rios Blvd",
|
|
];
|
|
const ZIPS = ["75023", "75024", "75025", "75074", "75075", "75093"];
|
|
const LEAD_JOB_TYPES = ["Roof Replacement", "Roof Inspection", "Gutter Replacement", "Siding Repair", "Window Replacement", "Roof Repair"];
|
|
const LEAD_STAGES = ["New Inquiry", "Contacted", "Qualifying", "Quote Requested", "Nurture"];
|
|
const LEAD_AGENTS = ["Cody Tatum", "Emma Wilson", "Liam Foster", "Sophie Turner", "Chloe Adams", "Unassigned"];
|
|
|
|
// Deterministic (no Math.random/Date.now) so server- and client-render match.
|
|
export const pipelineLeads: PipelineLead[] = Array.from({ length: 45 }, (_, i) => {
|
|
const first = FIRST_NAMES[i % FIRST_NAMES.length];
|
|
const last = LAST_NAMES[(i * 7) % LAST_NAMES.length];
|
|
const streetNum = 1000 + ((i * 137) % 8900);
|
|
const street = STREETS[(i * 3) % STREETS.length];
|
|
const zip = ZIPS[i % ZIPS.length];
|
|
const daysAgo = ((i * 3) % 21) + 1;
|
|
return {
|
|
id: `p${i + 1}`,
|
|
name: `${first} ${last}`,
|
|
address: `${streetNum} ${street}, Plano TX ${zip}`,
|
|
jobType: LEAD_JOB_TYPES[i % LEAD_JOB_TYPES.length],
|
|
stage: LEAD_STAGES[(i * 2) % LEAD_STAGES.length],
|
|
agent: LEAD_AGENTS[(i * 5) % LEAD_AGENTS.length],
|
|
createdAgo: daysAgo === 1 ? "1 day ago" : `${daysAgo} days ago`,
|
|
};
|
|
});
|