Merge branch 'dev' into fix/testdata-niet-meer-correct-opgezet

This commit is contained in:
laurejablonski 2025-05-13 09:42:18 +02:00
commit 4cb1469479
27 changed files with 9638 additions and 934 deletions

View file

@ -0,0 +1,54 @@
.h1 {
color: #0e6942;
text-transform: uppercase;
font-weight: bolder;
font-size: 50px;
padding-left: 1%;
}
.empty-message {
text-align: center;
font-size: 18px;
}
.header {
font-weight: bold !important;
background-color: #0e6942;
color: white;
padding: 10px;
}
.table thead th:first-child {
border-top-left-radius: 10px;
}
.table thead th:last-child {
border-top-right-radius: 10px;
}
.table tbody tr:nth-child(odd) {
background-color: white;
}
.table tbody tr:nth-child(even) {
background-color: #f6faf2;
}
.table td,
.table th {
border-bottom: 1px solid #0e6942;
border-top: 1px solid #0e6942;
}
.table {
width: 90%;
padding-top: 10px;
border-collapse: collapse;
}
@media screen and (max-width: 850px) {
.h1 {
text-align: center;
padding-left: 0;
}
}

View file

@ -57,6 +57,22 @@
</div>
<v-row v-else>
<v-col
cols="12"
sm="6"
md="4"
lg="4"
class="d-flex"
>
<ThemeCard
path="/learningPath/search"
:is-absolute-path="true"
:title="t('searchAllLearningPathsTitle')"
:description="t('searchAllLearningPathsDescription')"
icon="mdi-magnify"
class="fill-height grey-bg-card"
/>
</v-col>
<v-col
v-for="card in cards"
:key="card.key"
@ -74,24 +90,13 @@
class="fill-height"
/>
</v-col>
<v-col
cols="12"
sm="6"
md="4"
lg="4"
class="d-flex"
>
<ThemeCard
path="/learningPath/search"
:is-absolute-path="true"
:title="t('searchAllLearningPathsTitle')"
:description="t('searchAllLearningPathsDescription')"
icon="mdi-magnify"
class="fill-height"
/>
</v-col>
</v-row>
</v-container>
</template>
<style scoped></style>
<style scoped>
.grey-bg-card {
background-color: #f6faf2;
border: 2px solid #0e6942;
}
</style>

View file

