// Interactive Riyadh map page — the centerpiece
const MapPage = ({ t, lang, selectedParcel, setSelectedParcel, setPage, setPreselectedServices }) => {
const data = window.DAOOB_DATA;
const [tool, setTool] = React.useState("pin"); // pin | draw | upload | clear
const [basemap, setBasemap] = React.useState("street");
const [search, setSearch] = React.useState("");
const [filterZones, setFilterZones] = React.useState([]);
const [heatmap, setHeatmap] = React.useState(true);
const [layers, setLayers] = React.useState({ zoning: true, traffic: false, amenities: false, planning: false });
const toggleZone = (key) => {
setFilterZones(prev => prev.includes(key) ? prev.filter(z => z !== key) : [...prev, key]);
};
const toggleLayer = (key) => setLayers(l => ({ ...l, [key]: !l[key] }));
const sel = data.parcels.find(p => p.id === selectedParcel);
const visible = filterZones.length ? data.parcels.filter(p => filterZones.includes(p.zone)) : data.parcels;
return (
{/* Left: tools */}
{t.map.title}
{t.map.results_count.replace("{n}", visible.length)}
setSearch(e.target.value)}
/>
{lang === "ar" ? "أدوات الاختيار" : "Selection tools"}
{[
{ id: "pin", icon: "pin", lbl: t.map.tools.pin },
{ id: "draw", icon: "draw", lbl: t.map.tools.draw },
{ id: "upload", icon: "upload", lbl: t.map.tools.upload },
{ id: "clear", icon: "clear", lbl: t.map.tools.clear },
].map(x => (
{
setTool(x.id);
if (x.id === "clear") setSelectedParcel(null);
}}
>
{x.lbl}
))}
{t.map.basemap}
{["dark","street","satellite"].map(b => (
setBasemap(b)}>
{b === "dark" ? (lang === "ar" ? "داكن" : "Dark") : t.map[`basemap_${b}`]}
))}
{t.map.layers}
{[
{ k: "zoning", lbl: t.map.layer_zoning },
{ k: "traffic", lbl: t.map.layer_traffic },
{ k: "amenities", lbl: t.map.layer_amenities },
{ k: "planning", lbl: t.map.layer_planning },
].map(x => (
{x.lbl}
toggleLayer(x.k)}/>
))}
{t.map.filter_h}
{Object.entries(data.zoningTypes).map(([k, z]) => (
{
if (filterZones.length === 0) setFilterZones(Object.keys(data.zoningTypes).filter(x => x !== k));
else toggleZone(k);
}}
style={{ accentColor: "var(--green-700)" }}
/>
{t.map[`legend_${k === "industrial" ? "industrial" : k === "govt" ? "govt" : k}`] || z[lang]}
))}
{/* Center: map */}
{
// Drop a pin anywhere → snap to nearest parcel within range
if (nearestId) setSelectedParcel(nearestId);
}}
/>
{/* Heatmap toggle */}
setHeatmap(h => !h)}
>
{lang === "ar" ? "خريطة الأسعار" : "Price heatmap"}
{/* Legend overlay */}
{t.map.legend}
{[
{ k: "mixed", lbl: t.map.legend_mixed },
{ k: "commercial", lbl: t.map.legend_commercial },
{ k: "residential", lbl: t.map.legend_residential },
{ k: "industrial", lbl: t.map.legend_industrial },
{ k: "govt", lbl: t.map.legend_govt },
].map(x => (
{x.lbl}
))}
{/* Right: detail panel */}
);
};
const ParcelEmpty = ({ t }) => (
{t.map.no_select_t}
{t.map.no_select_d}
);
const ParcelDetail = ({ sel, t, lang, data, setPage, setPreselectedServices }) => {
const zone = data.zoningTypes[sel.zone];
const district = data.districts[sel.district];
const askPrice = sel.price ? `${(sel.price * sel.area / 1000).toFixed(1)}M ${t.map.sar}` : "—";
// 12-month price trend percentage change
const trend = sel.priceTrend;
const trendPct = trend ? Math.round(((trend[trend.length - 1] - trend[0]) / trend[0]) * 100) : null;
// Format last transaction date
const fmtDate = (d) => {
if (!d) return null;
const dt = new Date(d);
return dt.toLocaleDateString(lang === "ar" ? "ar-SA" : "en-GB", { day: "numeric", month: "short", year: "numeric" });
};
return (
{/* Header */}
{t.map.parcel} · {sel.id}
{district[lang]}
{zone[lang]} · {(lang === "ar" ? "ملكية " : "Ownership: ") + (sel.owner === "Govt." ? (lang === "ar" ? "حكومية" : "Govt.") : (lang === "ar" ? "خاصة" : "Private"))}
{/* Price headline */}
{sel.price && (
{askPrice}
{sel.price.toLocaleString()} {lang === "ar" ? "ر.س/م²" : "SAR/m²"}
{trendPct !== null && (
= 0 ? "up" : "down")}>
{trendPct >= 0 ? "↑" : "↓"} {Math.abs(trendPct)}%
{lang === "ar" ? "آخر 12 شهر" : "12 mo."}
)}
)}
{/* 12-month price trend sparkline */}
{trend &&
}
{/* Quick facts grid */}
{t.map.area}
{sel.area.toLocaleString()} {t.map.sqm}
{lang === "ar" ? "التصنيف" : "Zoning"}
{zone[lang]}
{sel.frontages &&
{lang === "ar" ? "الواجهات" : "Frontages"}
{sel.frontages}
}
{sel.width &&
{lang === "ar" ? "العرض × العمق" : "W × D"}
{sel.width} × {sel.depth} m
}
{sel.metro &&
{lang === "ar" ? "أقرب محطة مترو" : "Nearest metro"}
{sel.metro.station} ({sel.metro.line}) · {sel.metro.distance_m}m
}
{/* Building regulations */}
{sel.restrictions && (
{lang === "ar" ? "اشتراطات البناء" : "Building regulations"}
FAR {sel.restrictions.far}
{lang === "ar" ? "نسبة البناء" : "Coverage"} {Math.round(sel.restrictions.coverage * 100)}%
{lang === "ar" ? "ارتفاع أقصى" : "Max height"} {sel.restrictions.height}m
{lang === "ar" ? "الارتداد" : "Setback"} {sel.restrictions.setback}m
)}
{/* Utilities */}
{sel.utilities && (
{lang === "ar" ? "البنية التحتية" : "Utilities"}
{Object.entries({
water: lang === "ar" ? "ماء" : "Water",
sewage: lang === "ar" ? "صرف" : "Sewage",
electricity: lang === "ar" ? "كهرباء" : "Electricity",
gas: lang === "ar" ? "غاز" : "Gas",
fiber: lang === "ar" ? "ألياف" : "Fiber",
}).map(([k, lbl]) => (
{sel.utilities[k] ? "✓" : "—"}
{lbl}
))}
)}
{/* Last transaction */}
{sel.lastTx && (
{lang === "ar" ? "آخر معاملة" : "Last transaction"}
{lang === "ar" ? "القيمة" : "Value"}
{(sel.lastTx.price / 1_000_000).toFixed(1)}M {lang === "ar" ? "ر.س" : "SAR"}
{lang === "ar" ? "التاريخ" : "Date"}
{fmtDate(sel.lastTx.date)}
{sel.compsCount > 0 && (
{lang === "ar" ? "معاملات مشابهة" : "Comparable txns"}
{sel.compsCount} {lang === "ar" ? "آخر 12 شهر" : "last 12mo"}
)}
)}
{/* AI Score */}
{sel.score && (
{t.map.ai_score}
{sel.score >= 75 ? (lang === "ar" ? "ممتاز" : "Excellent") : sel.score >= 60 ? (lang === "ar" ? "جيد" : "Strong") : (lang === "ar" ? "متوسط" : "Moderate")}
)}
{t.map.ai_summary}
{t.map.ai_summary_body}
{/* Services — pick one to request */}
{lang === "ar" ? "اختر دراسة لهذه القطعة" : "Choose a study for this parcel"}
{t.services.list.length} {lang === "ar" ? "خدمات" : "services"}
{t.services.list.map((s, i) => (
{ setPreselectedServices([i]); setPage("request"); }}
>
{String(i + 1).padStart(2, "0")}
{s.t}
{(s.daoob || 500).toLocaleString()} {lang === "ar" ? "ر.س" : "SAR"} · {t.services.compare.ai_lead}
{s.price}
))}
{ setPreselectedServices(null); setPage("request"); }}
>
{lang === "ar" ? "اطلب باقة كاملة" : "Request bundled study"}
{t.map.add_to_compare}
);
};
// 12-month price trend sparkline
const PriceSpark = ({ trend, lang }) => {
const W = 280, H = 50, pad = 4;
const min = Math.min(...trend);
const max = Math.max(...trend);
const sx = (i) => pad + (i / (trend.length - 1)) * (W - pad * 2);
const sy = (v) => H - pad - ((v - min) / (max - min || 1)) * (H - pad * 2);
const d = trend.map((v, i) => `${i === 0 ? "M" : "L"} ${sx(i)} ${sy(v)}`).join(" ");
const area = d + ` L ${sx(trend.length - 1)} ${H} L ${sx(0)} ${H} Z`;
return (
{lang === "ar" ? "قبل 12 شهر" : "12 mo. ago"}
{trend[0]} → {trend[trend.length - 1]} {lang === "ar" ? "ر.س/م²" : "SAR/m²"}
);
};
Object.assign(window, { MapPage });