Refactor task progress to unified useTaskProgress hook
Replace WebSocket-only useTaskWebSocket with useTaskProgress that provides a unified task state interface. TaskIndicator no longer manages its own polling or auth — it receives task state from the parent via props. Rename wsTasks prop to tasks throughout.
This commit is contained in:
parent
3616e678ac
commit
2d86213db5
6 changed files with 130 additions and 363 deletions
|
|
@ -17,13 +17,16 @@ import { Sheet, SheetContent, SheetTrigger } from './components/ui/sheet';
|
|||
import { Button } from './components/ui/button';
|
||||
import { Filter } from 'lucide-react';
|
||||
import type { GeoJSONFeatureCollection, PropertyProperties, PropertyFeature, POI, POITravelFilter } from '@/types';
|
||||
import { refreshListings, fetchTasksForUser, streamListingGeoJSON, fetchUserPOIs, type StreamingProgress } from '@/services';
|
||||
import { refreshListings, streamListingGeoJSON, fetchUserPOIs, type StreamingProgress } from '@/services';
|
||||
import { poiMetricPropertyName, injectPoiMetricProperty } from '@/utils/poiUtils';
|
||||
import { useTaskWebSocket } from '@/hooks/useTaskWebSocket';
|
||||
import { useTaskProgress } from '@/hooks/useTaskProgress';
|
||||
|
||||
function isTerminalStatus(status: string): boolean {
|
||||
return status === 'SUCCESS' || status === 'FAILURE' || status === 'REVOKED';
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [listingData, setListingData] = useState<GeoJSONFeatureCollection | null>(null);
|
||||
const [taskID, setTaskID] = useState<string | null>(null);
|
||||
const [user, setUser] = useState<AuthUser | null>(null);
|
||||
const [queryParameters, setQueryParameters] = useState<ParameterValues | null>(null);
|
||||
const [submitError, setSubmitError] = useState<string | null>(null);
|
||||
|
|
@ -44,8 +47,26 @@ function App() {
|
|||
const [poiTravelFilters, setPoiTravelFilters] = useState<Record<number, POITravelFilter>>({});
|
||||
const [currentMetric, setCurrentMetric] = useState<Metric>(DEFAULT_FILTER_VALUES.metric);
|
||||
|
||||
// WebSocket-based real-time task progress
|
||||
const { tasks: wsTasks, isConnected: wsConnected, subscribe: wsSubscribe } = useTaskWebSocket(user);
|
||||
// Explicit task ID set by fetch-data action (to track as "active")
|
||||
const [explicitTaskId, setExplicitTaskId] = useState<string | null>(null);
|
||||
|
||||
// Unified task progress: WS primary, polling fallback
|
||||
const { tasks, isConnected, subscribe, cancelTask, clearAllTasks } = useTaskProgress(user);
|
||||
|
||||
// Derive activeTaskId: explicit ID if set, else most recent non-terminal task
|
||||
const activeTaskId = useMemo(() => {
|
||||
if (explicitTaskId && tasks[explicitTaskId]) return explicitTaskId;
|
||||
// Fall back to any non-terminal task
|
||||
const nonTerminal = Object.entries(tasks).filter(
|
||||
([, t]) => !isTerminalStatus(t.status),
|
||||
);
|
||||
if (nonTerminal.length > 0) return nonTerminal[0][0];
|
||||
// Fall back to explicit even if terminal (to show final status)
|
||||
if (explicitTaskId && tasks[explicitTaskId]) return explicitTaskId;
|
||||
// Show most recent task if any
|
||||
const allIds = Object.keys(tasks);
|
||||
return allIds.length > 0 ? allIds[allIds.length - 1] : null;
|
||||
}, [explicitTaskId, tasks]);
|
||||
|
||||
// Ref to track accumulated features during streaming
|
||||
const accumulatedFeaturesRef = useRef<PropertyFeature[]>([]);
|
||||
|
|
@ -77,17 +98,6 @@ function App() {
|
|||
setUser(passkeyUser);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
fetchTasksForUser(user).then((tasks) => {
|
||||
if (tasks && tasks.length > 0) {
|
||||
setTaskID(tasks[0]);
|
||||
}
|
||||
});
|
||||
}, [user, taskID]);
|
||||
|
||||
// Load user's POIs
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
|
|
@ -235,6 +245,10 @@ function App() {
|
|||
}
|
||||
}, [queryParameters, loadListings]);
|
||||
|
||||
const handleTaskCancelled = useCallback(() => {
|
||||
setExplicitTaskId(null);
|
||||
}, []);
|
||||
|
||||
if (!user) {
|
||||
return <LoginModal isOpen={user === null} onPasskeyLogin={handlePasskeyLogin} />;
|
||||
}
|
||||
|
|
@ -248,8 +262,8 @@ function App() {
|
|||
setIsLoading(true);
|
||||
try {
|
||||
const data = await refreshListings(user!, parameters);
|
||||
setTaskID(data.task_id);
|
||||
if (data.task_id) wsSubscribe(data.task_id);
|
||||
setExplicitTaskId(data.task_id);
|
||||
if (data.task_id) subscribe(data.task_id);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
setSubmitError(error.message);
|
||||
|
|
@ -347,13 +361,9 @@ function App() {
|
|||
);
|
||||
};
|
||||
|
||||
const handleTaskCancelled = () => {
|
||||
setTaskID(null);
|
||||
};
|
||||
|
||||
const handlePOITaskCreated = (taskId: string) => {
|
||||
setTaskID(taskId);
|
||||
if (taskId) wsSubscribe(taskId);
|
||||
setExplicitTaskId(taskId);
|
||||
if (taskId) subscribe(taskId);
|
||||
// Refresh POI list in case new ones were created
|
||||
if (user) {
|
||||
fetchUserPOIs(user).then(setUserPOIs).catch(() => {});
|
||||
|
|
@ -379,12 +389,18 @@ function App() {
|
|||
{/* Header */}
|
||||
<Header
|
||||
user={user}
|
||||
taskID={taskID}
|
||||
onTaskCancelled={handleTaskCancelled}
|
||||
tasks={tasks}
|
||||
activeTaskId={activeTaskId}
|
||||
isConnected={isConnected}
|
||||
onCancelTask={cancelTask}
|
||||
onClearAllTasks={async () => {
|
||||
const result = await clearAllTasks();
|
||||
if (result) {
|
||||
handleTaskCancelled();
|
||||
}
|
||||
return result;
|
||||
}}
|
||||
onTaskCompleted={handleTaskCompleted}
|
||||
wsTasks={wsTasks}
|
||||
wsConnected={wsConnected}
|
||||
wsSubscribe={wsSubscribe}
|
||||
/>
|
||||
|
||||
{/* Main content area */}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue