Restore guest city summaries and refetch missing Pro details
This commit is contained in:
@@ -142,6 +142,13 @@ export function DetailPanel() {
|
|||||||
: null,
|
: null,
|
||||||
[store.cities, store.selectedCity],
|
[store.cities, store.selectedCity],
|
||||||
);
|
);
|
||||||
|
const selectedSummary = useMemo(
|
||||||
|
() =>
|
||||||
|
store.selectedCity
|
||||||
|
? store.citySummariesByName[store.selectedCity] || null
|
||||||
|
: null,
|
||||||
|
[store.citySummariesByName, store.selectedCity],
|
||||||
|
);
|
||||||
const isPro = store.proAccess.subscriptionActive;
|
const isPro = store.proAccess.subscriptionActive;
|
||||||
const isAuthenticated = store.proAccess.authenticated;
|
const isAuthenticated = store.proAccess.authenticated;
|
||||||
const panelRef = useRef<HTMLElement | null>(null);
|
const panelRef = useRef<HTMLElement | null>(null);
|
||||||
@@ -153,12 +160,20 @@ export function DetailPanel() {
|
|||||||
store.isPanelOpen &&
|
store.isPanelOpen &&
|
||||||
Boolean(store.selectedCity) &&
|
Boolean(store.selectedCity) &&
|
||||||
!isOverlayOpen;
|
!isOverlayOpen;
|
||||||
|
const hasBasicPanelContent = Boolean(
|
||||||
|
detail || selectedSummary || selectedCityItem,
|
||||||
|
);
|
||||||
const panelDisplayName =
|
const panelDisplayName =
|
||||||
detail?.display_name ||
|
detail?.display_name ||
|
||||||
|
selectedSummary?.display_name ||
|
||||||
selectedCityItem?.display_name ||
|
selectedCityItem?.display_name ||
|
||||||
store.selectedCity ||
|
store.selectedCity ||
|
||||||
"...";
|
"...";
|
||||||
const panelRiskLevel = detail?.risk?.level || selectedCityItem?.risk_level || "low";
|
const panelRiskLevel =
|
||||||
|
detail?.risk?.level ||
|
||||||
|
selectedSummary?.risk?.level ||
|
||||||
|
selectedCityItem?.risk_level ||
|
||||||
|
"low";
|
||||||
const profileStats = useMemo(
|
const profileStats = useMemo(
|
||||||
() => (detail ? getCityProfileStats(detail, locale) : []),
|
() => (detail ? getCityProfileStats(detail, locale) : []),
|
||||||
[detail, locale],
|
[detail, locale],
|
||||||
@@ -172,6 +187,18 @@ export function DetailPanel() {
|
|||||||
[detail, locale],
|
[detail, locale],
|
||||||
);
|
);
|
||||||
const scenery = getCityScenery(detail?.name);
|
const scenery = getCityScenery(detail?.name);
|
||||||
|
const basicCurrentTemp = selectedSummary?.current?.temp;
|
||||||
|
const basicObsTime = selectedSummary?.current?.obs_time;
|
||||||
|
const basicDeb = selectedSummary?.deb?.prediction;
|
||||||
|
const basicSettlementLabel =
|
||||||
|
selectedSummary?.current?.settlement_source_label ||
|
||||||
|
selectedCityItem?.settlement_source_label ||
|
||||||
|
selectedCityItem?.settlement_source ||
|
||||||
|
(locale === "en-US" ? "Settlement source pending" : "结算口径待确认");
|
||||||
|
const basicAirportLabel =
|
||||||
|
selectedCityItem?.airport ||
|
||||||
|
selectedSummary?.icao ||
|
||||||
|
(locale === "en-US" ? "Airport pending" : "机场待确认");
|
||||||
const blurActiveElement = () => {
|
const blurActiveElement = () => {
|
||||||
if (typeof document === "undefined") return;
|
if (typeof document === "undefined") return;
|
||||||
const active = document.activeElement;
|
const active = document.activeElement;
|
||||||
@@ -340,7 +367,7 @@ export function DetailPanel() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="panel-body">
|
<div className="panel-body">
|
||||||
{!detail ? (
|
{!hasBasicPanelContent ? (
|
||||||
<section>
|
<section>
|
||||||
<div style={{ color: "var(--text-muted)", fontSize: "13px" }}>
|
<div style={{ color: "var(--text-muted)", fontSize: "13px" }}>
|
||||||
{store.loadingState.cityDetail
|
{store.loadingState.cityDetail
|
||||||
@@ -348,6 +375,69 @@ export function DetailPanel() {
|
|||||||
: t("detail.emptyHint")}
|
: t("detail.emptyHint")}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
) : !detail ? (
|
||||||
|
<>
|
||||||
|
<section className="detail-section">
|
||||||
|
<h3>{locale === "en-US" ? "City Snapshot" : "城市概览"}</h3>
|
||||||
|
<div className="detail-grid">
|
||||||
|
<div className="detail-card">
|
||||||
|
<span className="detail-label">
|
||||||
|
{locale === "en-US" ? "Current temp" : "当前温度"}
|
||||||
|
</span>
|
||||||
|
<span className="detail-value">
|
||||||
|
{basicCurrentTemp != null
|
||||||
|
? `${basicCurrentTemp}${selectedSummary?.temp_symbol || ""}`
|
||||||
|
: locale === "en-US"
|
||||||
|
? "--"
|
||||||
|
: "--"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="detail-card">
|
||||||
|
<span className="detail-label">
|
||||||
|
{locale === "en-US" ? "Observed" : "观测时间"}
|
||||||
|
</span>
|
||||||
|
<span className="detail-value">
|
||||||
|
{basicObsTime || (locale === "en-US" ? "Pending" : "待更新")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="detail-card">
|
||||||
|
<span className="detail-label">DEB</span>
|
||||||
|
<span className="detail-value">
|
||||||
|
{basicDeb != null
|
||||||
|
? `${basicDeb}${selectedSummary?.temp_symbol || ""}`
|
||||||
|
: locale === "en-US"
|
||||||
|
? "Pending"
|
||||||
|
: "待更新"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="detail-card">
|
||||||
|
<span className="detail-label">
|
||||||
|
{locale === "en-US" ? "Settlement" : "结算口径"}
|
||||||
|
</span>
|
||||||
|
<span className="detail-value">{basicSettlementLabel}</span>
|
||||||
|
</div>
|
||||||
|
<div className="detail-card">
|
||||||
|
<span className="detail-label">
|
||||||
|
{locale === "en-US" ? "Airport" : "结算机场"}
|
||||||
|
</span>
|
||||||
|
<span className="detail-value">{basicAirportLabel}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="detail-section">
|
||||||
|
<div className="detail-card">
|
||||||
|
<span className="detail-label">
|
||||||
|
{locale === "en-US" ? "Pro features" : "Pro 功能"}
|
||||||
|
</span>
|
||||||
|
<span className="detail-value" style={{ fontSize: "15px" }}>
|
||||||
|
{locale === "en-US"
|
||||||
|
? "Intraday analysis, history reconciliation, and deeper structure signals require Pro."
|
||||||
|
: "今日日内分析、历史对账和更深入的结构信号需要 Pro。"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<section className="detail-scenery-card">
|
<section className="detail-scenery-card">
|
||||||
|
|||||||
@@ -344,6 +344,39 @@ export function DashboardStoreProvider({
|
|||||||
return detail;
|
return detail;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (proAccess.loading) return;
|
||||||
|
if (!selectedCity) return;
|
||||||
|
if (!isPanelOpen) return;
|
||||||
|
if (!proAccess.authenticated || !proAccess.subscriptionActive) return;
|
||||||
|
if (cityDetailsByName[selectedCity]) return;
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
setLoadingState((current) => ({ ...current, cityDetail: true }));
|
||||||
|
void ensureCityDetail(selectedCity, false)
|
||||||
|
.then((detail) => {
|
||||||
|
if (cancelled) return;
|
||||||
|
setSelectedForecastDate(detail.local_date);
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
if (cancelled) return;
|
||||||
|
setLoadingState((current) => ({ ...current, cityDetail: false }));
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
cityDetailsByName,
|
||||||
|
ensureCityDetail,
|
||||||
|
isPanelOpen,
|
||||||
|
proAccess.authenticated,
|
||||||
|
proAccess.loading,
|
||||||
|
proAccess.subscriptionActive,
|
||||||
|
selectedCity,
|
||||||
|
]);
|
||||||
|
|
||||||
const ensureCityMarketScan = async (
|
const ensureCityMarketScan = async (
|
||||||
cityName: string,
|
cityName: string,
|
||||||
force = false,
|
force = false,
|
||||||
@@ -526,6 +559,19 @@ export function DashboardStoreProvider({
|
|||||||
await refreshProAccess();
|
await refreshProAccess();
|
||||||
}
|
}
|
||||||
const access = proAccessRef.current;
|
const access = proAccessRef.current;
|
||||||
|
if (!access.authenticated || !access.subscriptionActive) {
|
||||||
|
if (!citySummariesRef.current[cityName]) {
|
||||||
|
try {
|
||||||
|
const summary = await dashboardClient.getCitySummary(cityName);
|
||||||
|
setCitySummariesByName((current) => ({
|
||||||
|
...current,
|
||||||
|
[cityName]: summary,
|
||||||
|
}));
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const cachedDetail = cityDetailsByName[cityName];
|
const cachedDetail = cityDetailsByName[cityName];
|
||||||
const needsDetailRefresh = hasSparseDetailCoverage(
|
const needsDetailRefresh = hasSparseDetailCoverage(
|
||||||
cachedDetail,
|
cachedDetail,
|
||||||
|
|||||||
Reference in New Issue
Block a user