style: fix linting issues met Prettier

This commit is contained in:
Lint Action 2025-04-18 23:36:22 +00:00
parent af8c783a26
commit 5168ceaee0
56 changed files with 680 additions and 741 deletions

View file

@ -15,14 +15,14 @@ export class LearningPathController extends BaseController {
async getBy(
hruid: string,
language: Language,
forGroup?: { forGroup: number, assignmentNo: number, classId: string },
forGroup?: { forGroup: number; assignmentNo: number; classId: string },
): Promise<LearningPath> {
const dtos = await this.get<LearningPathDTO[]>("/", {
hruid,
language,
forGroup: forGroup?.forGroup,
assignmentNo: forGroup?.assignmentNo,
classId: forGroup?.classId
classId: forGroup?.classId,
});
return LearningPath.fromDTO(single(dtos));
}

View file

@ -1,6 +1,6 @@
import { BaseController } from "./base-controller";
import type { SubmissionDTO, SubmissionDTOId } from "@dwengo-1/common/interfaces/submission";
import type {Language} from "@dwengo-1/common/util/language";
import type { Language } from "@dwengo-1/common/util/language";
export interface SubmissionsResponse {
submissions: SubmissionDTO[] | SubmissionDTOId[];
@ -11,18 +11,19 @@ export interface SubmissionResponse {
}
export class SubmissionController extends BaseController {
constructor(hruid: string) {
super(`learningObject/${hruid}/submissions`);
}
async getAll(
language: Language, version: number, classId: string, assignmentId: number, groupId?: number, full = true
language: Language,
version: number,
classId: string,
assignmentId: number,
groupId?: number,
full = true,
): Promise<SubmissionsResponse> {
return this.get<SubmissionsResponse>(
`/`,
{ language, version, classId, assignmentId, groupId, full }
);
return this.get<SubmissionsResponse>(`/`, { language, version, classId, assignmentId, groupId, full });
}
async getByNumber(
@ -31,12 +32,15 @@ export class SubmissionController extends BaseController {
classId: string,
assignmentId: number,
groupId: number,
submissionNumber: number
submissionNumber: number,
): Promise<SubmissionResponse> {
return this.get<SubmissionResponse>(
`/${submissionNumber}`,
{ language, version, classId, assignmentId, groupId },
);
return this.get<SubmissionResponse>(`/${submissionNumber}`, {
language,
version,
classId,
assignmentId,
groupId,
});
}
async createSubmission(data: SubmissionDTO): Promise<SubmissionResponse> {

View file

@ -10,7 +10,7 @@ const learningPathController = getLearningPathController();
export function useGetLearningPathQuery(
hruid: MaybeRefOrGetter<string>,
language: MaybeRefOrGetter<Language>,
forGroup?: MaybeRefOrGetter<{forGroup: number, assignmentNo: number, classId: string} | undefined>,
forGroup?: MaybeRefOrGetter<{ forGroup: number; assignmentNo: number; classId: string } | undefined>,
): UseQueryReturnType<LearningPath, Error> {
return useQuery({
queryKey: [LEARNING_PATH_KEY, "get", hruid, language, forGroup],
@ -34,7 +34,7 @@ export function useGetAllLearningPathsByThemeQuery(
export function useSearchLearningPathQuery(
query: MaybeRefOrGetter<string | undefined>,
language: MaybeRefOrGetter<string | undefined>
language: MaybeRefOrGetter<string | undefined>,
): UseQueryReturnType<LearningPath[], Error> {
return useQuery({
queryKey: [LEARNING_PATH_KEY, "search", query, language],

View file

@ -9,9 +9,9 @@ import {
type UseQueryReturnType,
} from "@tanstack/vue-query";
import { computed, toValue, type MaybeRefOrGetter } from "vue";
import {LEARNING_PATH_KEY} from "@/queries/learning-paths.ts";
import {LEARNING_OBJECT_KEY} from "@/queries/learning-objects.ts";
import type {Language} from "@dwengo-1/common/util/language";
import { LEARNING_PATH_KEY } from "@/queries/learning-paths.ts";
import { LEARNING_OBJECT_KEY } from "@/queries/learning-objects.ts";
import type { Language } from "@dwengo-1/common/util/language";
export const SUBMISSION_KEY = "submissions";
@ -22,7 +22,7 @@ function submissionQueryKey(
classid: string,
assignmentNumber: number,
groupNumber: number,
submissionNumber: number
submissionNumber: number,
) {
return ["submission", hruid, language, version, classid, assignmentNumber, groupNumber, submissionNumber];
}
@ -41,29 +41,37 @@ export async function invalidateAllSubmissionKeys(
for (const key of keys) {
const queryKey = [
key, hruid, language, version, classid, assignmentNumber, groupNumber, submissionNumber
].filter(
(arg) => arg !== undefined,
);
key,
hruid,
language,
version,
classid,
assignmentNumber,
groupNumber,
submissionNumber,
].filter((arg) => arg !== undefined);
await queryClient.invalidateQueries({ queryKey: queryKey });
}
await queryClient.invalidateQueries({
queryKey: ["submissions", hruid, language, version, classid, assignmentNumber, groupNumber]
.filter((arg) => arg !== undefined),
queryKey: ["submissions", hruid, language, version, classid, assignmentNumber, groupNumber].filter(
(arg) => arg !== undefined,
),
});
await queryClient.invalidateQueries({
queryKey: ["group-submissions", hruid, language, version, classid, assignmentNumber, groupNumber]
.filter((arg) => arg !== undefined),
queryKey: ["group-submissions", hruid, language, version, classid, assignmentNumber, groupNumber].filter(
(arg) => arg !== undefined,
),
});
await queryClient.invalidateQueries({
queryKey: ["assignment-submissions", hruid, language, version,classid, assignmentNumber]
.filter((arg) => arg !== undefined),
queryKey: ["assignment-submissions", hruid, language, version, classid, assignmentNumber].filter(
(arg) => arg !== undefined,
),
});
}
function checkEnabled(properties: MaybeRefOrGetter<unknown>[]): boolean {
return properties.every(prop => Boolean(toValue(prop)));
return properties.every((prop) => Boolean(toValue(prop)));
}
export function useSubmissionsQuery(
@ -87,9 +95,14 @@ export function useSubmissionsQuery(
const fullVal = toValue(full);
const response = await new SubmissionController(hruidVal!).getAll(
languageVal, versionVal!, classIdVal!, assignmentNumberVal!, groupNumberVal, fullVal
languageVal,
versionVal!,
classIdVal!,
assignmentNumberVal!,
groupNumberVal,
fullVal,
);
return response ? response.submissions as SubmissionDTO[] : undefined;
return response ? (response.submissions as SubmissionDTO[]) : undefined;
},
enabled: () => checkEnabled([hruid, language, version, classid, assignmentNumber]),
});
@ -113,13 +126,33 @@ export function useSubmissionQuery(
const submissionNumberVal = toValue(submissionNumber);
return useQuery({
queryKey: computed(() => submissionQueryKey(
hruidVal!, languageVal, versionVal!, classIdVal!, assignmentNumberVal!, groupNumberVal!, submissionNumberVal!
)),
queryFn: async () => new SubmissionController(hruidVal!).getByNumber(
languageVal, versionVal!, classIdVal!, assignmentNumberVal!, groupNumberVal!, submissionNumberVal!
queryKey: computed(() =>
submissionQueryKey(
hruidVal!,
languageVal,
versionVal!,
classIdVal!,
assignmentNumberVal!,
groupNumberVal!,
submissionNumberVal!,
),
),
enabled: () => Boolean(hruidVal) && Boolean(languageVal) && Boolean(versionVal) && Boolean(classIdVal) && Boolean(assignmentNumberVal) && Boolean(submissionNumber),
queryFn: async () =>
new SubmissionController(hruidVal!).getByNumber(
languageVal,
versionVal!,
classIdVal!,
assignmentNumberVal!,
groupNumberVal!,
submissionNumberVal!,
),
enabled: () =>
Boolean(hruidVal) &&
Boolean(languageVal) &&
Boolean(versionVal) &&
Boolean(classIdVal) &&
Boolean(assignmentNumberVal) &&
Boolean(submissionNumber),
});
}
@ -132,7 +165,8 @@ export function useCreateSubmissionMutation(): UseMutationReturnType<
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ data }) => new SubmissionController(data.learningObjectIdentifier.hruid).createSubmission(data),
mutationFn: async ({ data }) =>
new SubmissionController(data.learningObjectIdentifier.hruid).createSubmission(data),
onSuccess: async (response) => {
if (!response.submission.group) {
await invalidateAllSubmissionKeys(queryClient);
@ -144,13 +178,13 @@ export function useCreateSubmissionMutation(): UseMutationReturnType<
const an = typeof assignment === "number" ? assignment : assignment.id;
const gn = response.submission.group.groupNumber;
const {hruid, language, version} = response.submission.learningObjectIdentifier;
const { hruid, language, version } = response.submission.learningObjectIdentifier;
await invalidateAllSubmissionKeys(queryClient, hruid, language, version, cid, an, gn);
await queryClient.invalidateQueries({queryKey: [LEARNING_PATH_KEY, "get"]});
await queryClient.invalidateQueries({ queryKey: [LEARNING_PATH_KEY, "get"] });
await queryClient.invalidateQueries({
queryKey: [LEARNING_OBJECT_KEY, "metadata", hruid, language, version]
queryKey: [LEARNING_OBJECT_KEY, "metadata", hruid, language, version],
});
}
},
@ -178,15 +212,9 @@ export function useDeleteSubmissionMutation(): UseMutationReturnType<
const an = typeof assignment === "number" ? assignment : assignment.id;
const gn = response.submission.group.groupNumber;
const {hruid, language, version} = response.submission.learningObjectIdentifier;
const { hruid, language, version } = response.submission.learningObjectIdentifier;
await invalidateAllSubmissionKeys(
queryClient,
hruid,
language,
version,
cid, an, gn
);
await invalidateAllSubmissionKeys(queryClient, hruid, language, version, cid, an, gn);
}
},
});

View file

@ -1,20 +1,29 @@
export function deepEquals<T>(a: T, b: T): boolean {
if (a === b) {return true;}
if (a === b) {
return true;
}
if (typeof a !== 'object' || typeof b !== 'object' || a == null || b == null)
{return false;}
if (typeof a !== "object" || typeof b !== "object" || a == null || b == null) {
return false;
}
if (Array.isArray(a) !== Array.isArray(b)) {return false;}
if (Array.isArray(a) !== Array.isArray(b)) {
return false;
}
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) {return false;}
if (a.length !== b.length) {
return false;
}
return a.every((val, i) => deepEquals(val, b[i]));
}
const keysA = Object.keys(a) as (keyof T)[];
const keysB = Object.keys(b) as (keyof T)[];
if (keysA.length !== keysB.length) {return false;}
if (keysA.length !== keysB.length) {
return false;
}
return keysA.every(key => deepEquals(a[key], b[key]));
return keysA.every((key) => deepEquals(a[key], b[key]));
}

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import {useRoute} from "vue-router";
import { useRoute } from "vue-router";
const route = useRoute();
</script>

View file

@ -1,24 +1,24 @@
<script setup lang="ts">
import UsingQueryResult from "@/components/UsingQueryResult.vue";
import {useGroupsQuery} from "@/queries/groups.ts";
import type {GroupsResponse} from "@/controllers/groups.ts";
import {useI18n} from "vue-i18n";
import type {GroupDTO} from "@dwengo-1/common/interfaces/group";
import { useGroupsQuery } from "@/queries/groups.ts";
import type { GroupsResponse } from "@/controllers/groups.ts";
import { useI18n } from "vue-i18n";
import type { GroupDTO } from "@dwengo-1/common/interfaces/group";
const { t } = useI18n();
const props = defineProps<{
classId: string,
assignmentNumber: number
classId: string;
assignmentNumber: number;
}>();
const model = defineModel<number | undefined>({default: undefined});
const model = defineModel<number | undefined>({ default: undefined });
const groupsQuery = useGroupsQuery(props.classId, props.assignmentNumber, true);
interface GroupSelectorOption {
groupNumber: number | undefined,
label: string
groupNumber: number | undefined;
label: string;
}
function groupOptions(groups: GroupDTO[]): GroupSelectorOption[] {
@ -26,7 +26,7 @@
.sort((a, b) => a.groupNumber - b.groupNumber)
.map((group, index) => ({
groupNumber: group.groupNumber,
label: `${index + 1}`
label: `${index + 1}`,
}));
}
</script>

View file

@ -3,7 +3,7 @@
import type { LearningPath } from "@/data-objects/learning-paths/learning-path.ts";
import { computed, type ComputedRef, ref } from "vue";
import type { LearningObject } from "@/data-objects/learning-objects/learning-object.ts";
import {useRoute, useRouter} from "vue-router";
import { useRoute, useRouter } from "vue-router";
import LearningObjectView from "@/views/learning-paths/learning-object/LearningObjectView.vue";
import { useI18n } from "vue-i18n";
import LearningPathSearchField from "@/components/LearningPathSearchField.vue";
@ -21,7 +21,7 @@
const props = defineProps<{
hruid: string;
language: Language;
learningObjectHruid?: string,
learningObjectHruid?: string;
}>();
interface LearningPathPageQuery {
@ -37,7 +37,7 @@
return {
forGroup: parseInt(query.value.forGroup),
assignmentNo: parseInt(query.value.assignmentNo),
classId: query.value.classId
classId: query.value.classId,
};
}
});
@ -112,7 +112,7 @@
let query = structuredClone(route.query);
query.forGroup = value;
router.push({ query });
}
},
});
function assign() {
@ -120,8 +120,8 @@
path: "/assignment/create",
query: {
hruid: props.hruid,
language: props.language
}
language: props.language,
},
});
}
</script>
@ -136,80 +136,86 @@
:width="350"
>
<div class="d-flex flex-column h-100">
<v-list-item>
<template v-slot:title>
<div class="learning-path-title">{{ learningPath.data.title }}</div>
</template>
<template v-slot:subtitle>
<div>{{ learningPath.data.description }}</div>
</template>
</v-list-item>
<v-list-item>
<template v-slot:subtitle>
<p>
<v-icon
:color="COLORS.notCompleted"
:icon="ICONS.notCompleted"
></v-icon>
{{ t("legendNotCompletedYet") }}
</p>
<p>
<v-icon
:color="COLORS.completed"
:icon="ICONS.completed"
></v-icon>
{{ t("legendCompleted") }}
</p>
<p>
<v-icon
:color="COLORS.teacherExclusive"
:icon="ICONS.teacherExclusive"
></v-icon>
{{ t("legendTeacherExclusive") }}
</p>
</template>
</v-list-item>
<v-list-item v-if="query.classId && query.assignmentNo && authService.authState.activeRole === 'teacher'">
<template v-slot:default>
<learning-path-group-selector
:class-id="query.classId"
:assignment-number="parseInt(query.assignmentNo)"
v-model="forGroupQueryParam"
/>
</template>
</v-list-item>
<v-divider></v-divider>
<div v-if="props.learningObjectHruid">
<using-query-result
:query-result="learningObjectListQueryResult"
v-slot="learningObjects: { data: LearningObject[] }"
>
<template v-for="node in learningObjects.data">
<v-list-item
link
:to="{ path: node.key, query: route.query }"
:title="node.title"
:active="node.key === props.learningObjectHruid"
:key="node.key"
v-if="!node.teacherExclusive || authService.authState.activeRole === 'teacher'"
>
<template v-slot:prepend>
<v-icon
:color="COLORS[getNavItemState(node)]"
:icon="ICONS[getNavItemState(node)]"
></v-icon>
</template>
<template v-slot:append> {{ node.estimatedTime }}' </template>
</v-list-item>
<v-list-item>
<template v-slot:title>
<div class="learning-path-title">{{ learningPath.data.title }}</div>
</template>
</using-query-result>
</div>
<v-spacer></v-spacer>
<v-list-item v-if="authService.authState.activeRole === 'teacher'">
<template v-slot:default>
<v-btn class="button-in-nav" @click="assign()">{{ t("assignLearningPath") }}</v-btn>
</template>
</v-list-item>
<template v-slot:subtitle>
<div>{{ learningPath.data.description }}</div>
</template>
</v-list-item>
<v-list-item>
<template v-slot:subtitle>
<p>
<v-icon
:color="COLORS.notCompleted"
:icon="ICONS.notCompleted"
></v-icon>
{{ t("legendNotCompletedYet") }}
</p>
<p>
<v-icon
:color="COLORS.completed"
:icon="ICONS.completed"
></v-icon>
{{ t("legendCompleted") }}
</p>
<p>
<v-icon
:color="COLORS.teacherExclusive"
:icon="ICONS.teacherExclusive"
></v-icon>
{{ t("legendTeacherExclusive") }}
</p>
</template>
</v-list-item>
<v-list-item
v-if="query.classId && query.assignmentNo && authService.authState.activeRole === 'teacher'"
>
<template v-slot:default>
<learning-path-group-selector
:class-id="query.classId"
:assignment-number="parseInt(query.assignmentNo)"
v-model="forGroupQueryParam"
/>
</template>
</v-list-item>
<v-divider></v-divider>
<div v-if="props.learningObjectHruid">
<using-query-result
:query-result="learningObjectListQueryResult"
v-slot="learningObjects: { data: LearningObject[] }"
>
<template v-for="node in learningObjects.data">
<v-list-item
link
:to="{ path: node.key, query: route.query }"
:title="node.title"
:active="node.key === props.learningObjectHruid"
:key="node.key"
v-if="!node.teacherExclusive || authService.authState.activeRole === 'teacher'"
>
<template v-slot:prepend>
<v-icon
:color="COLORS[getNavItemState(node)]"
:icon="ICONS[getNavItemState(node)]"
></v-icon>
</template>
<template v-slot:append> {{ node.estimatedTime }}' </template>
</v-list-item>
</template>
</using-query-result>
</div>
<v-spacer></v-spacer>
<v-list-item v-if="authService.authState.activeRole === 'teacher'">
<template v-slot:default>
<v-btn
class="button-in-nav"
@click="assign()"
>{{ t("assignLearningPath") }}</v-btn
>
</template>
</v-list-item>
</div>
</v-navigation-drawer>
<div class="control-bar-above-content">

View file

@ -1,13 +1,18 @@
export const essayQuestionAdapter: GiftAdapter = {
questionType: "Essay",
installListener(questionElement: Element, answerUpdateCallback: (newAnswer: string | number | object) => void): void {
const textArea = questionElement.querySelector('textarea')!;
textArea.addEventListener('input', () => { answerUpdateCallback(textArea.value); });
installListener(
questionElement: Element,
answerUpdateCallback: (newAnswer: string | number | object) => void,
): void {
const textArea = questionElement.querySelector("textarea")!;
textArea.addEventListener("input", () => {
answerUpdateCallback(textArea.value);
});
},
setAnswer(questionElement: Element, answer: string | number | object): void {
const textArea = questionElement.querySelector('textarea')!;
const textArea = questionElement.querySelector("textarea")!;
textArea.value = String(answer);
}
}
},
};

View file

@ -1,5 +1,8 @@
interface GiftAdapter {
questionType: string;
installListener(questionElement: Element, answerUpdateCallback: (newAnswer: string | number | object) => void): void;
installListener(
questionElement: Element,
answerUpdateCallback: (newAnswer: string | number | object) => void,
): void;
setAnswer(questionElement: Element, answer: string | number | object): void;
}

View file

@ -1,8 +1,8 @@
import {multipleChoiceQuestionAdapter} from "@/views/learning-paths/gift-adapters/multiple-choice-question-adapter.ts";
import {essayQuestionAdapter} from "@/views/learning-paths/gift-adapters/essay-question-adapter.ts";
import { multipleChoiceQuestionAdapter } from "@/views/learning-paths/gift-adapters/multiple-choice-question-adapter.ts";
import { essayQuestionAdapter } from "@/views/learning-paths/gift-adapters/essay-question-adapter.ts";
export const giftAdapters = [multipleChoiceQuestionAdapter, essayQuestionAdapter];
export function getGiftAdapterForType(questionType: string): GiftAdapter | undefined {
return giftAdapters.find(it => it.questionType === questionType);
return giftAdapters.find((it) => it.questionType === questionType);
}

View file

@ -1,11 +1,14 @@
export const multipleChoiceQuestionAdapter: GiftAdapter = {
questionType: "MC",
installListener(questionElement: Element, answerUpdateCallback: (newAnswer: string | number | object) => void): void {
questionElement.querySelectorAll('input[type=radio]').forEach(element => {
installListener(
questionElement: Element,
answerUpdateCallback: (newAnswer: string | number | object) => void,
): void {
questionElement.querySelectorAll("input[type=radio]").forEach((element) => {
const input = element as HTMLInputElement;
input.addEventListener('change', () => {
input.addEventListener("change", () => {
answerUpdateCallback(parseInt(input.value));
});
// Optional: initialize value if already selected
@ -16,9 +19,9 @@ export const multipleChoiceQuestionAdapter: GiftAdapter = {
},
setAnswer(questionElement: Element, answer: string | number | object): void {
questionElement.querySelectorAll('input[type=radio]').forEach(element => {
questionElement.querySelectorAll("input[type=radio]").forEach((element) => {
const input = element as HTMLInputElement;
input.checked = String(answer) === String(input.value);
});
}
}
},
};

View file

@ -3,9 +3,9 @@
import type { UseQueryReturnType } from "@tanstack/vue-query";
import { useLearningObjectHTMLQuery } from "@/queries/learning-objects.ts";
import UsingQueryResult from "@/components/UsingQueryResult.vue";
import {computed, ref} from "vue";
import { computed, ref } from "vue";
import authService from "@/services/auth/auth-service.ts";
import type {SubmissionData} from "@/views/learning-paths/learning-object/submission-data";
import type { SubmissionData } from "@/views/learning-paths/learning-object/submission-data";
import LearningObjectContentView from "@/views/learning-paths/learning-object/content/LearningObjectContentView.vue";
import LearningObjectSubmissionsView from "@/views/learning-paths/learning-object/submissions/LearningObjectSubmissionsView.vue";
@ -14,8 +14,8 @@
const props = defineProps<{
hruid: string;
language: Language;
version: number,
group?: {forGroup: number, assignmentNo: number, classId: string}
version: number;
group?: { forGroup: number; assignmentNo: number; classId: string };
}>();
const learningObjectHtmlQueryResult: UseQueryReturnType<Document, Error> = useLearningObjectHTMLQuery(
@ -35,7 +35,7 @@
:learning-object-content="learningPathHtml.data"
v-model:submission-data="currentSubmission"
/>
<div class="content-submissions-spacer"/>
<div class="content-submissions-spacer" />
<learning-object-submissions-view
v-if="props.group"
:group="props.group"
@ -48,26 +48,26 @@
</template>
<style scoped>
:deep(hr) {
margin-top: 10px;
margin-bottom: 10px;
}
:deep(li) {
margin-left: 30px;
margin-top: 5px;
margin-bottom: 5px;
}
:deep(img) {
max-width: 80%;
}
:deep(h2),
:deep(h3),
:deep(h4),
:deep(h5),
:deep(h6) {
margin-top: 10px;
}
.content-submissions-spacer {
height: 20px;
}
:deep(hr) {
margin-top: 10px;
margin-bottom: 10px;
}
:deep(li) {
margin-left: 30px;
margin-top: 5px;
margin-bottom: 5px;
}
:deep(img) {
max-width: 80%;
}
:deep(h2),
:deep(h3),
:deep(h4),
:deep(h5),
:deep(h6) {
margin-top: 10px;
}
.content-submissions-spacer {
height: 20px;
}
</style>

View file

@ -1,32 +1,32 @@
<script setup lang="ts">
import type {SubmissionData} from "@/views/learning-paths/learning-object/submission-data";
import {getGiftAdapterForType} from "@/views/learning-paths/gift-adapters/gift-adapters.ts";
import {computed, nextTick, onMounted, watch} from "vue";
import {copyArrayWith} from "@/utils/array-utils.ts";
import type { SubmissionData } from "@/views/learning-paths/learning-object/submission-data";
import { getGiftAdapterForType } from "@/views/learning-paths/gift-adapters/gift-adapters.ts";
import { computed, nextTick, onMounted, watch } from "vue";
import { copyArrayWith } from "@/utils/array-utils.ts";
const props = defineProps<{
learningObjectContent: Document
submissionData?: SubmissionData
learningObjectContent: Document;
submissionData?: SubmissionData;
}>();
const emit = defineEmits<{
(e: "update:submissionData", value: SubmissionData): void
(e: "update:submissionData", value: SubmissionData): void;
}>();
const submissionData = computed<SubmissionData | undefined>({
get: () => props.submissionData,
set: (v?: SubmissionData) => v ? emit('update:submissionData', v) : undefined,
set: (v?: SubmissionData) => (v ? emit("update:submissionData", v) : undefined),
});
function forEachQuestion(
doAction: (questionIndex: number, questionName: string, questionType: string, questionElement: Element) => void
doAction: (questionIndex: number, questionName: string, questionType: string, questionElement: Element) => void,
) {
const questions = document.querySelectorAll(".gift-question");
questions.forEach(question => {
const name = question.id.match(/gift-q(\d+)/)?.[1]
const questionType = question.className.split(" ")
.find(it => it.startsWith("gift-question-type"))
questions.forEach((question) => {
const name = question.id.match(/gift-q(\d+)/)?.[1];
const questionType = question.className
.split(" ")
.find((it) => it.startsWith("gift-question-type"))
?.match(/gift-question-type-([^ ]*)/)?.[1];
if (!name || isNaN(parseInt(name)) || !questionType) return;
@ -39,12 +39,9 @@
function attachQuestionListeners(): void {
forEachQuestion((index, _name, type, element) => {
getGiftAdapterForType(type)?.installListener(
element,
(newAnswer) => {
submissionData.value = copyArrayWith(index, newAnswer, submissionData.value ?? [])
}
);
getGiftAdapterForType(type)?.installListener(element, (newAnswer) => {
submissionData.value = copyArrayWith(index, newAnswer, submissionData.value ?? []);
});
});
}
@ -62,19 +59,25 @@
onMounted(() =>
nextTick(() => {
attachQuestionListeners()
attachQuestionListeners();
setAnswers(props.submissionData ?? []);
})
}),
);
watch(() => props.learningObjectContent, async () => {
await nextTick();
attachQuestionListeners();
});
watch(() => props.submissionData, async () => {
await nextTick();
setAnswers(props.submissionData ?? []);
});
watch(
() => props.learningObjectContent,
async () => {
await nextTick();
attachQuestionListeners();
},
);
watch(
() => props.submissionData,
async () => {
await nextTick();
setAnswers(props.submissionData ?? []);
},
);
</script>
<template>
@ -84,5 +87,4 @@
></div>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,36 +1,37 @@
<script setup lang="ts">
import type {SubmissionDTO} from "@dwengo-1/common/interfaces/submission";
import {computed} from "vue";
import {useI18n} from "vue-i18n";
import type { SubmissionDTO } from "@dwengo-1/common/interfaces/submission";
import { computed } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const props = defineProps<{
allSubmissions: SubmissionDTO[]
allSubmissions: SubmissionDTO[];
}>();
const emit = defineEmits<{
(e: "submission-selected", submission: SubmissionDTO): void
(e: "submission-selected", submission: SubmissionDTO): void;
}>();
const headers = computed(() => [
{ title: "#", value: "submissionNo", width: "50px" },
{ title: t("submittedBy"), value: "submittedBy" },
{ title: t("timestamp"), value: "timestamp"},
{ title: t("timestamp"), value: "timestamp" },
{ title: "", key: "action", width: "70px", sortable: false },
]);
const data = computed(() => [...props.allSubmissions]
.sort((a, b) => (a.submissionNumber ?? 0) - (b.submissionNumber ?? 0))
.map((submission, index) => ({
submissionNo: index + 1,
submittedBy: `${submission.submitter.firstName} ${submission.submitter.lastName}`,
timestamp: submission.time ? new Date(submission.time).toLocaleString(): "-",
dto: submission
})
));
const data = computed(() =>
[...props.allSubmissions]
.sort((a, b) => (a.submissionNumber ?? 0) - (b.submissionNumber ?? 0))
.map((submission, index) => ({
submissionNo: index + 1,
submittedBy: `${submission.submitter.firstName} ${submission.submitter.lastName}`,
timestamp: submission.time ? new Date(submission.time).toLocaleString() : "-",
dto: submission,
})),
);
function selectSubmission(submission: SubmissionDTO) {
emit('submission-selected', submission);
emit("submission-selected", submission);
}
</script>
@ -38,14 +39,19 @@
<v-card>
<v-card-title>{{ t("groupSubmissions") }}</v-card-title>
<v-card-text>
<v-data-table :headers="headers"
:items="data"
density="compact"
hide-default-footer
:no-data-text="t('noSubmissionsYet')"
<v-data-table
:headers="headers"
:items="data"
density="compact"
hide-default-footer
:no-data-text="t('noSubmissionsYet')"
>
<template v-slot:item.action="{ item }">
<v-btn density="compact" variant="plain" @click="selectSubmission(item.dto)">
<v-btn
density="compact"
variant="plain"
@click="selectSubmission(item.dto)"
>
{{ t("loadSubmission") }}
</v-btn>
</template>
@ -54,6 +60,4 @@
</v-card>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,26 +1,25 @@
<script setup lang="ts">
import type {SubmissionData} from "@/views/learning-paths/learning-object/submission-data";
import type {SubmissionDTO} from "@dwengo-1/common/interfaces/submission";
import {Language} from "@/data-objects/language.ts";
import {useSubmissionsQuery} from "@/queries/submissions.ts";
import type { SubmissionData } from "@/views/learning-paths/learning-object/submission-data";
import type { SubmissionDTO } from "@dwengo-1/common/interfaces/submission";
import { Language } from "@/data-objects/language.ts";
import { useSubmissionsQuery } from "@/queries/submissions.ts";
import UsingQueryResult from "@/components/UsingQueryResult.vue";
import SubmitButton from "@/views/learning-paths/learning-object/submissions/SubmitButton.vue";
import {computed, watch} from "vue";
import LearningObjectSubmissionsTable
from "@/views/learning-paths/learning-object/submissions/LearningObjectSubmissionsTable.vue";
import {useI18n} from "vue-i18n";
import { computed, watch } from "vue";
import LearningObjectSubmissionsTable from "@/views/learning-paths/learning-object/submissions/LearningObjectSubmissionsTable.vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const props = defineProps<{
submissionData?: SubmissionData,
submissionData?: SubmissionData;
hruid: string;
language: Language;
version: number,
group: {forGroup: number, assignmentNo: number, classId: string}
version: number;
group: { forGroup: number; assignmentNo: number; classId: string };
}>();
const emit = defineEmits<{
(e: "update:submissionData", value: SubmissionData): void
(e: "update:submissionData", value: SubmissionData): void;
}>();
const submissionQuery = useSubmissionsQuery(
@ -30,7 +29,7 @@
() => props.group.classId,
() => props.group.assignmentNo,
() => props.group.forGroup,
() => true
() => true,
);
function emitSubmissionData(submissionData: SubmissionData) {
@ -58,17 +57,16 @@
return JSON.parse(submissions[submissions.length - 1].content);
});
const showSubmissionTable = computed(() =>
props.submissionData !== undefined && props.submissionData.length > 0
);
const showSubmissionTable = computed(() => props.submissionData !== undefined && props.submissionData.length > 0);
const showIsDoneMessage = computed(() =>
lastSubmission.value !== undefined && lastSubmission.value.length === 0
);
const showIsDoneMessage = computed(() => lastSubmission.value !== undefined && lastSubmission.value.length === 0);
</script>
<template>
<using-query-result :query-result="submissionQuery" v-slot="submissions: { data: SubmissionDTO[] }">
<using-query-result
:query-result="submissionQuery"
v-slot="submissions: { data: SubmissionDTO[] }"
>
<submit-button
:hruid="props.hruid"
:language="props.language"
@ -78,12 +76,13 @@
:submissions="submissions.data"
/>
<div class="submit-submissions-spacer"></div>
<v-alert icon="mdi-check"
:text="t('taskCompleted')"
type="success"
variant="tonal"
density="compact"
v-if="showIsDoneMessage"
<v-alert
icon="mdi-check"
:text="t('taskCompleted')"
type="success"
variant="tonal"
density="compact"
v-if="showIsDoneMessage"
></v-alert>
<learning-object-submissions-table
v-if="submissionQuery.data && showSubmissionTable"
@ -94,7 +93,7 @@
</template>
<style scoped>
.submit-submissions-spacer {
height: 20px;
}
.submit-submissions-spacer {
height: 20px;
}
</style>

View file

@ -1,26 +1,26 @@
<script setup lang="ts">
import {computed} from "vue";
import { computed } from "vue";
import authService from "@/services/auth/auth-service.ts";
import type {SubmissionData} from "@/views/learning-paths/learning-object/submission-data";
import {Language} from "@/data-objects/language.ts";
import type {SubmissionDTO} from "@dwengo-1/common/interfaces/submission";
import {useCreateSubmissionMutation} from "@/queries/submissions.ts";
import {deepEquals} from "@/utils/deep-equals.ts";
import type {UserProfile} from "oidc-client-ts";
import type {LearningObjectIdentifierDTO} from "@dwengo-1/common/interfaces/learning-content";
import type {StudentDTO} from "@dwengo-1/common/interfaces/student";
import type {GroupDTO} from "@dwengo-1/common/interfaces/group";
import {useI18n} from "vue-i18n";
import type { SubmissionData } from "@/views/learning-paths/learning-object/submission-data";
import { Language } from "@/data-objects/language.ts";
import type { SubmissionDTO } from "@dwengo-1/common/interfaces/submission";
import { useCreateSubmissionMutation } from "@/queries/submissions.ts";
import { deepEquals } from "@/utils/deep-equals.ts";
import type { UserProfile } from "oidc-client-ts";
import type { LearningObjectIdentifierDTO } from "@dwengo-1/common/interfaces/learning-content";
import type { StudentDTO } from "@dwengo-1/common/interfaces/student";
import type { GroupDTO } from "@dwengo-1/common/interfaces/group";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const props = defineProps<{
submissionData?: SubmissionData,
submissions: SubmissionDTO[],
submissionData?: SubmissionData;
submissions: SubmissionDTO[];
hruid: string;
language: Language;
version: number,
group: {forGroup: number, assignmentNo: number, classId: string}
version: number;
group: { forGroup: number; assignmentNo: number; classId: string };
}>();
const {
@ -28,7 +28,7 @@
isError: submissionFailed,
error: submissionError,
isSuccess: submissionSuccess,
mutate: submitSolution
mutate: submitSolution,
} = useCreateSubmissionMutation();
const isStudent = computed(() => authService.authState.activeRole === "student");
@ -37,16 +37,13 @@
if (!props.submissionData || props.submissions === undefined) {
return true;
}
if (props.submissionData.some(answer => answer === null)) {
if (props.submissionData.some((answer) => answer === null)) {
return false;
}
if (props.submissions.length === 0) {
return false;
}
return deepEquals(
JSON.parse(props.submissions[props.submissions.length - 1].content),
props.submissionData
);
return deepEquals(JSON.parse(props.submissions[props.submissions.length - 1].content), props.submissionData);
});
function submitCurrentAnswer(): void {
@ -55,25 +52,25 @@
const learningObjectIdentifier: LearningObjectIdentifierDTO = {
hruid: props.hruid,
language: props.language as Language,
version: props.version
version: props.version,
};
const submitter: StudentDTO = {
id: currentUser.preferred_username!,
username: currentUser.preferred_username!,
firstName: currentUser.given_name!,
lastName: currentUser.family_name!
lastName: currentUser.family_name!,
};
const group: GroupDTO = {
class: classId,
assignment: assignmentNo,
groupNumber: forGroup
}
groupNumber: forGroup,
};
const submission: SubmissionDTO = {
learningObjectIdentifier,
submitter,
group,
content: JSON.stringify(props.submissionData)
}
content: JSON.stringify(props.submissionData),
};
submitSolution({ data: submission });
}
@ -86,17 +83,16 @@
</script>
<template>
<v-btn v-if="isStudent && !isSubmitDisabled"
prepend-icon="mdi-check"
variant="elevated"
:loading="submissionIsPending"
:disabled="isSubmitDisabled"
@click="submitCurrentAnswer()"
<v-btn
v-if="isStudent && !isSubmitDisabled"
prepend-icon="mdi-check"
variant="elevated"
:loading="submissionIsPending"
:disabled="isSubmitDisabled"
@click="submitCurrentAnswer()"
>
{{ buttonText }}
</v-btn>
</template>
<style scoped>
</style>
<style scoped></style>