parent
fc5519e744
commit
91ac3a5e36
91
web/src/app/operations/[operationId]/(styles)/page.scss
Normal file
91
web/src/app/operations/[operationId]/(styles)/page.scss
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
119
web/src/app/operations/[operationId]/page.tsx
Normal file
119
web/src/app/operations/[operationId]/page.tsx
Normal file
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -11,7 +11,7 @@ interface SubmissionPayload {
|
|||||||
AssetID: number;
|
AssetID: number;
|
||||||
}
|
}
|
||||||
interface IdResponse {
|
interface IdResponse {
|
||||||
ID: number;
|
OperationID: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SubmissionInfoPage() {
|
export default function SubmissionInfoPage() {
|
||||||
@ -49,7 +49,7 @@ export default function SubmissionInfoPage() {
|
|||||||
const id_response:IdResponse = await response.json();
|
const id_response:IdResponse = await response.json();
|
||||||
|
|
||||||
// navigate to newly created submission
|
// navigate to newly created submission
|
||||||
window.location.assign(`/submissions/${id_response.ID}`)
|
window.location.assign(`/operations/${id_response.OperationID}`)
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error submitting data:", error);
|
console.error("Error submitting data:", error);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user