@ -14,6 +14,7 @@
const _router = useRouter(); // Zonder '_' gaf dit een linter error voor unused variable
const name: string = auth.authState.user!.profile.name!;
const email = auth.authState.user!.profile.email;
const initials: string = name
.split(" ")
.map((n) => n[0])
@ -90,31 +91,34 @@
<!-- >-->
<!-- {{ t("discussions") }}-->
<!-- </v-btn>-->
<v-menu open-on-hover>
<template v-slot:activator="{ props }">
<v-btn
v-bind="props"
icon
variant="text"
>
<v-icon
icon="mdi-translate"
size="small"
color="#0e6942"
></v-icon>
</v-btn>
</template>
<v-list>
<v-list-item
v-for="(language, index) in languages"
:key="index"
@click="changeLanguage(language.code)"
>
<v-list-item-title>{{ language.name }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-toolbar-items>
<v-menu
open-on-hover
open-on-click
>
<template v-slot:activator="{ props }">
<v-btn
v-bind="props"
icon
variant="text"
>
<v-icon
icon="mdi-translate"
size="small"
color="#0e6942"
></v-icon>
</v-btn>
</template>
<v-list>
<v-list-item
v-for="(language, index) in languages"
:key="index"
@click="changeLanguage(language.code)"
>
<v-list-item-title>{{ language.name }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-spacer></v-spacer>
<v-dialog max-width="500">
<template v-slot:activator="{ props: activatorProps }">
@ -158,12 +162,43 @@
</v-card>
</template>
</v-dialog>
<v-avatar
size="large"
color="#0e6942"
class="user-button"
>{{ initials }}</v-avatar
>
<v-menu min-width="200px">
<template v-slot:activator="{ props }">
<v-btn
icon
v-bind="props"
>
<v-avatar
color="#0e6942"
size="large"
class="user-button"
>
<span>{{ initials }}</span>
</v-avatar>
</v-btn>
</template>
<v-card>
<v-card-text>
<div class="mx-auto text-center">
<v-avatar color="#0e6942">
<span class="text-h5">{{ initials }}</span>
</v-avatar>
<h3>{{ name }}</h3>
<p class="text-caption mt-1">{{ email }}</p>
<v-divider class="my-3"></v-divider>
<v-btn
variant="text"
rounded
append-icon="mdi-logout"
@click="performLogout"
to="/login"
>{{ t("logout") }}</v-btn
>
<v-divider class="my-3"></v-divider>
</div>
</v-card-text>
</v-card>
</v-menu>
</v-app-bar>
<v-navigation-drawer
v-model="drawer"
@ -248,6 +283,12 @@
text-transform: none;
}
.translate-button {
z-index: 1;
position: relative;
margin-left: 10px;
}
@media (max-width: 700px) {
.menu {
display: none;

View file

@ -21,6 +21,7 @@
"JoinClassExplanation": "Geben Sie den Code ein, den Ihnen die Lehrkraft mitgeteilt hat, um der Klasse beizutreten.",
"invalidFormat": "Ungültiges Format",
"submitCode": "senden",
"submit": "senden",
"members": "Mitglieder",
"themes": "Themen",
"choose-theme": "Wählen Sie ein Thema",
@ -68,10 +69,10 @@
"pick-class": "Wählen Sie eine klasse",
"choose-students": "Studenten auswählen",
"create-group": "Gruppe erstellen",
"class": "klasse",
"class": "Klasse",
"delete": "löschen",
"view-assignment": "Auftrag anzeigen",
"code": "code",
"code": "Code",
"invitations": "Einladungen",
"createClass": "Klasse erstellen",
"createClassInstructions": "Geben Sie einen Namen für Ihre Klasse ein und klicken Sie auf „Erstellen“. Es erscheint ein Fenster mit einem Code, den Sie kopieren können. Geben Sie diesen Code an Ihre Schüler weiter und sie können Ihrer Klasse beitreten.",
@ -83,7 +84,7 @@
"onlyUse": "nur Buchstaben, Zahlen, Bindestriche (-) und Unterstriche (_) verwenden",
"close": "schließen",
"copied": "kopiert!",
"accept": "akzeptieren",
"accept": "Akzeptieren",
"deny": "ablehnen",
"sent": "sent",
"failed": "fehlgeschlagen",
@ -110,7 +111,7 @@
"remove": "entfernen",
"students": "Studenten",
"classJoinRequests": "Beitrittsanfragen",
"reject": "ablehnen",
"reject": "Ablehnen",
"areusure": "Sind Sie sicher?",
"yes": "ja",
"teachers": "Lehrer",
@ -121,5 +122,16 @@
"invite": "einladen",
"assignmentIndicator": "AUFGABE",
"searchAllLearningPathsTitle": "Alle Lernpfade durchsuchen",
"searchAllLearningPathsDescription": "Nicht gefunden, was Sie gesucht haben? Klicken Sie hier, um unsere gesamte Lernpfad-Datenbank zu durchsuchen."
"searchAllLearningPathsDescription": "Nicht gefunden, was Sie gesucht haben? Klicken Sie hier, um unsere gesamte Lernpfad-Datenbank zu durchsuchen.",
"no-students-found": "Diese Klasse hat keine Schüler.",
"no-invitations-found": "Sie haben keine ausstehenden Einladungen.",
"no-join-requests-found": "Es gibt keine ausstehenden Beitrittsanfragen für diese Klasse.",
"no-classes-found": "Sie sind noch keinem Kurs beigetreten.",
"classCreated": "Klasse erstellt!",
"success": "Erfolg",
"submitted": "eingereicht",
"see-submission": "Einsendung anzeigen",
"view-submissions": "Einsendungen anzeigen",
"valid-username": "Bitte geben Sie einen gültigen Benutzernamen ein",
"creationFailed": "Erstellung fehlgeschlagen, bitte versuchen Sie es erneut"
}

View file

@ -33,6 +33,7 @@
"JoinClassExplanation": "Enter the code the teacher has given you to join the class.",
"invalidFormat": "Invalid format.",
"submitCode": "submit",
"submit": "submit",
"members": "Members",
"themes": "Themes",
"choose-theme": "Select a theme",
@ -68,21 +69,21 @@
"pick-class": "Pick a class",
"choose-students": "Select students",
"create-group": "Create group",
"class": "class",
"class": "Class",
"delete": "delete",
"view-assignment": "View assignment",
"code": "code",
"invitations": "invitations",
"createClass": "create class",
"code": "Code",
"invitations": "Invitations",
"createClass": "Create class",
"classname": "classname",
"EnterNameOfClass": "Enter a classname.",
"create": "create",
"sender": "sender",
"sender": "Sender",
"nameIsMandatory": "classname is mandatory",
"onlyUse": "only use letters, numbers, dashes (-) and underscores (_)",
"close": "close",
"copied": "copied!",
"accept": "accept",
"accept": "Accept",
"deny": "deny",
"createClassInstructions": "Enter a name for your class and click on create. A window will appear with a code that you can copy. Give this code to your students and they will be able to join.",
"sent": "sent",
@ -108,12 +109,12 @@
"progress": "Progress",
"created": "created",
"remove": "remove",
"students": "students",
"classJoinRequests": "join requests",
"reject": "reject",
"students": "Students",
"classJoinRequests": "Join requests",
"reject": "Reject",
"areusure": "Are you sure?",
"yes": "yes",
"teachers": "teachers",
"teachers": "Teachers",
"accepted": "accepted",
"rejected": "rejected",
"enterUsername": "enter the username of the teacher you would like to invite",
@ -121,5 +122,16 @@
"invite": "invite",
"assignmentIndicator": "ASSIGNMENT",
"searchAllLearningPathsTitle": "Search all learning paths",
"searchAllLearningPathsDescription": "You didn't find what you were looking for? Click here to search our whole database of available learning paths."
"searchAllLearningPathsDescription": "You didn't find what you were looking for? Click here to search our whole database of available learning paths.",
"no-students-found": "This class has no students.",
"no-invitations-found": "You have no pending invitations.",
"no-join-requests-found": "There are no pending join requests for this class.",
"no-classes-found": "You are not yet part of a class.",
"classCreated": "class created!",
"success": "success",
"submitted": "submitted",
"see-submission": "view submission",
"view-submissions": "view submissions",
"valid-username": "please enter a valid username",
"creationFailed": "creation failed, please try again"
}

View file

@ -33,6 +33,7 @@
"JoinClassExplanation": "Entrez le code que l'enseignant vous a donné pour rejoindre la classe.",
"invalidFormat": "Format non valide.",
"submitCode": "envoyer",
"submit": "envoyer",
"members": "Membres",
"themes": "Thèmes",
"choose-theme": "Choisis un thème",
@ -68,22 +69,22 @@
"pick-class": "Choisissez une classe",
"choose-students": "Sélectionnez des élèves",
"create-group": "Créer un groupe",
"class": "classe",
"class": "Classe",
"delete": "supprimer",
"view-assignment": "Voir le travail",
"code": "code",
"invitations": "invitations",
"createClass": "créer une classe",
"code": "Code",
"invitations": "Invitations",
"createClass": "Créer une classe",
"createClassInstructions": "Entrez un nom pour votre classe et cliquez sur créer. Une fenêtre apparaît avec un code que vous pouvez copier. Donnez ce code à vos élèves et ils pourront rejoindre votre classe.",
"classname": "nom de classe",
"EnterNameOfClass": "saisir un nom de classe.",
"create": "créer",
"sender": "expéditeur",
"sender": "Expéditeur",
"nameIsMandatory": "le nom de classe est obligatoire",
"onlyUse": "n'utiliser que des lettres, des chiffres, des tirets (-) et des traits de soulignement (_)",
"close": "fermer",
"copied": "copié!",
"accept": "accepter",
"accept": "Accepter",
"deny": "refuser",
"sent": "envoyé",
"failed": "échoué",
@ -108,12 +109,13 @@
"submission": "Soumission",
"progress": "Progrès",
"remove": "supprimer",
"students": "étudiants",
"classJoinRequests": "demandes d'adhésion",
"reject": "rejeter",
"students": "Étudiants",
"classJoinRequests": "Demandes d'adhésion",
"reject": "Rejeter",
"areusure": "Êtes-vous sûr?",
"yes": "oui",
"teachers": "enseignants",
"teachers": "Enseignants",
"accepted": "acceptée",
"rejected": "rejetée",
"enterUsername": "entrez le nom d'utilisateur de l'enseignant que vous souhaitez inviter",
@ -121,5 +123,16 @@
"invite": "inviter",
"assignmentIndicator": "DEVOIR",
"searchAllLearningPathsTitle": "Rechercher tous les parcours d'apprentissage",
"searchAllLearningPathsDescription": "Vous n'avez pas trouvé ce que vous cherchiez ? Cliquez ici pour rechercher dans toute notre base de données de parcours d'apprentissage disponibles."
"searchAllLearningPathsDescription": "Vous n'avez pas trouvé ce que vous cherchiez ? Cliquez ici pour rechercher dans toute notre base de données de parcours d'apprentissage disponibles.",
"no-students-found": "Cette classe n'a pas d'élèves.",
"no-invitations-found": "Vous n'avez aucune invitation en attente.",
"no-join-requests-found": "Il n'y a aucune demande d'adhésion en attente pour cette classe.",
"no-classes-found": "Vous ne faites pas encore partie d'une classe.",
"classCreated": "Classe créée !",
"success": "succès",
"submitted": "soumis",
"see-submission": "voir la soumission",
"view-submissions": "voir les soumissions",
"valid-username": "veuillez entrer un nom d'utilisateur valide",
"creationFailed": "échec de la création, veuillez réessayer"
}

