feat: implement geocoding search with unique city filtering and improved UI
This commit is contained in:
parent
0512122243
commit
cdcc92173c
1 changed files with 134 additions and 5 deletions
|
|
@ -1,13 +1,142 @@
|
|||
import type { NextPage } from "next";
|
||||
import toast from "react-hot-toast";
|
||||
import { useState } from "react";
|
||||
import { useDebounce } from "react-use";
|
||||
import z from "zod";
|
||||
import Input from "~/components/custom_ui/input";
|
||||
import { LoadingPage } from "~/components/loading";
|
||||
import { Button } from "~/components/ui/button";
|
||||
|
||||
type NominatimResponse = z.infer<typeof responseSchema>;
|
||||
export const responseSchema = z.object({
|
||||
type: z.string(),
|
||||
geocoding: z.object({
|
||||
version: z.string(),
|
||||
attribution: z.string(),
|
||||
licence: z.string(),
|
||||
query: z.string(),
|
||||
}),
|
||||
features: z.array(
|
||||
z.object({
|
||||
type: z.string(),
|
||||
properties: z.object({
|
||||
geocoding: z.object({
|
||||
place_id: z.number(),
|
||||
osm_type: z.string(),
|
||||
osm_id: z.number(),
|
||||
osm_key: z.string(),
|
||||
osm_value: z.string(),
|
||||
type: z.string(),
|
||||
label: z.string(),
|
||||
name: z.string(),
|
||||
postcode: z.string().optional(),
|
||||
county: z.string().optional(),
|
||||
state: z.string().optional(),
|
||||
country: z.string(),
|
||||
country_code: z.string(),
|
||||
admin: z.any(),
|
||||
district: z.string().optional(),
|
||||
city: z.string().optional(),
|
||||
}),
|
||||
}),
|
||||
geometry: z.object({
|
||||
type: z.string(),
|
||||
coordinates: z.array(z.number()),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const Test: NextPage = () => {
|
||||
const [value, setValue] = useState("");
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const [options, setOptions] = useState<NominatimResponse["features"]>([]);
|
||||
|
||||
useDebounce(
|
||||
() => {
|
||||
if (value !== "") {
|
||||
handleFetch();
|
||||
}
|
||||
},
|
||||
1000,
|
||||
[value],
|
||||
);
|
||||
|
||||
const handleFetch = () => {
|
||||
setLoading(true);
|
||||
const api = `https://nominatim.openstreetmap.org/search?format=geocodejson&limit=5&addressdetails=1&q=${encodeURI(
|
||||
value,
|
||||
)}`;
|
||||
fetch(api)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
const parsed = responseSchema.safeParse(data);
|
||||
if (parsed.success) {
|
||||
const filtered = parsed.data.features.filter(
|
||||
(f) =>
|
||||
f.properties.geocoding.osm_type === "way" &&
|
||||
f.properties.geocoding.type === "street" &&
|
||||
f.properties.geocoding.postcode,
|
||||
);
|
||||
|
||||
// Then deduplicate by city
|
||||
const uniqueResults = filtered.reduce(
|
||||
(acc, current) => {
|
||||
const city = current.properties.geocoding.city;
|
||||
// If this city doesn't exist or we haven't seen it yet
|
||||
if (
|
||||
!city ||
|
||||
!acc.some((item) => item.properties.geocoding.city === city)
|
||||
) {
|
||||
acc.push(current);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof filtered,
|
||||
);
|
||||
|
||||
setOptions(uniqueResults);
|
||||
} else {
|
||||
console.error("Failed to fetch location data", parsed.error);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-4 p-4">
|
||||
<Button onClick={() => toast.success("asdasdasdasdasda")} type="button">
|
||||
test
|
||||
</Button>
|
||||
<div className="flex flex-col gap-2 p-4">
|
||||
<Input
|
||||
onChange={({ currentTarget }) => {
|
||||
setValue(currentTarget.value);
|
||||
}}
|
||||
value={value}
|
||||
/>
|
||||
|
||||
{isLoading ? (
|
||||
<LoadingPage />
|
||||
) : (
|
||||
options.map((option, idx) => (
|
||||
<Button
|
||||
className="w-full justify-start truncate"
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
console.log(option.properties.geocoding);
|
||||
}}
|
||||
variant="outline"
|
||||
>
|
||||
<p>
|
||||
{option.properties.geocoding.name} -{" "}
|
||||
{option.properties.geocoding.city} -{" "}
|
||||
{option.properties.geocoding.postcode} -{" "}
|
||||
{option.properties.geocoding.county} -{" "}
|
||||
{option.properties.geocoding.country}{" "}
|
||||
{option.properties.geocoding.country_code}
|
||||
</p>
|
||||
</Button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue