import { useState, useEffect, useRef } from "preact/hooks"; export default function RangeForm() { const [city, setCity] = useState(""); const [citySuggest, setCitySuggest] = useState([]); const [highlightIndex, setHighlightIndex] = useState(-1); const [street, setStreet] = useState(""); const [streetSuggest, setStreetSuggest] = useState([]); const [streetHighlightIndex, setStreetHighlightIndex] = useState(-1); const [streetDisabled, setStreetDisabled] = useState(true); const [streetRequired, setStreetRequired] = useState(false); const [number, setNumber] = useState(""); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const cityListRef = useRef(null); const streetListRef = useRef(null); let timeoutCity = null; let timeoutStreet = null; async function fetchCitySuggestions(q) { const res = await fetch(`/api/cities-autocomplete?q=${encodeURIComponent(q)}`); setCitySuggest(await res.json()); setHighlightIndex(-1); } function onCityInput(e) { const value = e.target.value; setCity(value); setError(""); if (timeoutCity) clearTimeout(timeoutCity); if (value.trim().length < 2) { setCitySuggest([]); return; } timeoutCity = setTimeout(() => fetchCitySuggestions(value), 250); } function onCityKeyDown(e) { if (!citySuggest.length) return; if (e.key === "ArrowDown") { e.preventDefault(); setHighlightIndex((prev) => (prev + 1) % citySuggest.length); } if (e.key === "ArrowUp") { e.preventDefault(); setHighlightIndex((prev) => prev <= 0 ? citySuggest.length - 1 : prev - 1 ); } if (e.key === "Enter" && highlightIndex >= 0) { e.preventDefault(); const chosen = citySuggest[highlightIndex]; setCity(chosen); setCitySuggest([]); setHighlightIndex(-1); } if (e.key === "Escape") { setCitySuggest([]); setHighlightIndex(-1); } } useEffect(() => { if (highlightIndex < 0 || !cityListRef.current) return; cityListRef.current.children[highlightIndex]?.scrollIntoView({ block: "nearest" }); }, [highlightIndex]); useEffect(() => { if (!citySuggest.length) return; const exact = citySuggest.find( (c) => c.toLowerCase() === city.toLowerCase() ); if (exact) { setCity(exact); setCitySuggest([]); setHighlightIndex(-1); updateStreetAvailability(exact); } }, [city, citySuggest]); async function updateStreetAvailability(currentCity) { if (!currentCity) { setStreetDisabled(true); setStreetRequired(false); setStreet(""); setStreetSuggest([]); setStreetHighlightIndex(-1); return; } const res = await fetch(`/api/has-streets?city=${encodeURIComponent(currentCity)}`); const { hasStreets } = await res.json(); if (!hasStreets) { setStreetDisabled(true); setStreetRequired(false); setStreet(""); setStreetSuggest([]); setStreetHighlightIndex(-1); return; } setStreetDisabled(false); setStreetRequired(true); setStreetSuggest([]); setStreetHighlightIndex(-1); } useEffect(() => { updateStreetAvailability(city); }, [city]); async function fetchStreetSuggestions(q, c) { const res = await fetch( `/api/streets-autocomplete?city=${encodeURIComponent(c)}&q=${encodeURIComponent(q)}` ); setStreetSuggest(await res.json()); setStreetHighlightIndex(-1); } function onStreetInput(e) { const value = e.target.value; setStreet(value); setError(""); if (!city || streetDisabled) return; if (!value) { setStreetSuggest([]); setStreetHighlightIndex(-1); return; } if (timeoutStreet) clearTimeout(timeoutStreet); timeoutStreet = setTimeout(() => fetchStreetSuggestions(value, city), 250); } function onStreetKeyDown(e) { if (!streetSuggest.length) return; if (e.key === "ArrowDown") { e.preventDefault(); setStreetHighlightIndex((prev) => (prev + 1) % streetSuggest.length); } if (e.key === "ArrowUp") { e.preventDefault(); setStreetHighlightIndex((prev) => prev <= 0 ? streetSuggest.length - 1 : prev - 1 ); } if (e.key === "Enter" && streetHighlightIndex >= 0) { e.preventDefault(); setStreet(streetSuggest[streetHighlightIndex]); setStreetSuggest([]); setStreetHighlightIndex(-1); } if (e.key === "Escape") { setStreetSuggest([]); setStreetHighlightIndex(-1); } } useEffect(() => { if (streetHighlightIndex < 0 || !streetListRef.current) return; streetListRef.current.children[streetHighlightIndex]?.scrollIntoView({ block: "nearest" }); }, [streetHighlightIndex]); useEffect(() => { if (!streetSuggest.length) return; const exact = streetSuggest.find( (s) => s.toLowerCase() === street.toLowerCase() ); if (exact) { setStreet(exact); setStreetSuggest([]); setStreetHighlightIndex(-1); } }, [street, streetSuggest]); async function onSubmit(e) { e.preventDefault(); setError(""); if (streetRequired && !street.trim()) { setError("Ulica jest wymagana w tej miejscowości."); return; } setLoading(true); const res = await fetch("/api/search", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ city, street, number }), }); const data = await res.json(); if (!data.ok || !data.result) { setError("Adres nie znajduje się w zasięgu."); setLoading(false); return; } window.showAddressOnMap(data.result); setCity(""); setStreet(""); setNumber(""); updateStreetAvailability(city); setLoading(false); } return (
{/* CITY */}
{citySuggest.length > 0 && ( )}
{/* STREET */}
{streetSuggest.length > 0 && ( )}
{/* NUMBER */} setNumber(e.target.value)} required /> {error && (

{error}

)}
); }