From 333a36f0498c3c90b35e3a9c516ecae756b0098d Mon Sep 17 00:00:00 2001 From: bot-backend Date: Thu, 2 Jul 2026 20:56:50 +0300 Subject: [PATCH] feat(tradein/frontend): pass suggest fias_id as target_fias_id in estimate request --- .../src/components/trade-in/AddressInput.tsx | 11 ++++++++++- .../src/components/trade-in/EstimateForm.tsx | 12 +++++++++++- .../src/components/trade-in/v2/ParamsPanel.tsx | 10 ++++++++++ tradein-mvp/frontend/src/types/trade-in.ts | 8 ++++++++ 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/tradein-mvp/frontend/src/components/trade-in/AddressInput.tsx b/tradein-mvp/frontend/src/components/trade-in/AddressInput.tsx index e9b9027e..ecb950e8 100644 --- a/tradein-mvp/frontend/src/components/trade-in/AddressInput.tsx +++ b/tradein-mvp/frontend/src/components/trade-in/AddressInput.tsx @@ -22,6 +22,9 @@ interface SuggestItem { lat: number; lon: number; kind: string; + // ФИАС-идентификатор дома (заполнен только для house-level кандидатов + // DaData-тира; иначе null/absent). Пробрасывается наверх через onPickFias. + fias_id?: string | null; } interface SuggestResponse { @@ -43,6 +46,9 @@ interface Props { value: string; onChange: (val: string) => void; onPickCoords?: (coords: { lat: number; lon: number } | null) => void; + // ФИАС-ключ выбранного дома. Зеркалит поведение onPickCoords: значение при + // выборе house-level подсказки, null при ручной правке текста. + onPickFias?: (fiasId: string | null) => void; placeholder?: string; inputId?: string; } @@ -81,10 +87,11 @@ function presetsToItems(): SuggestItem[] { lat: 0, lon: 0, kind: p.kind, + fias_id: null, })); } -export function AddressInput({ value, onChange, onPickCoords, placeholder, inputId = "addr" }: Props) { +export function AddressInput({ value, onChange, onPickCoords, onPickFias, placeholder, inputId = "addr" }: Props) { const [open, setOpen] = useState(false); const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); @@ -150,6 +157,7 @@ export function AddressInput({ value, onChange, onPickCoords, placeholder, input item.lat !== 0 && item.lon !== 0; onPickCoords?.(hasCoords ? { lat: item.lat, lon: item.lon } : null); + onPickFias?.(item.fias_id ?? null); setOpen(false); setShowPresets(false); setItems([]); @@ -197,6 +205,7 @@ export function AddressInput({ value, onChange, onPickCoords, placeholder, input onChange(e.target.value); lastPickedRef.current = ""; // юзер начал редактировать — снимаем guard onPickCoords?.(null); // ручное редактирование сбрасывает ранее выбранные coords + onPickFias?.(null); // ...и ранее выбранный ФИАС (иначе stale перебьёт новый адрес) setShowPresets(false); }} onFocus={() => { diff --git a/tradein-mvp/frontend/src/components/trade-in/EstimateForm.tsx b/tradein-mvp/frontend/src/components/trade-in/EstimateForm.tsx index acd46fdf..31d09d44 100644 --- a/tradein-mvp/frontend/src/components/trade-in/EstimateForm.tsx +++ b/tradein-mvp/frontend/src/components/trade-in/EstimateForm.tsx @@ -76,6 +76,7 @@ function validate(f: FormState): string | null { function toPayload( f: FormState, coords: { lat: number; lon: number } | null, + fias: string | null, ): TradeInEstimateInput { const out: TradeInEstimateInput = { address: f.address.trim(), @@ -89,6 +90,9 @@ function toPayload( out.lat = coords.lat; out.lon = coords.lon; } + // Только когда адрес выбран из подсказки (иначе fias === null): даём бэкенду + // детерминированный ключ дома вместо/до геокодинга строки. + if (fias) out.target_fias_id = fias; if (f.year_built !== "") out.year_built = Number(f.year_built); const ht = asHouseType(f.house_type); if (ht) out.house_type = ht; @@ -111,6 +115,10 @@ export function EstimateForm({ }: Props) { const [form, setForm] = useState(INITIAL); const [coords, setCoords] = useState<{ lat: number; lon: number } | null>(null); + // ФИАС-ключ дома из выбранной подсказки. Живёт рядом с coords и сбрасывается + // по тем же событиям: ручная правка адреса (внутри AddressInput → onPickFias(null)) + // и выбор точки на карте (MapPicker onPick ниже). + const [fias, setFias] = useState(null); const [touched, setTouched] = useState(false); const [mapOpen, setMapOpen] = useState(false); // CRM-блок свёрнут по умолчанию — экономит ~190px высоты, чтобы форма влезала @@ -132,7 +140,7 @@ export function EstimateForm({ e.preventDefault(); setTouched(true); if (canSubmit) { - onSubmit(toPayload(form, coords)); + onSubmit(toPayload(form, coords, fias)); } } @@ -163,6 +171,7 @@ export function EstimateForm({ value={form.address} onChange={(val) => setForm((s) => ({ ...s, address: val }))} onPickCoords={setCoords} + onPickFias={setFias} placeholder="ул. Малышева, 30 · Куйбышева, 48…" /> @@ -522,6 +531,7 @@ export function EstimateForm({ onPick={(addr, mapCoords) => { setForm((s) => ({ ...s, address: addr })); setCoords(mapCoords ?? null); + setFias(null); // точка с карты ≠ house-level ФИАС → сбрасываем }} onClose={() => setMapOpen(false)} /> diff --git a/tradein-mvp/frontend/src/components/trade-in/v2/ParamsPanel.tsx b/tradein-mvp/frontend/src/components/trade-in/v2/ParamsPanel.tsx index 6a6e7747..3eab9cff 100644 --- a/tradein-mvp/frontend/src/components/trade-in/v2/ParamsPanel.tsx +++ b/tradein-mvp/frontend/src/components/trade-in/v2/ParamsPanel.tsx @@ -542,6 +542,12 @@ export default function ParamsPanel({ ? { lat: initialValues.lat, lon: initialValues.lon } : null, ); + // ФИАС-ключ дома из выбранной подсказки. Живёт рядом с coords и сбрасывается + // по тем же событиям (ручная правка адреса → handleAddressChange). Отправляется + // только когда адрес выбран из suggest — иначе stale fias перебил бы новый адрес. + const [fias, setFias] = useState( + initialValues?.target_fias_id ?? null, + ); const debounceRef = useRef | null>(null); // БАЛКОН radiogroup focus targets (roving tabindex). const balNoRef = useRef(null); @@ -551,6 +557,7 @@ export default function ParamsPanel({ const handleAddressChange = (v: string) => { setAddress(v); setCoords(null); // a manual edit invalidates a previously picked point + setFias(null); // ...and the previously picked house ФИАС (stale-guard) setSuggestOpen(v.trim().length >= 3); setAddressActive(-1); // a new query invalidates the previous highlight if (fieldErrors.address) @@ -563,6 +570,7 @@ export default function ParamsPanel({ setAddress(s.full_address); setAddressQuery(s.full_address); setCoords({ lat: s.lat, lon: s.lon }); + setFias(s.fias_id ?? null); setSuggestOpen(false); setAddressActive(-1); }; @@ -696,6 +704,8 @@ export default function ParamsPanel({ has_balcony: balcony ?? undefined, lat: coords?.lat ?? null, lon: coords?.lon ?? null, + // Детерминированный ключ дома — только из выбранной подсказки (иначе null). + target_fias_id: fias, // "Авто" → null → backend keeps its two-tier default (1000 m primary / // 2000 m fallback). A fixed value overrides both. (Не отправлять 500 по // умолчанию — это сузило бы поиск аналогов и убрало fallback.) diff --git a/tradein-mvp/frontend/src/types/trade-in.ts b/tradein-mvp/frontend/src/types/trade-in.ts index 9a4740a2..aeeb6d52 100644 --- a/tradein-mvp/frontend/src/types/trade-in.ts +++ b/tradein-mvp/frontend/src/types/trade-in.ts @@ -71,6 +71,11 @@ export interface TradeInEstimateInput { // Радиус поиска аналогов/сделок в метрах (опциональный override дефолта бэкенда). // Зеркалит новое поле request-схемы; absent → бэкенд применяет свой дефолт. radius_m?: number | null; + // ФИАС-идентификатор дома из выбранной подсказки автокомплита (house-level, + // DaData-тир). Детерминированный ключ дома — бэкенд использует его ПЕРВЫМ, + // до геокодинга строки. Отправляется ТОЛЬКО когда адрес выбран из подсказки; + // сбрасывается в null при ручной правке адреса или сдвиге пина на карте. + target_fias_id?: string | null; } export interface AnalogLot { @@ -424,6 +429,9 @@ export interface GeocodeSuggestion { lat: number; lon: number; kind: string; // тип объекта (house / street / …) + // ФИАС-идентификатор дома (заполнен только для house-level кандидатов + // DaData-тира; для street/locality и не-DaData-источников — null/absent). + fias_id?: string | null; } export interface GeocodeSuggestResponse {