The scan terminal had grown into overlapping CSS, request-state, AI-provider, and city-card data responsibilities. This refactor separates those boundaries without changing product behavior: CSS modules are split by surface, city AI prompt/provider/fallback logic is isolated, and scan terminal request state now has reusable RemoteData adapters plus business-state tests. Constraint: Preserve existing global scan-terminal class names and API responses during the refactor Constraint: No new dependencies; keep this as a file-boundary cleanup Rejected: Introduce React Query now | higher migration risk than the requested lightweight query-client path Rejected: Rewrite AI stream behavior | progressive/fallback states are product-sensitive and were only adapter-split Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep AI stream state changes covered by business snapshots before changing fallback/cache wording Tested: npm run test:business; npx tsc --noEmit; npm run build; python pytest -q; ruff check; py_compile targeted city AI modules Not-tested: Live DeepSeek provider network replay and browser visual QA
76 lines
1.9 KiB
TypeScript
76 lines
1.9 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
import { scanTerminalClient } from "@/components/dashboard/scan-terminal/scan-terminal-client";
|
|
import { useRemoteDataQuery } from "@/components/dashboard/scan-terminal/use-remote-data-query";
|
|
import type { CityDetail, MarketScan } from "@/lib/dashboard-types";
|
|
import {
|
|
deriveCityMarketScanView,
|
|
resolveCityMarketScanSnapshot,
|
|
writeCachedCityMarketScan,
|
|
} from "./market-scan-state";
|
|
|
|
export function useCityMarketScan({
|
|
detail,
|
|
detailCityName,
|
|
enabled = true,
|
|
}: {
|
|
detail: CityDetail | null;
|
|
detailCityName: string;
|
|
enabled?: boolean;
|
|
}) {
|
|
const {
|
|
remote: marketRemote,
|
|
reset: resetMarketRemote,
|
|
run: runMarketScanQuery,
|
|
setSuccess: setMarketScanSuccess,
|
|
} = useRemoteDataQuery<MarketScan>();
|
|
|
|
useEffect(() => {
|
|
const snapshot = resolveCityMarketScanSnapshot({
|
|
detail,
|
|
detailCityName,
|
|
enabled,
|
|
});
|
|
if (snapshot.action === "reset") {
|
|
resetMarketRemote();
|
|
return;
|
|
}
|
|
if (snapshot.action === "success") {
|
|
setMarketScanSuccess(snapshot.payload);
|
|
if (snapshot.shouldWriteCache) {
|
|
writeCachedCityMarketScan(snapshot.cacheKey, snapshot.payload);
|
|
}
|
|
return;
|
|
}
|
|
void runMarketScanQuery({
|
|
request: (signal) =>
|
|
scanTerminalClient.getMarketScan(detailCityName, {
|
|
lite: false,
|
|
signal,
|
|
targetDate: detail?.local_date || null,
|
|
}),
|
|
showLoading: true,
|
|
onSuccess: (payload) => {
|
|
if (payload) {
|
|
writeCachedCityMarketScan(snapshot.cacheKey, payload);
|
|
}
|
|
},
|
|
});
|
|
}, [
|
|
detail,
|
|
detailCityName,
|
|
enabled,
|
|
resetMarketRemote,
|
|
runMarketScanQuery,
|
|
setMarketScanSuccess,
|
|
]);
|
|
|
|
const { marketScan, marketStatus } = deriveCityMarketScanView({
|
|
detailMarketScan: detail?.market_scan,
|
|
marketRemote,
|
|
});
|
|
|
|
return { marketRemote, marketScan, marketStatus };
|
|
}
|