gendesign/tradein-mvp/frontend/src/components/trade-in/PriceRangeBar.tsx
lekss361 02267d0306 feat: add tradein-mvp subproject (Trade-In Estimator под /trade-in)
- FastAPI backend: PostGIS estimator + 3 scrapers (Avito/Cian/Yandex)
- Next.js 15 frontend: tradein.html mockup design, basePath=/trade-in
- WeasyPrint PDF (Брусника-style 4-page report)
- Address autocomplete с typo-tolerance + 6 EKB presets
- Изолированный docker stack gendesign-tradein (отдельная postgres БД)
- Caddy inline routes: gendsgn.ru/trade-in/* и /trade-in/api/v1/*
- Forgejo Actions: .forgejo/workflows/deploy-tradein.yml (shell-based GHCR login)
- Триггер только по paths: tradein-mvp/** (не пересекается с deploy.yml)
- Образы: ghcr.io/lekss361/gendesign-tradein-{backend,frontend}:latest

Первый запуск на сервере (вручную, один раз):
  - создать /opt/gendesign/tradein-mvp/.env.runtime (postgres pwd, contact email)
  - docker network create gendesign_shared (если нет)
  - docker compose -p gendesign-tradein up -d
  - docker compose -p gendesign exec caddy caddy reload
2026-05-21 00:25:39 +03:00

78 lines
1.8 KiB
TypeScript

"use client";
interface Props {
rangeLow: number;
rangeHigh: number;
median: number;
}
function fmtRub(value: number): string {
return value.toLocaleString("ru-RU", {
style: "currency",
currency: "RUB",
maximumFractionDigits: 0,
});
}
export function PriceRangeBar({ rangeLow, rangeHigh, median }: Props) {
const span = rangeHigh - rangeLow;
// Guard against degenerate case
const medianPct =
span > 0
? Math.max(0, Math.min(100, ((median - rangeLow) / span) * 100))
: 50;
return (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{/* Bar */}
<div
style={{
position: "relative",
height: 10,
borderRadius: 999,
background: "linear-gradient(to right, #bfdbfe, #1d4ed8)",
}}
>
{/* Median marker */}
<div
title={`Медиана: ${fmtRub(median)}`}
style={{
position: "absolute",
top: "50%",
left: `${medianPct}%`,
transform: "translate(-50%, -50%)",
width: 14,
height: 14,
borderRadius: "50%",
background: "#1d4ed8",
border: "2px solid #fff",
boxShadow: "0 1px 4px rgba(0,0,0,0.25)",
}}
/>
</div>
{/* Labels */}
<div
style={{
display: "flex",
justifyContent: "space-between",
fontSize: 12,
color: "#5b6066",
fontVariantNumeric: "tabular-nums",
}}
>
<span>{fmtRub(rangeLow)}</span>
<span
style={{
fontWeight: 700,
color: "#1d4ed8",
fontSize: 13,
}}
>
{fmtRub(median)}
</span>
<span>{fmtRub(rangeHigh)}</span>
</div>
</div>
);
}