| 5 |
5 |
// ----------------------------------------------------------------------------- |
| 6 |
6 |
import { useEffect, useState } from "react"; |
| 7 |
7 |
import { Link, useParams } from "react-router-dom"; |
| 8 |
|
−import { Listing, fetchListing, fetchSources, fmtPrice, registerSourceNames, sourceName } from "../api"; |
|
8 |
+import { Listing, fetchListing, fetchSources, fmtAvailability, fmtPrice, registerSourceNames, sourceName } from "../api"; |
|
9 |
+ |
|
10 |
+const PETS_LABEL: Record<string, string> = { |
|
11 |
+ oui: "Acceptés", non: "Refusés", conditions: "Sous conditions", |
|
12 |
+}; |
|
13 |
+ |
|
14 |
+/** Badges dérivés des détails structurés (inclusions, immeuble, stationnement). */ |
|
15 |
+function detailBadges(l: Listing): string[] { |
|
16 |
+ const d = l.details ?? {}; |
|
17 |
+ const out: string[] = []; |
|
18 |
+ const inc = d.inclusions ?? {}; |
|
19 |
+ if (inc.heating) out.push("Chauffage inclus"); |
|
20 |
+ if (inc.electricity) out.push("Électricité incluse"); |
|
21 |
+ if (inc.hot_water) out.push("Eau chaude incluse"); |
|
22 |
+ if (inc.internet) out.push("Internet inclus"); |
|
23 |
+ const app = d.appliances ?? {}; |
|
24 |
+ if (app.dishwasher) out.push("Lave-vaisselle"); |
|
25 |
+ if (app.washer_dryer) out.push("Laveuse-sécheuse"); |
|
26 |
+ if (app.fridge && app.stove) out.push("Électroménagers"); |
|
27 |
+ if (d.ac) out.push("Climatisation"); |
|
28 |
+ if (d.elevator) out.push("Ascenseur"); |
|
29 |
+ if (d.balcony) out.push("Balcon"); |
|
30 |
+ if (d.pool) out.push("Piscine"); |
|
31 |
+ if (d.gym) out.push("Gym"); |
|
32 |
+ if (d.laundry) out.push("Buanderie"); |
|
33 |
+ if (d.storage) out.push("Rangement"); |
|
34 |
+ if (d.parking?.available) |
|
35 |
+ out.push(`Stationnement${d.parking.type ? ` ${d.parking.type}` : ""}${d.parking.included ? " inclus" : ""}`); |
|
36 |
+ if (l.furnished) out.push("Meublé"); |
|
37 |
+ if (d.smoking === false) out.push("Non-fumeur"); |
|
38 |
+ return out; |
|
39 |
+} |
| 9 |
40 |
|
| 10 |
41 |
export default function ListingPage() { |
| 11 |
42 |
const { uid } = useParams<{ uid: string }>(); |
| 45 |
76 |
|
| 46 |
77 |
const imgs = l.images ?? []; |
| 47 |
78 |
const main = imgs[imgIdx]; |
|
79 |
+ const badges = detailBadges(l); |
| 48 |
80 |
const updated = l.updated_at |
| 49 |
81 |
? new Date(l.updated_at * 1000).toLocaleDateString("fr-CA", { |
| 50 |
82 |
day: "numeric", month: "long", year: "numeric", |
| 104 |
136 |
{l.unit_type && ( |
| 105 |
137 |
<div className="cell"><div className="k">Taille</div><div className="v">{l.unit_type}</div></div> |
| 106 |
138 |
)} |
| 107 |
|
− {l.availability && ( |
| 108 |
|
− <div className="cell"><div className="k">Disponibilité</div><div className="v">{l.availability}</div></div> |
|
139 |
+ {(fmtAvailability(l.availability_date) || l.availability) && ( |
|
140 |
+ <div className="cell"> |
|
141 |
+ <div className="k">Disponibilité</div> |
|
142 |
+ <div className="v">{fmtAvailability(l.availability_date) ?? l.availability}</div> |
|
143 |
+ </div> |
|
144 |
+ )} |
|
145 |
+ {l.area_sqft != null && ( |
|
146 |
+ <div className="cell"> |
|
147 |
+ <div className="k">Superficie</div> |
|
148 |
+ <div className="v">{Math.round(l.area_sqft).toLocaleString("fr-CA")} pi²</div> |
|
149 |
+ </div> |
|
150 |
+ )} |
|
151 |
+ {l.pets && ( |
|
152 |
+ <div className="cell"><div className="k">Animaux</div><div className="v">{PETS_LABEL[l.pets] ?? l.pets}</div></div> |
|
153 |
+ )} |
|
154 |
+ {l.details?.floor != null && ( |
|
155 |
+ <div className="cell"><div className="k">Étage</div><div className="v">{l.details.floor}ᵉ</div></div> |
| 109 |
156 |
)} |
| 110 |
157 |
<div className="cell"><div className="k">Gestionnaire</div><div className="v">{sourceName(l.source)}</div></div> |
| 111 |
158 |
{l.price_label && ( |
| 113 |
160 |
)} |
| 114 |
161 |
</div> |
| 115 |
162 |
|
| 116 |
|
− {l.amenities.length > 0 && ( |
|
163 |
+ {(badges.length > 0 || l.amenities.length > 0) && ( |
| 117 |
164 |
<> |
| 118 |
165 |
<div className="k" style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: "0.07em", color: "var(--ink-3)", fontWeight: 700, marginBottom: 8 }}> |
| 119 |
166 |
Inclusions et commodités |
| 120 |
167 |
</div> |
| 121 |
168 |
<div className="amenity-row"> |
| 122 |
|
− {l.amenities.map((a) => ( |
| 123 |
|
− <span className="amenity" key={a}>{a}</span> |
|
169 |
+ {badges.map((b) => ( |
|
170 |
+ <span className="amenity" key={`d-${b}`}>✓ {b}</span> |
| 124 |
171 |
))} |
|
172 |
+ {l.amenities |
|
173 |
+ .filter((a) => !badges.some((b) => b.toLowerCase().includes(a.toLowerCase()))) |
|
174 |
+ .map((a) => ( |
|
175 |
+ <span className="amenity" key={a}>{a}</span> |
|
176 |
+ ))} |
| 125 |
177 |
</div> |
| 126 |
178 |
</> |
| 127 |
179 |
)} |
| 128 |
180 |
|