View file

@ -33,6 +33,7 @@
"JoinClassExplanation": "Voer de code in die je van de docent hebt gekregen om lid te worden van de klas.",
"invalidFormat": "Ongeldig formaat.",
"submitCode": "verzenden",
"submit": "verzenden",
"members": "Leden",
"themes": "Lesthema's",
"choose-theme": "Kies een thema",
@ -68,22 +69,22 @@
"pick-class": "Kies een klas",
"choose-students": "Studenten selecteren",
"create-group": "Groep aanmaken",
"class": "klas",
"class": "Klas",
"delete": "verwijderen",
"view-assignment": "Opdracht bekijken",
"code": "code",
"invitations": "uitnodigingen",
"createClass": "klas aanmaken",
"code": "Code",
"invitations": "Uitnodigingen",
"createClass": "Klas aanmaken",
"createClassInstructions": "Voer een naam in voor je klas en klik op create. Er verschijnt een venster met een code die je kunt kopiëren. Geef deze code aan je leerlingen en ze kunnen deelnemen aan je klas.",
"classname": "klasnaam",
"EnterNameOfClass": "Geef een klasnaam op.",
"create": "aanmaken",
"sender": "afzender",
"sender": "Afzender",
"nameIsMandatory": "klasnaam is verplicht",
"onlyUse": "gebruik enkel letters, cijfers, dashes (-) en underscores (_)",
"close": "sluiten",
"copied": "gekopieerd!",
"accept": "accepteren",
"accept": "Accepteren",
"deny": "weigeren",
"sent": "verzonden",
"failed": "mislukt",
@ -108,12 +109,12 @@
"submission": "Indiening",
"progress": "Vooruitgang",
"remove": "verwijder",
"students": "studenten",
"classJoinRequests": "deelname verzoeken",
"reject": "weiger",
"students": "Studenten",
"classJoinRequests": "Deelname verzoeken",
"reject": "Weiger",
"areusure": "Bent u zeker?",
"yes": "ja",
"teachers": "leerkrachten",
"teachers": "Leerkrachten",
"accepted": "geaccepteerd",
"rejected": "geweigerd",
"enterUsername": "vul de gebruikersnaam van de leerkracht die je wilt uitnodigen in",
@ -121,5 +122,16 @@
"invite": "uitnodigen",
"assignmentIndicator": "OPDRACHT",
"searchAllLearningPathsTitle": "Alle leerpaden doorzoeken",
"searchAllLearningPathsDescription": "Niet gevonden waar je naar op zoek was? Klik hier om onze volledige databank van beschikbare leerpaden te doorzoeken."
"searchAllLearningPathsDescription": "Niet gevonden waar je naar op zoek was? Klik hier om onze volledige databank van beschikbare leerpaden te doorzoeken.",
"no-students-found": "Deze klas heeft geen leerlingen.",
"no-invitations-found": "U heeft geen openstaande uitnodigingen.",
"no-join-requests-found": "Er zijn geen openstaande verzoeken om lid te worden van deze klas.",
"no-classes-found": "U maakt nog geen deel uit van een klas.",
"classCreated": "Klas aangemaakt!",
"success": "succes",
"submitted": "ingediend",
"see-submission": "inzending bekijken",
"view-submissions": "inzendingen bekijken",
"valid-username": "voer een geldige gebruikersnaam in",
"creationFailed": "aanmaak mislukt, probeer het opnieuw"
}

View file

@ -28,7 +28,7 @@
alt="Dwengo logo"
style="align-self: center"
/>
<h1>{{ t("homeTitle") }}</h1>
<h1 class="h1">{{ t("homeTitle") }}</h1>
<p class="info">
{{ t("homeIntroduction1") }}
</p>
@ -84,7 +84,10 @@
</div>
</div>
<div class="container_right">
<v-menu open-on-hover>
<v-menu
open-on-hover
open-on-click
>
<template v-slot:activator="{ props }">
<v-btn
v-bind="props"

View file

@ -1,6 +1,20 @@
<script setup lang="ts">
import { useRouter } from "vue-router";
import dwengoLogo from "../../../assets/img/dwengo-groen-zwart.svg";
import auth from "@/services/auth/auth-service.ts";
import { watch } from "vue";
const router = useRouter();
watch(
() => auth.isLoggedIn.value,
async (newVal) => {
if (newVal) {
await router.push("/user");
}
},
{ immediate: true },
);
async function loginAsStudent(): Promise<void> {
await auth.loginAs("student");
@ -9,10 +23,6 @@
async function loginAsTeacher(): Promise<void> {
await auth.loginAs("teacher");
}
async function performLogout(): Promise<void> {
await auth.logout();
}
</script>
<template>
@ -65,13 +75,6 @@
</div>
</ul>
</div>
<div v-if="auth.isLoggedIn.value">
<p>
You are currently logged in as {{ auth.authState.user!.profile.name }} ({{ auth.authState.activeRole }})
</p>
<v-btn @click="performLogout">Logout</v-btn>
<v-btn to="/user">home</v-btn>
</div>
</main>
</template>

View file

@ -96,7 +96,7 @@
<template>
<div class="main-container">
<h1 class="title">{{ t("new-assignment") }}</h1>
<h1 class="h1">{{ t("new-assignment") }}</h1>
<v-card class="form-card">
<v-form
ref="form"

View file

@ -22,8 +22,7 @@
) => { groupProgressMap: Map<number, number> };
}>();
const { t, locale } = useI18n();
const language = ref<Language>(locale.value as Language);
const { t } = useI18n();
const learningPath = ref();
// Get the user's username/id
const username = asyncComputed(async () => {
@ -38,7 +37,7 @@
const lpQueryResult = useGetLearningPathQuery(
computed(() => assignmentQueryResult.data.value?.assignment?.learningPath ?? ""),
computed(() => language.value),
computed(() => assignmentQueryResult.data.value?.assignment.language as Language),
);
const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true);
@ -100,7 +99,7 @@ language
>
<v-btn
v-if="lpData"
:to="`/learningPath/${lpData.hruid}/${language}/${lpData.startNode.learningobjectHruid}?forGroup=${group?.groupNumber}&assignmentNo=${assignmentId}&classId=${classId}`"
:to="`/learningPath/${lpData.hruid}/${assignmentQueryResult.data.value?.assignment.language}/${lpData.startNode.learningobjectHruid}?forGroup=${group?.groupNumber}&assignmentNo=${assignmentId}&classId=${classId}`"
variant="tonal"
color="primary"
>

View file

@ -19,8 +19,7 @@
) => { groupProgressMap: Map<number, number> };
}>();
const { t, locale } = useI18n();
const language = computed(() => locale.value);
const { t } = useI18n();
const groups = ref();
const learningPath = ref();
@ -29,7 +28,7 @@
// Get learning path object
const lpQueryResult = useGetLearningPathQuery(
computed(() => assignmentQueryResult.data.value?.assignment?.learningPath ?? ""),
computed(() => language.value as Language),
computed(() => assignmentQueryResult.data.value?.assignment.language as Language),
);
// Get all the groups withing the assignment
@ -38,9 +37,9 @@
/* Crashes right now cause api data has inexistent hruid TODO: uncomment later and use it in progress bar
Const {groupProgressMap} = props.useGroupsWithProgress(
groups,
learningPath,
language
groups,
learningPath,
language
);
*/
@ -121,7 +120,7 @@ Const {groupProgressMap} = props.useGroupsWithProgress(
>
<v-btn
v-if="lpData"
:to="`/learningPath/${lpData.hruid}/${language}/${lpData.startNode.learningobjectHruid}?assignmentNo=${assignmentId}&classId=${classId}`"
:to="`/learningPath/${lpData.hruid}/${assignmentQueryResult.data.value?.assignment.language}/${lpData.startNode.learningobjectHruid}?assignmentNo=${assignmentId}&classId=${classId}`"
variant="tonal"
color="primary"
>
@ -203,8 +202,8 @@ Const {groupProgressMap} = props.useGroupsWithProgress(
<v-btn
color="primary"
@click="dialog = false"
>Close</v-btn
>
>Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>

View file

@ -9,6 +9,7 @@
import type { ClassDTO } from "@dwengo-1/common/interfaces/class";
import { asyncComputed } from "@vueuse/core";
import { useDeleteAssignmentMutation } from "@/queries/assignments.ts";
import "../../assets/common.css";
const { t } = useI18n();
const router = useRouter();
@ -80,7 +81,7 @@
<template>
<div class="assignments-container">
<h1>{{ t("assignments") }}</h1>
<h1 class="h1">{{ t("assignments") }}</h1>
<v-btn
v-if="isTeacher"
@ -139,7 +140,6 @@
.assignments-container {
width: 100%;
margin: 0 auto;
padding: 2% 4%;
box-sizing: border-box;
}

View file

@ -13,6 +13,7 @@
import { useCreateTeacherInvitationMutation } from "@/queries/teacher-invitations";
import type { TeacherInvitationData } from "@dwengo-1/common/interfaces/teacher-invitation";
import { useDisplay } from "vuetify";
import "../../assets/common.css";
const { t } = useI18n();
@ -112,7 +113,7 @@
function sentInvite(): void {
if (!usernameTeacher.value) {
showSnackbar(t("please enter a valid username"), "error");
showSnackbar(t("valid-username"), "error");
return;
}
const data: TeacherInvitationData = {
@ -186,7 +187,7 @@
v-slot="classResponse: { data: ClassResponse }"
>
<div>
<h1 class="title">{{ classResponse.data.class.displayName }}</h1>
<h1 class="h1">{{ classResponse.data.class.displayName }}</h1>
<using-query-result
:query-result="getStudents"
v-slot="studentsResponse: { data: StudentsResponse }"
@ -211,16 +212,31 @@
<th class="header"></th>
</tr>
</thead>
<tbody>
<tbody v-if="studentsResponse.data.students.length">
<tr
v-for="s in studentsResponse.data.students as StudentDTO[]"
:key="s.id"
>
<td>{{ s.firstName + " " + s.lastName }}</td>
<td>
{{ s.firstName + " " + s.lastName }}
<v-btn @click="showPopup(s)">{{ t("remove") }}</v-btn>
</td>
<td>
<v-btn @click="showPopup(s)"> {{ t("remove") }} </v-btn>
</tr>
</tbody>
<tbody v-else>
<tr>
<td
colspan="2"
class="empty-message"
>
<v-icon
icon="mdi-information-outline"
size="small"
>
</v-icon>
{{ t("no-students-found") }}
</td>
</tr>
</tbody>
@ -242,7 +258,7 @@
<th class="header">{{ t("accept") + "/" + t("reject") }}</th>
</tr>
</thead>
<tbody>
<tbody v-if="joinRequests.data.joinRequests.length">
<tr
v-for="jr in joinRequests.data.joinRequests as ClassJoinRequestDTO[]"
:key="(jr.class, jr.requester, jr.status)"
@ -287,6 +303,21 @@
</td>
</tr>
</tbody>
<tbody v-else>
<tr>
<td
colspan="2"
class="empty-message"
>
<v-icon
icon="mdi-information-outline"
size="small"
>
</v-icon>
{{ t("no-join-requests-found") }}
</td>
</tr>
</tbody>
</v-table>
</v-col>
</using-query-result>
@ -356,49 +387,6 @@
</main>
</template>
<style scoped>
.header {
font-weight: bold !important;
background-color: #0e6942;
color: white;
padding: 10px;
}
table thead th:first-child {
border-top-left-radius: 10px;
}
.table thead th:last-child {
border-top-right-radius: 10px;
}
.table tbody tr:nth-child(odd) {
background-color: white;
}
.table tbody tr:nth-child(even) {
background-color: #f6faf2;
}
td,
th {
border-bottom: 1px solid #0e6942;
border-top: 1px solid #0e6942;
}
.table {
width: 90%;
padding-top: 10px;
border-collapse: collapse;
}
h1 {
color: #0e6942;
text-transform: uppercase;
font-weight: bolder;
padding-top: 2%;
font-size: 50px;
}
h2 {
color: #0e6942;
font-size: 30px;
@ -407,6 +395,7 @@
.join {
display: flex;
flex-direction: column;
margin-left: 1%;
gap: 20px;
margin-top: 50px;
}
@ -416,16 +405,7 @@
text-decoration: underline;
}
main {
margin-left: 30px;
}
@media screen and (max-width: 800px) {
h1 {
text-align: center;
padding-left: 0;
}
.join {
text-align: center;
align-items: center;

View file

@ -12,6 +12,7 @@
import { useClassStudentsQuery, useClassTeachersQuery } from "@/queries/classes";
import type { StudentsResponse } from "@/controllers/students";
import type { TeachersResponse } from "@/controllers/teachers";
import "../../assets/common.css";
const { t } = useI18n();
@ -135,7 +136,7 @@
></v-empty-state>
</div>
<div v-else>
<h1 class="title">{{ t("classes") }}</h1>
<h1 class="h1">{{ t("classes") }}</h1>
<using-query-result
:query-result="classesQuery"
v-slot="classResponse: { data: ClassesResponse }"
@ -161,7 +162,7 @@
<th class="header">{{ t("members") }}</th>
</tr>
</thead>
<tbody>
<tbody v-if="classResponse.data.classes.length">
<tr
v-for="c in classResponse.data.classes as ClassDTO[]"
:key="c.id"
@ -181,6 +182,21 @@
</td>
</tr>
</tbody>
<tbody v-else>
<tr>
<td
colspan="3"
class="empty-message"
>
<v-icon
icon="mdi-information-outline"
size="small"
>
</v-icon>
{{ t("no-classes-found") }}
</td>
</tr>
</tbody>
</v-table>
</v-col>
</v-row>
@ -271,49 +287,6 @@
</main>
</template>
<style scoped>
.header {
font-weight: bold !important;
background-color: #0e6942;
color: white;
padding: 10px;
}
table thead th:first-child {
border-top-left-radius: 10px;
}
.table thead th:last-child {
border-top-right-radius: 10px;
}
.table tbody tr:nth-child(odd) {
background-color: white;
}
.table tbody tr:nth-child(even) {
background-color: #f6faf2;
}
td,
th {
border-bottom: 1px solid #0e6942;
border-top: 1px solid #0e6942;
}
.table {
width: 90%;
padding-top: 10px;
border-collapse: collapse;
}
h1 {
color: #0e6942;
text-transform: uppercase;
font-weight: bolder;
padding-top: 2%;
font-size: 50px;
}
h2 {
color: #0e6942;
font-size: 30px;
@ -321,6 +294,7 @@
.join {
display: flex;
margin-left: 1%;
flex-direction: column;
gap: 20px;
margin-top: 50px;
@ -331,16 +305,7 @@
text-decoration: underline;
}
main {
margin-left: 30px;
}
@media screen and (max-width: 800px) {
h1 {
text-align: center;
padding-left: 0;
}
.join {
text-align: center;
align-items: center;

View file

@ -15,6 +15,7 @@
useTeacherInvitationsReceivedQuery,
} from "@/queries/teacher-invitations";
import { useDisplay } from "vuetify";
import "../../assets/common.css";
const { t } = useI18n();
@ -112,7 +113,7 @@
dialog.value = true;
}
if (!className.value || className.value === "") {
showSnackbar(t("name is mandatory"), "error");
showSnackbar(t("nameIsMandatory"), "error");
}
}
@ -137,6 +138,13 @@
copied.value = true;
}
async function copyCode(selectedCode: string): Promise<void> {
code.value = selectedCode;
await copyToClipboard();
showSnackbar(t("copied"), "white");
copied.value = false;
}
// Custom breakpoints
const customBreakpoints = {
xs: 0,
@ -183,7 +191,7 @@
></v-empty-state>
</div>
<div v-else>
<h1 class="title">{{ t("classes") }}</h1>
<h1 class="h1">{{ t("classes") }}</h1>
<using-query-result
:query-result="classesQuery"
v-slot="classesResponse: { data: ClassesResponse }"
@ -212,7 +220,7 @@
<th class="header">{{ t("members") }}</th>
</tr>
</thead>
<tbody>
<tbody v-if="classesResponse.data.classes.length">
<tr
v-for="c in classesResponse.data.classes as ClassDTO[]"
:key="c.id"
@ -227,7 +235,14 @@
</v-btn>
</td>
<td>
<span v-if="!isMdAndDown">{{ c.id }}</span>
<v-btn
v-if="!isMdAndDown"
variant="text"
append-icon="mdi-content-copy"
@click="copyCode(c.id)"
>
{{ c.id }}
</v-btn>
<span
v-else
style="cursor: pointer"
@ -239,6 +254,21 @@
<td>{{ c.students.length }}</td>
</tr>
</tbody>
<tbody v-else>
<tr>
<td
colspan="3"
class="empty-message"
>
<v-icon
icon="mdi-information-outline"
size="small"
>
</v-icon>
{{ t("no-classes-found") }}
</td>
</tr>
</tbody>
</v-table>
</v-col>
<v-col
@ -318,7 +348,7 @@
</v-container>
</using-query-result>
<h1 class="title">
<h1 class="h1">
{{ t("invitations") }}
</h1>
<v-container
@ -342,61 +372,80 @@
:query-result="allClassesQuery"
v-slot="classesResponse: { data: ClassesResponse }"
>
<tr
v-for="i in invitationsResponse.data.invitations as TeacherInvitationDTO[]"
:key="i.classId"
>
<td>
{{
(classesResponse.data.classes as ClassDTO[]).filter(
(c) => c.id == i.classId,
)[0].displayName
}}
</td>
<td>
{{
(i.sender as TeacherDTO).firstName + " " + (i.sender as TeacherDTO).lastName
}}
</td>
<td class="text-right">
<span v-if="!isSmAndDown">
<div>
<v-btn
color="green"
@click="handleInvitation(i, true)"
class="mr-2"
>
{{ t("accept") }}
</v-btn>
<v-btn
color="red"
@click="handleInvitation(i, false)"
>
{{ t("deny") }}
</v-btn>
</div>
</span>
<span v-else>
<div>
<v-btn
@click="handleInvitation(i, true)"
class="mr-2"
icon="mdi-check-circle"
color="green"
variant="text"
>
</v-btn>
<v-btn
@click="handleInvitation(i, false)"
class="mr-2"
icon="mdi-close-circle"
color="red"
variant="text"
>
</v-btn></div
></span>
</td>
</tr>
<template v-if="invitationsResponse.data.invitations.length">
<tr
v-for="i in invitationsResponse.data.invitations as TeacherInvitationDTO[]"
:key="i.classId"
>
<td>
{{
(classesResponse.data.classes as ClassDTO[]).filter(
(c) => c.id == i.classId,
)[0].displayName
}}
</td>
<td>
{{
(i.sender as TeacherDTO).firstName +
" " +
(i.sender as TeacherDTO).lastName
}}
</td>
<td class="text-right">
<span v-if="!isSmAndDown">
<div>
<v-btn
color="green"
@click="handleInvitation(i, true)"
class="mr-2"
>
{{ t("accept") }}
</v-btn>
<v-btn
color="red"
@click="handleInvitation(i, false)"
>
{{ t("deny") }}
</v-btn>
</div>
</span>
<span v-else>
<div>
<v-btn
@click="handleInvitation(i, true)"
class="mr-2"
icon="mdi-check-circle"
color="green"
variant="text"
>
</v-btn>
<v-btn
@click="handleInvitation(i, false)"
class="mr-2"
icon="mdi-close-circle"
color="red"
variant="text"
>
</v-btn></div
></span>
</td>
</tr>
</template>
<template v-else>
<tr>
<td
colspan="3"
class="empty-message"
>
<v-icon
icon="mdi-information-outline"
size="small"
>
</v-icon>
{{ t("no-invitations-found") }}
</td>
</tr>
</template>
</using-query-result>
</using-query-result>
</tbody>
@ -449,49 +498,6 @@
</main>
</template>
<style scoped>
.header {
font-weight: bold !important;
background-color: #0e6942;
color: white;
padding: 10px;
}
table thead th:first-child {
border-top-left-radius: 10px;
}
.table thead th:last-child {
border-top-right-radius: 10px;
}
.table tbody tr:nth-child(odd) {
background-color: white;
}
.table tbody tr:nth-child(even) {
background-color: #f6faf2;
}
td,
th {
border-bottom: 1px solid #0e6942;
border-top: 1px solid #0e6942;
}
.table {
width: 90%;
padding-top: 10px;
border-collapse: collapse;
}
h1 {
color: #0e6942;
text-transform: uppercase;
font-weight: bolder;
padding-top: 2%;
font-size: 50px;
}
h2 {
color: #0e6942;
font-size: 30px;
@ -509,16 +515,7 @@
text-decoration: underline;
}
main {
margin-left: 30px;
}
@media screen and (max-width: 850px) {
h1 {
text-align: center;
padding-left: 0;
}
.join {
text-align: center;
align-items: center;
@ -541,10 +538,6 @@
flex-direction: column !important;
}
.table {
width: 100%;
}
.responsive-col {
max-width: 100% !important;
flex-basis: 100% !important;

View file

@ -3,6 +3,7 @@
import { useI18n } from "vue-i18n";
import { THEMESITEMS, AGE_TO_THEMES } from "@/utils/constants.ts";
import BrowseThemes from "@/components/BrowseThemes.vue";
import "../../assets/common.css";
const { t, locale } = useI18n();
@ -46,7 +47,7 @@
<template>
<div class="main-container">
<h1 class="title">{{ t("themes") }}</h1>
<h1 class="h1">{{ t("themes") }}</h1>
<v-container class="dropdowns">
<v-select
class="v-select"
@ -77,24 +78,6 @@
</template>
<style scoped>
.main-container {
min-height: 100vh;
min-width: 100vw;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
}
.title {
max-width: 50rem;
margin-left: 1rem;
margin-top: 1rem;
text-align: center;
display: flex;
justify-content: center;
}
.dropdowns {
display: flex;
justify-content: space-between;
@ -107,12 +90,6 @@
min-width: 100px;
}
@media (max-width: 768px) {
.main-container {
padding: 1rem;
}
}
@media (max-width: 700px) {
.dropdowns {
flex-direction: column;

View file

@ -287,6 +287,8 @@
<template v-slot:default>
<v-btn
class="button-in-nav"
width="100%"
:color="COLORS.teacherExclusive"
@click="assign()"
>{{ t("assignLearningPath") }}</v-btn
>

View file

@ -17,32 +17,52 @@
</script>
<template>
<div class="search-field-container">
<learning-path-search-field class="search-field"></learning-path-search-field>
</div>
<v-container class="search-page-container">
<v-row
justify="center"
class="mb-6"
>
<v-col
cols="12"
sm="8"
md="6"
lg="4"
>
<learning-path-search-field class="search-field" />
</v-col>
</v-row>
<using-query-result
:query-result="searchQueryResults"
v-slot="{ data }: { data: LearningPath[] }"
>
<learning-paths-grid :learning-paths="data"></learning-paths-grid>
</using-query-result>
<div content="empty-state-container">
<v-empty-state
v-if="!query"
icon="mdi-magnify"
:title="t('enterSearchTerm')"
:text="t('enterSearchTermDescription')"
></v-empty-state>
</div>
<v-row justify="center">
<v-col cols="12">
<using-query-result
:query-result="searchQueryResults"
v-slot="{ data }: { data: LearningPath[] }"
>
<learning-paths-grid :learning-paths="data" />
</using-query-result>
<div
v-if="!query"
class="empty-state-container"
>
<v-empty-state
icon="mdi-magnify"
:title="t('enterSearchTerm')"
:text="t('enterSearchTermDescription')"
/>
</div>
</v-col>
</v-row>
</v-container>
</template>
<style scoped>
.search-field-container {
display: block;
margin: 20px;
.search-page-container {
padding-top: 40px;
padding-bottom: 40px;
}
.search-field {
max-width: 300px;
max-width: 100%;
}
</style>

View file

@ -5,7 +5,7 @@ describe("AssignmentController Tests", () => {
let controller: AssignmentController;
beforeEach(() => {
controller = new AssignmentController("8764b861-90a6-42e5-9732-c0d9eb2f55f9"); // Example class ID
controller = new AssignmentController("X2J9QT"); // Example class ID (class01)
});
it("should fetch all assignments", async () => {

View file

@ -3,7 +3,7 @@ import { GroupController } from "../../src/controllers/groups";
describe("Test controller groups", () => {
it("Get groups", async () => {
const classId = "8764b861-90a6-42e5-9732-c0d9eb2f55f9";
const classId = "X2J9QT"; // Class01
const assignmentNumber = 21000;
const controller = new GroupController(classId, assignmentNumber);