"use client"; import { useCallback, useEffect, useRef } from "react"; import { ActionIcon, Alert, Anchor, Button, Fieldset, Group, Loader, NumberInput, Stack, Table, Text, TextInput, Tooltip, } from "@mantine/core"; import { IconClick, IconListSearch } from "@tabler/icons-react"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; import { createModal, useModalAction } from "@homarr/modals"; import { useCurrentIntlLocale, useI18n } from "@homarr/translation/client"; import { formatLocalizedCompactNumber } from "../common/locale"; import type { OptionLocation } from "../options"; import type { CommonWidgetInputProps } from "./common"; import { useWidgetInputTranslation } from "./common"; import { useFormContext } from "./form"; export const WidgetLocationInput = ({ property, kind, options }: CommonWidgetInputProps<"location">) => { const t = useWidgetInputTranslation(kind, property); const tLocation = useI18n("widget.common.location"); const form = useFormContext(); const { openModal } = useModalAction(LocationSearchModal); const fieldPath = `options.${property}`; const inputProps = form.getInputProps(fieldPath); const value = isOptionLocation(inputProps.value) ? inputProps.value : options.defaultValue; const nameInputProps = form.getInputProps(`options.${property}.name`); const latitudeInputProps = form.getInputProps(`options.${property}.latitude`); const longitudeInputProps = form.getInputProps(`options.${property}.longitude`); const selectionEnabled = value.name.length > 1; const initializedFieldPathRef = useRef(null); useEffect(() => { if (initializedFieldPathRef.current === fieldPath) return; initializedFieldPathRef.current = fieldPath; if (isOptionLocation(inputProps.value)) return; form.setFieldValue(fieldPath, options.defaultValue); }, [fieldPath, form, inputProps.value, options.defaultValue]); const handleChange = inputProps.onChange as LocationOnChange; const unknownLocation = tLocation("unknownLocation"); const onLocationSelect = useCallback( (location: OptionLocation) => { handleChange(location); form.clearFieldError(`options.${property}.latitude`); form.clearFieldError(`options.${property}.longitude`); }, [form, handleChange, property], ); const onSearch = useCallback(() => { if (!selectionEnabled) return; openModal({ query: value.name, onLocationSelect, }); }, [selectionEnabled, value.name, onLocationSelect, openModal]); form.watch(`options.${property}.latitude`, ({ value }) => { if (typeof value !== "number") return; form.setFieldValue(`options.${property}.name`, unknownLocation); }); form.watch(`options.${property}.longitude`, ({ value }) => { if (typeof value !== "number") return; form.setFieldValue(`options.${property}.name`, unknownLocation); }); return (
); }; const isOptionLocation = (value: unknown): value is OptionLocation => typeof value === "object" && value !== null && "name" in value && typeof value.name === "string" && "latitude" in value && typeof value.latitude === "number" && "longitude" in value && typeof value.longitude === "number"; type LocationOnChange = ( location: Pick & { latitude: OptionLocation["latitude"] | ""; longitude: OptionLocation["longitude"] | ""; }, ) => void; interface LocationSearchInnerProps { query: string; onLocationSelect: (location: OptionLocation) => void; } const LocationSearchModal = createModal(({ actions, innerProps }) => { const t = useI18n("widget.common.location.table"); const tCommon = useI18n("common"); const { data, isPending, error } = clientApi.location.searchCity.useQuery({ query: innerProps.query, }); if (error) { return ( {tCommon("action.tryAgain")} ); } return ( {t("header.city")} {t("header.country")} {t("header.coordinates")} {t("header.population")} {isPending && ( )} {data?.results.map((city) => ( ))}
); }).withOptions({ defaultTitle(t) { return t("widget.common.location.search"); }, size: "xl", }); interface LocationSearchTableRowProps { city: RouterOutputs["location"]["searchCity"]["results"][number]; onLocationSelect: (location: OptionLocation) => void; closeModal: () => void; } const LocationSelectTableRow = ({ city, onLocationSelect, closeModal }: LocationSearchTableRowProps) => { const t = useI18n("widget.common.location.table"); const locale = useCurrentIntlLocale(); const onSelect = useCallback(() => { onLocationSelect({ name: city.name, latitude: city.latitude, longitude: city.longitude, }); closeModal(); }, [city, onLocationSelect, closeModal]); return ( {city.name} {city.country} {city.latitude}, {city.longitude} {city.population ? ( {formatLocalizedCompactNumber(city.population, locale)} ) : ( {t("population.fallback")} )} ); };