From a86cce79af96ddece4b2a9a3d8b0b5a11808c110 Mon Sep 17 00:00:00 2001 From: lekss361 Date: Sun, 17 May 2026 14:17:05 +0000 Subject: [PATCH] =?UTF-8?q?refactor(sf-19):=20BestLayouts=20inline=20hex?= =?UTF-8?q?=20=E2=86=92=20Tailwind=20tokens=20(#296)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../site-finder/BestLayoutsBlock.tsx | 207 +++++++++++------- 1 file changed, 130 insertions(+), 77 deletions(-) diff --git a/frontend/src/components/site-finder/BestLayoutsBlock.tsx b/frontend/src/components/site-finder/BestLayoutsBlock.tsx index 99394dbc..d27fe6c7 100644 --- a/frontend/src/components/site-finder/BestLayoutsBlock.tsx +++ b/frontend/src/components/site-finder/BestLayoutsBlock.tsx @@ -14,13 +14,10 @@ import type { // ── Constants ───────────────────────────────────────────────────────────────── -const CONFIDENCE_STYLES: Record< - Confidence, - { bg: string; fg: string; label: string } -> = { - high: { bg: "#dcfce7", fg: "#166534", label: "Высокое" }, - medium: { bg: "#fef3c7", fg: "#854d0e", label: "Среднее" }, - low: { bg: "#fee2e2", fg: "#991b1b", label: "Низкое" }, +const CONFIDENCE_STYLES: Record = { + high: { cls: "bg-emerald-100 text-emerald-800", label: "Высокое" }, + medium: { cls: "bg-amber-100 text-amber-800", label: "Среднее" }, + low: { cls: "bg-rose-100 text-rose-800", label: "Низкое" }, }; const TIME_WINDOW_LABELS: Record = { @@ -40,17 +37,16 @@ const ROOM_BUCKET_LABELS: Record = { // ── Sub-components ──────────────────────────────────────────────────────────── function DataQualityCard({ dq }: { dq: BestLayoutsResponse["data_quality"] }) { - const style = CONFIDENCE_STYLES[dq.confidence]; + const conf = CONFIDENCE_STYLES[dq.confidence]; return (
Качество данных: - {style.label} + {conf.label} Покрытие {dq.velocity_coverage_pct.toFixed(0)}% ( @@ -68,7 +64,7 @@ function salesPeriodMonths(supply: number, velocity: number): number | null { } function SalesPeriodCell({ months }: { months: number | null }) { - if (months === null) return ; + if (months === null) return ; if (months < 12) return ( @@ -92,7 +88,7 @@ function CompetitorDrillIn({ const hidden = ids.length - COMPETITOR_PREVIEW_LIMIT; return ( - +
- + ЖК-источники: {visible.map((id) => ( @@ -186,7 +185,7 @@ function TopLayoutsTable({ rows }: { rows: TopLayoutRow[] }) { style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }} > - + {headers.map((h, idx) => ( @@ -249,18 +251,23 @@ function TopLayoutsTable({ rows }: { rows: TopLayoutRow[] }) { style={{ padding: "7px 12px", fontWeight: 500, - color: "#111827", + color: "var(--fg-primary)", }} > {ROOM_BUCKET_LABELS[row.room_bucket] ?? row.room_bucket} - + {row.area_bin} м² @@ -270,7 +277,7 @@ function TopLayoutsTable({ rows }: { rows: TopLayoutRow[] }) { style={{ padding: "7px 12px", textAlign: "right", - color: "#374151", + color: "var(--fg-secondary)", fontVariantNumeric: "tabular-nums", }} > @@ -280,7 +287,7 @@ function TopLayoutsTable({ rows }: { rows: TopLayoutRow[] }) { style={{ padding: "7px 12px", textAlign: "right", - color: "#374151", + color: "var(--fg-secondary)", fontVariantNumeric: "tabular-nums", }} > @@ -302,7 +309,7 @@ function TopLayoutsTable({ rows }: { rows: TopLayoutRow[] }) { style={{ padding: "7px 12px", textAlign: "right", - color: "#374151", + color: "var(--fg-secondary)", fontVariantNumeric: "tabular-nums", }} > @@ -339,7 +346,7 @@ function TopLayoutsTable({ rows }: { rows: TopLayoutRow[] }) { @@ -379,12 +386,12 @@ function TopLayoutsTable({ rows }: { rows: TopLayoutRow[] }) { function UnitMixBar({ mix }: { mix: LayoutTzMixRow[] }) { const COLORS = [ - "#1d4ed8", - "#7c3aed", - "#059669", - "#d97706", - "#dc2626", - "#0891b2", + "var(--viz-1)", + "var(--viz-5)", + "var(--viz-3)", + "var(--viz-4)", + "var(--viz-2)", + "var(--viz-3)", ]; return ( @@ -396,7 +403,7 @@ function UnitMixBar({ mix }: { mix: LayoutTzMixRow[] }) { height: 24, borderRadius: 6, overflow: "hidden", - border: "1px solid #e5e7eb", + border: "1px solid var(--border-card)", marginBottom: 10, }} > @@ -428,7 +435,7 @@ function UnitMixBar({ mix }: { mix: LayoutTzMixRow[] }) { flexShrink: 0, }} /> - + {ROOM_BUCKET_LABELS[row.room_bucket] ?? row.room_bucket} {row.pct} % @@ -443,7 +450,7 @@ function MixTable({ mix }: { mix: LayoutTzMixRow[] }) { return ( - + {["Тип", "Доля, %", "Кол-во квартир", "Ср. площадь, м²"].map((h) => ( @@ -472,7 +483,7 @@ function MixTable({ mix }: { mix: LayoutTzMixRow[] }) { style={{ padding: "7px 12px", fontVariantNumeric: "tabular-nums", - color: "#374151", + color: "var(--fg-secondary)", }} > {row.pct}% @@ -481,7 +492,7 @@ function MixTable({ mix }: { mix: LayoutTzMixRow[] }) { style={{ padding: "7px 12px", fontVariantNumeric: "tabular-nums", - color: "#374151", + color: "var(--fg-secondary)", }} > {row.abs_units != null @@ -492,7 +503,7 @@ function MixTable({ mix }: { mix: LayoutTzMixRow[] }) { style={{ padding: "7px 12px", fontVariantNumeric: "tabular-nums", - color: "#374151", + color: "var(--fg-secondary)", }} > {row.avg_target_area_m2 != null @@ -517,9 +528,9 @@ function RecommendationCard({ return (
@@ -545,7 +556,12 @@ function RecommendationCard({ {/* Rationale text — plain text only, no dangerouslySetInnerHTML */}

{rec.rationale_text}

@@ -557,7 +573,7 @@ function RecommendationCard({ style={{ fontSize: 12, fontWeight: 600, - color: "#6b7280", + color: "var(--fg-tertiary)", marginBottom: 8, textTransform: "uppercase", letterSpacing: "0.04em", @@ -573,7 +589,7 @@ function RecommendationCard({ {rec.mix.length > 0 && (
- Средневзвешенная цена: + + Средневзвешенная цена: + @@ -612,7 +630,7 @@ function RecommendationCard({ )} {/* Meta */} -
+
Основано на {rec.based_on_obj_count} ЖК ·{" "} {rec.based_on_total_deals.toLocaleString("ru-RU")} сделках · период{" "} {new Date(rec.data_window_start).toLocaleDateString("ru-RU")} —{" "} @@ -695,9 +713,9 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) { return (
@@ -705,8 +723,8 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
- + Анализ планировок - + data-driven ТЗ на проектирование
@@ -732,8 +758,10 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) { disabled={isPdfLoading} style={{ padding: "7px 16px", - background: isPdfLoading ? "#9ca3af" : "#1d4ed8", - color: "#fff", + background: isPdfLoading + ? "var(--fg-tertiary)" + : "var(--accent)", + color: "var(--fg-on-dark)", border: "none", borderRadius: 7, fontSize: 13, @@ -755,7 +783,7 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
-
@@ -794,7 +828,13 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) { minWidth: 160, }} > -
{/* Time window radio */}
- + Период анализа
@@ -823,7 +869,8 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) { gap: 4, fontSize: 12, cursor: "pointer", - color: timeWindow === tw ? "#1d4ed8" : "#374151", + color: + timeWindow === tw ? "var(--accent)" : "var(--fg-secondary)", fontWeight: timeWindow === tw ? 600 : 400, }} > @@ -833,7 +880,7 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) { value={tw} checked={timeWindow === tw} onChange={() => setTimeWindow(tw)} - style={{ accentColor: "#1d4ed8" }} + style={{ accentColor: "var(--accent)" }} /> {TIME_WINDOW_LABELS[tw]} @@ -843,7 +890,13 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) { {/* Target flats input */}
-
@@ -870,8 +923,8 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) { disabled={isPending} style={{ padding: "7px 20px", - background: isPending ? "#9ca3af" : "#1d4ed8", - color: "#fff", + background: isPending ? "var(--fg-tertiary)" : "var(--accent)", + color: "var(--fg-on-dark)", border: "none", borderRadius: 7, fontSize: 13, @@ -888,8 +941,8 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) { @@ -952,7 +1005,7 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) { style={{ padding: "24px 0", textAlign: "center", - color: "#d1d5db", + color: "var(--fg-tertiary)", fontSize: 13, }} >
{ROOM_BUCKET_LABELS[row.room_bucket] ?? row.room_bucket}