diff --git a/src/components/assets/missing-location/TagMissingLocationDialog/TagMissingLocationDialog.tsx b/src/components/assets/missing-location/TagMissingLocationDialog/TagMissingLocationDialog.tsx index d100f3b..a57f729 100644 --- a/src/components/assets/missing-location/TagMissingLocationDialog/TagMissingLocationDialog.tsx +++ b/src/components/assets/missing-location/TagMissingLocationDialog/TagMissingLocationDialog.tsx @@ -12,6 +12,7 @@ import { IPlace } from "@/types/common"; import React, { useState } from "react"; import TagMissingLocationSearchAndAdd from "./TagMissingLocationSearchAndAdd"; import TagMissingLocationSearchLatLong from "./TagMissingLocationSearchLatLong"; +import TagMissingLocationOSMSearchAndAdd from "./TagMissingLocationOSMSearchAndAdd"; import dynamic from "next/dynamic"; import { useMissingLocationContext } from "@/contexts/MissingLocationContext"; @@ -25,15 +26,14 @@ interface ITagMissingLocationDialogProps { export default function TagMissingLocationDialog({ onSubmit, }: ITagMissingLocationDialogProps) { - const {selectedIds} = useMissingLocationContext(); + const { selectedIds } = useMissingLocationContext(); const [open, setOpen] = useState(false); - const [mapPosition,setMapPosition] = useState({ - latitude: 48.0, - longitude: 16.0, - name: "home" + const [mapPosition, setMapPosition] = useState({ + latitude: 48.210033, + longitude: 16.363449, + name: "Vienna, Austria" }); - - + return ( @@ -46,20 +46,21 @@ export default function TagMissingLocationDialog({ Tagging a location will add the location to the selected assets. - - - Search and Pick - - Lat & Long - + + + Immich Search + OSM Search + Lat & Long Map + + + - +
diff --git a/src/components/assets/missing-location/TagMissingLocationDialog/TagMissingLocationOSMSearchAndAdd.tsx b/src/components/assets/missing-location/TagMissingLocationDialog/TagMissingLocationOSMSearchAndAdd.tsx new file mode 100644 index 0000000..e064ac2 --- /dev/null +++ b/src/components/assets/missing-location/TagMissingLocationDialog/TagMissingLocationOSMSearchAndAdd.tsx @@ -0,0 +1,188 @@ +import ErrorBlock from "@/components/shared/ErrorBlock"; +import { Button } from "@/components/ui/button"; + +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import Loader from "@/components/ui/loader"; +import { useToast } from "@/components/ui/use-toast"; +import { cn } from "@/lib/utils"; +import { IPlace } from "@/types/common"; +import axios from "axios"; +import { Check } from "lucide-react"; +import React, { useRef, useState } from "react"; + +interface ITagMissingLocationSearchAndAddProps { + onSubmit: (place: IPlace) => Promise; + onOpenChange: (open: boolean) => void; + location: IPlace; + onLocationChange: (place: IPlace) => void; +} + +export default function TagMissingLocationOSMSearchAndAdd( + { + onSubmit, + onOpenChange, + location, + onLocationChange + }: ITagMissingLocationSearchAndAddProps +) { + const { toast } = useToast(); + const [searchedPlaces, setSearchedPlaces] = useState(null); + const searchTimer = useRef(); + const [loading, setLoading] = useState(false); + const [submitting, setSubmitting] = useState(false); + const [selectedPlace, setSelectedPlace] = useState(null); + + const searchPlaces = async (value: string) => { + try { + const response = await axios.get("https://nominatim.openstreetmap.org/search", { + params: { + q: value, + format: "json", // Response in JSON format + addressdetails: 1, // Include address details + limit: 5, // Limit the results to 1 + }, + }); + + if (response.data.length > 0) { + const data = (response.data) as Array; + + const places = data.map((result): IPlace => ({ + name: result.display_name as string, // Full address or place name + latitude: parseFloat(result.lat), + longitude: parseFloat(result.lon), + })); + + return places; + } else { + console.log("No results found for the location."); + return null; + } + } catch (error) { + console.error("Error fetching coordinates:", error); + return null; + } + } + + const handleSearch = (value: string) => { + if (searchTimer.current) { + clearTimeout(searchTimer.current); + } + + searchTimer.current = setTimeout(() => { + if (!value || !value.trim().length) { + setSearchedPlaces(null); + return; + } + setSelectedPlace(null); + setSearchedPlaces([]); + setLoading(true); + if (!value) { + setSearchedPlaces([]); + setLoading(false); + return; + } + + return searchPlaces(value) + .then(setSearchedPlaces) + .finally(() => { + setLoading(false); + }); + }, 500); + }; + + const handleSelect = (place: IPlace) => { + if (selectedPlace && selectedPlace.name === place.name) { + setSelectedPlace(null); + } else { + setSelectedPlace(place); + debugger; + onLocationChange(place); + } + }; + + const handleSubmit = () => { + if (!selectedPlace) { + return; + } + + setSubmitting(true); + onSubmit(selectedPlace) + .then(() => { + toast({ + title: "Location updated", + description: "Location updated successfully", + }); + }) + .then(() => { + onOpenChange(false); + setSelectedPlace(null); + }) + .catch(() => { + toast({ + title: "Error", + description: "Error updating location", + }); + }) + .finally(() => { + setSubmitting(false); + }); + }; + + + return ( +
+
+ + { + handleSearch(e.target.value); + }} + /> +
+
+ {loading && } + {!loading && searchedPlaces && searchedPlaces.length === 0 && ( + + )} + {!loading && searchedPlaces && searchedPlaces.length > 0 && ( +
+ {searchedPlaces.map((place) => ( +
handleSelect(place)} + className={cn( + "hover:bg-gray-300 flex justify-between items-center px-2 py-1 rounded-lg cursor-pointer", + { + "bg-gray-300": + selectedPlace && selectedPlace.name === place.name, + } + )} + > +
+

{place.name}

+ + {place.latitude}, {place.longitude} + +
+ {selectedPlace && selectedPlace.name === place.name && ( + + )} +
+ ))} +
+ )} +
+
+ +
+
+ ) +} diff --git a/src/components/assets/missing-location/TagMissingLocationDialog/TagMissingLocationSearchLatLong.tsx b/src/components/assets/missing-location/TagMissingLocationDialog/TagMissingLocationSearchLatLong.tsx index eccee6b..d1b75f8 100644 --- a/src/components/assets/missing-location/TagMissingLocationDialog/TagMissingLocationSearchLatLong.tsx +++ b/src/components/assets/missing-location/TagMissingLocationDialog/TagMissingLocationSearchLatLong.tsx @@ -1,3 +1,4 @@ + import ErrorBlock from "@/components/shared/ErrorBlock"; import { Button } from "@/components/ui/button"; diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx index 01d7e92..61fa83c 100644 --- a/src/components/ui/dialog.tsx +++ b/src/components/ui/dialog.tsx @@ -41,7 +41,7 @@ const DialogContent = React.forwardRef<