feat: add AiCityTemperatureChart component and useAiPinnedCityWorkspace hook for deep-analysis city tracking
This commit is contained in:
@@ -78,14 +78,25 @@ export const AiCityTemperatureChart = memo(function AiCityTemperatureChart({ det
|
|||||||
cityKey: string;
|
cityKey: string;
|
||||||
data: TemperatureChartData;
|
data: TemperatureChartData;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
if (computedChartData) {
|
// Use a memo so we never mutate refs during render (React anti-pattern).
|
||||||
lastChartDataRef.current = { cityKey, data: computedChartData };
|
// When cityKey changes, discard any stale cache; once computedChartData
|
||||||
}
|
// arrives, save it into the ref and use it.
|
||||||
const chartData =
|
const chartData = useMemo(() => {
|
||||||
computedChartData ||
|
if (lastChartDataRef.current && lastChartDataRef.current.cityKey !== cityKey) {
|
||||||
(lastChartDataRef.current?.cityKey === cityKey
|
// City switched — clear stale cache so the old city's chart cannot
|
||||||
? lastChartDataRef.current.data
|
// bleed into the new city card while its detail is still loading.
|
||||||
: null);
|
lastChartDataRef.current = null;
|
||||||
|
}
|
||||||
|
if (computedChartData) {
|
||||||
|
lastChartDataRef.current = { cityKey, data: computedChartData };
|
||||||
|
return computedChartData;
|
||||||
|
}
|
||||||
|
if (lastChartDataRef.current?.cityKey === cityKey) {
|
||||||
|
return lastChartDataRef.current.data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [cityKey, computedChartData]);
|
||||||
const forecastLabel = locale === "en-US" ? "DEB baseline" : "DEB 原始路径";
|
const forecastLabel = locale === "en-US" ? "DEB baseline" : "DEB 原始路径";
|
||||||
const calibratedLabel =
|
const calibratedLabel =
|
||||||
locale === "en-US"
|
locale === "en-US"
|
||||||
@@ -205,6 +216,7 @@ export const AiCityTemperatureChart = memo(function AiCityTemperatureChart({ det
|
|||||||
}, [
|
}, [
|
||||||
calibratedLabel,
|
calibratedLabel,
|
||||||
chartData,
|
chartData,
|
||||||
|
cityKey,
|
||||||
detail.temp_symbol,
|
detail.temp_symbol,
|
||||||
forecastLabel,
|
forecastLabel,
|
||||||
hasCalibratedPath,
|
hasCalibratedPath,
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ export function useAiPinnedCityWorkspace({
|
|||||||
const nextCity = aiHydrationQueueRef.current.shift();
|
const nextCity = aiHydrationQueueRef.current.shift();
|
||||||
const key = normalizeCityKey(nextCity || "");
|
const key = normalizeCityKey(nextCity || "");
|
||||||
if (!nextCity || !key) continue;
|
if (!nextCity || !key) continue;
|
||||||
const existingDetail = findDetailForCity(store.cityDetailsByName, nextCity);
|
|
||||||
try {
|
try {
|
||||||
const detail = await store.ensureCityDetail(
|
const detail = await store.ensureCityDetail(
|
||||||
nextCity,
|
nextCity,
|
||||||
@@ -72,7 +71,7 @@ export function useAiPinnedCityWorkspace({
|
|||||||
void runAiHydrationQueue();
|
void runAiHydrationQueue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [store.cityDetailsByName, store.ensureCityDetail]);
|
}, [store.ensureCityDetail]);
|
||||||
|
|
||||||
const queueAiFullHydration = useCallback(
|
const queueAiFullHydration = useCallback(
|
||||||
(cityName: string) => {
|
(cityName: string) => {
|
||||||
@@ -97,6 +96,10 @@ export function useAiPinnedCityWorkspace({
|
|||||||
getLocalizedCityName(cleanName, prettyName || cleanName, locale) ||
|
getLocalizedCityName(cleanName, prettyName || cleanName, locale) ||
|
||||||
prettyName ||
|
prettyName ||
|
||||||
cleanName;
|
cleanName;
|
||||||
|
// Clear the hydration guard so that re-selecting this city always
|
||||||
|
// triggers a fresh hydration attempt (fixes second-city loading failure).
|
||||||
|
aiFullHydrationRef.current.delete(key);
|
||||||
|
aiHydrationRetriesRef.current.delete(key);
|
||||||
setAiPinnedCities((current) => {
|
setAiPinnedCities((current) => {
|
||||||
const existing = current.findIndex(
|
const existing = current.findIndex(
|
||||||
(item) => normalizeCityKey(item.cityName) === key,
|
(item) => normalizeCityKey(item.cityName) === key,
|
||||||
|
|||||||
Reference in New Issue
Block a user