feat: updated map legend and property colors
This commit is contained in:
+36
-12
@@ -181,7 +181,8 @@ function getWeightedRandom(items, weights) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function generateProperties() {
|
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";
|
const isCommercial = loc.type === "Commercial" || loc.type === "Apartments";
|
||||||
|
|
||||||
// Shuffle styles slightly differently than index-based to avoid repeating patterns
|
// 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;
|
const rentAmount = isRented ? Math.floor(value * 0.008 / 100) * 100 : null;
|
||||||
|
|
||||||
// Weighted Roof Condition
|
// Weighted Roof Condition
|
||||||
// 40% Good, 30% Fair, 20% Excellent, 10% Needs Repair
|
|
||||||
const roofCondition = getWeightedRandom(
|
const roofCondition = getWeightedRandom(
|
||||||
["Good", "Fair", "Excellent", "Needs Repair"],
|
["Good", "Fair", "Excellent", "Needs Repair"],
|
||||||
[0.4, 0.3, 0.2, 0.1]
|
[0.4, 0.3, 0.2, 0.1]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Canvassing Status Randomization
|
// Neighborhood Rating
|
||||||
// 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)
|
|
||||||
const neighRating = getWeightedRandom([7, 8, 9, 10, 6, 5], [0.3, 0.3, 0.2, 0.1, 0.05, 0.05]);
|
const neighRating = getWeightedRandom([7, 8, 9, 10, 6, 5], [0.3, 0.3, 0.2, 0.1, 0.05, 0.05]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: index + 1,
|
id: index + 1,
|
||||||
center: [loc.lat, loc.lng],
|
center: [loc.lat, loc.lng],
|
||||||
polygon: generatePolygon(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,
|
photos: isCommercial ? ["https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?w=800", "https://images.unsplash.com/photo-1542665952-14513db15293?w=800"] : style.photos,
|
||||||
propertyData: {
|
propertyData: {
|
||||||
propertyId: `P-${2600 + index}`,
|
propertyId: `P-${2600 + index}`,
|
||||||
@@ -259,7 +253,7 @@ function generateProperties() {
|
|||||||
propertyTaxAssessmentValue: Math.floor(value * 0.85),
|
propertyTaxAssessmentValue: Math.floor(value * 0.85),
|
||||||
roofCondition: roofCondition,
|
roofCondition: roofCondition,
|
||||||
renovationDescription: isCommercial ? "Commercial Maintenance" : style.desc,
|
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",
|
lastMajorRenovationDate: "2020-05-15",
|
||||||
lastRoofRepairReplacementDate: "2018-02-10",
|
lastRoofRepairReplacementDate: "2018-02-10",
|
||||||
recentMajorRepairs: "Routine Maintenance",
|
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) ---
|
// --- NEW DATA (Users & Meetings) ---
|
||||||
|
|||||||
+82
-10
@@ -28,7 +28,7 @@ L.Marker.prototype.options.icon = DefaultIcon;
|
|||||||
|
|
||||||
const StatusBadge = ({ status }) => {
|
const StatusBadge = ({ status }) => {
|
||||||
const colors = {
|
const colors = {
|
||||||
"Neutral": "bg-zinc-500",
|
"Neutral": "bg-violet-400",
|
||||||
"Hot Lead": "bg-red-500",
|
"Hot Lead": "bg-red-500",
|
||||||
"Renovated": "bg-blue-500",
|
"Renovated": "bg-blue-500",
|
||||||
"Customer": "bg-emerald-500",
|
"Customer": "bg-emerald-500",
|
||||||
@@ -51,6 +51,34 @@ const MapInteraction = ({ onMapClick }) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const MapLegend = () => (
|
||||||
|
<div className="absolute bottom-6 left-6 z-[1000] bg-white/90 dark:bg-black/90 backdrop-blur-md p-4 rounded-2xl border border-zinc-200 dark:border-white/10 shadow-xl">
|
||||||
|
<h4 className="text-[10px] font-black uppercase tracking-widest text-zinc-500 mb-3">Map Legend</h4>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-red-500 shadow-sm shadow-red-500/50"></div>
|
||||||
|
<span className="text-xs font-bold text-zinc-700 dark:text-zinc-300">Hot Lead</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-emerald-500 shadow-sm shadow-emerald-500/50"></div>
|
||||||
|
<span className="text-xs font-bold text-zinc-700 dark:text-zinc-300">Customer</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-blue-500 shadow-sm shadow-blue-500/50"></div>
|
||||||
|
<span className="text-xs font-bold text-zinc-700 dark:text-zinc-300">Renovated</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-violet-400 shadow-sm shadow-violet-400/50"></div>
|
||||||
|
<span className="text-xs font-bold text-zinc-700 dark:text-zinc-300">Neutral</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-zinc-800 dark:bg-zinc-600 border border-white/20"></div>
|
||||||
|
<span className="text-xs font-bold text-zinc-700 dark:text-zinc-300">Not Interested</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
// Map View
|
// Map View
|
||||||
const MapView = ({ data, onSelect, onMapCreate }) => {
|
const MapView = ({ data, onSelect, onMapCreate }) => {
|
||||||
const mapStart = [33.0708, -96.7455];
|
const mapStart = [33.0708, -96.7455];
|
||||||
@@ -74,13 +102,32 @@ const MapView = ({ data, onSelect, onMapCreate }) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<MapInteraction onMapClick={onMapCreate} />
|
<MapInteraction onMapClick={onMapCreate} />
|
||||||
|
<MapLegend />
|
||||||
|
|
||||||
{data.map((item) => {
|
{data.map((item) => {
|
||||||
let color = "#71717a"; // zinc-500 default
|
let color = "#a78bfa"; // Violet-400 (New Neutral)
|
||||||
// Adjust colors for light/dark mode visibility if needed, or keep consistent
|
let fillOpacity = 0.2;
|
||||||
if (item.canvassingStatus === "Hot Lead") color = "#ef4444"; // red-500
|
|
||||||
if (item.canvassingStatus === "Customer") color = "#10b981"; // emerald-500
|
switch (item.canvassingStatus) {
|
||||||
if (item.canvassingStatus === "Renovated") color = "#3b82f6"; // blue-500
|
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 (
|
return (
|
||||||
<Polygon
|
<Polygon
|
||||||
@@ -89,7 +136,7 @@ const MapView = ({ data, onSelect, onMapCreate }) => {
|
|||||||
pathOptions={{
|
pathOptions={{
|
||||||
color: color,
|
color: color,
|
||||||
fillColor: color,
|
fillColor: color,
|
||||||
fillOpacity: 0.2,
|
fillOpacity: fillOpacity,
|
||||||
weight: 2
|
weight: 2
|
||||||
}}
|
}}
|
||||||
eventHandlers={{
|
eventHandlers={{
|
||||||
@@ -176,9 +223,34 @@ const Drawer = ({ isOpen, onClose, data, newLocation, onUpdateStatus, onSave, lo
|
|||||||
marketValue: data.propertyData?.currentEstimatedMarketValue || 0,
|
marketValue: data.propertyData?.currentEstimatedMarketValue || 0,
|
||||||
notes: data.propertyData?.notes || '',
|
notes: data.propertyData?.notes || '',
|
||||||
propertyType: data.propertyData?.propertyType || 'House',
|
propertyType: data.propertyData?.propertyType || 'House',
|
||||||
// Map other fields if they existed in data, otherwise defaults
|
// Correctly map fields from mockStore structure (flat objects) to form state
|
||||||
...data.propertyData?.details, // Assuming we store extra details in a nested object `details` for now or spread root
|
builtUpArea: data.propertyData?.totalBuiltUpArea || '',
|
||||||
...data.ownerData?.details
|
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);
|
setIsEditing(false);
|
||||||
} else if (newLocation) {
|
} else if (newLocation) {
|
||||||
|
|||||||
Reference in New Issue
Block a user