Merge pull request 'feat(tradein/frontend): pass suggest fias_id as target_fias_id in estimate request' (#2186) from feat/tradein-fias-estimate-frontend into main
All checks were successful
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / deploy (push) Successful in 53s
Deploy Trade-In / changes (push) Successful in 8s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 1m58s

This commit is contained in:
lekss361 2026-07-02 18:08:27 +00:00
commit 3b9dfdb44e
4 changed files with 39 additions and 2 deletions

View file

@ -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<SuggestItem[]>([]);
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={() => {

View file

@ -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<FormState>(INITIAL);
const [coords, setCoords] = useState<{ lat: number; lon: number } | null>(null);
// ФИАС-ключ дома из выбранной подсказки. Живёт рядом с coords и сбрасывается
// по тем же событиям: ручная правка адреса (внутри AddressInput → onPickFias(null))
// и выбор точки на карте (MapPicker onPick ниже).
const [fias, setFias] = useState<string | null>(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…"
/>
</div>
@ -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)}
/>

View file

@ -542,6 +542,12 @@ export default function ParamsPanel({
? { lat: initialValues.lat, lon: initialValues.lon }
: null,
);
// ФИАС-ключ дома из выбранной подсказки. Живёт рядом с coords и сбрасывается
// по тем же событиям (ручная правка адреса → handleAddressChange). Отправляется
// только когда адрес выбран из suggest — иначе stale fias перебил бы новый адрес.
const [fias, setFias] = useState<string | null>(
initialValues?.target_fias_id ?? null,
);
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// БАЛКОН radiogroup focus targets (roving tabindex).
const balNoRef = useRef<HTMLButtonElement>(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.)

View file

@ -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 {