feat: add global weather support
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m54s
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m54s
This commit is contained in:
@@ -2,13 +2,13 @@
|
||||
|
||||
import { useMemo, useState, type KeyboardEvent } from "react";
|
||||
import { LoaderCircle, MapPin, Search, X } from "lucide-react";
|
||||
import { CurrentLocationControl } from "@/components/weather/current-location-control";
|
||||
import { useLocationSearch } from "@/hooks/use-location-search";
|
||||
import { useWeatherStore } from "@/lib/store";
|
||||
import { findNearestSynopStation, locateSynopStations } from "@/lib/location-utils";
|
||||
import { useI18n } from "@/lib/i18n";
|
||||
import { createSelectedLocation, locateSynopStations } from "@/lib/location-utils";
|
||||
import { useWeatherStore } from "@/lib/store";
|
||||
import type { MeteoStationPosition, SynopStation } from "@/types/imgw";
|
||||
import type { SelectedLocation } from "@/types/location";
|
||||
import { CurrentLocationControl } from "@/components/weather/current-location-control";
|
||||
|
||||
export function LocationSearch({ stations, positions }: { stations: SynopStation[]; positions: MeteoStationPosition[] }) {
|
||||
const { language, t } = useI18n();
|
||||
@@ -20,10 +20,10 @@ export function LocationSearch({ stations, positions }: { stations: SynopStation
|
||||
const locatedStations = useMemo(() => locateSynopStations(stations, positions), [positions, stations]);
|
||||
const suggestions = useMemo(() => (locations ?? []).map((location) => ({
|
||||
location,
|
||||
nearest: findNearestSynopStation(location, locatedStations),
|
||||
})).filter((suggestion) => suggestion.nearest !== null), [locatedStations, locations]);
|
||||
selected: createSelectedLocation(location, locatedStations),
|
||||
})), [locatedStations, locations]);
|
||||
const showSuggestions = isFocused && query.trim().length >= 2;
|
||||
const isPreparingStations = positions.length === 0;
|
||||
const isPreparingStations = positions.length === 0 && suggestions.some(({ selected }) => selected.region === "PL" && !selected.stationName);
|
||||
|
||||
function chooseLocation(location: SelectedLocation) {
|
||||
selectLocation(location);
|
||||
@@ -32,7 +32,7 @@ export function LocationSearch({ stations, positions }: { stations: SynopStation
|
||||
}
|
||||
|
||||
function handleSearchKeyDown(event: KeyboardEvent<HTMLInputElement>) {
|
||||
const firstSuggestion = suggestions[0]?.nearest;
|
||||
const firstSuggestion = suggestions[0]?.selected;
|
||||
if (event.key !== "Enter" || !showSuggestions || !firstSuggestion) return;
|
||||
event.preventDefault();
|
||||
chooseLocation(firstSuggestion);
|
||||
@@ -66,18 +66,22 @@ export function LocationSearch({ stations, positions }: { stations: SynopStation
|
||||
{isPreparingStations ? <p className="px-3 py-4 text-sm text-muted">{t("location.preparing")}</p> :
|
||||
isError ? <p className="px-3 py-4 text-sm text-muted">{t("location.error")}</p> :
|
||||
!isFetching && !suggestions.length ? <p className="px-3 py-4 text-sm text-muted">{t("location.empty")}</p> :
|
||||
suggestions.map(({ location, nearest }) => nearest && (
|
||||
suggestions.map(({ location, selected }) => (
|
||||
<button
|
||||
type="button"
|
||||
key={location.id}
|
||||
onClick={() => chooseLocation(nearest)}
|
||||
onClick={() => chooseLocation(selected)}
|
||||
className="flex w-full items-start justify-between gap-3 rounded-card px-3 py-3 text-left transition hover:bg-surface-muted/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent"
|
||||
>
|
||||
<span>
|
||||
<span className="block text-sm font-semibold">{location.name}</span>
|
||||
<span className="mt-0.5 block text-xs text-muted">{[location.district, location.province].filter(Boolean).join(" · ")}</span>
|
||||
<span className="mt-0.5 block text-xs text-muted">{[location.admin2, location.admin1, location.country].filter(Boolean).join(" · ")}</span>
|
||||
</span>
|
||||
<span className="shrink-0 text-right text-[0.68rem] leading-5 text-muted">{t("location.nearest")}<br /><strong className="font-semibold text-foreground">{nearest.stationName} · {nearest.distanceKm} km</strong></span>
|
||||
{selected.stationName && selected.distanceKm !== null ? (
|
||||
<span className="shrink-0 text-right text-[0.68rem] leading-5 text-muted">{t("location.nearest")}<br /><strong className="font-semibold text-foreground">{selected.stationName} · {selected.distanceKm} km</strong></span>
|
||||
) : (
|
||||
<span className="shrink-0 text-right text-[0.68rem] leading-5 text-muted">{t("location.modelSource")}<br /><strong className="font-semibold text-foreground">Open-Meteo</strong></span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -86,7 +90,9 @@ export function LocationSearch({ stations, positions }: { stations: SynopStation
|
||||
<CurrentLocationControl stations={locatedStations} />
|
||||
{selectedLocation && (
|
||||
<p className="mt-3 px-1 text-xs text-muted">
|
||||
{t("location.currentSource", { location: selectedLocation.name, station: selectedLocation.stationName, distance: selectedLocation.distanceKm })}
|
||||
{selectedLocation.stationName && selectedLocation.distanceKm !== null
|
||||
? t("location.currentSource", { location: selectedLocation.name, station: selectedLocation.stationName, distance: selectedLocation.distanceKm })
|
||||
: t("location.currentSourceGlobal", { location: selectedLocation.name })}
|
||||
</p>
|
||||
)}
|
||||
<p className="mt-3 px-1 text-[0.68rem] text-muted">
|
||||
|
||||
Reference in New Issue
Block a user