// Vrai-Prix — méthode du coût : 01 Propriété (données du rôle + provenance) et 02 Caractéristiques (BuildingSpec complet). "use client"; import Link from "next/link"; import type { AttributeSource, BuildingSpec, CostInput, CostLocation, Quality } from "@/lib/cost/types"; import { BUILDING_TYPES, QUALITIES } from "@/lib/cost/taxonomy"; import { money, num } from "@/components/avendre/fmt"; import { AttrBadge } from "./Provenance"; import { Block, Field, MixField, NumField, SelectField, Toggle } from "./fields"; export interface PrefillMeta { unit: { id: string; adresse: string | null; municipalite: string | null; typeProp: string; genre: string | null; lien: string | null; cubf: number | null; cubfLibelle: string | null; nbLogements: number | null; aireM2: number | null; terrainM2: number | null } | null; missing: string[]; location: CostLocation | null; locationMethod: string | null; } const Q = (fr: boolean) => QUALITIES.map((q) => ({ key: q.key, label: fr ? q.fr : q.en })); export function PropertyPanel({ input, meta, fr, locations, patch, setSrc }: { input: CostInput; meta: PrefillMeta | null; fr: boolean; locations: CostLocation[]; patch: (p: Partial) => void; setSrc: (k: string, s: AttributeSource) => void }) { const lang = fr ? "fr" : "en"; const u = meta?.unit; const src = input.attributeSources; const isProp = input.mode === "property"; return ( {isProp ? (fr ? "Mode propriété" : "Property mode") : (fr ? "Mode construction" : "Construction mode")}}> {isProp && u ? ( <>

{input.address ?? u.adresse ?? "—"}

{u.municipalite ?? "—"}{input.lat != null && input.lng != null ? ` · ${input.lat.toFixed(5)}, ${input.lng.toFixed(5)}` : ""}

{fr ? "Rapport de valeur Vrai-Prix" : "Vrai-Prix value report"} → {fr ? "Changer d'adresse" : "Change address"}
{([ [fr ? "Genre de propriété" : "Property kind", u.typeProp, "MAMH"], [fr ? "Genre de construction" : "Construction kind", u.genre || "—", "MAMH"], [fr ? "Lien physique" : "Physical link", u.lien || "—", "MAMH"], ["CUBF", u.cubf != null ? `${u.cubf} — ${u.cubfLibelle ?? ""}` : "—", "MAMH"], [fr ? "Année de construction" : "Year built", input.building.yearBuilt ?? "—", src.yearBuilt ?? "assumed"], [fr ? "Aire d'étages (rôle)" : "Floor area (roll)", u.aireM2 ? `${num(u.aireM2, lang)} m² · ${num(input.building.grossFloorAreaSqft, lang)} pi²` : "—", src.grossFloorAreaSqft ?? "assumed"], [fr ? "Étages" : "Storeys", input.building.stories, src.stories ?? "assumed"], [fr ? "Logements" : "Dwellings", u.nbLogements ?? 1, src.units ?? "assumed"], [fr ? "Superficie du terrain" : "Lot area", u.terrainM2 ? `${num(u.terrainM2, lang)} m²` : "—", "MAMH"], [fr ? "Valeur du terrain au rôle" : "Land value (roll)", money(input.roll?.landValue, lang), "MAMH"], [fr ? "Valeur du bâtiment au rôle" : "Building value (roll)", money(input.roll?.buildingValue, lang), "MAMH"], [fr ? "Valeur totale au rôle 2026" : "Total roll value 2026", money(input.roll?.totalValue, lang), "MAMH"], ] as [string, string | number, AttributeSource][]).map(([k, v, s]) => (

{k}

{v}

))}
{meta?.missing?.length ? (

{fr ? `${meta.missing.length} caractéristiques ne sont pas au rôle (sous-sol, garage, revêtements, toiture, chauffage, salles de bain, qualité…) : elles sont supposées par défaut — complétez-les à la section 02 pour améliorer l'indice de confiance.` : `${meta.missing.length} attributes are not on the roll (basement, garage, cladding, roof, heating, bathrooms, quality…): they are default assumptions — complete them in section 02 to improve the confidence index.`}

) : null} ) : (
{ patch({ municipality: e.target.value || null }); setSrc("municipality", "user"); }} placeholder="Gatineau, Québec, Montréal…" className="vp-input text-[14px]" />

