diff --git a/backend/src/controllers/assignments.ts b/backend/src/controllers/assignments.ts index 2ca6d2fc..793a1f45 100644 --- a/backend/src/controllers/assignments.ts +++ b/backend/src/controllers/assignments.ts @@ -11,8 +11,7 @@ import { import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; import { requireFields } from './error-helper.js'; import { BadRequestException } from '../exceptions/bad-request-exception.js'; -import { Assignment } from '../entities/assignments/assignment.entity.js'; -import { EntityDTO } from '@mikro-orm/core'; +import { FALLBACK_LANG } from '../config.js'; function getAssignmentParams(req: Request): { classid: string; assignmentNumber: number; full: boolean } { const classid = req.params.classid; @@ -38,14 +37,19 @@ export async function getAllAssignmentsHandler(req: Request, res: Response): Pro export async function createAssignmentHandler(req: Request, res: Response): Promise { const classid = req.params.classid; - const description = req.body.description; - const language = req.body.language; - const learningPath = req.body.learningPath; + const description = req.body.description || ''; + const language = req.body.language || FALLBACK_LANG; + const learningPath = req.body.learningPath || ''; const title = req.body.title; - requireFields({ description, language, learningPath, title }); + requireFields({ title }); - const assignmentData = req.body as AssignmentDTO; + const assignmentData = { + description: description, + language: language, + learningPath: learningPath, + title: title, + } as AssignmentDTO; const assignment = await createAssignment(classid, assignmentData); res.json({ assignment }); @@ -62,7 +66,7 @@ export async function getAssignmentHandler(req: Request, res: Response): Promise export async function putAssignmentHandler(req: Request, res: Response): Promise { const { classid, assignmentNumber } = getAssignmentParams(req); - const assignmentData = req.body as Partial>; + const assignmentData = req.body as Partial; const assignment = await putAssignment(classid, assignmentNumber, assignmentData); res.json({ assignment }); diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index 6d8ab0bc..4eb15059 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -7,6 +7,7 @@ import { getJoinRequestsByClass, getStudentsByTeacher, getTeacher, + getTeacherAssignments, updateClassJoinRequestStatus, } from '../services/teachers.js'; import { requireFields } from './error-helper.js'; @@ -59,6 +60,16 @@ export async function getTeacherClassHandler(req: Request, res: Response): Promi res.json({ classes }); } +export async function getTeacherAssignmentsHandler(req: Request, res: Response): Promise { + const username = req.params.username; + const full = req.query.full === 'true'; + requireFields({ username }); + + const assignments = await getTeacherAssignments(username, full); + + res.json({ assignments }); +} + export async function getTeacherStudentHandler(req: Request, res: Response): Promise { const username = req.params.username; const full = req.query.full === 'true'; diff --git a/backend/src/data/assignments/assignment-repository.ts b/backend/src/data/assignments/assignment-repository.ts index 1c8bb504..2ca13545 100644 --- a/backend/src/data/assignments/assignment-repository.ts +++ b/backend/src/data/assignments/assignment-repository.ts @@ -7,7 +7,7 @@ export class AssignmentRepository extends DwengoEntityRepository { return this.findOne({ within: within, id: id }, { populate: ['groups', 'groups.members'] }); } public async findByClassIdAndAssignmentId(withinClass: string, id: number): Promise { - return this.findOne({ within: { classId: withinClass }, id: id }); + return this.findOne({ within: { classId: withinClass }, id: id }, { populate: ['groups', 'groups.members'] }); } public async findAllByResponsibleTeacher(teacherUsername: string): Promise { return this.findAll({ @@ -20,6 +20,7 @@ export class AssignmentRepository extends DwengoEntityRepository { }, }, }, + populate: ['groups', 'groups.members'], }); } public async findAllAssignmentsInClass(within: Class): Promise { diff --git a/backend/src/data/assignments/group-repository.ts b/backend/src/data/assignments/group-repository.ts index f06080f7..2e8ec067 100644 --- a/backend/src/data/assignments/group-repository.ts +++ b/backend/src/data/assignments/group-repository.ts @@ -28,4 +28,9 @@ export class GroupRepository extends DwengoEntityRepository { groupNumber: groupNumber, }); } + public async deleteAllByAssignment(assignment: Assignment): Promise { + return this.deleteAllWhere({ + assignment: assignment, + }); + } } diff --git a/backend/src/data/dwengo-entity-repository.ts b/backend/src/data/dwengo-entity-repository.ts index 1267c726..f17b6976 100644 --- a/backend/src/data/dwengo-entity-repository.ts +++ b/backend/src/data/dwengo-entity-repository.ts @@ -16,4 +16,13 @@ export abstract class DwengoEntityRepository extends EntityRep await em.flush(); } } + public async deleteAllWhere(query: FilterQuery): Promise { + const toDelete = await this.find(query); + const em = this.getEntityManager(); + + if (toDelete) { + em.remove(toDelete); + await em.flush(); + } + } } diff --git a/backend/src/interfaces/assignment.ts b/backend/src/interfaces/assignment.ts index 2dc158d2..32c2f5c8 100644 --- a/backend/src/interfaces/assignment.ts +++ b/backend/src/interfaces/assignment.ts @@ -20,7 +20,7 @@ export function mapToAssignmentDTO(assignment: Assignment): AssignmentDTO { description: assignment.description, learningPath: assignment.learningPathHruid, language: assignment.learningPathLanguage, - deadline: assignment.deadline ?? new Date(), + deadline: assignment.deadline ?? null, groups: assignment.groups.map((group) => mapToGroupDTO(group, assignment.within)), }; } diff --git a/backend/src/middleware/auth/checks/submission-checks.ts b/backend/src/middleware/auth/checks/submission-checks.ts index 893371c2..9caae176 100644 --- a/backend/src/middleware/auth/checks/submission-checks.ts +++ b/backend/src/middleware/auth/checks/submission-checks.ts @@ -7,10 +7,16 @@ import { authorize } from './auth-checks.js'; import { FALLBACK_LANG } from '../../../config.js'; import { mapToUsername } from '../../../interfaces/user.js'; import { AccountType } from '@dwengo-1/common/util/account-types'; +import { fetchClass } from '../../../services/classes.js'; +import { fetchGroup } from '../../../services/groups.js'; +import { requireFields } from '../../../controllers/error-helper.js'; +import { SubmissionDTO } from '@dwengo-1/common/interfaces/submission'; -export const onlyAllowSubmitter = authorize( - (auth: AuthenticationInfo, req: AuthenticatedRequest) => (req.body as { submitter: string }).submitter === auth.username -); +export const onlyAllowSubmitter = authorize((auth: AuthenticationInfo, req: AuthenticatedRequest) => { + const submittedFor = (req.body as SubmissionDTO).submitter.username; + const submittedBy = auth.username; + return submittedFor === submittedBy; +}); export const onlyAllowIfHasAccessToSubmission = authorize(async (auth: AuthenticationInfo, req: AuthenticatedRequest) => { const { hruid: lohruid, id: submissionNumber } = req.params; @@ -26,3 +32,17 @@ export const onlyAllowIfHasAccessToSubmission = authorize(async (auth: Authentic return submission.onBehalfOf.members.map(mapToUsername).includes(auth.username); }); + +export const onlyAllowIfHasAccessToSubmissionFromParams = authorize(async (auth: AuthenticationInfo, req: AuthenticatedRequest) => { + const { classId, assignmentId, groupId } = req.query; + + requireFields({ classId, assignmentId, groupId }); + + if (auth.accountType === AccountType.Teacher) { + const cls = await fetchClass(classId as string); + return cls.teachers.map(mapToUsername).includes(auth.username); + } + + const group = await fetchGroup(classId as string, Number(assignmentId as string), Number(groupId as string)); + return group.members.map(mapToUsername).includes(auth.username); +}); diff --git a/backend/src/routes/submissions.ts b/backend/src/routes/submissions.ts index 88309ce8..58176b40 100644 --- a/backend/src/routes/submissions.ts +++ b/backend/src/routes/submissions.ts @@ -1,10 +1,14 @@ import express from 'express'; import { createSubmissionHandler, deleteSubmissionHandler, getSubmissionHandler, getSubmissionsHandler } from '../controllers/submissions.js'; -import { onlyAllowIfHasAccessToSubmission, onlyAllowSubmitter } from '../middleware/auth/checks/submission-checks.js'; -import { adminOnly, studentsOnly } from '../middleware/auth/checks/auth-checks.js'; +import { + onlyAllowIfHasAccessToSubmission, + onlyAllowIfHasAccessToSubmissionFromParams, + onlyAllowSubmitter, +} from '../middleware/auth/checks/submission-checks.js'; +import { studentsOnly } from '../middleware/auth/checks/auth-checks.js'; const router = express.Router({ mergeParams: true }); -router.get('/', adminOnly, getSubmissionsHandler); +router.get('/', onlyAllowIfHasAccessToSubmissionFromParams, getSubmissionsHandler); router.post('/', studentsOnly, onlyAllowSubmitter, createSubmissionHandler); diff --git a/backend/src/routes/teachers.ts b/backend/src/routes/teachers.ts index cb2405aa..c7280f02 100644 --- a/backend/src/routes/teachers.ts +++ b/backend/src/routes/teachers.ts @@ -4,6 +4,7 @@ import { deleteTeacherHandler, getAllTeachersHandler, getStudentJoinRequestHandler, + getTeacherAssignmentsHandler, getTeacherClassHandler, getTeacherHandler, getTeacherStudentHandler, @@ -28,6 +29,8 @@ router.get('/:username/classes', preventImpersonation, getTeacherClassHandler); router.get('/:username/students', preventImpersonation, getTeacherStudentHandler); +router.get(`/:username/assignments`, getTeacherAssignmentsHandler); + router.get('/:username/joinRequests/:classId', onlyAllowTeacherOfClass, getStudentJoinRequestHandler); router.put('/:username/joinRequests/:classId/:studentUsername', onlyAllowTeacherOfClass, updateStudentJoinRequestHandler); diff --git a/backend/src/services/assignments.ts b/backend/src/services/assignments.ts index 2379ecfb..b9ad9212 100644 --- a/backend/src/services/assignments.ts +++ b/backend/src/services/assignments.ts @@ -14,10 +14,13 @@ import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submissi import { fetchClass } from './classes.js'; import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; -import { EntityDTO } from '@mikro-orm/core'; +import { EntityDTO, ForeignKeyConstraintViolationException } from '@mikro-orm/core'; import { putObject } from './service-helper.js'; import { fetchStudents } from './students.js'; import { ServerErrorException } from '../exceptions/server-error-exception.js'; +import { BadRequestException } from '../exceptions/bad-request-exception.js'; +import { ConflictException } from '../exceptions/conflict-exception.js'; +import { PostgreSqlExceptionConverter } from '@mikro-orm/postgresql'; export async function fetchAssignment(classid: string, assignmentNumber: number): Promise { const classRepository = getClassRepository(); @@ -59,7 +62,7 @@ export async function createAssignment(classid: string, assignmentData: Assignme if (assignmentData.groups) { /* - For some reason when trying to add groups, it does not work when using the original assignment variable. + For some reason when trying to add groups, it does not work when using the original assignment variable. The assignment needs to be refetched in order for it to work. */ @@ -93,10 +96,36 @@ export async function getAssignment(classid: string, id: number): Promise>): Promise { +function hasDuplicates(arr: string[]): boolean { + return new Set(arr).size !== arr.length; +} + +export async function putAssignment(classid: string, id: number, assignmentData: Partial): Promise { const assignment = await fetchAssignment(classid, id); - await putObject(assignment, assignmentData, getAssignmentRepository()); + if (assignmentData.groups) { + if (hasDuplicates(assignmentData.groups.flat() as string[])) { + throw new BadRequestException('Student can only be in one group'); + } + + const studentLists = await Promise.all((assignmentData.groups as string[][]).map(async (group) => await fetchStudents(group))); + + const groupRepository = getGroupRepository(); + await groupRepository.deleteAllByAssignment(assignment); + await Promise.all( + studentLists.map(async (students) => { + const newGroup = groupRepository.create({ + assignment: assignment, + members: students, + }); + await groupRepository.save(newGroup); + }) + ); + + delete assignmentData.groups; + } + + await putObject(assignment, assignmentData as Partial>, getAssignmentRepository()); return mapToAssignmentDTO(assignment); } @@ -106,7 +135,16 @@ export async function deleteAssignment(classid: string, id: number): Promise !it.done).length, + num_nodes: nodesActuallyOnPath.length, + num_nodes_left: nodesActuallyOnPath.filter((it) => !it.done).length, keywords: keywords.join(' '), target_ages: targetAges, max_age: Math.max(...targetAges), @@ -180,7 +182,6 @@ function convertTransition( return { _id: String(index), // Retained for backwards compatibility. The index uniquely identifies the transition within the learning path. default: false, // We don't work with default transitions but retain this for backwards compatibility. - condition: transition.condition, next: { _id: nextNode._id ? nextNode._id + index : v4(), // Construct a unique ID for the transition for backwards compatibility. hruid: transition.next.learningObjectHruid, @@ -191,6 +192,29 @@ function convertTransition( } } +/** + * Start from the start node and then always take the first transition until there are no transitions anymore. + * Returns the traversed nodes as an array. (This effectively filters outs nodes that cannot be reached.) + */ +function traverseLearningPath(nodes: LearningObjectNode[]): LearningObjectNode[] { + const traversedNodes: LearningObjectNode[] = []; + let currentNode = nodes.find((it) => it.start_node); + + while (currentNode) { + traversedNodes.push(currentNode); + + const next = currentNode.transitions[0]?.next; + + if (next) { + currentNode = nodes.find((it) => it.learningobject_hruid === next.hruid && it.language === next.language && it.version === next.version); + } else { + currentNode = undefined; + } + } + + return traversedNodes; +} + /** * Service providing access to data about learning paths from the database. */ diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 3ccd2dba..9050e278 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -10,7 +10,7 @@ import { mapToClassDTO } from '../interfaces/class.js'; import { mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js'; import { mapToStudent, mapToStudentDTO } from '../interfaces/student.js'; import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submission.js'; -import { getAllAssignments } from './assignments.js'; +import { fetchAssignment } from './assignments.js'; import { mapToQuestionDTO, mapToQuestionDTOId } from '../interfaces/question.js'; import { mapToStudentRequest, mapToStudentRequestDTO } from '../interfaces/student-request.js'; import { Student } from '../entities/users/student.entity.js'; @@ -26,6 +26,7 @@ import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-requ import { ConflictException } from '../exceptions/conflict-exception.js'; import { Submission } from '../entities/assignments/submission.entity.js'; import { mapToUsername } from '../interfaces/user.js'; +import { mapToAssignmentDTO, mapToAssignmentDTOId } from '../interfaces/assignment.js'; export async function getAllStudents(full: boolean): Promise { const studentRepository = getStudentRepository(); @@ -50,8 +51,7 @@ export async function fetchStudent(username: string): Promise { } export async function fetchStudents(usernames: string[]): Promise { - const members = await Promise.all(usernames.map(async (username) => await fetchStudent(username))); - return members; + return await Promise.all(usernames.map(async (username) => await fetchStudent(username))); } export async function getStudent(username: string): Promise { @@ -102,10 +102,14 @@ export async function getStudentClasses(username: string, full: boolean): Promis export async function getStudentAssignments(username: string, full: boolean): Promise { const student = await fetchStudent(username); - const classRepository = getClassRepository(); - const classes = await classRepository.findByStudent(student); + const groupRepository = getGroupRepository(); + const groups = await groupRepository.findAllGroupsWithStudent(student); + const assignments = await Promise.all(groups.map(async (group) => await fetchAssignment(group.assignment.within.classId!, group.assignment.id!))); - return (await Promise.all(classes.map(async (cls) => await getAllAssignments(cls.classId!, full)))).flat(); + if (full) { + return assignments.map(mapToAssignmentDTO); + } + return assignments.map(mapToAssignmentDTOId); } export async function getStudentGroups(username: string, full: boolean): Promise { diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index 3d07960f..4d65231d 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -1,4 +1,4 @@ -import { getClassJoinRequestRepository, getClassRepository, getTeacherRepository } from '../data/repositories.js'; +import { getAssignmentRepository, getClassJoinRequestRepository, getClassRepository, getTeacherRepository } from '../data/repositories.js'; import { mapToClassDTO } from '../interfaces/class.js'; import { mapToTeacher, mapToTeacherDTO } from '../interfaces/teacher.js'; import { Teacher } from '../entities/users/teacher.entity.js'; @@ -18,6 +18,8 @@ import { StudentDTO } from '@dwengo-1/common/interfaces/student'; import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request'; import { ClassStatus } from '@dwengo-1/common/util/class-join-request'; import { ConflictException } from '../exceptions/conflict-exception.js'; +import { AssignmentDTO, AssignmentDTOId } from '@dwengo-1/common/interfaces/assignment'; +import { mapToAssignmentDTO, mapToAssignmentDTOId } from '../interfaces/assignment.js'; import { mapToUsername } from '../interfaces/user.js'; export async function getAllTeachers(full: boolean): Promise { @@ -91,6 +93,17 @@ export async function getClassesByTeacher(username: string, full: boolean): Prom return classes.map((cls) => cls.id); } +export async function getTeacherAssignments(username: string, full: boolean): Promise { + const assignmentRepository = getAssignmentRepository(); + const assignments = await assignmentRepository.findAllByResponsibleTeacher(username); + + if (full) { + return assignments.map(mapToAssignmentDTO); + } + + return assignments.map(mapToAssignmentDTOId); +} + export async function getStudentsByTeacher(username: string, full: boolean): Promise { const classes: ClassDTO[] = await fetchClassesByTeacher(username); diff --git a/backend/tests/controllers/students.test.ts b/backend/tests/controllers/students.test.ts index b5ac1e0d..c3d84fd8 100644 --- a/backend/tests/controllers/students.test.ts +++ b/backend/tests/controllers/students.test.ts @@ -14,6 +14,7 @@ import { getStudentRequestsHandler, deleteClassJoinRequestHandler, getStudentRequestHandler, + getStudentAssignmentsHandler, } from '../../src/controllers/students.js'; import { getDireStraits, getNoordkaap, getTheDoors, TEST_STUDENTS } from '../test_assets/users/students.testdata.js'; import { NotFoundException } from '../../src/exceptions/not-found-exception.js'; @@ -150,6 +151,19 @@ describe('Student controllers', () => { expect(result.groups).to.have.length.greaterThan(0); }); + it('Student assignments', async () => { + const group = getTestGroup01(); + const member = group.members[0]; + req = { params: { username: member.username }, query: {} }; + + await getStudentAssignmentsHandler(req as Request, res as Response); + + expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ assignments: expect.anything() })); + + const result = jsonMock.mock.lastCall?.[0]; + expect(result.assignments).to.have.length.greaterThan(0); + }); + it('Student submissions', async () => { const submission = getSubmission01(); req = { params: { username: submission.submitter.username }, query: { full: 'true' } }; diff --git a/backend/tests/test_assets/assignments/groups.testdata.ts b/backend/tests/test_assets/assignments/groups.testdata.ts index f2508108..342d8693 100644 --- a/backend/tests/test_assets/assignments/groups.testdata.ts +++ b/backend/tests/test_assets/assignments/groups.testdata.ts @@ -51,7 +51,7 @@ export function makeTestGroups(em: EntityManager): Group[] { */ group05 = em.create(Group, { assignment: getAssignment04(), - groupNumber: 21001, + groupNumber: 21006, members: [getNoordkaap(), getDireStraits()], }); diff --git a/backend/tool/seedORM.ts b/backend/tool/seedORM.ts index eec73a77..8a3b7a37 100644 --- a/backend/tool/seedORM.ts +++ b/backend/tool/seedORM.ts @@ -5,8 +5,22 @@ import { makeTestTeachers } from '../tests/test_assets/users/teachers.testdata'; import { makeTestLearningObjects } from '../tests/test_assets/content/learning-objects.testdata'; import { makeTestLearningPaths } from '../tests/test_assets/content/learning-paths.testdata'; import { makeTestClasses } from '../tests/test_assets/classes/classes.testdata'; -import { makeTestAssignemnts } from '../tests/test_assets/assignments/assignments.testdata'; -import { getTestGroup01, getTestGroup02, getTestGroup03, getTestGroup04, makeTestGroups } from '../tests/test_assets/assignments/groups.testdata'; +import { + getAssignment01, + getAssignment02, + getAssignment04, + getConditionalPathAssignment, + makeTestAssignemnts, +} from '../tests/test_assets/assignments/assignments.testdata'; +import { + getGroup1ConditionalLearningPath, + getTestGroup01, + getTestGroup02, + getTestGroup03, + getTestGroup04, + getTestGroup05, + makeTestGroups, +} from '../tests/test_assets/assignments/groups.testdata'; import { Group } from '../src/entities/assignments/group.entity'; import { makeTestTeacherInvitations } from '../tests/test_assets/classes/teacher-invitations.testdata'; import { makeTestClassJoinRequests } from '../tests/test_assets/classes/class-join-requests.testdata'; @@ -36,8 +50,14 @@ export async function seedORM(orm: MikroORM): Promise { const groups = makeTestGroups(em); - assignments[0].groups = new Collection([getTestGroup01(), getTestGroup02(), getTestGroup03()]); - assignments[1].groups = new Collection([getTestGroup04()]); + let assignment = getAssignment01(); + assignment.groups = new Collection([getTestGroup01(), getTestGroup02(), getTestGroup03()]); + assignment = getAssignment02(); + assignment.groups = new Collection([getTestGroup04()]); + assignment = getAssignment04(); + assignment.groups = new Collection([getTestGroup05()]); + assignment = getConditionalPathAssignment(); + assignment.groups = new Collection([getGroup1ConditionalLearningPath()]); const teacherInvitations = makeTestTeacherInvitations(em); const classJoinRequests = makeTestClassJoinRequests(em); diff --git a/common/src/interfaces/assignment.ts b/common/src/interfaces/assignment.ts index 677221f1..49e2a84b 100644 --- a/common/src/interfaces/assignment.ts +++ b/common/src/interfaces/assignment.ts @@ -7,7 +7,7 @@ export interface AssignmentDTO { description: string; learningPath: string; language: string; - deadline: Date; + deadline: Date | null; groups: GroupDTO[] | string[][]; } diff --git a/frontend/src/assets/assignment.css b/frontend/src/assets/assignment.css index 029dec22..c23e6585 100644 --- a/frontend/src/assets/assignment.css +++ b/frontend/src/assets/assignment.css @@ -18,10 +18,19 @@ font-size: 1.1rem; } -.top-right-btn { - position: absolute; - right: 2%; - color: red; +.top-buttons-wrapper { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + position: relative; +} + +.right-buttons { + display: flex; + gap: 0.5rem; + align-items: center; + color: #0e6942; } .group-section { diff --git a/frontend/src/components/DwengoTable.vue b/frontend/src/components/DwengoTable.vue new file mode 100644 index 00000000..b04616e8 --- /dev/null +++ b/frontend/src/components/DwengoTable.vue @@ -0,0 +1,52 @@ + + + diff --git a/frontend/src/components/GroupProgressRow.vue b/frontend/src/components/GroupProgressRow.vue new file mode 100644 index 00000000..2704c494 --- /dev/null +++ b/frontend/src/components/GroupProgressRow.vue @@ -0,0 +1,47 @@ + + + diff --git a/frontend/src/components/GroupSubmissionStatus.vue b/frontend/src/components/GroupSubmissionStatus.vue new file mode 100644 index 00000000..d8559cab --- /dev/null +++ b/frontend/src/components/GroupSubmissionStatus.vue @@ -0,0 +1,50 @@ + + + diff --git a/frontend/src/components/MenuBar.vue b/frontend/src/components/MenuBar.vue index 229a7266..217555b3 100644 --- a/frontend/src/components/MenuBar.vue +++ b/frontend/src/components/MenuBar.vue @@ -148,7 +148,8 @@