From f63f4e5f10263ff48eade4e4268061fbf4fe077a Mon Sep 17 00:00:00 2001 From: Dylan De Faoite Date: Tue, 3 Mar 2026 18:13:51 +0000 Subject: [PATCH] feat(frontend): add status page for loading dataset --- frontend/src/pages/DatasetStatus.tsx | 111 +++++++++++++++++++++++++++ frontend/src/pages/Upload.tsx | 6 +- 2 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 frontend/src/pages/DatasetStatus.tsx diff --git a/frontend/src/pages/DatasetStatus.tsx b/frontend/src/pages/DatasetStatus.tsx new file mode 100644 index 0000000..04837ca --- /dev/null +++ b/frontend/src/pages/DatasetStatus.tsx @@ -0,0 +1,111 @@ +import { useEffect, useMemo, useState } from "react"; +import axios from "axios"; +import { useNavigate, useParams } from "react-router-dom"; +import StatsStyling from "../styles/stats_styling"; + +type DatasetStatusResponse = { + status?: "processing" | "complete" | "error"; + status_message?: string | null; + completed_at?: string | null; +}; + +const styles = StatsStyling; +const API_BASE_URL = "http://localhost:5000"; + +const DatasetStatusPage = () => { + const navigate = useNavigate(); + const { datasetId } = useParams<{ datasetId: string }>(); + const [loading, setLoading] = useState(true); + const [status, setStatus] = useState("processing"); + const [statusMessage, setStatusMessage] = useState(""); + const parsedDatasetId = useMemo(() => Number(datasetId), [datasetId]); + + useEffect(() => { + if (!Number.isInteger(parsedDatasetId) || parsedDatasetId <= 0) { + setLoading(false); + setStatus("error"); + setStatusMessage("Invalid dataset id."); + return; + } + + let pollTimer: number | undefined; + + const pollStatus = async () => { + try { + const response = await axios.get( + `${API_BASE_URL}/dataset/${parsedDatasetId}/status` + ); + + const nextStatus = response.data.status ?? "processing"; + setStatus(nextStatus); + setStatusMessage(String(response.data.status_message ?? "")); + setLoading(false); + + if (nextStatus === "complete") { + window.setTimeout(() => { + navigate("/stats", { replace: true }); + }, 800); + } + } catch (error: unknown) { + setLoading(false); + setStatus("error"); + if (axios.isAxiosError(error)) { + const message = String(error.response?.data?.error || error.message || "Request failed"); + setStatusMessage(message); + } else { + setStatusMessage("Unable to fetch dataset status."); + } + } + }; + + void pollStatus(); + pollTimer = window.setInterval(() => { + if (status !== "complete" && status !== "error") { + void pollStatus(); + } + }, 2000); + + return () => { + if (pollTimer) { + window.clearInterval(pollTimer); + } + }; + }, [navigate, parsedDatasetId, status]); + + const isProcessing = loading || status === "processing"; + const isError = status === "error"; + + return ( +
+
+
+

+ {isProcessing ? "Processing dataset..." : isError ? "Dataset processing failed" : "Dataset ready"} +

+ +

+ {isProcessing && + "Your dataset is being analyzed. This page will redirect to stats automatically once complete."} + {isError && "There was an issue while processing your dataset. Please review the error details."} + {status === "complete" && "Processing complete. Redirecting to your stats now..."} +

+ +
+ {statusMessage || (isProcessing ? "Waiting for updates from the worker queue..." : "No details provided.")} +
+
+
+
+ ); +}; + +export default DatasetStatusPage; diff --git a/frontend/src/pages/Upload.tsx b/frontend/src/pages/Upload.tsx index 712d853..47b893d 100644 --- a/frontend/src/pages/Upload.tsx +++ b/frontend/src/pages/Upload.tsx @@ -46,12 +46,14 @@ const UploadPage = () => { }, }); + const datasetId = Number(response.data.dataset_id); + setReturnMessage( - `Upload queued successfully (dataset #${response.data.dataset_id}). Redirecting to insights...` + `Upload queued successfully (dataset #${datasetId}). Redirecting to processing status...` ); setTimeout(() => { - navigate("/stats"); + navigate(`/dataset/${datasetId}/status`); }, 400); } catch (error: unknown) { setHasError(true);