diff --git a/src/data/mockStore.jsx b/src/data/mockStore.jsx index b361f05..95f92f0 100644 --- a/src/data/mockStore.jsx +++ b/src/data/mockStore.jsx @@ -181,7 +181,8 @@ function getWeightedRandom(items, weights) { } function generateProperties() { - return RAW_LOCATIONS.map((loc, index) => { + // 1. Generate Base Properties + const allProperties = RAW_LOCATIONS.map((loc, index) => { const isCommercial = loc.type === "Commercial" || loc.type === "Apartments"; // Shuffle styles slightly differently than index-based to avoid repeating patterns @@ -224,27 +225,20 @@ function generateProperties() { const rentAmount = isRented ? Math.floor(value * 0.008 / 100) * 100 : null; // Weighted Roof Condition - // 40% Good, 30% Fair, 20% Excellent, 10% Needs Repair const roofCondition = getWeightedRandom( ["Good", "Fair", "Excellent", "Needs Repair"], [0.4, 0.3, 0.2, 0.1] ); - // Canvassing Status Randomization - // 60% Neutral, 20% Hot Lead, 10% Customer, 10% Renovated - const status = isCommercial ? "Neutral" : getWeightedRandom( - ["Neutral", "Hot Lead", "Customer", "Renovated"], - [0.6, 0.2, 0.1, 0.1] - ); - - // Neighborhood Rating (Weighted towards high 7-9) + // Neighborhood Rating const neighRating = getWeightedRandom([7, 8, 9, 10, 6, 5], [0.3, 0.3, 0.2, 0.1, 0.05, 0.05]); return { id: index + 1, center: [loc.lat, loc.lng], polygon: generatePolygon(loc.lat, loc.lng), - canvassingStatus: status, + // Placeholder status, will be overwritten + canvassingStatus: "Neutral", 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}`, @@ -259,7 +253,7 @@ function generateProperties() { propertyTaxAssessmentValue: Math.floor(value * 0.85), roofCondition: roofCondition, renovationDescription: isCommercial ? "Commercial Maintenance" : style.desc, - renovationCost: Math.floor(value * (Math.random() * 0.05 + 0.01)), // 1-6% of value + renovationCost: Math.floor(value * (Math.random() * 0.05 + 0.01)), lastMajorRenovationDate: "2020-05-15", lastRoofRepairReplacementDate: "2018-02-10", recentMajorRepairs: "Routine Maintenance", @@ -309,6 +303,36 @@ function generateProperties() { } }; }); + + // 2. Assign Statuses with Strict Counts + // User Request: 4-9 Hot Leads, 15-20 Customers + const countHotLeads = Math.floor(Math.random() * (9 - 4 + 1)) + 4; // 4 to 9 + const countCustomers = Math.floor(Math.random() * (20 - 15 + 1)) + 15; // 15 to 20 + + // Create indices array and shuffle it + const indices = allProperties.map((_, i) => i); + for (let i = indices.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [indices[i], indices[j]] = [indices[j], indices[i]]; + } + + // Assign based on shuffled indices + indices.forEach((propIndex, i) => { + if (i < countHotLeads) { + allProperties[propIndex].canvassingStatus = "Hot Lead"; + } else if (i < countHotLeads + countCustomers) { + allProperties[propIndex].canvassingStatus = "Customer"; + } else { + // Remaining are Neutral, Renovated, or Not Interested + // Weighted: 70% Neutral, 20% Not Interested, 10% Renovated + allProperties[propIndex].canvassingStatus = getWeightedRandom( + ["Neutral", "Not Interested", "Renovated"], + [0.7, 0.2, 0.1] + ); + } + }); + + return allProperties; } // --- NEW DATA (Users & Meetings) --- diff --git a/src/pages/Maps.jsx b/src/pages/Maps.jsx index 105b5ce..06d6dc4 100644 --- a/src/pages/Maps.jsx +++ b/src/pages/Maps.jsx @@ -28,7 +28,7 @@ L.Marker.prototype.options.icon = DefaultIcon; const StatusBadge = ({ status }) => { const colors = { - "Neutral": "bg-zinc-500", + "Neutral": "bg-violet-400", "Hot Lead": "bg-red-500", "Renovated": "bg-blue-500", "Customer": "bg-emerald-500", @@ -51,6 +51,34 @@ const MapInteraction = ({ onMapClick }) => { return null; }; +const MapLegend = () => ( +
+

Map Legend

+
+
+
+ Hot Lead +
+
+
+ Customer +
+
+
+ Renovated +
+
+
+ Neutral +
+
+
+ Not Interested +
+
+
+); + // Map View const MapView = ({ data, onSelect, onMapCreate }) => { const mapStart = [33.0708, -96.7455]; @@ -74,13 +102,32 @@ const MapView = ({ data, onSelect, onMapCreate }) => { /> + {data.map((item) => { - let color = "#71717a"; // zinc-500 default - // Adjust colors for light/dark mode visibility if needed, or keep consistent - if (item.canvassingStatus === "Hot Lead") color = "#ef4444"; // red-500 - if (item.canvassingStatus === "Customer") color = "#10b981"; // emerald-500 - if (item.canvassingStatus === "Renovated") color = "#3b82f6"; // blue-500 + let color = "#a78bfa"; // Violet-400 (New Neutral) + let fillOpacity = 0.2; + + switch (item.canvassingStatus) { + case "Hot Lead": + color = "#ef4444"; // Red-500 + fillOpacity = 0.4; + break; + case "Customer": + color = "#10b981"; // Emerald-500 + fillOpacity = 0.3; + break; + case "Renovated": + color = "#3b82f6"; // Blue-500 + break; + case "Not Interested": + color = "#18181b"; // Zinc-900 (Blackish) + fillOpacity = 0.1; + break; + default: + // Neutral (Violet) + break; + } return ( { pathOptions={{ color: color, fillColor: color, - fillOpacity: 0.2, + fillOpacity: fillOpacity, weight: 2 }} eventHandlers={{ @@ -176,9 +223,34 @@ const Drawer = ({ isOpen, onClose, data, newLocation, onUpdateStatus, onSave, lo marketValue: data.propertyData?.currentEstimatedMarketValue || 0, notes: data.propertyData?.notes || '', propertyType: data.propertyData?.propertyType || 'House', - // Map other fields if they existed in data, otherwise defaults - ...data.propertyData?.details, // Assuming we store extra details in a nested object `details` for now or spread root - ...data.ownerData?.details + // Correctly map fields from mockStore structure (flat objects) to form state + builtUpArea: data.propertyData?.totalBuiltUpArea || '', + lotSize: data.propertyData?.lotPlotSize || '', // mockStore might return string "0.22 Acres", assuming input handles text or needs parsing if strictly number + yearBuilt: data.propertyData?.yearBuilt || '', + bedrooms: data.propertyData?.numberOfBedrooms || '', + bathrooms: data.propertyData?.numberOfBathrooms || '', + parkingSpaces: data.propertyData?.numberOfParkingSpaces || '', + + taxValue: data.propertyData?.propertyTaxAssessmentValue || '', + roofCondition: data.propertyData?.roofCondition || 'Good', + lastRenovationDate: data.propertyData?.lastMajorRenovationDate || '', + + isRented: data.propertyData?.currentlyRented ? 'Yes' : 'No', + tenantName: data.propertyData?.currentTenantName || '', + currentRent: data.propertyData?.currentMonthlyRentAmount || '', + leaseEnd: data.propertyData?.leaseEndDate || '', + ownerWillingToRent: data.propertyData?.ownerWillingToRent ? 'Yes' : 'No', + expectedRent: data.propertyData?.expectedMonthlyRentAmount || '', + + schoolDistrict: data.propertyData?.schoolDistrict || '', + neighborhoodRating: data.propertyData?.neighborhoodRating || 5, + + occupation: data.ownerData?.occupation || '', + ownershipType: data.ownerData?.ownershipType || 'Individual', + willingToSell: data.ownerData?.willingToSellProperty ? 'Yes' : 'No', + desiredPrice: data.ownerData?.desiredSellingPrice || '', + minPrice: data.ownerData?.minimumAcceptableSellingPrice || '', + ownerAddress: data.ownerData?.mailingAddress || '', }); setIsEditing(false); } else if (newLocation) {