feat: Release CRM V2 (Clean History)

Squashed commits for V2 release including Dark Mode, Chatbot, and Landing Page enhancements.
This commit is contained in:
Satyam
2026-02-01 03:49:20 +05:30
commit 8a749d3041
42 changed files with 12518 additions and 0 deletions
+385
View File
@@ -0,0 +1,385 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
// --- DATA CONSTANTS (Extracted from old App.jsx) ---
const RAW_LOCATIONS = [
// Residential
{ address: "2612 Dunwick Dr, Plano, TX 75023", lat: 33.07084060970922, lng: -96.74558398435258, type: "Residential" },
{ address: "2608 Dunwick Dr, Plano, TX 75023", lat: 33.070834939506284, lng: -96.74532009555337, type: "Residential" },
{ address: "2604 Dunwick Dr, Plano, TX 75023", lat: 33.07081036862272, lng: -96.74507199497292, type: "Residential" },
{ address: "2600 Dunwick Dr, Plano, TX 75023", lat: 33.07083115937081, lng: -96.74483066077192, type: "Residential" },
{ address: "2516 Dunwick Dr, Plano, TX 75023", lat: 33.070770677180924, lng: -96.74459158203076, type: "Residential" },
{ address: "2637 Rothland Ln, Plano, TX 75023", lat: 33.070426683935366, lng: -96.74557721797312, type: "Residential" },
{ address: "2633 Rothland Ln, Plano, TX 75023", lat: 33.07042290378234, lng: -96.74534265015158, type: "Residential" },
{ address: "2629 Rothland Ln, Plano, TX 75023", lat: 33.07042857401182, lng: -96.74508552773183, type: "Residential" },
{ address: "2617 Rothland Ln, Plano, TX 75023", lat: 33.070432354164595, lng: -96.74435024782977, type: "Residential" },
{ address: "2513 Rothland Ln, Plano, TX 75023", lat: 33.07016207283135, lng: -96.74304208113281, type: "Residential" },
// New Residential Data
{ address: "6613 Phoenix Pl, Plano, TX 75023, USA", lat: 33.063266298016536, lng: -96.76932248857813, type: "Residential" },
{ address: "6609 Phoenix Pl, Plano, TX 75023, USA", lat: 33.063141994627365, lng: -96.76932089372718, type: "Residential" },
{ address: "6605 Phoenix Pl, Plano, TX 75023, USA", lat: 33.06302437405446, lng: -96.76933365253466, type: "Residential" },
{ address: "6601 Phoenix Pl, Plano, TX 75023, USA", lat: 33.06290541672423, lng: -96.7693424242148, type: "Residential" },
{ address: "6617 Phoenix Pl, Plano, TX 75023, USA", lat: 33.063379067434504, lng: -96.7693376006764, type: "Residential" },
{ address: "3913 Arizona Pl, Plano, TX 75023, USA", lat: 33.06318117622371, lng: -96.7698079594919, type: "Residential" },
{ address: "3917 Arizona Pl, Plano, TX 75023, USA", lat: 33.063193097393324, lng: -96.76997201609075, type: "Residential" },
{ address: "3921 Arizona Pl, Plano, TX 75023, USA", lat: 33.0631946868825, lng: -96.77014555573001, type: "Residential" },
{ address: "3920 Arizona Pl, Plano, TX 75023, USA", lat: 33.06252788822516, lng: -96.77010020638036, type: "Residential" },
{ address: "3916 Arizona Pl, Plano, TX 75023, USA", lat: 33.06254219373469, lng: -96.76993520147747, type: "Residential" },
{ address: "3912 Arizona Pl, Plano, TX 75023, USA", lat: 33.06254378323562, lng: -96.76975502370993, type: "Residential" },
{ address: "3908 Arizona Pl, Plano, TX 75023, USA", lat: 33.06253583573075, lng: -96.76959191541512, type: "Residential" },
{ address: "3904 Arizona Pl, Plano, TX 75023, USA", lat: 33.062526298723945, lng: -96.769445876593, type: "Residential" },
{ address: "3900 Arizona Pl, Plano, TX 75023, USA", lat: 33.06252153022017, lng: -96.76928656151433, type: "Residential" },
{ address: "3917 Sailmaker Ln, Plano, TX 75023, USA", lat: 33.0622004503711, lng: -96.76993899469362, type: "Residential" },
{ address: "3913 Sailmaker Ln, Plano, TX 75023, USA", lat: 33.06218773431336, lng: -96.76979295587151, type: "Residential" },
{ address: "3909 Sailmaker Ln, Plano, TX 75023, USA", lat: 33.06220362938525, lng: -96.76961846792823, type: "Residential" },
{ address: "3905 Sailmaker Ln, Plano, TX 75023, USA", lat: 33.06221157692012, lng: -96.76945725624147, type: "Residential" },
{ address: "3901 Sailmaker Ln, Plano, TX 75023, USA", lat: 33.06219091332797, lng: -96.7692808716901, type: "Residential" },
// Commercial
{ address: "6909 Custer Rd, Plano, TX 75023", lat: 33.07020596727293, lng: -96.7390826077087, type: "Commercial" },
{ address: "2112 Winslow Dr, Plano, TX 75023", lat: 33.069360828751016, lng: -96.73770127012321, type: "Commercial" },
{ address: "7224 Independence Pkwy, Plano, TX 75025", lat: 33.07506839285908, lng: -96.74984052259599, type: "Commercial" },
// New Commercial Data
{ address: "Dallas/Plano Marriott at Legacy Town Center, 7121 Bishop Rd, Plano, TX 75024, United States", lat: 33.074457402913424, lng: -96.8222322519585, type: "Commercial" },
{ address: "Legacy Town Center Plano, 5760 Daniel Rd, Plano, TX 75024, United States", lat: 33.07360554164325, lng: -96.82142050370054, type: "Commercial" },
{ address: "WeWork Office Space & Coworking, 6900 Dallas Pkwy Suite 300, Plano, TX 75024, United States", lat: 33.07281088750022, lng: -96.8235902233433, type: "Commercial" },
{ address: "Truluck's Ocean's Finest Seafood and Crab, 7161 Bishop Rd, Plano, TX 75024, United States", lat: 33.07535565705897, lng: -96.82174939646889, type: "Commercial" },
{ address: "Del Frisco's Grille, 7200 Bishop Rd Suite D9, Plano, TX 75024, United States", lat: 33.07630819707901, lng: -96.82124417346868, type: "Commercial" },
{ address: "Scruffy Duffies, 5865 Kincaid Rd E8, Plano, TX 75024, United States", lat: 33.07601336437884, lng: -96.82257940570773, type: "Commercial" },
{ address: "Half Shells, 7201 Bishop Rd #E4, Plano, TX 75024, United States", lat: 33.07635733578195, lng: -96.82171330913596, type: "Commercial" },
{ address: "Mi Cocina, 5760 Legacy Dr #B7, Plano, TX 75024, United States", lat: 33.07743082031222, lng: -96.82109531315771, type: "Commercial" },
{ address: "Leasing Office space and The Kincaid at Legacy Apartment, 7200 Dallas Pkwy, Plano, TX 75024, United States", lat: 33.07619102009707, lng: -96.82340039310428, type: "Commercial" },
{ address: "Issil Beauty Spa, 7140 Bishop Rd #F4, Plano, TX 75024, United States", lat: 33.07550110630488, lng: -96.82122287598153, type: "Commercial" },
// Apartments
{ address: "2100 Legacy Dr, Plano, TX 75023", lat: 33.07035321131275, lng: -96.73493675556306, type: "Apartments" },
{ address: "2401 Brown Deer Trail, Plano, TX 75023", lat: 33.06897509321587, lng: -96.7419549494157, type: "Residential" },
];
const PROPERTY_STYLES = [
{
label: "Luxury Estate",
canvassingStatus: "Customer",
photos: ["https://images.unsplash.com/photo-1600596542815-2a4d9f6facb8?w=800", "https://images.unsplash.com/photo-1564013799919-ab600027ffc6?w=800", "https://images.unsplash.com/photo-1512917774080-9991f1c4c750?w=800"],
baseValue: 850000,
areaBase: 4200,
beds: 5,
baths: 4.5,
yearBase: 2010,
desc: "Luxury estate with custom finishes throughout.",
condition: 5
},
{
label: "Standard Family Home",
canvassingStatus: "Neutral",
photos: ["https://images.unsplash.com/photo-1512917774080-9991f1c4c750?w=800", "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=800", "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?w=800"],
baseValue: 450000,
areaBase: 2800,
beds: 4,
baths: 3,
yearBase: 1995,
desc: "Well-maintained family home in established neighborhood.",
condition: 4
},
{
label: "Fixer Upper",
canvassingStatus: "Hot Lead",
photos: ["https://images.unsplash.com/photo-1605276374104-dee2a0ed3cd6?w=800", "https://images.unsplash.com/photo-1600566753376-12c8ab7fb75b?w=800", "https://images.unsplash.com/photo-1600585152220-90363fe7e115?w=800"],
baseValue: 320000,
areaBase: 2400,
beds: 3,
baths: 2,
yearBase: 1980,
desc: "Great potential, needs cosmetic updates.",
condition: 2
},
{
label: "Renovated Modern",
canvassingStatus: "Renovated",
photos: ["https://images.unsplash.com/photo-1600585154526-990dced4db0d?w=800", "https://images.unsplash.com/photo-1600573472592-401b489a3cdc?w=800", "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=800"],
baseValue: 580000,
areaBase: 3200,
beds: 4,
baths: 3.5,
yearBase: 1990,
desc: "Recently renovated with modern aesthetics.",
condition: 5
}
];
const COMMERCIAL_VARIANTS = [
{ name: "Legacy Retail Group", type: "Retail Mix", email: "leasing@legacyretail.com" },
{ name: "Custer Office Park LLC", type: "Office Space", email: "mgmt@custeroffice.com" },
{ name: "Plano Investments Inc", type: "Mixed Use", email: "contact@planoinvest.com" },
{ name: "North Texas Prop Co", type: "Service Center", email: "info@ntxprops.com" }
];
const REAL_NAMES = [
"James Beauford", "Sarah Miller", "Robert Henderson", "Karen Smith", "Michael Chang",
"Patricia O'Neil", "Dr. Anish Patel", "Marcus Johnson", "Elena Rodriguez", "Greg Alston",
"Linda Grey", "Bill Thompson", "Jennifer Wu", "David Kowalski", "Omar Farooq",
"Lisa Chen", "Tom Baker", "Rachel Green", "Steve Martin", "Angela White"
];
const OCCUPATIONS = [
"Software Engineer", "Petroleum Engineer", "Teacher", "Nurse Practitioner",
"Small Business Owner", "Accountant", "Marketing Director", "retired",
"Attorney", "Real Estate Agent", "Sales Manager", "Consultant"
];
const EMPLOYERS_BY_OCCUPATION = {
"Software Engineer": ["Google", "Microsoft", "Toyota Connected", "Capital One", "Intuit"],
"Petroleum Engineer": ["ExxonMobil", "Chevron", "Pioneer Natural Resources", "Denbury"],
"Teacher": ["Plano ISD", "Frisco ISD", "Dallas ISD", "Richardson ISD"],
"Nurse Practitioner": ["Baylor Scott & White", "Medical City Plano", "Texas Health Resources"],
"Small Business Owner": ["Self Employed", "Local Boutique", "Consultancy Firm"],
"Accountant": ["Deloitte", "PwC", "EY", "KPMG", "Local CPA Firm"],
"Marketing Director": ["PepsiCo", "Toyota", "Frito-Lay", "Yum! Brands"],
"retired": ["N/A"],
"Attorney": ["Jones Day", "Baker Botts", "Norton Rose Fulbright", "Local Law Firm"],
"Real Estate Agent": ["Keller Williams", "Ebby Halliday", "Compass", "Remax"],
"Sales Manager": ["Oracle", "Salesforce", "AT&T", "Samsung"],
"Consultant": ["McKinsey", "BCG", "Accenture", "Freelance"]
};
// --- DATA GENERATION HELPERS ---
function generatePolygon(lat, lng) {
const dLat = 0.0001; // ~11 meters
const dLng = 0.0001; // ~11 meters
return [
[lat + dLat, lng - dLng], // Top Left
[lat + dLat, lng + dLng], // Top Right
[lat - dLat, lng + dLng], // Bottom Right
[lat - dLat, lng - dLng], // Bottom Left
];
}
function generateEmail(name, company = null) {
if (company) return company;
const domains = ["gmail.com", "yahoo.com", "outlook.com", "icloud.com"];
const domain = domains[Math.floor(Math.random() * domains.length)];
const parts = name.toLowerCase().split(' ');
const last = parts[parts.length - 1].replace(/[^a-z]/g, '');
const first = parts[0].replace(/[^a-z]/g, '');
return `${first}.${last}@${domain}`;
}
function generateProperties() {
return RAW_LOCATIONS.map((loc, index) => {
const isCommercial = loc.type === "Commercial" || loc.type === "Apartments";
const styleIndex = (index * 7) % PROPERTY_STYLES.length;
const style = isCommercial ? null : PROPERTY_STYLES[styleIndex];
let ownerName, ownerEmail, occupation, income, employerName;
if (isCommercial) {
const commVariant = COMMERCIAL_VARIANTS[index % COMMERCIAL_VARIANTS.length];
ownerName = commVariant.name;
ownerEmail = commVariant.email;
occupation = "Commercial Owner";
income = "$1M+";
employerName = "Self";
} else {
ownerName = REAL_NAMES[index % REAL_NAMES.length];
ownerEmail = generateEmail(ownerName);
occupation = OCCUPATIONS[index % OCCUPATIONS.length];
const potentialEmployers = EMPLOYERS_BY_OCCUPATION[occupation] || ["Self Employed"];
employerName = potentialEmployers[index % potentialEmployers.length];
income = ["$80k - $120k", "$120k - $180k", "$200k+", "$60k - $90k"][index % 4];
}
const builtYear = isCommercial ? 1995 + (index % 10) : style.yearBase + (index % 15);
const sqft = isCommercial ? 12000 + (index * 500) : style.areaBase + (index * 132);
const value = isCommercial ? 2500000 + (index * 100000) : style.baseValue + (index * 12500);
const isRented = index % 3 === 0;
const tenantName = isRented ? (isCommercial ? "Multiple Commercial Tenants" : REAL_NAMES[(index + 5) % REAL_NAMES.length]) : null;
const tenantOcc = isRented ? (isCommercial ? "Retail/Office Mix" : OCCUPATIONS[(index + 3) % OCCUPATIONS.length]) : null;
const rentAmount = isRented ? Math.floor(value * 0.008 / 100) * 100 : null;
return {
id: index + 1,
center: [loc.lat, loc.lng],
polygon: generatePolygon(loc.lat, loc.lng),
canvassingStatus: isCommercial ? "Neutral" : style.canvassingStatus,
photos: isCommercial ? ["https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?w=800", "https://images.unsplash.com/photo-1542665952-14513db15293?w=800"] : style.photos,
propertyData: {
propertyId: `P-${2600 + index}`,
propertyAddress: loc.address,
propertyType: loc.type,
totalBuiltUpArea: sqft,
yearBuilt: builtYear,
numberOfBedrooms: isCommercial ? 0 : style.beds,
numberOfBathrooms: isCommercial ? 4 : style.baths,
numberOfParkingSpaces: isCommercial ? 25 : 2,
currentEstimatedMarketValue: value,
propertyTaxAssessmentValue: Math.floor(value * 0.85),
roofCondition: ["Good", "Fair", "Excellent", "Needs Repair"][index % 4],
renovationDescription: isCommercial ? "Commercial Maintenance" : style.desc,
renovationCost: Math.floor(value * 0.05),
lastMajorRenovationDate: "2020-05-15",
lastRoofRepairReplacementDate: "2018-02-10",
recentMajorRepairs: "Routine Maintenance",
currentlyRented: isRented,
currentTenantName: tenantName,
tenantOccupation: tenantOcc,
tenantAnnualIncome: isRented ? "$100k - $150k" : null,
currentMonthlyRentAmount: rentAmount,
leaseStartDate: isRented ? "2023-01-01" : null,
leaseEndDate: isRented ? "2025-01-01" : null,
ownerWillingToRent: index % 4 === 0,
expectedMonthlyRentAmount: Math.floor(value * 0.008 / 100) * 100,
rentalAvailabilityDate: null,
rentalTermsPreference: null,
schoolDistrict: "Plano ISD",
neighborhoodRating: 8 + (index % 3),
crimeRateIndex: "Low",
publicTransportationAccess: "Bus Line 204",
propertyConditionRating: isCommercial ? 4 : style.condition,
furnishingStatus: "Unfurnished",
notes: isCommercial ? "Prime commercial location" : style.desc,
latestPurchasePrice: Math.floor(value * 0.6),
latestPurchaseDate: "2015-06-20",
latestSaleListingDate: null,
latestSaleAskingPrice: null,
lotPlotSize: isCommercial ? "1.5 Acres" : "0.22 Acres"
},
ownerData: {
ownerId: `OWN-${200 + index}`,
fullName: ownerName,
primaryPhoneNumber: `972-555-${1000 + index}`,
secondaryPhoneNumber: null,
emailAddress: ownerEmail,
mailingAddress: loc.address,
occupation: occupation,
employerName: employerName,
annualIncomeRange: income,
creditScoreRange: "720+",
ownershipType: isCommercial ? "Corporate" : "Individual",
ownershipPercentage: "100%",
willingToRentProperty: false,
desiredMonthlyRentalPrice: null,
rentalConditions: null,
willingToSellProperty: index % 5 === 0,
desiredSellingPrice: Math.floor(value * 1.1),
minimumAcceptableSellingPrice: Math.floor(value * 1.05)
}
};
});
}
// --- NEW DATA (Users & Meetings) ---
// --- NEW DATA (Users & Meetings) ---
const MOCK_USERS = [
// Customers
{ id: 'c1', type: 'customer', username: 'alice', email: 'alice@example.com', password: 'password', name: 'Alice Customer' },
{ id: 'c2', type: 'customer', username: 'bob', email: 'bob@example.com', password: 'password', name: 'Bob Buyer' },
{ id: 'c3', type: 'customer', username: 'charlie', email: 'charlie@example.com', password: 'password', name: 'Charlie Client' },
// Field Agents (5 Agents)
{ id: 'e1', type: 'employee', empId: 'FA001', email: 'agent1@plano.com', password: 'password', role: 'FIELD_AGENT', name: 'Frank Agent' },
{ id: 'e2', type: 'employee', empId: 'FA002', email: 'agent2@plano.com', password: 'password', role: 'FIELD_AGENT', name: 'Fiona Field' },
{ id: 'e3', type: 'employee', empId: 'FA003', email: 'agent3@plano.com', password: 'password', role: 'FIELD_AGENT', name: 'Fred Flyer' },
{ id: 'e4', type: 'employee', empId: 'FA004', email: 'agent4@plano.com', password: 'password', role: 'FIELD_AGENT', name: 'Felicity Fast' },
{ id: 'e5', type: 'employee', empId: 'FA005', email: 'agent5@plano.com', password: 'password', role: 'FIELD_AGENT', name: 'Felix Fixer' },
// Admins (3 Admins)
{ id: 'a1', type: 'employee', empId: 'ADM01', email: 'admin@plano.com', password: 'password', role: 'ADMIN', name: 'Adam Admin' },
{ id: 'a2', type: 'employee', empId: 'ADM02', email: 'admin2@plano.com', password: 'password', role: 'ADMIN', name: 'Amanda Manager' },
{ id: 'a3', type: 'employee', empId: 'ADM03', email: 'admin3@plano.com', password: 'password', role: 'ADMIN', name: 'Arthur Director' }
];
// Helper to generate meetings
function generateMockMeetings() {
const agents = ['e1', 'e2', 'e3', 'e4', 'e5'];
const statuses = ['Scheduled', 'Rescheduled', 'Completed', 'Converted', 'Cancelled'];
const meetings = [];
let meetingIdCounter = 1;
agents.forEach(agentId => {
// Generate ~7 meetings per agent
for (let i = 0; i < 7; i++) {
const isPast = i < 4; // First 4 are past/completed/converted
const status = isPast
? (Math.random() > 0.3 ? 'Completed' : (Math.random() > 0.5 ? 'Converted' : 'Cancelled')) // Past mix
: (Math.random() > 0.3 ? 'Scheduled' : 'Rescheduled'); // Future mix
// Date Logic
const date = new Date();
date.setDate(date.getDate() + (isPast ? -1 * (i + 1) * 2 : (i + 1) * 2)); // Spread out days
// Deal Value (only for relevant statuses)
let dealValue = null;
if (['Completed', 'Converted'].includes(status)) {
dealValue = Math.floor(Math.random() * (50000 - 5000) + 5000); // 5k to 50k
}
// Time Logic (09:00 to 16:00)
const hour = 9 + (i % 8);
const timeString = `${hour.toString().padStart(2, '0')}:00`;
meetings.push({
id: `m${meetingIdCounter++}`,
agentId: agentId,
customerName: REAL_NAMES[(meetingIdCounter * 3) % REAL_NAMES.length], // Pick random names
propertyId: `P-${2600 + (meetingIdCounter * 2)}`,
date: date.toISOString().split('T')[0],
time: timeString,
status: status,
dealValue: dealValue,
notes: status === 'Converted' ? 'Contract signed for full roof replacement.' :
status === 'Rescheduled' ? 'Client verified availability.' : 'Standard consultation.'
});
}
});
return meetings;
}
const MOCK_MEETINGS = generateMockMeetings();
// --- CONTEXT SETUP ---
const MockStoreContext = createContext();
export const MockStoreProvider = ({ children }) => {
const [properties, setProperties] = useState([]);
const [users, setUsers] = useState(MOCK_USERS);
const [meetings, setMeetings] = useState(MOCK_MEETINGS);
// Initialize properties once
useEffect(() => {
const data = generateProperties();
setProperties(data);
}, []);
const updatePropertyStatus = (id, newStatus) => {
setProperties(prev => prev.map(p =>
p.id === id ? { ...p, canvassingStatus: newStatus } : p
));
};
const addMeeting = (meeting) => {
setMeetings(prev => [...prev, { ...meeting, id: `m${prev.length + 1}` }]);
};
return (
<MockStoreContext.Provider value={{
properties,
setProperties,
users,
meetings,
updatePropertyStatus,
addMeeting
}}>
{children}
</MockStoreContext.Provider>
);
};
export const useMockStore = () => useContext(MockStoreContext);