Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
499fd31
Add hylimo
c-neli-r Jan 16, 2026
9ef8dd5
Add subpages
c-neli-r Jan 16, 2026
5ce3bb4
Add Assignment modal
c-neli-r Jan 17, 2026
f1144ed
Add student assignment page
c-neli-r Jan 17, 2026
d8f6553
Add modal in content modal
c-neli-r Jan 17, 2026
c23d570
Update hylimo
c-neli-r Jan 22, 2026
dfdb95d
Fix hylimo styling
c-neli-r Jan 22, 2026
fbc7abe
Fix hylimo styling
c-neli-r Jan 22, 2026
63457a1
Add Circular Loader
c-neli-r Jan 22, 2026
a93cf7c
Add assessment modal
c-neli-r Jan 25, 2026
0342a82
Add api
c-neli-r Jan 25, 2026
cd2f27c
Add student assessment page
c-neli-r Jan 25, 2026
b4de31c
Add uml assessment to content link and edit content modal
c-neli-r Jan 25, 2026
15c84f6
Add test pages
c-neli-r Jan 25, 2026
6c3cc98
Adjust schema
c-neli-r Jan 25, 2026
5412dee
Delete unused code
c-neli-r Jan 31, 2026
a9291cc
Add assignment results component
c-neli-r Jan 31, 2026
b2c71c0
Improve add uml modal and some fixes
c-neli-r Jan 31, 2026
6ceeb3d
Add readonly mode after submit
c-neli-r Jan 31, 2026
2774967
Add uml page for students including pagination, saving and submitting…
c-neli-r Jan 31, 2026
9d607c3
Update API for uml
c-neli-r Jan 31, 2026
1ee2b79
Update schema
c-neli-r Jan 31, 2026
aa44b9c
Add uml assessment to editcontentmodal
c-neli-r Jan 31, 2026
02b08e7
Delete readonly (add it later back but currently has weird behaviour)
c-neli-r Jan 31, 2026
852e66f
Update schema and queries
ZiyaSelim Feb 2, 2026
35b2964
Add WIP lecturer View
ZiyaSelim Feb 2, 2026
19ecf4f
Make Edit content modal more organized
ZiyaSelim Feb 2, 2026
4d25686
Refactor studentview and its components
ZiyaSelim Feb 2, 2026
add654c
Change component based on user role/view
ZiyaSelim Feb 2, 2026
8e4a949
Remove background color in StudentRow
ZiyaSelim Feb 9, 2026
e15b4a3
Bug Fix: Set default attemt value to prevent buggy ui when no attempt…
c-neli-r Feb 9, 2026
ab5ef67
Add semanticModel evaluation of diagram code
ZiyaSelim Feb 9, 2026
9a5b22d
Some refactoring + save when navigating between attemps when there ar…
c-neli-r Feb 9, 2026
d785a19
Merge
c-neli-r Feb 9, 2026
e28b9e7
Add read only mode
c-neli-r Feb 10, 2026
e71ba46
Convert diagram to semanticModel
c-neli-r Feb 10, 2026
9990c15
Only calculate semantic model when submitting
c-neli-r Feb 14, 2026
e0b1509
Fix window resizing
c-neli-r Feb 14, 2026
a9ff94c
add read only mode, add resize observer, improve performance, center…
c-neli-r Feb 14, 2026
53b143c
Update schema and mutations for new UmlDiagram type instead of pure S…
ZiyaSelim Feb 16, 2026
3bfafcd
Fix null error
c-neli-r Feb 21, 2026
15343bc
Make editor larger and total points and required percentage
c-neli-r Feb 21, 2026
b08c2d6
Load initial data in uml edit lecturer
c-neli-r Feb 21, 2026
664f8cc
Improve Hylimo editor and add undo redo to sprotty
c-neli-r Feb 22, 2026
fef895e
Update List after Creating Uml Exercise
c-neli-r Feb 22, 2026
677f938
Only load modal when content is there
c-neli-r Feb 22, 2026
28a5dcd
Fix state issuess when editing
c-neli-r Feb 22, 2026
0e16790
Fix issue of hylimo not loading when in modal
c-neli-r Feb 22, 2026
fbefda2
Add Fullscreen mode
c-neli-r Feb 23, 2026
5c38ec7
Some hylimo improvements
c-neli-r Feb 23, 2026
b054caa
Add info
c-neli-r Feb 23, 2026
a8c7216
Load sprotty correctly this is kinda hacky do this better later
c-neli-r Feb 23, 2026
ada70c2
Update Exercise
c-neli-r Mar 1, 2026
8b4968a
fix submissionTab with solutionList in query
ZiyaSelim Mar 2, 2026
ea168cb
Add Hylimo download
c-neli-r Mar 28, 2026
3faa4ab
Merge branch 'FoPro_WS_UML-Assessment' of origin
c-neli-r Mar 28, 2026
dfabac5
SOme adjustments in the fullscreen for downloading
c-neli-r Mar 28, 2026
36ff498
Add grading rules
ZiyaSelim Apr 1, 2026
5f331ae
Merge branch 'FoPro_WS_UML-Assessment' of github.com:MEITREX/frontend…
ZiyaSelim Apr 1, 2026
50c15e4
Disable New Attempt and allow solution viewing
ZiyaSelim Apr 1, 2026
0963a32
Merge branch 'FoPro_WS_UML-Assessment' of https://github.com/MEITREX/…
c-neli-r Apr 27, 2026
decf32e
Add doc
c-neli-r Apr 29, 2026
96b69ce
Update schema
ZiyaSelim May 2, 2026
3a5c780
Update uml evlauation and tutor view
ZiyaSelim May 2, 2026
f90ce97
Add sw
c-neli-r May 11, 2026
d851b8d
Add lock
c-neli-r May 11, 2026
54d3256
Run prettier
c-neli-r May 12, 2026
9552996
delete unused files
c-neli-r May 12, 2026
b387fcc
fix lint error
c-neli-r May 12, 2026
5b34dc8
Update schema and change bundler
c-neli-r May 12, 2026
515d2d2
Fix eslint
c-neli-r May 12, 2026
1c772d9
fix schema
c-neli-r May 12, 2026
0ad0f1d
Run prettier
c-neli-r May 12, 2026
b4b03df
Merge remote-tracking branch 'origin/main' into FoPro_WS_UML-Assessme…
ZiyaSelim May 17, 2026
e9a29d7
Potentially fix pipeline
ZiyaSelim May 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions app/courses/[courseId]/progress/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
IconButton,
Slide,
Typography,
useTheme,
} from "@mui/material";
import { useParams } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
Expand Down Expand Up @@ -127,7 +126,7 @@ export default function LearningProgress() {

const progressItem = progressBySkillValues.get(key)!;

progressItem.progressSum += skill.skillValue.skillValue;
progressItem.progressSum += skill.skillValue?.skillValue ?? 0;
/*progressItem.averageProgressSum +=
skill.skillAllUsersStats.skillValueSum;*/
progressItem.count++;
Expand Down Expand Up @@ -262,8 +261,6 @@ export default function LearningProgress() {
sessionStorage.setItem("previousProgress", JSON.stringify([...tempMap]));
}, [course.skills, progressBySkill, uniqueCategories.length]);

