diff --git a/backend/project/endpoints/projects/project_submissions_download.py b/backend/project/endpoints/projects/project_submissions_download.py
index 8e6ec83e..6ba93e93 100644
--- a/backend/project/endpoints/projects/project_submissions_download.py
+++ b/backend/project/endpoints/projects/project_submissions_download.py
@@ -51,9 +51,6 @@ def get_last_submissions_per_user(project_id):
(Submission.submission_time == latest_submissions.c.max_time)
).all()
- if not submissions:
- return {"message": "No submissions found", "url": BASE_URL}, 404
-
return {"message": "Resource fetched succesfully", "data": submissions}, 200
class SubmissionDownload(Resource):
diff --git a/backend/project/endpoints/users.py b/backend/project/endpoints/users.py
index 34e65817..cf5e054a 100644
--- a/backend/project/endpoints/users.py
+++ b/backend/project/endpoints/users.py
@@ -34,6 +34,10 @@ def get(self):
role = Role[role.upper()]
query = query.filter(userModel.role == role)
+ uid = request.args.getlist("uid")
+ if len(uid) > 0:
+ query = query.filter(userModel.uid.in_(uid))
+
users = query.all()
users = [user.to_dict() for user in users]
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index a1402c22..c3a967a0 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -22,6 +22,7 @@ import ProjectOverView from "./pages/project/projectOverview.tsx";
import { synchronizeJoinCode } from "./loaders/join-code.ts";
import { fetchMe } from "./utils/fetches/FetchMe.ts";
import {fetchProjectForm} from "./components/ProjectForm/project-form.ts";
+import loadSubmissionOverview from "./loaders/submission-overview-loader.ts";
const router = createBrowserRouter(
createRoutesFromElements(
@@ -34,10 +35,6 @@ const router = createBrowserRouter(
} loader={fetchProjectPage} />
}>
} loader={fetchProjectPage} />
- }
- />
} loader={dataLoaderCourses}/>
@@ -49,6 +46,11 @@ const router = createBrowserRouter(
element={}
loader={fetchProjectPage}
/>
+ }
+ />
}>
} loader={fetchProjectForm}/>
diff --git a/frontend/src/components/ProjectSubmissionOverview/ProjectSubmissionOverview.tsx b/frontend/src/components/ProjectSubmissionOverview/ProjectSubmissionOverview.tsx
index 0df85b87..956d2f51 100644
--- a/frontend/src/components/ProjectSubmissionOverview/ProjectSubmissionOverview.tsx
+++ b/frontend/src/components/ProjectSubmissionOverview/ProjectSubmissionOverview.tsx
@@ -1,42 +1,39 @@
-import {Box, Button, Typography} from "@mui/material";
-import {useEffect, useState} from "react";
-import {useParams} from "react-router-dom";
+import { Box, Button, Typography } from "@mui/material";
+import { useLoaderData, useParams } from "react-router-dom";
import ProjectSubmissionsOverviewDatagrid from "./ProjectSubmissionOverviewDatagrid.tsx";
-import download from 'downloadjs';
-import {useTranslation} from "react-i18next";
+import download from "downloadjs";
+import { useTranslation } from "react-i18next";
import { authenticatedFetch } from "../../utils/authenticated-fetch.ts";
-const apiUrl = import.meta.env.VITE_APP_API_HOST;
+import { Project } from "../Courses/CourseUtils.tsx";
+import { Submission } from "../../types/submission.ts";
+
+const APIURL = import.meta.env.VITE_APP_API_HOST;
/**
* @returns Overview page for submissions
*/
export default function ProjectSubmissionOverview() {
-
- const { t } = useTranslation('submissionOverview', { keyPrefix: 'submissionOverview' });
-
- useEffect(() => {
- fetchProject();
+ const { t } = useTranslation("submissionOverview", {
+ keyPrefix: "submissionOverview",
});
- const fetchProject = async () => {
- const response = await authenticatedFetch(`${apiUrl}/projects/${projectId}`)
- const jsonData = await response.json();
- setProjectTitle(jsonData["data"].title);
-
- }
+ const { projectId } = useParams<{ projectId: string }>();
+ const { projectData, submissionsWithUsers } = useLoaderData() as {
+ projectData: Project;
+ submissionsWithUsers: Submission[];
+ };
const downloadProjectSubmissions = async () => {
- await authenticatedFetch(`${apiUrl}/projects/${projectId}/submissions-download`)
- .then(res => {
+ await authenticatedFetch(
+ `${APIURL}/projects/${projectId}/submissions-download`
+ )
+ .then((res) => {
return res.blob();
})
- .then(blob => {
- download(blob, 'submissions.zip');
+ .then((blob) => {
+ download(blob, "submissions.zip");
});
- }
-
- const [projectTitle, setProjectTitle] = useState("")
- const { projectId } = useParams<{ projectId: string }>();
+ };
return (
- {projectTitle}
-
+
+ {projectData["title"]}
+
+
-
+
- )
-}
\ No newline at end of file
+ );
+}
diff --git a/frontend/src/components/ProjectSubmissionOverview/ProjectSubmissionOverviewDatagrid.tsx b/frontend/src/components/ProjectSubmissionOverview/ProjectSubmissionOverviewDatagrid.tsx
index 34f59ef8..171c7976 100644
--- a/frontend/src/components/ProjectSubmissionOverview/ProjectSubmissionOverviewDatagrid.tsx
+++ b/frontend/src/components/ProjectSubmissionOverview/ProjectSubmissionOverviewDatagrid.tsx
@@ -1,25 +1,14 @@
-import {useParams} from "react-router-dom";
-import {useEffect, useState} from "react";
-import {DataGrid, GridColDef, GridRenderCellParams} from "@mui/x-data-grid";
-import {Box, IconButton} from "@mui/material";
-import CheckCircleIcon from '@mui/icons-material/CheckCircle';
-import { green, red } from '@mui/material/colors';
-import CancelIcon from '@mui/icons-material/Cancel';
-import DownloadIcon from '@mui/icons-material/Download';
+import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
+import { Box, IconButton } from "@mui/material";
+import CheckCircleIcon from "@mui/icons-material/CheckCircle";
+import { green, red } from "@mui/material/colors";
+import CancelIcon from "@mui/icons-material/Cancel";
+import DownloadIcon from "@mui/icons-material/Download";
import download from "downloadjs";
import { authenticatedFetch } from "../../utils/authenticated-fetch";
+import { Submission } from "../../types/submission";
-const apiUrl = import.meta.env.VITE_APP_API_HOST;
-
- interface Submission {
- grading: string;
- project_id: string;
- submission_id: string;
- submission_path: string;
- submission_status: string;
- submission_time: string;
- uid: string;
- }
+const APIURL = import.meta.env.VITE_APP_API_HOST;
/**
* @returns unique id for datarows
@@ -29,68 +18,60 @@ function getRowId(row: Submission) {
}
const fetchSubmissionsFromUser = async (submission_id: string) => {
- await authenticatedFetch(`${apiUrl}/submissions/${submission_id}/download`)
- .then(res => {
+ await authenticatedFetch(`${APIURL}/submissions/${submission_id}/download`)
+ .then((res) => {
return res.blob();
})
- .then(blob => {
+ .then((blob) => {
download(blob, `submissions_${submission_id}.zip`);
});
-}
+};
const columns: GridColDef[] = [
- { field: 'submission_id', headerName: 'Submission ID', flex: 0.4 },
- { field: 'uid', headerName: 'Student ID', width: 160, flex: 0.4 },
+ { field: "submission_id", headerName: "Submission ID", flex: 0.4 },
+ { field: "display_name", headerName: "Student", width: 160, flex: 0.4 },
{
- field: 'grading',
- headerName: 'Grading',
+ field: "grading",
+ headerName: "Grading",
editable: true,
- flex: 0.2
+ flex: 0.2,
},
{
- field: 'submission_status',
- headerName: 'Status',
+ field: "submission_status",
+ headerName: "Status",
renderCell: (params: GridRenderCellParams) => (
<>
- {
- params.row.submission_status === "SUCCESS" ? (
-
- ) :
- }
+ {params.row.submission_status === "SUCCESS" ? (
+
+ ) : (
+
+ )}
>
- )
+ ),
},
{
- field: 'submission_path',
- headerName: 'Download',
+ field: "submission_path",
+ headerName: "Download",
renderCell: (params: GridRenderCellParams) => (
- fetchSubmissionsFromUser(params.row.submission_id)}>
+ fetchSubmissionsFromUser(params.row.submission_id)}
+ >
- )
- }];
+ ),
+ },
+];
/**
* @returns the datagrid for displaying submissiosn
*/
-export default function ProjectSubmissionsOverviewDatagrid() {
- const { projectId } = useParams<{ projectId: string }>();
- const [submissions, setSubmissions] = useState([])
-
- useEffect(() => {
- fetchLastSubmissionsByUser();
- });
-
- const fetchLastSubmissionsByUser = async () => {
- const response = await authenticatedFetch(`${apiUrl}/projects/${projectId}/latest-per-user`)
- const jsonData = await response.json();
- setSubmissions(jsonData.data);
- }
-
+export default function ProjectSubmissionsOverviewDatagrid({
+ submissions,
+}: {
+ submissions: Submission[];
+}) {
return (
-
+
- )
-}
\ No newline at end of file
+ );
+}
diff --git a/frontend/src/loaders/submission-overview-loader.ts b/frontend/src/loaders/submission-overview-loader.ts
new file mode 100644
index 00000000..2ee5ab7c
--- /dev/null
+++ b/frontend/src/loaders/submission-overview-loader.ts
@@ -0,0 +1,57 @@
+import { Params } from "react-router-dom";
+import { Me } from "../types/me";
+import { Submission } from "../types/submission";
+import { authenticatedFetch } from "../utils/authenticated-fetch";
+
+const APIURL = import.meta.env.VITE_APP_API_HOST;
+
+const fetchDisplaynameByUid = async (uids: [string]) => {
+ const uidParams = new URLSearchParams();
+ for (const uid of uids) {
+ uidParams.append("uid", uid);
+ }
+ const uidUrl = `${APIURL}/users?` + uidParams;
+ const response = await authenticatedFetch(uidUrl);
+ const jsonData = await response.json();
+
+ return jsonData.data;
+};
+
+/**
+ *
+ * @param param0 - projectId
+ * @returns - projectData and submissionsWithUsers
+ */
+export default async function loadSubmissionOverview({
+ params,
+}: {
+ params: Params;
+}) {
+ const projectId = params.projectId;
+ const projectResponse = await authenticatedFetch(
+ `${APIURL}/projects/${projectId}`
+ );
+ const projectData = (await projectResponse.json())["data"];
+
+ const overviewResponse = await authenticatedFetch(
+ `${APIURL}/projects/${projectId}/latest-per-user`
+ );
+ const jsonData = await overviewResponse.json();
+ const uids = jsonData.data.map((submission: Submission) => submission.uid);
+ const users = await fetchDisplaynameByUid(uids);
+
+ const submissionsWithUsers = jsonData.data.map((submission: Submission) => {
+ // Find the corresponding user for this submission's UID
+ const user = users.find((user: Me) => user.uid === submission.uid);
+ // Add user information to the submission
+ return {
+ ...submission,
+ display_name: user.display_name,
+ };
+ });
+
+ return {
+ projectData,
+ submissionsWithUsers,
+ };
+}
diff --git a/frontend/src/types/submission.ts b/frontend/src/types/submission.ts
index 4522dbac..4eab356f 100644
--- a/frontend/src/types/submission.ts
+++ b/frontend/src/types/submission.ts
@@ -2,4 +2,5 @@ export interface Submission {
submission_id: string;
submission_time: string;
submission_status: string;
+ uid: string;
}