204 lines
5.8 KiB
TypeScript
204 lines
5.8 KiB
TypeScript
import type { ObjectFullDetail } from "@/types/analytics";
|
||
|
||
interface SpecRow {
|
||
label: string;
|
||
value: string | null;
|
||
}
|
||
|
||
function yn(v: boolean | null | undefined): string | null {
|
||
if (v === null || v === undefined) return null;
|
||
return v ? "Да" : "Нет";
|
||
}
|
||
|
||
function num(v: number | null | undefined, suffix = ""): string | null {
|
||
if (v === null || v === undefined) return null;
|
||
return `${v.toLocaleString("ru-RU")}${suffix}`;
|
||
}
|
||
|
||
function numFmt(
|
||
v: number | null | undefined,
|
||
decimals = 0,
|
||
suffix = "",
|
||
): string | null {
|
||
if (v === null || v === undefined) return null;
|
||
return (
|
||
v.toLocaleString("ru-RU", {
|
||
minimumFractionDigits: decimals,
|
||
maximumFractionDigits: decimals,
|
||
}) + suffix
|
||
);
|
||
}
|
||
|
||
function price(v: number | null | undefined): string | null {
|
||
if (v === null || v === undefined) return null;
|
||
return (
|
||
(v / 1_000_000).toLocaleString("ru-RU", {
|
||
minimumFractionDigits: 1,
|
||
maximumFractionDigits: 1,
|
||
}) + " млн ₽"
|
||
);
|
||
}
|
||
|
||
function priceM2(v: number | null | undefined): string | null {
|
||
if (v === null || v === undefined) return null;
|
||
return v.toLocaleString("ru-RU", { maximumFractionDigits: 0 }) + " ₽/м²";
|
||
}
|
||
|
||
export function ObjectSpecsTable({ d }: { d: ObjectFullDetail }) {
|
||
const specs: SpecRow[] = [
|
||
// Identity
|
||
{ label: "Класс объекта", value: d.obj_class },
|
||
{ label: "Класс энергоэффективности", value: d.energy_eff },
|
||
{ label: "Материал стен", value: d.wall_type },
|
||
{ label: "Первый этаж", value: d.first_floor_type },
|
||
// Building
|
||
{ label: "Подъездов", value: num(d.section_count) },
|
||
{ label: "Этажей (max)", value: num(d.floor_max) },
|
||
{ label: "Высота потолков", value: numFmt(d.ceiling_height_m, 2, " м") },
|
||
{
|
||
label: "Лифты пассажирские",
|
||
value:
|
||
d.elevators_passenger_count === 0
|
||
? "Нет"
|
||
: num(d.elevators_passenger_count),
|
||
},
|
||
{
|
||
label: "Лифты грузовые",
|
||
value:
|
||
d.elevators_cargo_count === 0 ? "Нет" : num(d.elevators_cargo_count),
|
||
},
|
||
// Parking
|
||
{
|
||
label: "Машино-мест (парковка)",
|
||
value: num(d.parking_total_slots),
|
||
},
|
||
{
|
||
label: "Гостевые (приказ.)",
|
||
value: num(d.guest_parking_inside_count),
|
||
},
|
||
{
|
||
label: "Гостевые (улица)",
|
||
value: num(d.guest_parking_outside_count),
|
||
},
|
||
{
|
||
label: "Обеспеченность парковкой",
|
||
value: numFmt(d.parking_provision_pct, 1, "%"),
|
||
},
|
||
// Apartments
|
||
{ label: "Квартир всего", value: num(d.flat_count) },
|
||
{
|
||
label: "Площадь квартир",
|
||
value:
|
||
d.flat_area_min != null && d.flat_area_max != null
|
||
? `${numFmt(d.flat_area_min, 1)} – ${numFmt(d.flat_area_max, 1)} м²`
|
||
: null,
|
||
},
|
||
{
|
||
label: "Средняя площадь квартиры",
|
||
value: numFmt(d.avg_flat_area_m2, 1, " м²"),
|
||
},
|
||
{
|
||
label: "Вариантов отделки",
|
||
value: num(d.finishing_variants_count),
|
||
},
|
||
{ label: "Свободная планировка", value: yn(d.has_free_planning) },
|
||
// Price
|
||
{
|
||
label: "Цена (от)",
|
||
value: price(d.price_min_rub),
|
||
},
|
||
{
|
||
label: "Цена (до)",
|
||
value: price(d.price_max_rub),
|
||
},
|
||
{
|
||
label: "Цена/м² (от)",
|
||
value: priceM2(d.price_per_m2_min),
|
||
},
|
||
{
|
||
label: "Цена/м² (до)",
|
||
value: priceM2(d.price_per_m2_max),
|
||
},
|
||
// Yard
|
||
{
|
||
label: "Детских площадок",
|
||
value: num(d.playground_kids_count),
|
||
},
|
||
{
|
||
label: "Спортивных площадок",
|
||
value: num(d.playground_sport_count),
|
||
},
|
||
{ label: "Велодорожки", value: yn(d.has_bike_paths) },
|
||
{
|
||
label: "Мусорные площадки",
|
||
value: num(d.trash_areas_count),
|
||
},
|
||
// OVZ
|
||
{ label: "Пандус", value: yn(d.has_ramp) },
|
||
{ label: "Понижающие платформы (ОВЗ)", value: yn(d.has_low_platforms) },
|
||
{
|
||
label: "Инвалидный подъёмник",
|
||
value: yn(d.has_wheelchair_lift),
|
||
},
|
||
// Developer
|
||
{ label: "Девелопер", value: d.dev_name },
|
||
{ label: "Группа компаний", value: d.dev_group_name },
|
||
{ label: "Эскроу", value: yn(d.escrow) },
|
||
{
|
||
label: "Декларация №",
|
||
value: d.project_declaration_num,
|
||
},
|
||
{
|
||
label: "Дата публикации проекта",
|
||
value: d.project_published_at?.slice(0, 10) ?? null,
|
||
},
|
||
];
|
||
|
||
const filtered = specs.filter((s) => s.value !== null);
|
||
if (filtered.length === 0) {
|
||
return (
|
||
<p style={{ color: "#9ca3af", fontSize: 13 }}>
|
||
Характеристики ещё не загружены.
|
||
</p>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div style={{ overflowX: "auto" }}>
|
||
<table
|
||
style={{
|
||
width: "100%",
|
||
borderCollapse: "collapse",
|
||
fontSize: 13,
|
||
}}
|
||
>
|
||
<tbody>
|
||
{filtered.map((s) => (
|
||
<tr key={s.label} style={{ borderTop: "1px solid #f3f4f6" }}>
|
||
<td
|
||
style={{
|
||
padding: "7px 12px 7px 0",
|
||
color: "#5b6066",
|
||
whiteSpace: "nowrap",
|
||
verticalAlign: "top",
|
||
width: "50%",
|
||
}}
|
||
>
|
||
{s.label}
|
||
</td>
|
||
<td
|
||
style={{
|
||
padding: "7px 0",
|
||
fontWeight: 500,
|
||
fontVariantNumeric: "tabular-nums",
|
||
}}
|
||
>
|
||
{s.value}
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
);
|
||
}
|