const theme = useTheme();

if (uniqueCategories.length === 0) {
return (
<Typography variant="body1">
Expand Down Expand Up @@ -395,7 +392,9 @@ export default function LearningProgress() {
competencyName={category}
startProgress={Math.floor(previousCategoryProgressValue)}
endProgress={Math.floor(categoryProgressValue)}
averageProgress={/*Math.floor(categoryAverageProgressValue)*/0}
averageProgress={
/*Math.floor(categoryAverageProgressValue)*/ 0
}
color={stringToColor(category)}
onClick={() => {
setSelectedCategory(
Expand All @@ -407,7 +406,7 @@ export default function LearningProgress() {
isSelected={category === sortedCategories[selectedCategory]}
isUrgent={urgent}
showAverageProgress={showAverageProgress}
participantCount={/*maxParticipantCountForaSkill*/0}
participantCount={/*maxParticipantCountForaSkill*/ 0}
courseMemberCount={course.numberOfCourseMemberships}
openTaskCount={
filteredSuggestionsByCategory(category).length
Expand Down Expand Up @@ -507,7 +506,9 @@ export default function LearningProgress() {
small={true}
startProgress={Math.floor(previousSkillProgressValue)}
endProgress={Math.floor(skillProgressValue)}
averageProgress={/*Math.floor(skillAverageProgressValue)*/0}
averageProgress={
/*Math.floor(skillAverageProgressValue)*/ 0
}
color={stringToColor(currentSkill.skillCategory)}
onClick={() => {
const currentIndex = currentUniqueSkills.findIndex(
Expand All @@ -525,7 +526,7 @@ export default function LearningProgress() {
}
isUrgent={urgent}
showAverageProgress={showAverageProgress}
participantCount={/*maxParticipantCount*/0}
participantCount={/*maxParticipantCount*/ 0}
courseMemberCount={course.numberOfCourseMemberships}
openTaskCount={
filteredSuggestionsBySkill(currentSkill.skillName).length
Expand Down
192 changes: 192 additions & 0 deletions app/courses/[courseId]/uml/[umlId]/lecturer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
"use client";

import EditIcon from "@mui/icons-material/Edit";
import InfoIcon from "@mui/icons-material/Info";
import PeopleIcon from "@mui/icons-material/People";
import {
Box,
Button,
CircularProgress,
Snackbar,
Stack,
Tab,
Tabs,
} from "@mui/material";
import { useParams } from "next/navigation";
import { useMemo, useState } from "react";
import { useLazyLoadQuery, useMutation } from "react-relay";

import {
umlApiFindUserInfosQuery,
umlApiGetLecturerExerciseOverviewQuery,
umlApiUpdateTutorSolutionMutation,
} from "@/components/hylimo/api/UmlApi";
import { getSemanticModel } from "@/components/hylimo/semanticModelGenerator";
import { AddUMLAssignmentModal } from "@/components/uml-assignment/AddUMLAssignmentModal";
import ExerciseInfoTab from "@/components/uml-assignment/ExerciseInfoTab";
import SubmissionsTab from "@/components/uml-assignment/SubmissionsTab";

export default function LecturerUmlAssignment() {
const { umlId } = useParams();
const [tabIndex, setTabIndex] = useState(0);
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [refreshKey, setRefreshKey] = useState(0);
const [snackbar, setSnackbar] = useState({ open: false, message: "" });

const data = useLazyLoadQuery<any>(
umlApiGetLecturerExerciseOverviewQuery,
{ assessmentId: umlId },
{ fetchPolicy: "network-only", fetchKey: refreshKey }
);

const studentIds = useMemo(() => {
const ids =
data?.getUmlExerciseByAssessmentId?.studentSubmissions?.map(
(sub: any) => sub.studentId
) || [];

// Defensive: filter out empty values and duplicates before querying user infos.
return Array.from(
new Set(ids.filter((id: any) => typeof id === "string" && id.length > 0))
);
}, [data?.getUmlExerciseByAssessmentId?.studentSubmissions]);

// Fetch user infos for all students
const userInfosData = useLazyLoadQuery<any>(
umlApiFindUserInfosQuery,
{ ids: studentIds },
{ fetchPolicy: "network-only" }
);

const userInfos = useMemo(() => {
const userMap: Record<string, any> = {};
if (userInfosData?.findUserInfos) {
userInfosData.findUserInfos.forEach((user: any) => {
// Backend may return null entries when referenced users no longer exist.
if (user?.id) {
userMap[user.id] = user;
}
});
}
return userMap;
}, [userInfosData?.findUserInfos]);

const [updateTutorSolution, isUpdating] = useMutation(
umlApiUpdateTutorSolutionMutation
);

const exercise = data?.getUmlExerciseByAssessmentId;
const content = data?.findContentsByIds?.[0];

const handleUpdateTutorSolution = async (newCode: string) => {
let semanticModelJson: string | null = null;
const semanticModelResult = await getSemanticModel(newCode);
semanticModelJson = JSON.stringify(semanticModelResult);
updateTutorSolution({
variables: {
assessmentId: umlId,
tutorSolution: {
diagramCode: newCode,
semanticModel: semanticModelJson,
},
},
onCompleted: () =>
setSnackbar({
open: true,
message: "Tutor solution updated successfully!",
}),
onError: () =>
setSnackbar({
open: true,
message: "Failed to update tutor solution.",
}),
});
};

if (!exercise || !content)
return (
<Box p={4} textAlign="center">
<CircularProgress />
</Box>
);

return (
<Box sx={{ width: "100%", position: "relative" }}>
{/* Top Action Bar */}
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
mb={2}
>
<Tabs
value={tabIndex}
onChange={(_, v) => setTabIndex(v)}
color="primary"
>
<Tab icon={<InfoIcon />} iconPosition="start" label="Exercise Info" />
<Tab icon={<PeopleIcon />} iconPosition="start" label="Submissions" />
</Tabs>

<Button
variant="outlined"
startIcon={<EditIcon />}
onClick={() => setIsEditModalOpen(true)}
sx={{ borderRadius: 2 }}
>
Edit Exercise
</Button>
</Stack>

{/* Tab Content */}
<Box mt={2}>
{tabIndex === 0 && !isEditModalOpen ? (
<ExerciseInfoTab
exercise={exercise}
onUpdateTutorSolution={handleUpdateTutorSolution}
isUpdating={isUpdating}
/>
) : tabIndex === 1 ? (
<SubmissionsTab exercise={exercise} userInfos={userInfos} />
) : null}
</Box>
{isEditModalOpen && exercise && content && (
<AddUMLAssignmentModal
key={umlId + "-" + String(isEditModalOpen)}
open={isEditModalOpen}
onClose={() => setIsEditModalOpen(false)}
onUpdated={() => setRefreshKey((prev) => prev + 1)}
assessmentId={umlId as string}
chapterId={content.metadata?.chapterId}
initialData={{
description: exercise.description,
gradingRules: exercise.gradingRules || "",
diagramCode: exercise.tutorSolution?.diagramCode,
totalPoints: exercise.totalPoints,
requiredPercentage: exercise.requiredPercentage,
showSolution: exercise.showSolution ?? true,
metadata: {
name: content.metadata?.name || "",
rewardPoints: content.metadata?.rewardPoints || 0,
suggestedDate: content.metadata?.suggestedDate,
tagNames: content.metadata?.tagNames || [],
},
assessmentMetadata: {
skillPoints: content.assessmentMetadata?.skillPoints || 0,
skillTypes: content.assessmentMetadata?.skillTypes || [],
initialLearningInterval:
content.assessmentMetadata?.initialLearningInterval ?? null,
},
}}
/>
)}

<Snackbar
open={snackbar.open}
autoHideDuration={4000}
onClose={() => setSnackbar({ ...snackbar, open: false })}
message={snackbar.message}
/>
</Box>
);
}
33 changes: 33 additions & 0 deletions app/courses/[courseId]/uml/[umlId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"use client";

import LecturerUmlAssignment from "@/app/courses/[courseId]/uml/[umlId]/lecturer";
import StudentUMLAssignment from "@/app/courses/[courseId]/uml/[umlId]/student";
import { PageError } from "@/components/PageError";
import { PageView, usePageView } from "@/src/currentView";
import { isUUID } from "@/src/utils";
import { Container } from "@mui/material";
import { useParams } from "next/navigation";

export default function UMLAssignmentPage() {
const [pageView] = usePageView();
const { umlId } = useParams();

if (!isUUID(umlId)) {
return <PageError message="Invalid UML assessment id." />;
}

switch (pageView) {
case PageView.Student:
return <StudentUMLAssignment />;

case PageView.Lecturer:
return (
<Container maxWidth={false} sx={{ py: 2 }}>
<LecturerUmlAssignment />
</Container>
);

default:
return <PageError message="Unauthorized or unknown view state." />;
}
}
Loading
Loading