diff --git a/web/src/app/operations/[operationId]/(styles)/page.scss b/web/src/app/operations/[operationId]/(styles)/page.scss new file mode 100644 index 0000000..aefa235 --- /dev/null +++ b/web/src/app/operations/[operationId]/(styles)/page.scss @@ -0,0 +1,91 @@ +.operation-status { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + background-color: #121212; + color: #e0e0e0; + + .operation-card { + width: 400px; + padding: 20px; + background: #1e1e1e; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3); + border-radius: 8px; + + h5 { + margin-bottom: 15px; + font-weight: bold; + color: #ffffff; + } + + p { + margin: 5px 0; + color: #b0b0b0; + } + } + + .status-indicator { + display: flex; + align-items: center; + gap: 8px; + font-weight: bold; + margin-top: 10px; + + &.created { + color: #ffca28; + } + &.completed { + color: #66bb6a; + } + &.failed { + color: #ef5350; + } + + .status-icon { + width: 12px; + height: 12px; + border-radius: 50%; + display: inline-block; + + &.created { + background-color: #ffca28; + } + &.completed { + background-color: #66bb6a; + } + &.failed { + background-color: #ef5350; + } + } + } + + .MuiCircularProgress-root { + color: #90caf9; + } + + .submission-button { + margin-top: 20px; + display: flex; + justify-content: center; + width: 100%; + + button { + width: 100%; + height: 60px; + background-color: #66bb6a; + color: white; + font-size: 20px; + font-weight: bold; + padding: 20px; + border: none; + border-radius: 5px; + cursor: pointer; + transition: background 0.3s; + + &:hover { + background-color: #57a05a; + } + } + } +} \ No newline at end of file diff --git a/web/src/app/operations/[operationId]/page.tsx b/web/src/app/operations/[operationId]/page.tsx new file mode 100644 index 0000000..6f0763c --- /dev/null +++ b/web/src/app/operations/[operationId]/page.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useParams, useRouter } from "next/navigation"; +import { CircularProgress, Typography, Card, CardContent, Button } from "@mui/material"; +import Webpage from "@/app/_components/webpage"; + +import "./(styles)/page.scss"; + +interface Operation { + OperationID: number; + Status: number; + StatusMessage: string; + Owner: string; + Date: number; + Path: string; + } + +export default function OperationStatusPage() { + const { operationId } = useParams(); + const router = useRouter(); + const [operation, setOperation] = useState<Operation | null>(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState<string | null>(null); + + useEffect(() => { + if (!operationId) return; + + const fetchOperation = async () => { + try { + const response = await fetch(`/api/operations/${operationId}`); + + if (!response.ok) throw new Error("Failed to fetch operation"); + + const data = await response.json(); + setOperation(data); + } catch (err: unknown) { + if (err instanceof Error) { + setError(err.message); + } else { + setError("An unknown error occurred"); + } + } finally { + setLoading(false); + } + }; + + fetchOperation(); + const interval = setInterval(fetchOperation, 5000); + + return () => clearInterval(interval); + }, [operationId]); + + const getStatusClass = (status: number) => { + switch (status) { + case 0: + return "created"; + case 1: + return "completed"; + case 2: + return "failed"; + default: + return ""; + } + }; + + const getStatusText = (status: number) => { + switch (status) { + case 0: + return "Created"; + case 1: + return "Completed"; + case 2: + return "Failed"; + default: + return "Unknown"; + } + }; + + return ( + <Webpage> + <main className="operation-status"> + {loading ? ( + <CircularProgress /> + ) : error ? ( + <Typography color="error">{error}</Typography> + ) : operation ? ( + <Card className="operation-card"> + <CardContent> + <Typography variant="h5">Operation ID: {operation.OperationID}</Typography> + <div className={`status-indicator ${getStatusClass(operation.Status)}`}> + <span className={`status-icon ${getStatusClass(operation.Status)}`} /> + {getStatusText(operation.Status)} + </div> + <Typography>Status Message: {operation.StatusMessage}</Typography> + <Typography>Owner: {operation.Owner}</Typography> + <Typography>Date: {new Date(operation.Date * 1000).toLocaleString()}</Typography> + <Typography>Path: {operation.Path}</Typography> + + {operation.Status === 1 && ( + <div className="submission-button"> + <Button + variant="contained" + color="success" + onClick={() => router.push(`/submissions/${operation.OperationID}`)} + > + View Submission + </Button> + </div> + )} + </CardContent> + </Card> + ) : ( + <Typography>No operation found.</Typography> + )} + </main> + </Webpage> + ); +} \ No newline at end of file diff --git a/web/src/app/submit/page.tsx b/web/src/app/submit/page.tsx index caaef78..a0b09ee 100644 --- a/web/src/app/submit/page.tsx +++ b/web/src/app/submit/page.tsx @@ -11,7 +11,7 @@ interface SubmissionPayload { AssetID: number; } interface IdResponse { - ID: number; + OperationID: number; } export default function SubmissionInfoPage() { @@ -49,7 +49,7 @@ export default function SubmissionInfoPage() { const id_response:IdResponse = await response.json(); // navigate to newly created submission - window.location.assign(`/submissions/${id_response.ID}`) + window.location.assign(`/operations/${id_response.OperationID}`) } catch (error) { console.error("Error submitting data:", error);