/
Dgunamel
/
META_SCAN
Обзор
Документация
Войти
/
Dgunamel
/
META_SCAN
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
dev
src/lib/useLiveData.ts
173 строки
5 KB
studio-agent
Squash 'studio-workflow-6eb028e2-b353-4d1a-ae22-3375fedd4076' into dev
30 июл 2026, 00:22
30 июл 2026, 00:22
f3c9a96
Код
Авторство
О чём код?
"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { DEFAULT_LOCATIONS, fetchAll, getLocation, reverseGeocode, type Location, type WeatherData, } from "@/lib/weather"; export type GeoStatus = "idle" | "pending" | "ok" | "denied" | "unresolved"; interface UseLiveDataResult { data: WeatherData | null; location: Location | null; loading: boolean; refreshing: boolean; error: string | null; locations: Location[]; switchLocation: (loc: Location) => void; tryGeo: () => void; refresh: () => void; geoStatus: GeoStatus; geoDistanceKm: number | null; } const GEO_STORAGE_KEY = "metascan:geo:v1"; interface StoredGeo { lat: number; lon: number; name: string; distanceKm: number; } function loadStoredGeo(): Location | null { if (typeof window === "undefined") return null; try { const raw = window.localStorage.getItem(GEO_STORAGE_KEY); if (!raw) return null; const parsed: StoredGeo = JSON.parse(raw); if (typeof parsed.lat !== "number" || typeof parsed.lon !== "number") return null; return { lat: parsed.lat, lon: parsed.lon, name: parsed.name, fromGeo: true, }; } catch { return null; } } function saveStoredGeo(loc: Location, distanceKm: number) { if (typeof window === "undefined") return; try { const payload: StoredGeo = { lat: loc.lat, lon: loc.lon, name: loc.name ?? "", distanceKm, }; window.localStorage.setItem(GEO_STORAGE_KEY, JSON.stringify(payload)); } catch {} } function clearStoredGeo() { if (typeof window === "undefined") return; try { window.localStorage.removeItem(GEO_STORAGE_KEY); } catch {} } function formatCoords(lat: number, lon: number): string { return `${lat.toFixed(3)}, ${lon.toFixed(3)}`; } export function useLiveData(): UseLiveDataResult { const [data, setData] = useState<WeatherData | null>(null); const [location, setLocation] = useState<Location | null>(null); const [geoLocation, setGeoLocation] = useState<Location | null>(null); const [geoDistanceKm, setGeoDistanceKm] = useState<number | null>(null); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [error, setError] = useState<string | null>(null); const [geoStatus, setGeoStatus] = useState<GeoStatus>("idle"); const requestSeq = useRef(0); const compute = useCallback(async (loc: Location, isRefresh = false) => { const seq = ++requestSeq.current; if (isRefresh) setRefreshing(true); setError(null); setLocation(loc); try { const result = await fetchAll(loc); if (seq !== requestSeq.current) return; setData(result); if (result.source === "mock") { setError("Не удалось получить актуальные данные — показаны расчётные значения"); } } catch (e) { if (seq !== requestSeq.current) return; setError(e instanceof Error ? e.message : "Ошибка загрузки"); } finally { if (seq === requestSeq.current) { setLoading(false); setRefreshing(false); } } }, []); useEffect(() => { const stored = loadStoredGeo(); if (stored) { setGeoLocation(stored); setGeoDistanceKm(0); compute(stored); } else { compute(DEFAULT_LOCATIONS[0]); } }, [compute]); const tryGeo = useCallback(() => { setGeoStatus("pending"); getLocation().then((loc) => { if (!loc) { setGeoStatus("denied"); return; } const resolved = reverseGeocode(loc.lat, loc.lon); const next: Location = resolved ? { ...loc, name: resolved.name, fromGeo: true } : { ...loc, name: formatCoords(loc.lat, loc.lon), fromGeo: true }; setGeoLocation(next); setGeoDistanceKm(resolved ? resolved.distanceKm : 0); saveStoredGeo(next, resolved ? resolved.distanceKm : 0); compute(next); setGeoStatus(resolved ? "ok" : "unresolved"); }); }, [compute]); const refresh = useCallback(() => { if (location) compute(location, true); }, [location, compute]); const locations = useMemo<Location[]>(() => { if (!geoLocation) return DEFAULT_LOCATIONS; const filtered = DEFAULT_LOCATIONS.filter( (l) => !(l.name && geoLocation.name && l.name === geoLocation.name), ); return [geoLocation, ...filtered]; }, [geoLocation]); return { data, location, loading, refreshing, error, locations, switchLocation: compute, tryGeo, refresh, geoStatus, geoDistanceKm, }; } export function clearStoredGeoLocation() { clearStoredGeo(); }