{fr ? "Bâtiment hypothétique : aucune donnée du rôle. Décrivez-le à la section 02 et saisissez une valeur de terrain à la section 07." : "Hypothetical building: no roll data. Describe it in section 02 and enter a land value in section 07."}

)} {meta?.location && (

{fr ? "Localisation" : "Location"} : {fr ? meta.location.nameFr : meta.location.nameEn} · M × {num(meta.location.materialFactor, lang, 2)} · MO × {num(meta.location.labourFactor, lang, 2)} · É × {num(meta.location.equipmentFactor, lang, 2)} · {meta.locationMethod}

)}
); } export function BuildingFormPanel({ b, src, fr, patchB, setSrc }: { b: BuildingSpec; src: Partial>; fr: boolean; patchB: (p: Partial) => void; setSrc: (k: string, s: AttributeSource) => void }) { const set = (k: K, v: BuildingSpec[K]) => { patchB({ [k]: v } as Partial); setSrc(String(k), "user"); }; const qOpts = Q(fr); return ( {fr ? "chaque champ porte son origine" : "each field carries its origin"}}>
set("type", v)} options={BUILDING_TYPES.map((t) => ({ key: t.key, label: fr ? t.fr : t.en }))} /> { set("quality", v); }} options={qOpts} /> set("units", v ?? 1)} /> set("grossFloorAreaSqft", v ?? 1800)} /> set("footprintSqft", v)} /> set("stories", v ?? 1)} /> set("yearBuilt", v)} /> set("basement", v)} options={[["none", "Aucun (dalle sur sol)", "None (slab on grade)"], ["crawl", "Vide sanitaire", "Crawl space"], ["unfinished", "Non fini", "Unfinished"], ["partial", "Partiellement fini", "Partially finished"], ["finished", "Fini", "Finished"], ["walkout", "Fini avec sortie extérieure", "Finished walkout"]].map(([k, f, e]) => ({ key: k as BuildingSpec["basement"], label: fr ? f : e }))} /> set("basementFinishedPct", Math.min(1, Math.max(0, (v ?? 0) / 100)))} /> set("garage", { ...b.garage, type: v, spaces: v === "none" ? 0 : Math.max(1, b.garage.spaces) })} options={[["none", "Aucun", "None"], ["attached", "Attenant", "Attached"], ["detached", "Détaché", "Detached"], ["integrated", "Intégré", "Integrated"], ["carport", "Abri d'auto", "Carport"]].map(([k, f, e]) => ({ key: k as BuildingSpec["garage"]["type"], label: fr ? f : e }))} /> set("garage", { ...b.garage, spaces: v ?? 0 })} /> set("garage", { ...b.garage, areaSqft: v })} /> set("structure", v)} options={[["wood_frame", "Ossature de bois", "Wood frame"], ["steel", "Acier", "Steel"], ["concrete", "Béton", "Concrete"], ["log", "Bois rond", "Log"], ["masonry", "Maçonnerie porteuse", "Load-bearing masonry"]].map(([k, f, e]) => ({ key: k as BuildingSpec["structure"], label: fr ? f : e }))} /> set("foundation", v)} options={[["poured_concrete", "Béton coulé", "Poured concrete"], ["concrete_block", "Blocs de béton", "Concrete block"], ["slab_on_grade", "Dalle sur sol", "Slab on grade"], ["piers", "Pieux / pilotis", "Piers"], ["stone", "Pierre", "Stone"]].map(([k, f, e]) => ({ key: k as BuildingSpec["foundation"], label: fr ? f : e }))} /> set("roof", v)} options={[["asphalt_shingle", "Bardeau d'asphalte", "Asphalt shingle"], ["metal", "Tôle d'acier", "Steel"], ["membrane", "Membrane (toit plat)", "Membrane (flat roof)"], ["cedar", "Bardeau de cèdre", "Cedar shake"], ["slate_tile", "Ardoise / tuile", "Slate / tile"]].map(([k, f, e]) => ({ key: k as BuildingSpec["roof"], label: fr ? f : e }))} /> set("roofGeometry", v)} options={[["gable", "À deux versants", "Gable"], ["hip", "À quatre versants", "Hip"], ["flat", "Plat", "Flat"], ["mansard", "Mansardé", "Mansard"], ["complex", "Complexe", "Complex"]].map(([k, f, e]) => ({ key: k as BuildingSpec["roofGeometry"], label: fr ? f : e }))} /> set("roofPitch", v ?? 6)} /> set("windows", v)} options={[["pvc", "PVC", "PVC"], ["hybrid", "Hybride PVC/aluminium", "Hybrid PVC/aluminum"], ["aluminum", "Aluminium", "Aluminum"], ["wood", "Bois", "Wood"]].map(([k, f, e]) => ({ key: k as BuildingSpec["windows"], label: fr ? f : e }))} /> set("windowCount", v)} /> set("heating", v)} options={[["electric_baseboard", "Plinthes électriques", "Electric baseboards"], ["heat_pump", "Thermopompe centrale", "Central heat pump"], ["furnace_electric", "Fournaise électrique", "Electric furnace"], ["furnace_gas", "Fournaise au gaz", "Gas furnace"], ["furnace_oil", "Fournaise au mazout", "Oil furnace"], ["hydronic", "Eau chaude (hydronique)", "Hydronic"], ["geothermal", "Géothermie", "Geothermal"], ["wood", "Bois", "Wood"]].map(([k, f, e]) => ({ key: k as BuildingSpec["heating"], label: fr ? f : e }))} />
set("hasAirConditioning", v)} label={fr ? "Climatisation" : "Air conditioning"} /> set("hasAirExchanger", v)} label={fr ? "Échangeur d'air (VRC)" : "Air exchanger (HRV)"} />
set("kitchens", v ?? 1)} /> set("kitchenQuality", v as Quality)} options={qOpts} /> set("bathrooms", v ?? 0)} /> set("powderRooms", v ?? 0)} /> set("bathroomQuality", v as Quality)} options={qOpts} /> set("bedrooms", v)} />
set("siding", v)} options={[{ key: "vinyl", fr: "Vinyle", en: "Vinyl" }, { key: "brick", fr: "Brique", en: "Brick" }, { key: "fiber_cement", fr: "Fibrociment", en: "Fiber cement" }, { key: "wood", fr: "Bois", en: "Wood" }, { key: "stone", fr: "Pierre", en: "Stone" }, { key: "stucco", fr: "Stuc / acrylique", en: "Stucco" }, { key: "aluminum", fr: "Aluminium", en: "Aluminum" }, { key: "steel", fr: "Acier", en: "Steel" }] as { key: keyof BuildingSpec["siding"]; fr: string; en: string }[]} /> set("flooring", v)} options={[{ key: "hardwood", fr: "Bois franc", en: "Hardwood" }, { key: "engineered", fr: "Ingénierie", en: "Engineered" }, { key: "vinyl_plank", fr: "Vinyle SPC", en: "Vinyl plank" }, { key: "laminate", fr: "Stratifié", en: "Laminate" }, { key: "ceramic", fr: "Céramique", en: "Ceramic" }, { key: "carpet", fr: "Tapis", en: "Carpet" }] as { key: keyof BuildingSpec["flooring"]; fr: string; en: string }[]} />
set("deckSqft", v ?? 0)} /> set("driveway", v)} options={[["none", "Aucune", "None"], ["asphalt", "Asphalte", "Asphalt"], ["pavers", "Pavés", "Pavers"], ["gravel", "Gravier", "Gravel"], ["concrete", "Béton", "Concrete"]].map(([k, f, e]) => ({ key: k as BuildingSpec["driveway"], label: fr ? f : e }))} /> set("drivewaySqft", v ?? 0)} /> set("fenceLinFt", v ?? 0)} /> set("landscapingSqft", v ?? 0)} /> set("pool", v)} options={[["none", "Aucune", "None"], ["above_ground", "Hors terre", "Above ground"], ["inground", "Creusée", "Inground"]].map(([k, f, e]) => ({ key: k as BuildingSpec["pool"], label: fr ? f : e }))} />

{fr ? "Coût de REMPLACEMENT : quelle que soit l'année, le bâtiment est chiffré avec les composantes modernes équivalentes (2 × 6 R24, R50 au comble, poutrelles ajourées)." : "REPLACEMENT cost: whatever the year, the building is priced with modern equivalent components (2 × 6 R24, R50 attic, I-joists)."}

); }