gendesign/frontend/src/components/analytics/ObjectSpecsTable.tsx
lekss361 f8c382f885
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-backend (push) Successful in 1m35s
Deploy / build-worker (push) Successful in 2m37s
Deploy / build-frontend (push) Successful in 3m2s
Deploy / deploy (push) Successful in 56s
feat(22k): object page — full exposure 9 blocks (sales/quartirography/metro/photos/docs/specs/checks) (#310)
2026-05-17 15:56:39 +00:00

204 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
);
}