// pages-deals.jsx — Appartements (filter+plan), Investir (simulator), Contact
// ──────────────────────────────────────────────────────────────────────────
// APPARTEMENTS — filterable
// ──────────────────────────────────────────────────────────────────────────
function AppartementsPage() {
const { t, setTweak } = useTheme();
const [filters, setFilters] = useState({
floor: "all",
type: "all",
orientation: "all",
status: "available",
maxPrice: 200000
});
const [selected, setSelected] = useState(null);
const filtered = LOTS.filter((l) =>
(filters.floor === "all" || l.floor === Number(filters.floor)) && (
filters.type === "all" || l.type === Number(filters.type)) && (
filters.orientation === "all" || l.orientation === filters.orientation) && (
filters.status === "all" || l.status === filters.status) &&
l.price <= filters.maxPrice
);
return (
Huit T2, deux typologies,chacun son orientation .>}
intro="Filtrez selon votre étage, votre orientation, votre budget. Cliquez un lot pour voir le plan détaillé et lancer la mise en option." />
{/* Filters */}
setFilters({ ...filters, floor: v })} />
setFilters({ ...filters, type: v })} />
setFilters({ ...filters, orientation: v })} />
setFilters({ ...filters, status: v })} />
setFilters({ ...filters, maxPrice: v })} />
setFilters({ floor: "all", type: "all", orientation: "all", status: "available", maxPrice: 200000 })} style={{
fontFamily: "var(--font-b)", fontSize: 12,
letterSpacing: "0.08em", textTransform: "uppercase",
background: "transparent", border: "1px solid var(--line)",
padding: "12px 18px", cursor: "pointer", color: "var(--ink2)",
alignSelf: "stretch"
}}>Réinitialiser
{/* Result count + view switch */}
{filtered.length} appartement{filtered.length > 1 ? "s" : ""} correspondant{filtered.length > 1 ? "s" : ""}
{[["grid", "Grille"], ["table", "Tableau"], ["plan", "Plan masse"]].map(([k, label]) =>
setTweak("lotsView", k)}
data-active={t.lotsView === k}
style={{
fontFamily: "var(--font-b)", fontSize: 12,
letterSpacing: "0.08em", textTransform: "uppercase",
padding: "8px 16px", cursor: "pointer", border: 0,
background: t.lotsView === k ? "var(--ink)" : "transparent",
color: t.lotsView === k ? "var(--bg)" : "var(--ink2)"
}}>{label}
)}
{/* Views */}
{t.lotsView === "grid" &&
{filtered.map((lot) => )}
}
{t.lotsView === "table" && }
{t.lotsView === "plan" && }
{filtered.length === 0 &&
Aucun lot ne correspond à vos critères. setFilters({ floor: "all", type: "all", orientation: "all", status: "available", maxPrice: 200000 })} style={{ background: "none", border: 0, color: "var(--accent)", textDecoration: "underline", cursor: "pointer", fontFamily: "inherit", fontSize: "inherit" }}>Réinitialiser →
}
{selected && setSelected(null)} />}
);
}
function FilterSelect({ label, value, options, onChange }) {
return (
{label}
onChange(e.target.value)}
style={{
fontFamily: "var(--font-b)", fontSize: 14, color: "var(--ink)",
background: "var(--bg)", border: "1px solid var(--line)",
padding: "10px 12px", borderRadius: 0, cursor: "pointer"
}}>
{options.map(([v, label]) => {label} )}
);
}
function FilterSlider({ label, value, min, max, step, onChange }) {
return (
{label} — {fmtPrice(value)}
onChange(Number(e.target.value))}
style={{ accentColor: "var(--accent)", height: 32 }} />
);
}
function LotCard({ lot, onSelect }) {
return (
onSelect(lot)}
style={{
background: "var(--surface)", border: "1px solid var(--line)",
cursor: lot.status === "sold" ? "not-allowed" : "pointer",
opacity: lot.status === "sold" ? 0.55 : 1,
transition: "transform .25s ease, box-shadow .25s ease"
}}
onMouseEnter={(e) => {if (lot.status !== "sold") {e.currentTarget.style.transform = "translateY(-4px)";e.currentTarget.style.boxShadow = "0 30px 60px -30px rgba(31,27,22,.25)";}}}
onMouseLeave={(e) => {e.currentTarget.style.transform = "translateY(0)";e.currentTarget.style.boxShadow = "none";}}>
Lot {lot.id}
{lot.floor}{lot.floor === 1 ? "er" : "e"} étage
{fmtPrice(lot.price)}
{fmtN(lot.price / lot.surface)} €/m²
« {lot.exposure} »
);
}
function SpecCell({ label, value }) {
return (
);
}
function LotTable({ lots, onSelect }) {
return (
Lot Étage Type Surface Loggia Orientation Exposition Prix Statut
{lots.map((lot) =>
lot.status !== "sold" && onSelect(lot)}
style={{
display: "grid",
gridTemplateColumns: "0.7fr 0.7fr 0.7fr 1fr 0.8fr 1.1fr 1.4fr 1fr 0.8fr",
width: "100%", padding: "20px 24px", border: 0,
borderBottom: "1px solid var(--line)", background: "transparent",
fontFamily: "var(--font-b)", fontSize: 14, color: "var(--ink)",
textAlign: "left", cursor: lot.status === "sold" ? "not-allowed" : "pointer",
opacity: lot.status === "sold" ? 0.5 : 1,
alignItems: "center"
}}
onMouseEnter={(e) => {if (lot.status !== "sold") e.currentTarget.style.background = "var(--bg2)";}}
onMouseLeave={(e) => {e.currentTarget.style.background = "transparent";}}>
{lot.id}
R+{lot.floor}
Type {lot.type}
{lot.surface} m²
{lot.loggia} m²
{lot.orientation}
{lot.exposure}
{fmtPrice(lot.price)}
)}
);
}
function PlanMasse({ lots, onSelect }) {
// 2 floors (R+1, R+2) x 4 columns; render as wireframe-ish floor plates
const floors = [2, 1];
return (
{floors.map((f) =>
R+{f}
{LOTS.filter((l) => l.floor === f).map((l) => {
const inFilter = lots.some((x) => x.id === l.id);
const color = !inFilter ? "var(--bg2)" :
l.status === "sold" ? "var(--muted)" :
l.status === "optioned" ? "var(--ink2)" :
"var(--accent)";
return (
l.status !== "sold" && inFilter && onSelect(l)}
style={{
position: "relative",
aspectRatio: "4/3",
background: color,
border: 0, padding: 20, cursor: inFilter && l.status !== "sold" ? "pointer" : "default",
opacity: !inFilter ? 0.3 : 1,
color: !inFilter ? "var(--ink2)" : l.status === "available" ? "#1F2A33" : "var(--bg)",
textAlign: "left", display: "flex", flexDirection: "column", justifyContent: "space-between",
transition: "transform .15s ease"
}}
onMouseEnter={(e) => {if (inFilter && l.status !== "sold") e.currentTarget.style.transform = "scale(1.02)";}}
onMouseLeave={(e) => {e.currentTarget.style.transform = "scale(1)";}}>
{l.id}
Type {l.type} · {l.surface} m²
{l.orientation}
{fmtPrice(l.price)}
);
})}
)}
);
}
function LegendItem({ color, label }) {
return (
{label}
);
}
function LotModal({ lot, onClose }) {
useEffect(() => {
const onEsc = (e) => e.key === "Escape" && onClose();
document.addEventListener("keydown", onEsc);
document.body.style.overflow = "hidden";
return () => {document.removeEventListener("keydown", onEsc);document.body.style.overflow = "";};
}, [onClose]);
return (
e.stopPropagation()} style={{
background: "var(--bg)", maxWidth: 1080, width: "100%", maxHeight: "90vh",
overflow: "auto", display: "grid", gridTemplateColumns: "1.2fr 1fr",
boxShadow: "0 40px 80px -20px rgba(0,0,0,.4)"
}}>
Lot {lot.id} · R+{lot.floor} · Type {lot.type}
T2 · {lot.surface} m²
« {lot.exposure} »
×
Prix TTC
{fmtPrice(lot.price)}
{fmtN(lot.price / lot.surface)} €/m² · compromis chez le notaire · frais notariés réduits
Loyer estimé HT
{lot.loyer} € /mois
Rendement brut
{lot.renta.toFixed(2).replace(".", ",")} %
Mettre en option (24 h) →
Télécharger la fiche PDF
);
}
function SpecBox({ label, value }) {
return (
);
}
// ──────────────────────────────────────────────────────────────────────────
// INVESTIR — simulator
// ──────────────────────────────────────────────────────────────────────────
function InvestirPage() {
const [price, setPrice] = useState(190000);
const [apport, setApport] = useState(30000);
const [duree, setDuree] = useState(20);
const [taux, setTaux] = useState(3.6);
const [loyer, setLoyer] = useState(640);
const emprunt = Math.max(0, price - apport);
const mensualite = useMemo(() => {
const r = taux / 100 / 12;
const n = duree * 12;
if (r === 0) return emprunt / n;
return emprunt * r / (1 - Math.pow(1 + r, -n));
}, [emprunt, taux, duree]);
const rentaBrute = loyer * 12 / price * 100;
const cashflow = loyer - mensualite - 80; // rough charges
return (
Un T2 à Orange,c'est mathématique .>}
intro="Sur la grille NOVA, le rendement brut s'échelonne de 3,96 % (A24) à 4,11 % (A12) selon le lot, pour des loyers estimés de 580 à 660 € HT mensuels. Voici, en deux minutes, ce que peut donner un placement dans la résidence." />
{/* Inputs */}
Vos hypothèses
v + " €"} />
{/* Outputs */}
Le résultat
= 0 ? "+" : "") + fmtN(cashflow) + " €"}
label="Cashflow mensuel estimé"
tone={cashflow >= 0 ? "positive" : "negative"} />
Note : simulation indicative. Charges, fiscalité et amortissement (LMNP) à valider avec votre conseiller. Nous vous mettons en relation avec notre partenaire courtier sur simple demande.
Recevoir mon étude personnalisée →
);
}
function SimInput({ label, value, setValue, min, max, step, unit, fmt }) {
return (
{label}
{fmt ? fmt(value) : `${value} ${unit || ""}`}
setValue(Number(e.target.value))}
style={{ width: "100%", marginTop: 8, accentColor: "var(--accent)" }} />
{fmt ? fmt(min) : `${min} ${unit || ""}`}
{fmt ? fmt(max) : `${max} ${unit || ""}`}
);
}
function ResultBlock({ value, label, big, tone }) {
const color = tone === "positive" ? "var(--accent)" : tone === "negative" ? "var(--accent-dark)" : "var(--ink)";
return (
);
}
function DispositifCard({ tag, title, body, kpi, kpiLabel }) {
return (
);
}
// ──────────────────────────────────────────────────────────────────────────
// CONTACT
// ──────────────────────────────────────────────────────────────────────────
function ContactPage() {
const [form, setForm] = useState({ nom: "", email: "", tel: "", projet: "habiter", message: "", consent: false });
const [submitted, setSubmitted] = useState(false);
const submit = (e) => {
e.preventDefault();
if (!form.nom || !form.email || !form.consent) return;
setSubmitted(true);
};
return (
Une question, une visite,une plaquette ?>}
intro="Réponse sous 24 h ouvrées. Anne Magnoni, conseillère Myamo Développement, vous accompagne de la première question à la signature chez le notaire." />
{/* Form */}
{submitted ?
◇
Merci, {form.nom.split(" ")[0]} .
Votre demande est bien reçue. Anne vous rappelle sous 24 h ouvrées au numéro indiqué, et vous a envoyé la plaquette commerciale à l'adresse {form.email} .
{setSubmitted(false);setForm({ nom: "", email: "", tel: "", projet: "habiter", message: "", consent: false });}}>
Faire une nouvelle demande
:
}
{/* Sidebar */}
Votre interlocutrice
Anne Magnoni
Conseillère · Myamo Développement
Votre interlocutrice unique sur le programme NOVA — visite, dossier financier, signature notaire.
Bureaux Myamo
4-6 Place de la République 13 200 Arles
Visite sur rendez-vous — orange.myt2.fr
);
}
function FormRow({ children }) {
return {children}
;
}
function FormField({ label, value, onChange, type = "text", placeholder, required }) {
const Tag = type === "textarea" ? "textarea" : "input";
return (
{label}
onChange(e.target.value)}
placeholder={placeholder}
required={required}
rows={type === "textarea" ? 5 : undefined}
style={{
fontFamily: "var(--font-b)", fontSize: 15, color: "var(--ink)",
background: "var(--surface)", border: "1px solid var(--line)",
padding: "14px 16px", borderRadius: 0, outline: "none",
resize: type === "textarea" ? "vertical" : undefined
}}
onFocus={(e) => e.target.style.borderColor = "var(--accent)"}
onBlur={(e) => e.target.style.borderColor = "var(--line)"} />
);
}
function FormSelect({ label, value, onChange, options }) {
return (
{label}
onChange(e.target.value)}
style={{
fontFamily: "var(--font-b)", fontSize: 15, color: "var(--ink)",
background: "var(--surface)", border: "1px solid var(--line)",
padding: "14px 16px", borderRadius: 0, cursor: "pointer"
}}>
{options.map(([v, l]) => {l} )}
);
}
// expose
Object.assign(window, {
AppartementsPage, InvestirPage, ContactPage
});