refactor(sf-19): BestLayouts inline hex → Tailwind tokens (#296)
This commit is contained in:
parent
4b967343f8
commit
a86cce79af
1 changed files with 130 additions and 77 deletions
|
|
@ -14,13 +14,10 @@ import type {
|
||||||
|
|
||||||
// ── Constants ─────────────────────────────────────────────────────────────────
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const CONFIDENCE_STYLES: Record<
|
const CONFIDENCE_STYLES: Record<Confidence, { cls: string; label: string }> = {
|
||||||
Confidence,
|
high: { cls: "bg-emerald-100 text-emerald-800", label: "Высокое" },
|
||||||
{ bg: string; fg: string; label: string }
|
medium: { cls: "bg-amber-100 text-amber-800", label: "Среднее" },
|
||||||
> = {
|
low: { cls: "bg-rose-100 text-rose-800", label: "Низкое" },
|
||||||
high: { bg: "#dcfce7", fg: "#166534", label: "Высокое" },
|
|
||||||
medium: { bg: "#fef3c7", fg: "#854d0e", label: "Среднее" },
|
|
||||||
low: { bg: "#fee2e2", fg: "#991b1b", label: "Низкое" },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const TIME_WINDOW_LABELS: Record<TimeWindow, string> = {
|
const TIME_WINDOW_LABELS: Record<TimeWindow, string> = {
|
||||||
|
|
@ -40,17 +37,16 @@ const ROOM_BUCKET_LABELS: Record<string, string> = {
|
||||||
// ── Sub-components ────────────────────────────────────────────────────────────
|
// ── Sub-components ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function DataQualityCard({ dq }: { dq: BestLayoutsResponse["data_quality"] }) {
|
function DataQualityCard({ dq }: { dq: BestLayoutsResponse["data_quality"] }) {
|
||||||
const style = CONFIDENCE_STYLES[dq.confidence];
|
const conf = CONFIDENCE_STYLES[dq.confidence];
|
||||||
return (
|
return (
|
||||||
<div className="border border-gray-200 rounded-xl px-[18px] py-[14px] bg-white flex items-center gap-4 flex-wrap">
|
<div className="border border-gray-200 rounded-xl px-[18px] py-[14px] bg-white flex items-center gap-4 flex-wrap">
|
||||||
<span className="font-semibold text-[13px] text-gray-700 mr-1">
|
<span className="font-semibold text-[13px] text-gray-700 mr-1">
|
||||||
Качество данных:
|
Качество данных:
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
style={{ background: style.bg, color: style.fg }}
|
className={`px-[10px] py-[2px] rounded-md text-xs font-semibold ${conf.cls}`}
|
||||||
className="px-[10px] py-[2px] rounded-md text-xs font-semibold"
|
|
||||||
>
|
>
|
||||||
{style.label}
|
{conf.label}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-gray-500">
|
<span className="text-xs text-gray-500">
|
||||||
Покрытие {dq.velocity_coverage_pct.toFixed(0)}% (
|
Покрытие {dq.velocity_coverage_pct.toFixed(0)}% (
|
||||||
|
|
@ -68,7 +64,7 @@ function salesPeriodMonths(supply: number, velocity: number): number | null {
|
||||||
}
|
}
|
||||||
|
|
||||||
function SalesPeriodCell({ months }: { months: number | null }) {
|
function SalesPeriodCell({ months }: { months: number | null }) {
|
||||||
if (months === null) return <span style={{ color: "#9ca3af" }}>—</span>;
|
if (months === null) return <span className="text-gray-400">—</span>;
|
||||||
if (months < 12)
|
if (months < 12)
|
||||||
return (
|
return (
|
||||||
<span className="text-emerald-600 font-semibold tabular-nums">
|
<span className="text-emerald-600 font-semibold tabular-nums">
|
||||||
|
|
@ -92,7 +88,7 @@ function CompetitorDrillIn({
|
||||||
const hidden = ids.length - COMPETITOR_PREVIEW_LIMIT;
|
const hidden = ids.length - COMPETITOR_PREVIEW_LIMIT;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr style={{ background: "#f0f4ff" }}>
|
<tr className="bg-blue-50">
|
||||||
<td colSpan={colSpan} style={{ padding: "8px 16px" }}>
|
<td colSpan={colSpan} style={{ padding: "8px 16px" }}>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -103,7 +99,10 @@ function CompetitorDrillIn({
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ color: "#6b7280", fontWeight: 600, marginRight: 4 }}>
|
<span
|
||||||
|
className="text-gray-500 font-semibold"
|
||||||
|
style={{ marginRight: 4 }}
|
||||||
|
>
|
||||||
ЖК-источники:
|
ЖК-источники:
|
||||||
</span>
|
</span>
|
||||||
{visible.map((id) => (
|
{visible.map((id) => (
|
||||||
|
|
@ -186,7 +185,7 @@ function TopLayoutsTable({ rows }: { rows: TopLayoutRow[] }) {
|
||||||
style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}
|
style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}
|
||||||
>
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ background: "#f6f7f9" }}>
|
<tr className="bg-gray-50">
|
||||||
{headers.map((h, idx) => (
|
{headers.map((h, idx) => (
|
||||||
<th
|
<th
|
||||||
key={idx}
|
key={idx}
|
||||||
|
|
@ -225,8 +224,11 @@ function TopLayoutsTable({ rows }: { rows: TopLayoutRow[] }) {
|
||||||
role={hasCompetitors ? "button" : undefined}
|
role={hasCompetitors ? "button" : undefined}
|
||||||
aria-expanded={hasCompetitors ? isExpanded : undefined}
|
aria-expanded={hasCompetitors ? isExpanded : undefined}
|
||||||
style={{
|
style={{
|
||||||
background: i % 2 === 0 ? "#fff" : "#fafbfc",
|
background:
|
||||||
borderBottom: isExpanded ? "none" : "1px solid #f3f4f6",
|
i % 2 === 0 ? "var(--bg-card)" : "var(--bg-card-alt)",
|
||||||
|
borderBottom: isExpanded
|
||||||
|
? "none"
|
||||||
|
: "1px solid var(--border-soft)",
|
||||||
cursor: hasCompetitors ? "pointer" : "default",
|
cursor: hasCompetitors ? "pointer" : "default",
|
||||||
}}
|
}}
|
||||||
title={
|
title={
|
||||||
|
|
@ -239,7 +241,7 @@ function TopLayoutsTable({ rows }: { rows: TopLayoutRow[] }) {
|
||||||
style={{
|
style={{
|
||||||
padding: "7px 12px",
|
padding: "7px 12px",
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: "#1d4ed8",
|
color: "var(--accent)",
|
||||||
fontVariantNumeric: "tabular-nums",
|
fontVariantNumeric: "tabular-nums",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -249,18 +251,23 @@ function TopLayoutsTable({ rows }: { rows: TopLayoutRow[] }) {
|
||||||
style={{
|
style={{
|
||||||
padding: "7px 12px",
|
padding: "7px 12px",
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
color: "#111827",
|
color: "var(--fg-primary)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ROOM_BUCKET_LABELS[row.room_bucket] ?? row.room_bucket}
|
{ROOM_BUCKET_LABELS[row.room_bucket] ?? row.room_bucket}
|
||||||
</td>
|
</td>
|
||||||
<td style={{ padding: "7px 12px", color: "#374151" }}>
|
<td
|
||||||
|
style={{
|
||||||
|
padding: "7px 12px",
|
||||||
|
color: "var(--fg-secondary)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{row.area_bin} м²
|
{row.area_bin} м²
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
style={{
|
style={{
|
||||||
padding: "7px 12px",
|
padding: "7px 12px",
|
||||||
color: "#374151",
|
color: "var(--fg-secondary)",
|
||||||
fontVariantNumeric: "tabular-nums",
|
fontVariantNumeric: "tabular-nums",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -270,7 +277,7 @@ function TopLayoutsTable({ rows }: { rows: TopLayoutRow[] }) {
|
||||||
style={{
|
style={{
|
||||||
padding: "7px 12px",
|
padding: "7px 12px",
|
||||||
textAlign: "right",
|
textAlign: "right",
|
||||||
color: "#374151",
|
color: "var(--fg-secondary)",
|
||||||
fontVariantNumeric: "tabular-nums",
|
fontVariantNumeric: "tabular-nums",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -280,7 +287,7 @@ function TopLayoutsTable({ rows }: { rows: TopLayoutRow[] }) {
|
||||||
style={{
|
style={{
|
||||||
padding: "7px 12px",
|
padding: "7px 12px",
|
||||||
textAlign: "right",
|
textAlign: "right",
|
||||||
color: "#374151",
|
color: "var(--fg-secondary)",
|
||||||
fontVariantNumeric: "tabular-nums",
|
fontVariantNumeric: "tabular-nums",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -302,7 +309,7 @@ function TopLayoutsTable({ rows }: { rows: TopLayoutRow[] }) {
|
||||||
style={{
|
style={{
|
||||||
padding: "7px 12px",
|
padding: "7px 12px",
|
||||||
textAlign: "right",
|
textAlign: "right",
|
||||||
color: "#374151",
|
color: "var(--fg-secondary)",
|
||||||
fontVariantNumeric: "tabular-nums",
|
fontVariantNumeric: "tabular-nums",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -339,7 +346,7 @@ function TopLayoutsTable({ rows }: { rows: TopLayoutRow[] }) {
|
||||||
<td
|
<td
|
||||||
style={{
|
style={{
|
||||||
padding: "7px 10px",
|
padding: "7px 10px",
|
||||||
color: "#9ca3af",
|
color: "var(--fg-tertiary)",
|
||||||
width: 24,
|
width: 24,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -379,12 +386,12 @@ function TopLayoutsTable({ rows }: { rows: TopLayoutRow[] }) {
|
||||||
|
|
||||||
function UnitMixBar({ mix }: { mix: LayoutTzMixRow[] }) {
|
function UnitMixBar({ mix }: { mix: LayoutTzMixRow[] }) {
|
||||||
const COLORS = [
|
const COLORS = [
|
||||||
"#1d4ed8",
|
"var(--viz-1)",
|
||||||
"#7c3aed",
|
"var(--viz-5)",
|
||||||
"#059669",
|
"var(--viz-3)",
|
||||||
"#d97706",
|
"var(--viz-4)",
|
||||||
"#dc2626",
|
"var(--viz-2)",
|
||||||
"#0891b2",
|
"var(--viz-3)",
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -396,7 +403,7 @@ function UnitMixBar({ mix }: { mix: LayoutTzMixRow[] }) {
|
||||||
height: 24,
|
height: 24,
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
border: "1px solid #e5e7eb",
|
border: "1px solid var(--border-card)",
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -428,7 +435,7 @@ function UnitMixBar({ mix }: { mix: LayoutTzMixRow[] }) {
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span style={{ fontSize: 11, color: "#374151" }}>
|
<span style={{ fontSize: 11, color: "var(--fg-secondary)" }}>
|
||||||
{ROOM_BUCKET_LABELS[row.room_bucket] ?? row.room_bucket} {row.pct}
|
{ROOM_BUCKET_LABELS[row.room_bucket] ?? row.room_bucket} {row.pct}
|
||||||
%
|
%
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -443,7 +450,7 @@ function MixTable({ mix }: { mix: LayoutTzMixRow[] }) {
|
||||||
return (
|
return (
|
||||||
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}>
|
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ background: "#f6f7f9" }}>
|
<tr className="bg-gray-50">
|
||||||
{["Тип", "Доля, %", "Кол-во квартир", "Ср. площадь, м²"].map((h) => (
|
{["Тип", "Доля, %", "Кол-во квартир", "Ср. площадь, м²"].map((h) => (
|
||||||
<th
|
<th
|
||||||
key={h}
|
key={h}
|
||||||
|
|
@ -459,12 +466,16 @@ function MixTable({ mix }: { mix: LayoutTzMixRow[] }) {
|
||||||
<tr
|
<tr
|
||||||
key={row.room_bucket}
|
key={row.room_bucket}
|
||||||
style={{
|
style={{
|
||||||
background: i % 2 === 0 ? "#fff" : "#fafbfc",
|
background: i % 2 === 0 ? "var(--bg-card)" : "var(--bg-card-alt)",
|
||||||
borderBottom: "1px solid #f3f4f6",
|
borderBottom: "1px solid var(--border-soft)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<td
|
<td
|
||||||
style={{ padding: "7px 12px", fontWeight: 500, color: "#111827" }}
|
style={{
|
||||||
|
padding: "7px 12px",
|
||||||
|
fontWeight: 500,
|
||||||
|
color: "var(--fg-primary)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{ROOM_BUCKET_LABELS[row.room_bucket] ?? row.room_bucket}
|
{ROOM_BUCKET_LABELS[row.room_bucket] ?? row.room_bucket}
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -472,7 +483,7 @@ function MixTable({ mix }: { mix: LayoutTzMixRow[] }) {
|
||||||
style={{
|
style={{
|
||||||
padding: "7px 12px",
|
padding: "7px 12px",
|
||||||
fontVariantNumeric: "tabular-nums",
|
fontVariantNumeric: "tabular-nums",
|
||||||
color: "#374151",
|
color: "var(--fg-secondary)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{row.pct}%
|
{row.pct}%
|
||||||
|
|
@ -481,7 +492,7 @@ function MixTable({ mix }: { mix: LayoutTzMixRow[] }) {
|
||||||
style={{
|
style={{
|
||||||
padding: "7px 12px",
|
padding: "7px 12px",
|
||||||
fontVariantNumeric: "tabular-nums",
|
fontVariantNumeric: "tabular-nums",
|
||||||
color: "#374151",
|
color: "var(--fg-secondary)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{row.abs_units != null
|
{row.abs_units != null
|
||||||
|
|
@ -492,7 +503,7 @@ function MixTable({ mix }: { mix: LayoutTzMixRow[] }) {
|
||||||
style={{
|
style={{
|
||||||
padding: "7px 12px",
|
padding: "7px 12px",
|
||||||
fontVariantNumeric: "tabular-nums",
|
fontVariantNumeric: "tabular-nums",
|
||||||
color: "#374151",
|
color: "var(--fg-secondary)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{row.avg_target_area_m2 != null
|
{row.avg_target_area_m2 != null
|
||||||
|
|
@ -517,9 +528,9 @@ function RecommendationCard({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
border: "1px solid #e5e7eb",
|
border: "1px solid var(--border-card)",
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
background: "#fff",
|
background: "var(--bg-card)",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -545,7 +556,12 @@ function RecommendationCard({
|
||||||
|
|
||||||
{/* Rationale text — plain text only, no dangerouslySetInnerHTML */}
|
{/* Rationale text — plain text only, no dangerouslySetInnerHTML */}
|
||||||
<p
|
<p
|
||||||
style={{ fontSize: 13, color: "#374151", margin: 0, lineHeight: 1.6 }}
|
style={{
|
||||||
|
fontSize: 13,
|
||||||
|
color: "var(--fg-secondary)",
|
||||||
|
margin: 0,
|
||||||
|
lineHeight: 1.6,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{rec.rationale_text}
|
{rec.rationale_text}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -557,7 +573,7 @@ function RecommendationCard({
|
||||||
style={{
|
style={{
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
color: "#6b7280",
|
color: "var(--fg-tertiary)",
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
textTransform: "uppercase",
|
textTransform: "uppercase",
|
||||||
letterSpacing: "0.04em",
|
letterSpacing: "0.04em",
|
||||||
|
|
@ -573,7 +589,7 @@ function RecommendationCard({
|
||||||
{rec.mix.length > 0 && (
|
{rec.mix.length > 0 && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
border: "1px solid #e5e7eb",
|
border: "1px solid var(--border-card)",
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
|
|
@ -590,16 +606,18 @@ function RecommendationCard({
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: 8,
|
gap: 8,
|
||||||
padding: "10px 14px",
|
padding: "10px 14px",
|
||||||
background: "#eff6ff",
|
background: "var(--accent-soft)",
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ color: "#6b7280" }}>Средневзвешенная цена:</span>
|
<span style={{ color: "var(--fg-tertiary)" }}>
|
||||||
|
Средневзвешенная цена:
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
color: "#1d4ed8",
|
color: "var(--accent)",
|
||||||
fontVariantNumeric: "tabular-nums",
|
fontVariantNumeric: "tabular-nums",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -612,7 +630,7 @@ function RecommendationCard({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Meta */}
|
{/* Meta */}
|
||||||
<div style={{ fontSize: 11, color: "#9ca3af" }}>
|
<div style={{ fontSize: 11, color: "var(--fg-tertiary)" }}>
|
||||||
Основано на {rec.based_on_obj_count} ЖК ·{" "}
|
Основано на {rec.based_on_obj_count} ЖК ·{" "}
|
||||||
{rec.based_on_total_deals.toLocaleString("ru-RU")} сделках · период{" "}
|
{rec.based_on_total_deals.toLocaleString("ru-RU")} сделках · период{" "}
|
||||||
{new Date(rec.data_window_start).toLocaleDateString("ru-RU")} —{" "}
|
{new Date(rec.data_window_start).toLocaleDateString("ru-RU")} —{" "}
|
||||||
|
|
@ -695,9 +713,9 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
border: "1px solid #e5e7eb",
|
border: "1px solid var(--border-card)",
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
background: "#fff",
|
background: "var(--bg-card)",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -705,8 +723,8 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: "14px 20px",
|
padding: "14px 20px",
|
||||||
background: "#f9fafb",
|
background: "var(--bg-app)",
|
||||||
borderBottom: "1px solid #e5e7eb",
|
borderBottom: "1px solid var(--border-card)",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
|
@ -715,10 +733,18 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ fontWeight: 600, fontSize: 14, color: "#111827" }}>
|
<span
|
||||||
|
style={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 14,
|
||||||
|
color: "var(--fg-primary)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
Анализ планировок
|
Анализ планировок
|
||||||
</span>
|
</span>
|
||||||
<span style={{ fontSize: 12, color: "#6b7280", marginLeft: 8 }}>
|
<span
|
||||||
|
style={{ fontSize: 12, color: "var(--fg-tertiary)", marginLeft: 8 }}
|
||||||
|
>
|
||||||
data-driven ТЗ на проектирование
|
data-driven ТЗ на проектирование
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -732,8 +758,10 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
disabled={isPdfLoading}
|
disabled={isPdfLoading}
|
||||||
style={{
|
style={{
|
||||||
padding: "7px 16px",
|
padding: "7px 16px",
|
||||||
background: isPdfLoading ? "#9ca3af" : "#1d4ed8",
|
background: isPdfLoading
|
||||||
color: "#fff",
|
? "var(--fg-tertiary)"
|
||||||
|
: "var(--accent)",
|
||||||
|
color: "var(--fg-on-dark)",
|
||||||
border: "none",
|
border: "none",
|
||||||
borderRadius: 7,
|
borderRadius: 7,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
|
|
@ -755,7 +783,7 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: "16px 20px",
|
padding: "16px 20px",
|
||||||
borderBottom: "1px solid #f3f4f6",
|
borderBottom: "1px solid var(--border-soft)",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexWrap: "wrap",
|
flexWrap: "wrap",
|
||||||
gap: 20,
|
gap: 20,
|
||||||
|
|
@ -771,7 +799,13 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<label style={{ fontSize: 12, color: "#6b7280", fontWeight: 500 }}>
|
<label
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: "var(--fg-tertiary)",
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
|
>
|
||||||
Радиус поиска: {radiusKm.toFixed(1)} км
|
Радиус поиска: {radiusKm.toFixed(1)} км
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
|
@ -781,7 +815,7 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
step={0.1}
|
step={0.1}
|
||||||
value={radiusKm}
|
value={radiusKm}
|
||||||
onChange={(e) => setRadiusKm(parseFloat(e.target.value))}
|
onChange={(e) => setRadiusKm(parseFloat(e.target.value))}
|
||||||
style={{ width: 160, accentColor: "#1d4ed8" }}
|
style={{ width: 160, accentColor: "var(--accent)" }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -794,7 +828,13 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<label style={{ fontSize: 12, color: "#6b7280", fontWeight: 500 }}>
|
<label
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: "var(--fg-tertiary)",
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
|
>
|
||||||
Мин. скорость: {minVelocity.toFixed(1)} кв/мес
|
Мин. скорость: {minVelocity.toFixed(1)} кв/мес
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
|
@ -804,13 +844,19 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
step={0.1}
|
step={0.1}
|
||||||
value={minVelocity}
|
value={minVelocity}
|
||||||
onChange={(e) => setMinVelocity(parseFloat(e.target.value))}
|
onChange={(e) => setMinVelocity(parseFloat(e.target.value))}
|
||||||
style={{ width: 160, accentColor: "#1d4ed8" }}
|
style={{ width: 160, accentColor: "var(--accent)" }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Time window radio */}
|
{/* Time window radio */}
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||||
<span style={{ fontSize: 12, color: "#6b7280", fontWeight: 500 }}>
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: "var(--fg-tertiary)",
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
|
>
|
||||||
Период анализа
|
Период анализа
|
||||||
</span>
|
</span>
|
||||||
<div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
|
<div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
|
||||||
|
|
@ -823,7 +869,8 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
gap: 4,
|
gap: 4,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
color: timeWindow === tw ? "#1d4ed8" : "#374151",
|
color:
|
||||||
|
timeWindow === tw ? "var(--accent)" : "var(--fg-secondary)",
|
||||||
fontWeight: timeWindow === tw ? 600 : 400,
|
fontWeight: timeWindow === tw ? 600 : 400,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -833,7 +880,7 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
value={tw}
|
value={tw}
|
||||||
checked={timeWindow === tw}
|
checked={timeWindow === tw}
|
||||||
onChange={() => setTimeWindow(tw)}
|
onChange={() => setTimeWindow(tw)}
|
||||||
style={{ accentColor: "#1d4ed8" }}
|
style={{ accentColor: "var(--accent)" }}
|
||||||
/>
|
/>
|
||||||
{TIME_WINDOW_LABELS[tw]}
|
{TIME_WINDOW_LABELS[tw]}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -843,7 +890,13 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
|
|
||||||
{/* Target flats input */}
|
{/* Target flats input */}
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||||
<label style={{ fontSize: 12, color: "#6b7280", fontWeight: 500 }}>
|
<label
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: "var(--fg-tertiary)",
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
|
>
|
||||||
Целевой объём (квартир)
|
Целевой объём (квартир)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
|
@ -855,11 +908,11 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
placeholder="300"
|
placeholder="300"
|
||||||
style={{
|
style={{
|
||||||
padding: "5px 10px",
|
padding: "5px 10px",
|
||||||
border: "1px solid #d1d5db",
|
border: "1px solid var(--border-strong)",
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
width: 110,
|
width: 110,
|
||||||
color: "#111827",
|
color: "var(--fg-primary)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -870,8 +923,8 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
style={{
|
style={{
|
||||||
padding: "7px 20px",
|
padding: "7px 20px",
|
||||||
background: isPending ? "#9ca3af" : "#1d4ed8",
|
background: isPending ? "var(--fg-tertiary)" : "var(--accent)",
|
||||||
color: "#fff",
|
color: "var(--fg-on-dark)",
|
||||||
border: "none",
|
border: "none",
|
||||||
borderRadius: 7,
|
borderRadius: 7,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
|
|
@ -888,8 +941,8 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: "#1d4ed8",
|
color: "var(--accent)",
|
||||||
background: "#eff6ff",
|
background: "var(--accent-soft)",
|
||||||
padding: "3px 8px",
|
padding: "3px 8px",
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
alignSelf: "flex-end",
|
alignSelf: "flex-end",
|
||||||
|
|
@ -912,7 +965,7 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
style={{
|
style={{
|
||||||
height: 18,
|
height: 18,
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
background: "#f3f4f6",
|
background: "var(--bg-card-alt)",
|
||||||
width: `${w}%`,
|
width: `${w}%`,
|
||||||
animation: "pulse 1.5s ease-in-out infinite",
|
animation: "pulse 1.5s ease-in-out infinite",
|
||||||
}}
|
}}
|
||||||
|
|
@ -926,10 +979,10 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: "12px 16px",
|
padding: "12px 16px",
|
||||||
background: "#fef2f2",
|
background: "var(--danger-soft)",
|
||||||
border: "1px solid #fca5a5",
|
border: "1px solid var(--danger-soft)",
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
color: "#dc2626",
|
color: "var(--danger)",
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -952,7 +1005,7 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
style={{
|
style={{
|
||||||
padding: "24px 0",
|
padding: "24px 0",
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
color: "#d1d5db",
|
color: "var(--fg-tertiary)",
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue