"use client"; import { useState } from "react"; import type { TradeInEstimateInput } from "@/types/trade-in"; import { asHouseType, asRepairState } from "@/types/trade-in"; import { AddressInput } from "@/components/trade-in/AddressInput"; import { MapPicker } from "@/components/trade-in/MapPicker"; interface Props { onSubmit: (input: TradeInEstimateInput) => void; isPending: boolean; error: string | null; blocked?: boolean; remaining?: number | null; limit?: number | null; } interface FormState { address: string; area_m2: string; rooms: string; floor: string; total_floors: string; year_built: string; house_type: string; repair_state: string; has_balcony: boolean; ownership_type: string; has_mortgage: boolean; client_name: string; client_phone: string; } const INITIAL: FormState = { address: "", area_m2: "", rooms: "", floor: "", total_floors: "", year_built: "", house_type: "", repair_state: "", has_balcony: false, ownership_type: "", has_mortgage: false, client_name: "", client_phone: "", }; function validate(f: FormState): string | null { if (f.address.trim().length < 3) return "Введите адрес (минимум 3 символа)"; const a = Number(f.area_m2); if (!f.area_m2 || isNaN(a) || a <= 10 || a >= 500) return "Площадь: от 10 до 500 м²"; const r = Number(f.rooms); if (f.rooms === "" || isNaN(r) || r < 0 || r > 10) return "Комнат: 0 (студия) — 10"; if (f.floor !== "") { const fl = Number(f.floor); if (isNaN(fl) || fl < 1 || fl > 100) return "Этаж: 1—100"; if (f.total_floors !== "") { const tf = Number(f.total_floors); if (!isNaN(tf) && fl > tf) return "Этаж не может быть больше этажей в доме"; } } if (f.total_floors !== "") { const tf = Number(f.total_floors); if (isNaN(tf) || tf < 1 || tf > 100) return "Этажей в доме: 1—100"; } if (f.year_built !== "") { const y = Number(f.year_built); if (isNaN(y) || y < 1800 || y > 2100) return "Год постройки: 1800—2100"; } return null; } function toPayload( f: FormState, coords: { lat: number; lon: number } | null, ): TradeInEstimateInput { const out: TradeInEstimateInput = { address: f.address.trim(), area_m2: Number(f.area_m2), rooms: Number(f.rooms), floor: f.floor !== "" ? Number(f.floor) : null, total_floors: f.total_floors !== "" ? Number(f.total_floors) : null, has_balcony: f.has_balcony, }; if (coords) { out.lat = coords.lat; out.lon = coords.lon; } if (f.year_built !== "") out.year_built = Number(f.year_built); const ht = asHouseType(f.house_type); if (ht) out.house_type = ht; const rs = asRepairState(f.repair_state); if (rs) out.repair_state = rs; if (f.ownership_type) out.ownership_type = f.ownership_type; if (f.has_mortgage) out.has_mortgage = true; if (f.client_name.trim()) out.client_name = f.client_name.trim(); if (f.client_phone.trim()) out.client_phone = f.client_phone.trim(); return out; } export function EstimateForm({ onSubmit, isPending, error, blocked = false, remaining = null, limit = null, }: Props) { const [form, setForm] = useState(INITIAL); const [coords, setCoords] = useState<{ lat: number; lon: number } | null>(null); const [touched, setTouched] = useState(false); const [mapOpen, setMapOpen] = useState(false); // CRM-блок свёрнут по умолчанию — экономит ~190px высоты, чтобы форма влезала // на ноутбуки без скролла (поля для менеджера опциональны, открываются по клику). const [crmOpen, setCrmOpen] = useState(false); const valError = validate(form); const canSubmit = !valError && !isPending && !blocked; function field(key: keyof FormState) { return (e: React.ChangeEvent) => { const value = e.target.type === "checkbox" ? (e.target as HTMLInputElement).checked : e.target.value; setForm((s) => ({ ...s, [key]: value })); }; } function handleSubmit(e: React.FormEvent) { e.preventDefault(); setTouched(true); if (canSubmit) { onSubmit(toPayload(form, coords)); } } return (

Параметры квартиры

шаг 1/1
{/* Address with autocomplete */}
setForm((s) => ({ ...s, address: val }))} onPickCoords={setCoords} placeholder="ул. Малышева, 30 · Куйбышева, 48…" />
{/* Area + Rooms */}
м²
{/* Floor + Total floors */}
1-й и последний этаж снижают цену на 5–10% — заполни если знаешь {/* Year + House type */}
{/* Repair */}
{/* Balcony */}
{/* CRM — для менеджера (#395) — сворачиваемый, по умолчанию закрыт */}
{crmOpen && ( <>
)}
{touched && valError && (
{valError}
)} {error && (
{error}
)}
{blocked ? (
Лимит из {limit ?? 15} оценок в этом месяце исчерпан. За полной версией обращайтесь к Копылову.
) : ( <> {remaining != null && limit != null && (
Осталось{" "} {remaining} {" "} из{" "} {limit}{" "} оценок в этом месяце
)} )}
Кэш по адресу — 24 ч ● готов
{mapOpen && ( { setForm((s) => ({ ...s, address: addr })); setCoords(mapCoords ?? null); }} onClose={() => setMapOpen(false)} /> )} ); }