Files
latosa-escrima/frontend/components/locations/location-form.tsx
2025-03-10 16:25:12 +01:00

253 lines
6.2 KiB
TypeScript

"use client";
import * as React from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import useDebounce from "@/hooks/use-debounce";
import { OpenStreetMapLocation } from "@/types/types";
import getOsmEmbedUrl from "@/lib/osmEmbed";
import { Location } from "@/types/types";
// Zod schema for validation
const locationFormSchema = z.object({
id: z.number().optional(),
street: z.string().min(1, "Street is required"),
city: z.string().min(1, "City is required"),
postalCode: z.string().min(1, "Postal code is required"),
latitude: z.number().optional(),
longitude: z.number().optional(),
});
export type LocationFormValues = z.infer<typeof locationFormSchema>;
export const LocationForm: React.FC<{
location?: Location;
setForm: React.Dispatch<
React.SetStateAction<
ReturnType<typeof useForm<LocationFormValues>> | undefined
>
>;
}> = ({ location, setForm }) => {
const [osmQuery, setOsmQuery] = React.useState("");
const [suggestions, setSuggestions] = React.useState<
OpenStreetMapLocation[]
>([]);
const [isLoading, setIsLoading] = React.useState(false);
const form = useForm<LocationFormValues>({
resolver: zodResolver(locationFormSchema),
defaultValues: location || {
street: "",
city: "",
postalCode: "",
},
});
React.useEffect(() => {
setForm(form);
}, [form, setForm]);
// Fetch suggestions from OpenStreetMap Nominatim API
const fetchSuggestions = async (query: string) => {
if (!query || query.length < 3) {
setSuggestions([]);
return;
}
setIsLoading(true);
try {
const url = new URL("https://nominatim.openstreetmap.org/search");
url.searchParams.append("q", query);
url.searchParams.append("format", "json");
url.searchParams.append("addressdetails", "1");
url.searchParams.append("limit", "5");
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: OpenStreetMapLocation[] = await response.json();
setSuggestions(data);
} catch (error) {
console.error("Error fetching OSM suggestions:", error);
setSuggestions([]);
} finally {
setIsLoading(false);
}
};
const debouncedFetchSuggestions = useDebounce(fetchSuggestions, 300);
// Handle form submission
const onSubmit = (data: LocationFormValues) => {
console.log("New Location:", data);
// Here you can send the data to your backend (e.g., via API call)
};
const longitude = form.watch("longitude");
const latitude = form.watch("latitude");
// Helper function to construct street from OSM address
const getStreetFromAddress = (
address: OpenStreetMapLocation["address"],
): string => {
const houseNumber = address.house_number || "";
const road = address.road || "";
return (
[houseNumber, road].filter(Boolean).join(" ").trim() ||
"Unknown Street"
);
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* Autocomplete Input */}
<Command className="rounded-lg border shadow-md h-40">
<CommandInput
placeholder="Écrivez pour trouver une adresse..."
value={osmQuery}
onValueChange={(value) => {
setOsmQuery(value);
debouncedFetchSuggestions(value);
}}
/>
<CommandList>
{isLoading && <CommandEmpty>Loading...</CommandEmpty>}
{!isLoading &&
suggestions.length === 0 &&
osmQuery.length < 1 && (
<CommandEmpty>No results found.</CommandEmpty>
)}
{!isLoading && suggestions.length > 0 && (
<CommandGroup heading="Suggestions">
{suggestions.map((suggestion) => (
<CommandItem
key={suggestion.place_id}
onSelect={() => {
const address = suggestion.address;
form.setValue(
"street",
getStreetFromAddress(address),
);
form.setValue(
"city",
address.city ||
address.town ||
address.village ||
"",
);
form.setValue(
"postalCode",
address.postcode || "",
);
form.setValue(
"latitude",
parseFloat(suggestion.lat),
);
form.setValue(
"longitude",
parseFloat(suggestion.lon),
);
}}
>
{suggestion.display_name}
</CommandItem>
))}
</CommandGroup>
)}
</CommandList>
</Command>
{/* Editable Fields */}
<FormField
control={form.control}
name="street"
render={({ field }) => (
<FormItem>
<FormLabel>Rue</FormLabel>
<FormControl>
<Input
placeholder="Entrer une rue"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="city"
render={({ field }) => (
<FormItem>
<FormLabel>Ville</FormLabel>
<FormControl>
<Input
placeholder="Entrer une ville"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="postalCode"
render={({ field }) => (
<FormItem>
<FormLabel>Code postal</FormLabel>
<FormControl>
<Input
placeholder="Entrer un code postal"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* OSM Embed Map Preview */}
{latitude && longitude && (
<div className="mt-6">
<h3 className="text-lg font-semibold mb-2">
Prévisualisation
</h3>
<div className="h-[300px] w-full rounded-lg border overflow-hidden">
<iframe
width="100%"
height="100%"
src={getOsmEmbedUrl(latitude, longitude)}
title="OpenStreetMap Preview"
/>
</div>
</div>
)}
</form>
</Form>
);
};