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