style: fix linting issues met Prettier
This commit is contained in:
		
							parent
							
								
									2f5bb333db
								
							
						
					
					
						commit
						fc92570282
					
				
					 16 changed files with 498 additions and 505 deletions
				
			
		|  | @ -66,7 +66,7 @@ export async function getLearningPaths(req: AuthenticatedRequest, res: Response) | ||||||
| 
 | 
 | ||||||
|             if (req.auth) { |             if (req.auth) { | ||||||
|                 const adminUsername = req.auth.username; |                 const adminUsername = req.auth.username; | ||||||
|                 const userLearningPaths = await learningPathService.getLearningPathsAdministratedBy(adminUsername) || []; |                 const userLearningPaths = (await learningPathService.getLearningPathsAdministratedBy(adminUsername)) || []; | ||||||
|                 allLearningPaths = apiLearningPaths.concat(userLearningPaths); |                 allLearningPaths = apiLearningPaths.concat(userLearningPaths); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | @ -78,7 +78,7 @@ export async function getLearningPaths(req: AuthenticatedRequest, res: Response) | ||||||
|             hruidList, |             hruidList, | ||||||
|             language as Language, |             language as Language, | ||||||
|             `HRUIDs: ${hruidList.join(', ')}`, |             `HRUIDs: ${hruidList.join(', ')}`, | ||||||
|             forGroup, |             forGroup | ||||||
|         ); |         ); | ||||||
|         res.json(learningPaths.data); |         res.json(learningPaths.data); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -7,10 +7,13 @@ import { LearningPathTransition } from '../../entities/content/learning-path-tra | ||||||
| 
 | 
 | ||||||
| export class LearningPathRepository extends DwengoEntityRepository<LearningPath> { | export class LearningPathRepository extends DwengoEntityRepository<LearningPath> { | ||||||
|     public async findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> { |     public async findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> { | ||||||
|         return this.findOne({ |         return this.findOne( | ||||||
|             hruid: hruid, |             { | ||||||
|             language: language, |                 hruid: hruid, | ||||||
|         }, { populate: ['nodes', 'nodes.transitions', 'admins'] }); |                 language: language, | ||||||
|  |             }, | ||||||
|  |             { populate: ['nodes', 'nodes.transitions', 'admins'] } | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  |  | ||||||
|  | @ -4,11 +4,7 @@ import { getLearningPathRepository } from '../../data/repositories.js'; | ||||||
| import learningObjectService from '../learning-objects/learning-object-service.js'; | import learningObjectService from '../learning-objects/learning-object-service.js'; | ||||||
| import { LearningPathNode } from '../../entities/content/learning-path-node.entity.js'; | import { LearningPathNode } from '../../entities/content/learning-path-node.entity.js'; | ||||||
| import { LearningPathTransition } from '../../entities/content/learning-path-transition.entity.js'; | import { LearningPathTransition } from '../../entities/content/learning-path-transition.entity.js'; | ||||||
| import { | import { getLastSubmissionForGroup, idFromLearningPathNode, isTransitionPossible } from './learning-path-personalization-util.js'; | ||||||
|     getLastSubmissionForGroup, |  | ||||||
|     idFromLearningPathNode, |  | ||||||
|     isTransitionPossible, |  | ||||||
| } from './learning-path-personalization-util.js'; |  | ||||||
| import { | import { | ||||||
|     FilteredLearningObject, |     FilteredLearningObject, | ||||||
|     LearningObjectNode, |     LearningObjectNode, | ||||||
|  | @ -41,9 +37,9 @@ async function getLearningObjectsForNodes(nodes: Collection<LearningPathNode>): | ||||||
|                         version: node.version, |                         version: node.version, | ||||||
|                         language: node.language, |                         language: node.language, | ||||||
|                     }) |                     }) | ||||||
|                     .then((learningObject) => [node, learningObject] as [LearningPathNode, FilteredLearningObject | null]), |                     .then((learningObject) => [node, learningObject] as [LearningPathNode, FilteredLearningObject | null]) | ||||||
|             ), |             ) | ||||||
|         ), |         ) | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     // Ignore all learning objects that cannot be found such that the rest of the learning path keeps working.
 |     // Ignore all learning objects that cannot be found such that the rest of the learning path keeps working.
 | ||||||
|  | @ -105,14 +101,14 @@ async function convertNode( | ||||||
|     node: LearningPathNode, |     node: LearningPathNode, | ||||||
|     learningObject: FilteredLearningObject, |     learningObject: FilteredLearningObject, | ||||||
|     personalizedFor: Group | undefined, |     personalizedFor: Group | undefined, | ||||||
|     nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject>, |     nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject> | ||||||
| ): Promise<LearningObjectNode> { | ): Promise<LearningObjectNode> { | ||||||
|     const lastSubmission = personalizedFor ? await getLastSubmissionForGroup(idFromLearningPathNode(node), personalizedFor) : null; |     const lastSubmission = personalizedFor ? await getLastSubmissionForGroup(idFromLearningPathNode(node), personalizedFor) : null; | ||||||
|     const transitions = node.transitions |     const transitions = node.transitions | ||||||
|         .filter( |         .filter( | ||||||
|             (trans) => |             (trans) => | ||||||
|                 !personalizedFor || // If we do not want a personalized learning path, keep all transitions
 |                 !personalizedFor || // If we do not want a personalized learning path, keep all transitions
 | ||||||
|                 isTransitionPossible(trans, optionalJsonStringToObject(lastSubmission?.content)), // Otherwise remove all transitions that aren't possible.
 |                 isTransitionPossible(trans, optionalJsonStringToObject(lastSubmission?.content)) // Otherwise remove all transitions that aren't possible.
 | ||||||
|         ) |         ) | ||||||
|         .map((trans, i) => { |         .map((trans, i) => { | ||||||
|             try { |             try { | ||||||
|  | @ -145,10 +141,10 @@ async function convertNode( | ||||||
|  */ |  */ | ||||||
| async function convertNodes( | async function convertNodes( | ||||||
|     nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject>, |     nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject>, | ||||||
|     personalizedFor?: Group, |     personalizedFor?: Group | ||||||
| ): Promise<LearningObjectNode[]> { | ): Promise<LearningObjectNode[]> { | ||||||
|     const nodesPromise = Array.from(nodesToLearningObjects.entries()).map(async (entry) => |     const nodesPromise = Array.from(nodesToLearningObjects.entries()).map(async (entry) => | ||||||
|         convertNode(entry[0], entry[1], personalizedFor, nodesToLearningObjects), |         convertNode(entry[0], entry[1], personalizedFor, nodesToLearningObjects) | ||||||
|     ); |     ); | ||||||
|     return await Promise.all(nodesPromise); |     return await Promise.all(nodesPromise); | ||||||
| } | } | ||||||
|  | @ -175,7 +171,7 @@ function optionalJsonStringToObject(jsonString?: string): object | null { | ||||||
| function convertTransition( | function convertTransition( | ||||||
|     transition: LearningPathTransition, |     transition: LearningPathTransition, | ||||||
|     index: number, |     index: number, | ||||||
|     nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject>, |     nodesToLearningObjects: Map<LearningPathNode, FilteredLearningObject> | ||||||
| ): Transition { | ): Transition { | ||||||
|     const nextNode = nodesToLearningObjects.get(transition.next); |     const nextNode = nodesToLearningObjects.get(transition.next); | ||||||
|     if (!nextNode) { |     if (!nextNode) { | ||||||
|  | @ -206,10 +202,10 @@ const databaseLearningPathProvider: LearningPathProvider = { | ||||||
|         const learningPathRepo = getLearningPathRepository(); |         const learningPathRepo = getLearningPathRepository(); | ||||||
| 
 | 
 | ||||||
|         const learningPaths = (await Promise.all(hruids.map(async (hruid) => learningPathRepo.findByHruidAndLanguage(hruid, language)))).filter( |         const learningPaths = (await Promise.all(hruids.map(async (hruid) => learningPathRepo.findByHruidAndLanguage(hruid, language)))).filter( | ||||||
|             (learningPath) => learningPath !== null, |             (learningPath) => learningPath !== null | ||||||
|         ); |         ); | ||||||
|         const filteredLearningPaths = await Promise.all( |         const filteredLearningPaths = await Promise.all( | ||||||
|             learningPaths.map(async (learningPath, index) => convertLearningPath(learningPath, index, personalizedFor)), |             learningPaths.map(async (learningPath, index) => convertLearningPath(learningPath, index, personalizedFor)) | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         return { |         return { | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ async function addProgressToLearningPath(learningPath: LearningPath, personalize | ||||||
|         learningPath.nodes.map(async (node) => { |         learningPath.nodes.map(async (node) => { | ||||||
|             const lastSubmission = personalizedFor ? await getLastSubmissionForGroup(idFromLearningObjectNode(node), personalizedFor) : null; |             const lastSubmission = personalizedFor ? await getLastSubmissionForGroup(idFromLearningObjectNode(node), personalizedFor) : null; | ||||||
|             node.done = Boolean(lastSubmission); |             node.done = Boolean(lastSubmission); | ||||||
|         }), |         }) | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     learningPath.num_nodes = learningPath.nodes.length; |     learningPath.num_nodes = learningPath.nodes.length; | ||||||
|  |  | ||||||
|  | @ -1,12 +1,7 @@ | ||||||
| import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider.js'; | import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider.js'; | ||||||
| import databaseLearningPathProvider from './database-learning-path-provider.js'; | import databaseLearningPathProvider from './database-learning-path-provider.js'; | ||||||
| import { envVars, getEnvVar } from '../../util/envVars.js'; | import { envVars, getEnvVar } from '../../util/envVars.js'; | ||||||
| import { | import { LearningObjectNode, LearningPath, LearningPathIdentifier, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
|     LearningObjectNode, |  | ||||||
|     LearningPath, |  | ||||||
|     LearningPathIdentifier, |  | ||||||
|     LearningPathResponse, |  | ||||||
| } from '@dwengo-1/common/interfaces/learning-content'; |  | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { Group } from '../../entities/assignments/group.entity.js'; | import { Group } from '../../entities/assignments/group.entity.js'; | ||||||
| import { LearningPath as LearningPathEntity } from '../../entities/content/learning-path.entity.js'; | import { LearningPath as LearningPathEntity } from '../../entities/content/learning-path.entity.js'; | ||||||
|  | @ -46,16 +41,16 @@ export function mapToLearningPath(dto: LearningPath, adminsDto: TeacherDTO[]): L | ||||||
|             startNode: nodeDto.start_node ?? false, |             startNode: nodeDto.start_node ?? false, | ||||||
|             createdAt: new Date(), |             createdAt: new Date(), | ||||||
|             updatedAt: new Date(), |             updatedAt: new Date(), | ||||||
|         }), |         }) | ||||||
|     ); |     ); | ||||||
|     dto.nodes.forEach((nodeDto) => { |     dto.nodes.forEach((nodeDto) => { | ||||||
|         const fromNode = nodes.find( |         const fromNode = nodes.find( | ||||||
|             (it) => it.learningObjectHruid === nodeDto.learningobject_hruid && it.language === nodeDto.language && it.version === nodeDto.version, |             (it) => it.learningObjectHruid === nodeDto.learningobject_hruid && it.language === nodeDto.language && it.version === nodeDto.version | ||||||
|         )!; |         )!; | ||||||
|         const transitions = nodeDto.transitions.map((transDto, i) => { |         const transitions = nodeDto.transitions.map((transDto, i) => { | ||||||
|             const toNode = nodes.find( |             const toNode = nodes.find( | ||||||
|                 (it) => |                 (it) => | ||||||
|                     it.learningObjectHruid === transDto.next.hruid && it.language === transDto.next.language && it.version === transDto.next.version, |                     it.learningObjectHruid === transDto.next.hruid && it.language === transDto.next.language && it.version === transDto.next.version | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|             if (toNode) { |             if (toNode) { | ||||||
|  | @ -99,7 +94,7 @@ const learningPathService = { | ||||||
|             nonUserContentHruids, |             nonUserContentHruids, | ||||||
|             language, |             language, | ||||||
|             source, |             source, | ||||||
|             personalizedFor, |             personalizedFor | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         const result = (userContentLearningPaths.data || []).concat(nonUserContentLearningPaths.data || []); |         const result = (userContentLearningPaths.data || []).concat(nonUserContentLearningPaths.data || []); | ||||||
|  | @ -124,7 +119,7 @@ const learningPathService = { | ||||||
|      */ |      */ | ||||||
|     async searchLearningPaths(query: string, language: Language, personalizedFor?: Group): Promise<LearningPath[]> { |     async searchLearningPaths(query: string, language: Language, personalizedFor?: Group): Promise<LearningPath[]> { | ||||||
|         const providerResponses = await Promise.all( |         const providerResponses = await Promise.all( | ||||||
|             allProviders.map(async (provider) => provider.searchLearningPaths(query, language, personalizedFor)), |             allProviders.map(async (provider) => provider.searchLearningPaths(query, language, personalizedFor)) | ||||||
|         ); |         ); | ||||||
|         return providerResponses.flat(); |         return providerResponses.flat(); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  | @ -151,19 +151,13 @@ describe('DatabaseLearningPathProvider', () => { | ||||||
|     describe('searchLearningPathsByAdmin', () => { |     describe('searchLearningPathsByAdmin', () => { | ||||||
|         it('returns the learning path owned by the admin', async () => { |         it('returns the learning path owned by the admin', async () => { | ||||||
|             const expectedLearningPath = mapToLearningPath(testLearningPath02, [mapToTeacherDTO(teacherB)]); |             const expectedLearningPath = mapToLearningPath(testLearningPath02, [mapToTeacherDTO(teacherB)]); | ||||||
|             const result = await databaseLearningPathProvider.searchLearningPathsByAdmin( |             const result = await databaseLearningPathProvider.searchLearningPathsByAdmin([teacherB], expectedLearningPath.language); | ||||||
|                 [teacherB], |  | ||||||
|                 expectedLearningPath.language |  | ||||||
|             ); |  | ||||||
|             expect(result.length).toBe(1); |             expect(result.length).toBe(1); | ||||||
|             expect(result[0].title).toBe(expectedLearningPath.title); |             expect(result[0].title).toBe(expectedLearningPath.title); | ||||||
|             expect(result[0].description).toBe(expectedLearningPath.description); |             expect(result[0].description).toBe(expectedLearningPath.description); | ||||||
|         }); |         }); | ||||||
|         it('returns an empty result when querying admins that do not have custom learning paths', async() => { |         it('returns an empty result when querying admins that do not have custom learning paths', async () => { | ||||||
|             const result = await databaseLearningPathProvider.searchLearningPathsByAdmin( |             const result = await databaseLearningPathProvider.searchLearningPathsByAdmin([teacherA], testLearningPath.language); | ||||||
|                 [teacherA], |  | ||||||
|                 testLearningPath.language |  | ||||||
|             ); |  | ||||||
|             expect(result.length).toBe(0); |             expect(result.length).toBe(0); | ||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  | @ -1,19 +1,19 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import type { LearningObject } from '@/data-objects/learning-objects/learning-object'; |     import type { LearningObject } from "@/data-objects/learning-objects/learning-object"; | ||||||
| import type { LearningPath } from '@/data-objects/learning-paths/learning-path'; |     import type { LearningPath } from "@/data-objects/learning-paths/learning-path"; | ||||||
| import { useLearningObjectListForPathQuery } from '@/queries/learning-objects'; |     import { useLearningObjectListForPathQuery } from "@/queries/learning-objects"; | ||||||
| import { useRoute } from 'vue-router'; |     import { useRoute } from "vue-router"; | ||||||
| import UsingQueryResult from "@/components/UsingQueryResult.vue"; |     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
| import { ref, watchEffect } from 'vue'; |     import { ref, watchEffect } from "vue"; | ||||||
| 
 | 
 | ||||||
|     const route = useRoute(); |     const route = useRoute(); | ||||||
| 
 | 
 | ||||||
|     const props = defineProps<{ |     const props = defineProps<{ | ||||||
|         path: LearningPath; |         path: LearningPath; | ||||||
|         activeObjectId: string  |         activeObjectId: string; | ||||||
|     }>(); |     }>(); | ||||||
| 
 | 
 | ||||||
|     const currentPath = ref(props.path) |     const currentPath = ref(props.path); | ||||||
| 
 | 
 | ||||||
|     const learningObjectListQueryResult = useLearningObjectListForPathQuery(currentPath); |     const learningObjectListQueryResult = useLearningObjectListForPathQuery(currentPath); | ||||||
| 
 | 
 | ||||||
|  | @ -33,21 +33,34 @@ import { ref, watchEffect } from 'vue'; | ||||||
|     function toggleDropdown(): void { |     function toggleDropdown(): void { | ||||||
|         dropdownEnabled.value = !dropdownEnabled.value; |         dropdownEnabled.value = !dropdownEnabled.value; | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <main> |     <main> | ||||||
|         <div class="dropdown-toggle" @click="toggleDropdown()">▼{{path.title}}</div> |         <div | ||||||
|         <div class="dropdown" v-if="dropdownEnabled"> |             class="dropdown-toggle" | ||||||
|  |             @click="toggleDropdown()" | ||||||
|  |         > | ||||||
|  |             ▼{{ path.title }} | ||||||
|  |         </div> | ||||||
|  |         <div | ||||||
|  |             class="dropdown" | ||||||
|  |             v-if="dropdownEnabled" | ||||||
|  |         > | ||||||
|             <using-query-result |             <using-query-result | ||||||
|                 :query-result="learningObjectListQueryResult" |                 :query-result="learningObjectListQueryResult" | ||||||
|                 v-slot="learningObjects: { data: LearningObject[] }" |                 v-slot="learningObjects: { data: LearningObject[] }" | ||||||
|             > |             > | ||||||
|                 <template v-for="node in learningObjects.data" :key="node.key"> |                 <template | ||||||
|  |                     v-for="node in learningObjects.data" | ||||||
|  |                     :key="node.key" | ||||||
|  |                 > | ||||||
|                     <v-list-item |                     <v-list-item | ||||||
|                         link |                         link | ||||||
|                         :to="{ path: `/discussion-reload/${currentPath.hruid}/${node.language}/${node.key}`, query: route.query }" |                         :to="{ | ||||||
|  |                             path: `/discussion-reload/${currentPath.hruid}/${node.language}/${node.key}`, | ||||||
|  |                             query: route.query, | ||||||
|  |                         }" | ||||||
|                         :title="node.title" |                         :title="node.title" | ||||||
|                         :active="node.key === props.activeObjectId" |                         :active="node.key === props.activeObjectId" | ||||||
|                     > |                     > | ||||||
|  | @ -59,31 +72,31 @@ import { ref, watchEffect } from 'vue'; | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
| .dropdown { |     .dropdown { | ||||||
|     margin-left: 0.5rem; |         margin-left: 0.5rem; | ||||||
|     padding-left: 1rem; |         padding-left: 1rem; | ||||||
|     border-left: 2px solid #e0e0e0; |         border-left: 2px solid #e0e0e0; | ||||||
|     background-color: #f9f9f9; |         background-color: #f9f9f9; | ||||||
|     border-radius: 4px; |         border-radius: 4px; | ||||||
|     padding-top: 0.5rem; |         padding-top: 0.5rem; | ||||||
|     padding-bottom: 0.5rem; |         padding-bottom: 0.5rem; | ||||||
| } |     } | ||||||
| .dropdown-toggle { |     .dropdown-toggle { | ||||||
|     cursor: pointer; |         cursor: pointer; | ||||||
|     display: flex; |         display: flex; | ||||||
|     align-items: center; |         align-items: center; | ||||||
|     font-weight: 600; |         font-weight: 600; | ||||||
|     user-select: none; |         user-select: none; | ||||||
|     padding: 0.5rem; |         padding: 0.5rem; | ||||||
|     transition: color 0.2s; |         transition: color 0.2s; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .dropdown-toggle:hover { |     .dropdown-toggle:hover { | ||||||
|     color: #27c53f; |         color: #27c53f; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .dropdown-icon { |     .dropdown-icon { | ||||||
|     margin-right: 0.5rem; |         margin-right: 0.5rem; | ||||||
|     font-size: 0.9rem; |         font-size: 0.9rem; | ||||||
| } |     } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -1,18 +1,16 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|  |     import type { LearningPath } from "@/data-objects/learning-paths/learning-path.ts"; | ||||||
|  |     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
|  |     import DiscussionSideBarElement from "@/components/DiscussionSideBarElement.vue"; | ||||||
|  |     import { useI18n } from "vue-i18n"; | ||||||
|  |     import { useGetAllLearningPaths } from "@/queries/learning-paths.ts"; | ||||||
|  |     import { ref } from "vue"; | ||||||
| 
 | 
 | ||||||
| import type { LearningPath } from '@/data-objects/learning-paths/learning-path.ts'; |     const { t, locale } = useI18n(); | ||||||
| import UsingQueryResult from '@/components/UsingQueryResult.vue'; |  | ||||||
| import DiscussionSideBarElement from '@/components/DiscussionSideBarElement.vue'; |  | ||||||
| import { useI18n } from 'vue-i18n'; |  | ||||||
| import { useGetAllLearningPaths } from '@/queries/learning-paths.ts'; |  | ||||||
| import { ref } from 'vue'; |  | ||||||
| 
 | 
 | ||||||
| const { t, locale } = useI18n(); |     const navigationDrawerShown = ref(true); | ||||||
| 
 |  | ||||||
| const navigationDrawerShown = ref(true); |  | ||||||
| 
 |  | ||||||
| const allLearningPathsResult = useGetAllLearningPaths(locale.value); |  | ||||||
| 
 | 
 | ||||||
|  |     const allLearningPathsResult = useGetAllLearningPaths(locale.value); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  | @ -24,14 +22,15 @@ const allLearningPathsResult = useGetAllLearningPaths(locale.value); | ||||||
|         <div class="d-flex flex-column h-100"> |         <div class="d-flex flex-column h-100"> | ||||||
|             <v-list-item> |             <v-list-item> | ||||||
|                 <template v-slot:title> |                 <template v-slot:title> | ||||||
|                     <div class="title">{{ t('discussions') }}</div> |                     <div class="title">{{ t("discussions") }}</div> | ||||||
|                 </template> |                 </template> | ||||||
|             </v-list-item> |             </v-list-item> | ||||||
|             <v-divider></v-divider> |             <v-divider></v-divider> | ||||||
|             <div> |             <div> | ||||||
|                 <using-query-result |                 <using-query-result | ||||||
|                     :query-result="allLearningPathsResult" |                     :query-result="allLearningPathsResult" | ||||||
|                     v-slot="learningPaths: {data: LearningPath[]}"> |                     v-slot="learningPaths: { data: LearningPath[] }" | ||||||
|  |                 > | ||||||
|                     <DiscussionSideBarElement |                     <DiscussionSideBarElement | ||||||
|                         v-for="learningPath in learningPaths.data" |                         v-for="learningPath in learningPaths.data" | ||||||
|                         :path="learningPath" |                         :path="learningPath" | ||||||
|  | @ -54,13 +53,11 @@ const allLearningPathsResult = useGetAllLearningPaths(locale.value); | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
| .title { |     .title { | ||||||
|     color: #0e6942; |         color: #0e6942; | ||||||
|     text-transform: uppercase; |         text-transform: uppercase; | ||||||
|     font-weight: bolder; |         font-weight: bolder; | ||||||
|     padding-top: 2%; |         padding-top: 2%; | ||||||
|     font-size: 36px; |         font-size: 36px; | ||||||
| } |     } | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|     import type { QuestionDTO } from "@dwengo-1/common/interfaces/question"; |     import type { QuestionDTO } from "@dwengo-1/common/interfaces/question"; | ||||||
|     import SingleQuestion from "./SingleQuestion.vue"; |     import SingleQuestion from "./SingleQuestion.vue"; | ||||||
|     import { useI18n } from 'vue-i18n'; |     import { useI18n } from "vue-i18n"; | ||||||
| 
 | 
 | ||||||
|     const { t } = useI18n(); |     const { t } = useI18n(); | ||||||
| 
 | 
 | ||||||
|  | @ -21,19 +21,19 @@ | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|         <div v-else> |         <div v-else> | ||||||
|             <p class="no-questions">{{t("no-questions")}}</p> |             <p class="no-questions">{{ t("no-questions") }}</p> | ||||||
|         </div> |         </div> | ||||||
|     </div> |     </div> | ||||||
| </template> | </template> | ||||||
| <style scoped> | <style scoped> | ||||||
| .no-questions { |     .no-questions { | ||||||
|     display: flex; |         display: flex; | ||||||
|     justify-content: center; |         justify-content: center; | ||||||
|     align-items: center; |         align-items: center; | ||||||
|     height: 40vh; |         height: 40vh; | ||||||
|     text-align: center; |         text-align: center; | ||||||
|     font-size: 18px; |         font-size: 18px; | ||||||
|     color: #666; |         color: #666; | ||||||
|     padding: 0 20px; |         padding: 0 20px; | ||||||
| } |     } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -1,97 +1,95 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|  |     import authService from "@/services/auth/auth-service.ts"; | ||||||
|  |     import { Language } from "@/data-objects/language.ts"; | ||||||
|  |     import { computed, type ComputedRef, ref } from "vue"; | ||||||
|  |     import type { AssignmentDTO } from "@dwengo-1/common/interfaces/assignment"; | ||||||
|  |     import { useStudentAssignmentsQuery, useStudentGroupsQuery } from "@/queries/students.ts"; | ||||||
|  |     import type { GroupDTO, GroupDTOId } from "@dwengo-1/common/interfaces/group"; | ||||||
|  |     import type { QuestionData } from "@dwengo-1/common/interfaces/question"; | ||||||
|  |     import type { LearningObjectIdentifierDTO } from "@dwengo-1/interfaces/learning-content"; | ||||||
|  |     import { useCreateQuestionMutation, useQuestionsQuery } from "@/queries/questions.ts"; | ||||||
|  |     import { LearningPathNode } from "@/data-objects/learning-paths/learning-path-node.ts"; | ||||||
|  |     import { useGetLearningPathQuery } from "@/queries/learning-paths.ts"; | ||||||
|  |     import { useLearningObjectListForPathQuery } from "@/queries/learning-objects.ts"; | ||||||
|  |     import { useI18n } from "vue-i18n"; | ||||||
|  |     import { AccountType } from "@dwengo-1/common/src/util/account-types.ts"; | ||||||
| 
 | 
 | ||||||
| import authService from '@/services/auth/auth-service.ts'; |     const props = defineProps<{ | ||||||
| import { Language } from '@/data-objects/language.ts'; |         hruid: string; | ||||||
| import { computed, type ComputedRef, ref } from 'vue'; |         language: Language; | ||||||
| import type { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; |         learningObjectHruid?: string; | ||||||
| import { useStudentAssignmentsQuery, useStudentGroupsQuery } from '@/queries/students.ts'; |         forGroup?: GroupDTOId | undefined; | ||||||
| import type { GroupDTO, GroupDTOId } from '@dwengo-1/common/interfaces/group'; |     }>(); | ||||||
| import type { QuestionData } from '@dwengo-1/common/interfaces/question'; |  | ||||||
| import type { LearningObjectIdentifierDTO } from '@dwengo-1/interfaces/learning-content'; |  | ||||||
| import { useCreateQuestionMutation, useQuestionsQuery } from '@/queries/questions.ts'; |  | ||||||
| import { LearningPathNode } from '@/data-objects/learning-paths/learning-path-node.ts'; |  | ||||||
| import { useGetLearningPathQuery } from '@/queries/learning-paths.ts'; |  | ||||||
| import { useLearningObjectListForPathQuery } from '@/queries/learning-objects.ts'; |  | ||||||
| import { useI18n } from 'vue-i18n'; |  | ||||||
| import { AccountType } from '@dwengo-1/common/src/util/account-types.ts'; |  | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ |     const { t } = useI18n(); | ||||||
|     hruid: string; |  | ||||||
|     language: Language; |  | ||||||
|     learningObjectHruid?: string; |  | ||||||
|     forGroup?: GroupDTOId | undefined |  | ||||||
| }>(); |  | ||||||
| 
 | 
 | ||||||
| const { t } = useI18n(); |     const studentAssignmentsQueryResult = useStudentAssignmentsQuery( | ||||||
| 
 |         authService.authState.user?.profile.preferred_username, | ||||||
| const studentAssignmentsQueryResult = useStudentAssignmentsQuery( |  | ||||||
|     authService.authState.user?.profile.preferred_username, |  | ||||||
| ); |  | ||||||
| const learningPathQueryResult = useGetLearningPathQuery(props.hruid, props.language, props.forGroup); |  | ||||||
| const learningObjectListQueryResult = useLearningObjectListForPathQuery(learningPathQueryResult.data); |  | ||||||
| 
 |  | ||||||
| const pathIsAssignment = computed(() => { |  | ||||||
|     const assignments = (studentAssignmentsQueryResult.data.value?.assignments as AssignmentDTO[]) || []; |  | ||||||
|     return assignments.some( |  | ||||||
|         (assignment) => assignment.learningPath === props.hruid && assignment.language === props.language, |  | ||||||
|     ); |     ); | ||||||
| }); |     const learningPathQueryResult = useGetLearningPathQuery(props.hruid, props.language, props.forGroup); | ||||||
|  |     const learningObjectListQueryResult = useLearningObjectListForPathQuery(learningPathQueryResult.data); | ||||||
| 
 | 
 | ||||||
| const nodesList: ComputedRef<LearningPathNode[] | null> = computed( |     const pathIsAssignment = computed(() => { | ||||||
|     () => learningPathQueryResult.data.value?.nodesAsList ?? null, |         const assignments = (studentAssignmentsQueryResult.data.value?.assignments as AssignmentDTO[]) || []; | ||||||
| ); |         return assignments.some( | ||||||
|  |             (assignment) => assignment.learningPath === props.hruid && assignment.language === props.language, | ||||||
|  |         ); | ||||||
|  |     }); | ||||||
| 
 | 
 | ||||||
| 
 |     const nodesList: ComputedRef<LearningPathNode[] | null> = computed( | ||||||
| const currentNode = computed(() => { |         () => learningPathQueryResult.data.value?.nodesAsList ?? null, | ||||||
|     const currentHruid = props.learningObjectHruid; |  | ||||||
|     return nodesList.value?.find((it) => it.learningobjectHruid === currentHruid); |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| const getQuestionsQuery = useQuestionsQuery( |  | ||||||
|     computed( |  | ||||||
|         () => |  | ||||||
|             ({ |  | ||||||
|                 language: currentNode.value?.language, |  | ||||||
|                 hruid: currentNode.value?.learningobjectHruid, |  | ||||||
|                 version: currentNode.value?.version, |  | ||||||
|             }) as LearningObjectIdentifierDTO, |  | ||||||
|     ), |  | ||||||
| ); |  | ||||||
| 
 |  | ||||||
| const questionInput = ref(''); |  | ||||||
| 
 |  | ||||||
| const loID: LearningObjectIdentifierDTO = { |  | ||||||
|     hruid: props.learningObjectHruid as string, |  | ||||||
|     language: props.language, |  | ||||||
| }; |  | ||||||
| const createQuestionMutation = useCreateQuestionMutation(loID); |  | ||||||
| const groupsQueryResult = useStudentGroupsQuery(authService.authState.user?.profile.preferred_username); |  | ||||||
| 
 |  | ||||||
| function submitQuestion(): void { |  | ||||||
|     const assignments = studentAssignmentsQueryResult.data.value?.assignments as AssignmentDTO[]; |  | ||||||
|     const assignment = assignments.find( |  | ||||||
|         (assignment) => assignment.learningPath === props.hruid && assignment.language === props.language, |  | ||||||
|     ); |     ); | ||||||
|     const groups = groupsQueryResult.data.value?.groups as GroupDTO[]; | 
 | ||||||
|     const group = groups?.find((group) => group.assignment === assignment?.id) as GroupDTO; |     const currentNode = computed(() => { | ||||||
|     const questionData: QuestionData = { |         const currentHruid = props.learningObjectHruid; | ||||||
|         author: authService.authState.user?.profile.preferred_username, |         return nodesList.value?.find((it) => it.learningobjectHruid === currentHruid); | ||||||
|         content: questionInput.value, |     }); | ||||||
|         inGroup: group, | 
 | ||||||
|  |     const getQuestionsQuery = useQuestionsQuery( | ||||||
|  |         computed( | ||||||
|  |             () => | ||||||
|  |                 ({ | ||||||
|  |                     language: currentNode.value?.language, | ||||||
|  |                     hruid: currentNode.value?.learningobjectHruid, | ||||||
|  |                     version: currentNode.value?.version, | ||||||
|  |                 }) as LearningObjectIdentifierDTO, | ||||||
|  |         ), | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     const questionInput = ref(""); | ||||||
|  | 
 | ||||||
|  |     const loID: LearningObjectIdentifierDTO = { | ||||||
|  |         hruid: props.learningObjectHruid as string, | ||||||
|  |         language: props.language, | ||||||
|     }; |     }; | ||||||
|     if (questionInput.value !== '') { |     const createQuestionMutation = useCreateQuestionMutation(loID); | ||||||
|         createQuestionMutation.mutate(questionData, { |     const groupsQueryResult = useStudentGroupsQuery(authService.authState.user?.profile.preferred_username); | ||||||
|             onSuccess: async () => { | 
 | ||||||
|                 questionInput.value = ''; // Clear the input field after submission |     function submitQuestion(): void { | ||||||
|                 await getQuestionsQuery.refetch(); // Reload the questions |         const assignments = studentAssignmentsQueryResult.data.value?.assignments as AssignmentDTO[]; | ||||||
|             }, |         const assignment = assignments.find( | ||||||
|             onError: (_) => { |             (assignment) => assignment.learningPath === props.hruid && assignment.language === props.language, | ||||||
|                 // TODO Handle error |         ); | ||||||
|                 // - console.error(e); |         const groups = groupsQueryResult.data.value?.groups as GroupDTO[]; | ||||||
|             }, |         const group = groups?.find((group) => group.assignment === assignment?.id) as GroupDTO; | ||||||
|         }); |         const questionData: QuestionData = { | ||||||
|  |             author: authService.authState.user?.profile.preferred_username, | ||||||
|  |             content: questionInput.value, | ||||||
|  |             inGroup: group, | ||||||
|  |         }; | ||||||
|  |         if (questionInput.value !== "") { | ||||||
|  |             createQuestionMutation.mutate(questionData, { | ||||||
|  |                 onSuccess: async () => { | ||||||
|  |                     questionInput.value = ""; // Clear the input field after submission | ||||||
|  |                     await getQuestionsQuery.refetch(); // Reload the questions | ||||||
|  |                 }, | ||||||
|  |                 onError: (_) => { | ||||||
|  |                     // TODO Handle error | ||||||
|  |                     // - console.error(e); | ||||||
|  |                 }, | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  | @ -114,48 +112,47 @@ function submitQuestion(): void { | ||||||
|             </button> |             </button> | ||||||
|         </div> |         </div> | ||||||
|     </div> |     </div> | ||||||
| 
 |  | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
| .question-box { |     .question-box { | ||||||
|     width: 100%; |         width: 100%; | ||||||
|     max-width: 400px; |         max-width: 400px; | ||||||
|     margin: 20px auto; |         margin: 20px auto; | ||||||
|     font-family: sans-serif; |         font-family: sans-serif; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .input-wrapper { |     .input-wrapper { | ||||||
|     display: flex; |         display: flex; | ||||||
|     align-items: center; |         align-items: center; | ||||||
|     border: 1px solid #ccc; |         border: 1px solid #ccc; | ||||||
|     border-radius: 999px; |         border-radius: 999px; | ||||||
|     padding: 8px 12px; |         padding: 8px 12px; | ||||||
|     box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |         box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .question-input { |     .question-input { | ||||||
|     flex: 1; |         flex: 1; | ||||||
|     border: none; |         border: none; | ||||||
|     outline: none; |         outline: none; | ||||||
|     font-size: 14px; |         font-size: 14px; | ||||||
|     background-color: transparent; |         background-color: transparent; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .question-input::placeholder { |     .question-input::placeholder { | ||||||
|     color: #999; |         color: #999; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .send-button { |     .send-button { | ||||||
|     background: none; |         background: none; | ||||||
|     border: none; |         border: none; | ||||||
|     cursor: pointer; |         cursor: pointer; | ||||||
|     font-size: 16px; |         font-size: 16px; | ||||||
|     color: #555; |         color: #555; | ||||||
|     transition: color 0.2s ease; |         transition: color 0.2s ease; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .send-button:hover { |     .send-button:hover { | ||||||
|     color: #000; |         color: #000; | ||||||
| } |     } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -8,7 +8,6 @@ | ||||||
|     import authService from "@/services/auth/auth-service"; |     import authService from "@/services/auth/auth-service"; | ||||||
|     import { useI18n } from "vue-i18n"; |     import { useI18n } from "vue-i18n"; | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     const { t } = useI18n(); |     const { t } = useI18n(); | ||||||
| 
 | 
 | ||||||
|     const props = defineProps<{ |     const props = defineProps<{ | ||||||
|  | @ -135,7 +134,7 @@ | ||||||
|                     :key="answerIndex" |                     :key="answerIndex" | ||||||
|                     class="text-gray-600" |                     class="text-gray-600" | ||||||
|                 > |                 > | ||||||
|                     <v-divider :thickness=2 /> |                     <v-divider :thickness="2" /> | ||||||
|                     <div |                     <div | ||||||
|                         class="flex justify-between items-center mb-2" |                         class="flex justify-between items-center mb-2" | ||||||
|                         style=" |                         style=" | ||||||
|  |  | ||||||
|  | @ -1,23 +1,23 @@ | ||||||
| import { createRouter, createWebHistory } from 'vue-router'; | import { createRouter, createWebHistory } from "vue-router"; | ||||||
| import SingleAssignment from '@/views/assignments/SingleAssignment.vue'; | import SingleAssignment from "@/views/assignments/SingleAssignment.vue"; | ||||||
| import SingleClass from '@/views/classes/SingleClass.vue'; | import SingleClass from "@/views/classes/SingleClass.vue"; | ||||||
| import SingleDiscussion from '@/views/discussions/SingleDiscussion.vue'; | import SingleDiscussion from "@/views/discussions/SingleDiscussion.vue"; | ||||||
| import NotFound from '@/components/errors/NotFound.vue'; | import NotFound from "@/components/errors/NotFound.vue"; | ||||||
| import CreateAssignment from '@/views/assignments/CreateAssignment.vue'; | import CreateAssignment from "@/views/assignments/CreateAssignment.vue"; | ||||||
| import CreateDiscussion from '@/views/discussions/CreateDiscussion.vue'; | import CreateDiscussion from "@/views/discussions/CreateDiscussion.vue"; | ||||||
| import CallbackPage from '@/views/CallbackPage.vue'; | import CallbackPage from "@/views/CallbackPage.vue"; | ||||||
| import UserClasses from '@/views/classes/UserClasses.vue'; | import UserClasses from "@/views/classes/UserClasses.vue"; | ||||||
| import UserAssignments from '@/views/assignments/UserAssignments.vue'; | import UserAssignments from "@/views/assignments/UserAssignments.vue"; | ||||||
| import LearningPathPage from '@/views/learning-paths/LearningPathPage.vue'; | import LearningPathPage from "@/views/learning-paths/LearningPathPage.vue"; | ||||||
| import LearningPathSearchPage from '@/views/learning-paths/LearningPathSearchPage.vue'; | import LearningPathSearchPage from "@/views/learning-paths/LearningPathSearchPage.vue"; | ||||||
| import UserHomePage from '@/views/homepage/UserHomePage.vue'; | import UserHomePage from "@/views/homepage/UserHomePage.vue"; | ||||||
| import SingleTheme from '@/views/SingleTheme.vue'; | import SingleTheme from "@/views/SingleTheme.vue"; | ||||||
| import LearningObjectView from '@/views/learning-paths/learning-object/LearningObjectView.vue'; | import LearningObjectView from "@/views/learning-paths/learning-object/LearningObjectView.vue"; | ||||||
| import authService from '@/services/auth/auth-service'; | import authService from "@/services/auth/auth-service"; | ||||||
| import DiscussionForward from '@/views/discussions/DiscussionForward.vue'; | import DiscussionForward from "@/views/discussions/DiscussionForward.vue"; | ||||||
| import NoDiscussion from '@/views/discussions/NoDiscussion.vue'; | import NoDiscussion from "@/views/discussions/NoDiscussion.vue"; | ||||||
| import OwnLearningContentPage from '@/views/own-learning-content/OwnLearningContentPage.vue'; | import OwnLearningContentPage from "@/views/own-learning-content/OwnLearningContentPage.vue"; | ||||||
| import { allowRedirect, Redirect } from '@/utils/redirect.ts'; | import { allowRedirect, Redirect } from "@/utils/redirect.ts"; | ||||||
| 
 | 
 | ||||||
| const router = createRouter({ | const router = createRouter({ | ||||||
|     history: createWebHistory(import.meta.env.BASE_URL), |     history: createWebHistory(import.meta.env.BASE_URL), | ||||||
|  |  | ||||||
|  | @ -1,26 +1,21 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import type { Language } from '@/data-objects/language'; |     import type { Language } from "@/data-objects/language"; | ||||||
| import { onMounted } from 'vue'; |     import { onMounted } from "vue"; | ||||||
| import { useRouter } from 'vue-router'; |     import { useRouter } from "vue-router"; | ||||||
| 
 | 
 | ||||||
| const router = useRouter(); |     const router = useRouter(); | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ |     const props = defineProps<{ | ||||||
|     hruid: string; |         hruid: string; | ||||||
|     language: Language; |         language: Language; | ||||||
|     learningObjectHruid?: string; |         learningObjectHruid?: string; | ||||||
| }>(); |     }>(); | ||||||
| 
 | 
 | ||||||
| const discussionURL = "/discussion"  |     const discussionURL = "/discussion" + "/" + props.hruid + "/" + props.language + "/" + props.learningObjectHruid; | ||||||
|     + "/" + props.hruid |  | ||||||
|     + "/" + props.language |  | ||||||
|     + "/" + props.learningObjectHruid |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| onMounted(async () => { |  | ||||||
|     await router.replace(discussionURL); |  | ||||||
| }) |  | ||||||
| 
 | 
 | ||||||
|  |     onMounted(async () => { | ||||||
|  |         await router.replace(discussionURL); | ||||||
|  |     }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  |  | ||||||
|  | @ -1,15 +1,14 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { useI18n } from 'vue-i18n'; |     import { useI18n } from "vue-i18n"; | ||||||
| import DiscussionsSideBar from '@/components/DiscussionsSideBar.vue'; |     import DiscussionsSideBar from "@/components/DiscussionsSideBar.vue"; | ||||||
| 
 |  | ||||||
| const { t } = useI18n(); |  | ||||||
| 
 | 
 | ||||||
|  |     const { t } = useI18n(); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <DiscussionsSideBar></DiscussionsSideBar> |     <DiscussionsSideBar></DiscussionsSideBar> | ||||||
|     <div> |     <div> | ||||||
|         <p class="no-discussion-tip">{{t("no-discussion-tip")}}</p> |         <p class="no-discussion-tip">{{ t("no-discussion-tip") }}</p> | ||||||
|     </div> |     </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,127 +1,130 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { Language } from '@/data-objects/language.ts'; |     import { Language } from "@/data-objects/language.ts"; | ||||||
| import type { LearningPath } from '@/data-objects/learning-paths/learning-path.ts'; |     import type { LearningPath } from "@/data-objects/learning-paths/learning-path.ts"; | ||||||
| import { computed, type ComputedRef, ref, watch } from 'vue'; |     import { computed, type ComputedRef, ref, watch } from "vue"; | ||||||
| import type { LearningObject } from '@/data-objects/learning-objects/learning-object.ts'; |     import type { LearningObject } from "@/data-objects/learning-objects/learning-object.ts"; | ||||||
| import { useRoute } from 'vue-router'; |     import { useRoute } from "vue-router"; | ||||||
| import { useGetAllLearningPaths, useGetLearningPathQuery } from '@/queries/learning-paths.ts'; |     import { useGetAllLearningPaths, useGetLearningPathQuery } from "@/queries/learning-paths.ts"; | ||||||
| import { useLearningObjectListForPathQuery } from '@/queries/learning-objects.ts'; |     import { useLearningObjectListForPathQuery } from "@/queries/learning-objects.ts"; | ||||||
| import UsingQueryResult from '@/components/UsingQueryResult.vue'; |     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
| import { LearningPathNode } from '@/data-objects/learning-paths/learning-path-node.ts'; |     import { LearningPathNode } from "@/data-objects/learning-paths/learning-path-node.ts"; | ||||||
| import { useQuestionsQuery } from '@/queries/questions'; |     import { useQuestionsQuery } from "@/queries/questions"; | ||||||
| import type { QuestionsResponse } from '@/controllers/questions'; |     import type { QuestionsResponse } from "@/controllers/questions"; | ||||||
| import type { LearningObjectIdentifierDTO } from '@dwengo-1/common/interfaces/learning-content'; |     import type { LearningObjectIdentifierDTO } from "@dwengo-1/common/interfaces/learning-content"; | ||||||
| import QandA from '@/components/QandA.vue'; |     import QandA from "@/components/QandA.vue"; | ||||||
| import type { QuestionDTO } from '@dwengo-1/common/interfaces/question'; |     import type { QuestionDTO } from "@dwengo-1/common/interfaces/question"; | ||||||
| import DiscussionsSideBar from '@/components/DiscussionsSideBar.vue'; |     import DiscussionsSideBar from "@/components/DiscussionsSideBar.vue"; | ||||||
| import QuestionBox from '@/components/QuestionBox.vue'; |     import QuestionBox from "@/components/QuestionBox.vue"; | ||||||
| 
 | 
 | ||||||
| const route = useRoute(); |     const route = useRoute(); | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ |     const props = defineProps<{ | ||||||
|     hruid: string; |         hruid: string; | ||||||
|     language: Language; |         language: Language; | ||||||
|     learningObjectHruid?: string; |         learningObjectHruid?: string; | ||||||
| }>(); |     }>(); | ||||||
| 
 | 
 | ||||||
| interface LearningPathPageQuery { |     interface LearningPathPageQuery { | ||||||
|     forGroup?: string; |         forGroup?: string; | ||||||
|     assignmentNo?: string; |         assignmentNo?: string; | ||||||
|     classId?: string; |         classId?: string; | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const query = computed(() => route.query as LearningPathPageQuery); |  | ||||||
| 
 |  | ||||||
| const forGroup = computed(() => { |  | ||||||
|     if (query.value.forGroup && query.value.assignmentNo && query.value.classId) { |  | ||||||
|         return { |  | ||||||
|             forGroup: parseInt(query.value.forGroup), |  | ||||||
|             assignmentNo: parseInt(query.value.assignmentNo), |  | ||||||
|             classId: query.value.classId, |  | ||||||
|         }; |  | ||||||
|     } |     } | ||||||
|     return undefined; |  | ||||||
| }); |  | ||||||
| 
 | 
 | ||||||
| const allLearningPathsResult = useGetAllLearningPaths(props.language); |     const query = computed(() => route.query as LearningPathPageQuery); | ||||||
| 
 | 
 | ||||||
| async function learningObjectHasQuestions(learningObject: LearningObject): Promise<boolean> { |     const forGroup = computed(() => { | ||||||
|     const loid = { |         if (query.value.forGroup && query.value.assignmentNo && query.value.classId) { | ||||||
|         hruid: learningObject.key, |             return { | ||||||
|         version: learningObject.version, |                 forGroup: parseInt(query.value.forGroup), | ||||||
|         language: learningObject.language, |                 assignmentNo: parseInt(query.value.assignmentNo), | ||||||
|     } as LearningObjectIdentifierDTO; |                 classId: query.value.classId, | ||||||
|     const { data } = useQuestionsQuery(loid); |             }; | ||||||
|     return (data.value?.questions.length ?? 0) > 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async function learningPathHasQuestions(learningPath: LearningPath): Promise<boolean> { |  | ||||||
|     const learningPathQueryResult = useGetLearningPathQuery(learningPath.hruid, learningPath.language as Language, forGroup); |  | ||||||
|     const learningObjectListQueryResult = useLearningObjectListForPathQuery(learningPathQueryResult.data); |  | ||||||
|     const learningObjects = learningObjectListQueryResult.data.value || []; |  | ||||||
|     const hasQuestions = await Promise.all( |  | ||||||
|         learningObjects.map(async (learningObject) => learningObjectHasQuestions(learningObject)), |  | ||||||
|     ); |  | ||||||
|     return hasQuestions.some((hasQuestion) => hasQuestion); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const questionedLearningPaths = ref<LearningPath[] | null>(null); |  | ||||||
| 
 |  | ||||||
| watch( |  | ||||||
|     () => allLearningPathsResult.data.value, |  | ||||||
|     async (learningPaths) => { |  | ||||||
|         if (learningPaths) { |  | ||||||
|             const pathsWithQuestions = await Promise.all( |  | ||||||
|                 learningPaths.map(async (learningPath) => { |  | ||||||
|                     const hasQuestions = await learningPathHasQuestions(learningPath); |  | ||||||
|                     return hasQuestions ? learningPath : null; |  | ||||||
|                 }), |  | ||||||
|             ); |  | ||||||
|             questionedLearningPaths.value = pathsWithQuestions.filter((path) => path !== null); |  | ||||||
|         } |         } | ||||||
|     }, |         return undefined; | ||||||
|     { immediate: true }, |     }); | ||||||
| ); |  | ||||||
| 
 | 
 | ||||||
| const learningPathQueryResult = useGetLearningPathQuery(props.hruid, props.language, forGroup); |     const allLearningPathsResult = useGetAllLearningPaths(props.language); | ||||||
| 
 | 
 | ||||||
| const nodesList: ComputedRef<LearningPathNode[] | null> = computed( |     async function learningObjectHasQuestions(learningObject: LearningObject): Promise<boolean> { | ||||||
|     () => learningPathQueryResult.data.value?.nodesAsList ?? null, |         const loid = { | ||||||
| ); |             hruid: learningObject.key, | ||||||
|  |             version: learningObject.version, | ||||||
|  |             language: learningObject.language, | ||||||
|  |         } as LearningObjectIdentifierDTO; | ||||||
|  |         const { data } = useQuestionsQuery(loid); | ||||||
|  |         return (data.value?.questions.length ?? 0) > 0; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| const currentNode = computed(() => { |     async function learningPathHasQuestions(learningPath: LearningPath): Promise<boolean> { | ||||||
|     const currentHruid = props.learningObjectHruid; |         const learningPathQueryResult = useGetLearningPathQuery( | ||||||
|     return nodesList.value?.find((it) => it.learningobjectHruid === currentHruid); |             learningPath.hruid, | ||||||
| }); |             learningPath.language as Language, | ||||||
|  |             forGroup, | ||||||
|  |         ); | ||||||
|  |         const learningObjectListQueryResult = useLearningObjectListForPathQuery(learningPathQueryResult.data); | ||||||
|  |         const learningObjects = learningObjectListQueryResult.data.value || []; | ||||||
|  |         const hasQuestions = await Promise.all( | ||||||
|  |             learningObjects.map(async (learningObject) => learningObjectHasQuestions(learningObject)), | ||||||
|  |         ); | ||||||
|  |         return hasQuestions.some((hasQuestion) => hasQuestion); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| const getQuestionsQuery = useQuestionsQuery( |     const questionedLearningPaths = ref<LearningPath[] | null>(null); | ||||||
|     computed( |  | ||||||
|         () => |  | ||||||
|             ({ |  | ||||||
|                 language: currentNode.value?.language, |  | ||||||
|                 hruid: currentNode.value?.learningobjectHruid, |  | ||||||
|                 version: currentNode.value?.version, |  | ||||||
|             }) as LearningObjectIdentifierDTO, |  | ||||||
|     ), |  | ||||||
| ); |  | ||||||
| 
 | 
 | ||||||
| watch( |     watch( | ||||||
|     () => [route.params.hruid, route.params.language, route.params.learningObjectHruid], |         () => allLearningPathsResult.data.value, | ||||||
|     () => { |         async (learningPaths) => { | ||||||
|         //TODO: moet op een of andere manier createQuestionMutation opnieuw kunnen instellen |             if (learningPaths) { | ||||||
|         //      Momenteel opgelost door de DiscussionsForward page workaround |                 const pathsWithQuestions = await Promise.all( | ||||||
|     }, |                     learningPaths.map(async (learningPath) => { | ||||||
| ); |                         const hasQuestions = await learningPathHasQuestions(learningPath); | ||||||
|  |                         return hasQuestions ? learningPath : null; | ||||||
|  |                     }), | ||||||
|  |                 ); | ||||||
|  |                 questionedLearningPaths.value = pathsWithQuestions.filter((path) => path !== null); | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         { immediate: true }, | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|  |     const learningPathQueryResult = useGetLearningPathQuery(props.hruid, props.language, forGroup); | ||||||
|  | 
 | ||||||
|  |     const nodesList: ComputedRef<LearningPathNode[] | null> = computed( | ||||||
|  |         () => learningPathQueryResult.data.value?.nodesAsList ?? null, | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     const currentNode = computed(() => { | ||||||
|  |         const currentHruid = props.learningObjectHruid; | ||||||
|  |         return nodesList.value?.find((it) => it.learningobjectHruid === currentHruid); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     const getQuestionsQuery = useQuestionsQuery( | ||||||
|  |         computed( | ||||||
|  |             () => | ||||||
|  |                 ({ | ||||||
|  |                     language: currentNode.value?.language, | ||||||
|  |                     hruid: currentNode.value?.learningobjectHruid, | ||||||
|  |                     version: currentNode.value?.version, | ||||||
|  |                 }) as LearningObjectIdentifierDTO, | ||||||
|  |         ), | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     watch( | ||||||
|  |         () => [route.params.hruid, route.params.language, route.params.learningObjectHruid], | ||||||
|  |         () => { | ||||||
|  |             //TODO: moet op een of andere manier createQuestionMutation opnieuw kunnen instellen | ||||||
|  |             //      Momenteel opgelost door de DiscussionsForward page workaround | ||||||
|  |         }, | ||||||
|  |     ); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <DiscussionsSideBar></DiscussionsSideBar> |     <DiscussionsSideBar></DiscussionsSideBar> | ||||||
|     <QuestionBox |     <QuestionBox | ||||||
|         :hruid=props.hruid |         :hruid="props.hruid" | ||||||
|         :language=props.language |         :language="props.language" | ||||||
|         :learningObjectHruid=props.learningObjectHruid |         :learningObjectHruid="props.learningObjectHruid" | ||||||
|         :forGroup=forGroup |         :forGroup="forGroup" | ||||||
|     /> |     /> | ||||||
|     <using-query-result |     <using-query-result | ||||||
|         :query-result="getQuestionsQuery" |         :query-result="getQuestionsQuery" | ||||||
|  | @ -132,97 +135,97 @@ watch( | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
| .learning-path-title { |     .learning-path-title { | ||||||
|     white-space: normal; |         white-space: normal; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .search-field-container { |     .search-field-container { | ||||||
|     min-width: 250px; |         min-width: 250px; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .control-bar-above-content { |     .control-bar-above-content { | ||||||
|     margin-left: 5px; |         margin-left: 5px; | ||||||
|     margin-right: 5px; |         margin-right: 5px; | ||||||
|     margin-bottom: -30px; |         margin-bottom: -30px; | ||||||
|     display: flex; |         display: flex; | ||||||
|     justify-content: space-between; |         justify-content: space-between; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .learning-object-view-container { |     .learning-object-view-container { | ||||||
|     padding-left: 20px; |         padding-left: 20px; | ||||||
|     padding-right: 20px; |         padding-right: 20px; | ||||||
|     padding-bottom: 20px; |         padding-bottom: 20px; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .navigation-buttons-container { |     .navigation-buttons-container { | ||||||
|     padding: 20px; |         padding: 20px; | ||||||
|     display: flex; |         display: flex; | ||||||
|     justify-content: space-between; |         justify-content: space-between; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .assignment-indicator { |     .assignment-indicator { | ||||||
|     position: absolute; |         position: absolute; | ||||||
|     bottom: 10px; |         bottom: 10px; | ||||||
|     left: 10px; |         left: 10px; | ||||||
|     padding: 4px 12px; |         padding: 4px 12px; | ||||||
|     border: 2px solid #f8bcbc; |         border: 2px solid #f8bcbc; | ||||||
|     border-radius: 20px; |         border-radius: 20px; | ||||||
|     color: #f36c6c; |         color: #f36c6c; | ||||||
|     background-color: rgba(248, 188, 188, 0.1); |         background-color: rgba(248, 188, 188, 0.1); | ||||||
|     font-weight: bold; |         font-weight: bold; | ||||||
|     font-family: Arial, sans-serif; |         font-family: Arial, sans-serif; | ||||||
|     font-size: 14px; |         font-size: 14px; | ||||||
|     text-transform: uppercase; |         text-transform: uppercase; | ||||||
|     z-index: 2; /* Less than modals/popups */ |         z-index: 2; /* Less than modals/popups */ | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .question-box { |     .question-box { | ||||||
|     width: 100%; |         width: 100%; | ||||||
|     max-width: 400px; |         max-width: 400px; | ||||||
|     margin: 20px auto; |         margin: 20px auto; | ||||||
|     font-family: sans-serif; |         font-family: sans-serif; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .input-wrapper { |     .input-wrapper { | ||||||
|     display: flex; |         display: flex; | ||||||
|     align-items: center; |         align-items: center; | ||||||
|     border: 1px solid #ccc; |         border: 1px solid #ccc; | ||||||
|     border-radius: 999px; |         border-radius: 999px; | ||||||
|     padding: 8px 12px; |         padding: 8px 12px; | ||||||
|     box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |         box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .question-input { |     .question-input { | ||||||
|     flex: 1; |         flex: 1; | ||||||
|     border: none; |         border: none; | ||||||
|     outline: none; |         outline: none; | ||||||
|     font-size: 14px; |         font-size: 14px; | ||||||
|     background-color: transparent; |         background-color: transparent; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .question-input::placeholder { |     .question-input::placeholder { | ||||||
|     color: #999; |         color: #999; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .send-button { |     .send-button { | ||||||
|     background: none; |         background: none; | ||||||
|     border: none; |         border: none; | ||||||
|     cursor: pointer; |         cursor: pointer; | ||||||
|     font-size: 16px; |         font-size: 16px; | ||||||
|     color: #555; |         color: #555; | ||||||
|     transition: color 0.2s ease; |         transition: color 0.2s ease; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .send-button:hover { |     .send-button:hover { | ||||||
|     color: #000; |         color: #000; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .discussion-link a { |     .discussion-link a { | ||||||
|     color: #3b82f6; /* blue */ |         color: #3b82f6; /* blue */ | ||||||
|     text-decoration: none; |         text-decoration: none; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .discussion-link a:hover { |     .discussion-link a:hover { | ||||||
|     text-decoration: underline; |         text-decoration: underline; | ||||||
| } |     } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -1,30 +1,30 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { Language } from '@/data-objects/language.ts'; |     import { Language } from "@/data-objects/language.ts"; | ||||||
| import type { LearningPath } from '@/data-objects/learning-paths/learning-path.ts'; |     import type { LearningPath } from "@/data-objects/learning-paths/learning-path.ts"; | ||||||
| import { computed, type ComputedRef, ref } from 'vue'; |     import { computed, type ComputedRef, ref } from "vue"; | ||||||
| import type { LearningObject } from '@/data-objects/learning-objects/learning-object.ts'; |     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 LearningObjectView from "@/views/learning-paths/learning-object/LearningObjectView.vue"; | ||||||
| import { useI18n } from 'vue-i18n'; |     import { useI18n } from "vue-i18n"; | ||||||
| import LearningPathSearchField from '@/components/LearningPathSearchField.vue'; |     import LearningPathSearchField from "@/components/LearningPathSearchField.vue"; | ||||||
| import { useGetLearningPathQuery } from '@/queries/learning-paths.ts'; |     import { useGetLearningPathQuery } from "@/queries/learning-paths.ts"; | ||||||
| import { useLearningObjectListForPathQuery } from '@/queries/learning-objects.ts'; |     import { useLearningObjectListForPathQuery } from "@/queries/learning-objects.ts"; | ||||||
| import UsingQueryResult from '@/components/UsingQueryResult.vue'; |     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
| import authService from '@/services/auth/auth-service.ts'; |     import authService from "@/services/auth/auth-service.ts"; | ||||||
| import { LearningPathNode } from '@/data-objects/learning-paths/learning-path-node.ts'; |     import { LearningPathNode } from "@/data-objects/learning-paths/learning-path-node.ts"; | ||||||
| import LearningPathGroupSelector from '@/views/learning-paths/LearningPathGroupSelector.vue'; |     import LearningPathGroupSelector from "@/views/learning-paths/LearningPathGroupSelector.vue"; | ||||||
| import { useQuestionsQuery } from '@/queries/questions'; |     import { useQuestionsQuery } from "@/queries/questions"; | ||||||
| import type { QuestionsResponse } from '@/controllers/questions'; |     import type { QuestionsResponse } from "@/controllers/questions"; | ||||||
| import type { LearningObjectIdentifierDTO } from '@dwengo-1/common/interfaces/learning-content'; |     import type { LearningObjectIdentifierDTO } from "@dwengo-1/common/interfaces/learning-content"; | ||||||
| import QandA from '@/components/QandA.vue'; |     import QandA from "@/components/QandA.vue"; | ||||||
| import type { QuestionDTO } from '@dwengo-1/common/interfaces/question'; |     import type { QuestionDTO } from "@dwengo-1/common/interfaces/question"; | ||||||
| import { useStudentAssignmentsQuery } from '@/queries/students'; |     import { useStudentAssignmentsQuery } from "@/queries/students"; | ||||||
| import type { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; |     import type { AssignmentDTO } from "@dwengo-1/common/interfaces/assignment"; | ||||||
| import QuestionNotification from '@/components/QuestionNotification.vue'; |     import QuestionNotification from "@/components/QuestionNotification.vue"; | ||||||
| import QuestionBox from '@/components/QuestionBox.vue'; |     import QuestionBox from "@/components/QuestionBox.vue"; | ||||||
|     import { AccountType } from "@dwengo-1/common/util/account-types"; |     import { AccountType } from "@dwengo-1/common/util/account-types"; | ||||||
| 
 | 
 | ||||||
| const router = useRouter(); |     const router = useRouter(); | ||||||
|     const route = useRoute(); |     const route = useRoute(); | ||||||
|     const { t } = useI18n(); |     const { t } = useI18n(); | ||||||
| 
 | 
 | ||||||
|  | @ -157,12 +157,16 @@ const router = useRouter(); | ||||||
|         ); |         ); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const discussionLink = computed(() => |     const discussionLink = computed( | ||||||
|         "/discussion" |         () => | ||||||
|         + "/" + props.hruid |             "/discussion" + | ||||||
|         + "/" + currentNode.value?.language |             "/" + | ||||||
|         + "/" + currentNode.value?.learningobjectHruid); |             props.hruid + | ||||||
| 
 |             "/" + | ||||||
|  |             currentNode.value?.language + | ||||||
|  |             "/" + | ||||||
|  |             currentNode.value?.learningobjectHruid, | ||||||
|  |     ); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  | @ -302,10 +306,10 @@ const router = useRouter(); | ||||||
|             ></learning-object-view> |             ></learning-object-view> | ||||||
|         </div> |         </div> | ||||||
|         <QuestionBox |         <QuestionBox | ||||||
|             :hruid=props.hruid |             :hruid="props.hruid" | ||||||
|             :language=props.language |             :language="props.language" | ||||||
|             :learningObjectHruid=props.learningObjectHruid |             :learningObjectHruid="props.learningObjectHruid" | ||||||
|             :forGroup=forGroup |             :forGroup="forGroup" | ||||||
|         /> |         /> | ||||||
|         <div class="navigation-buttons-container"> |         <div class="navigation-buttons-container"> | ||||||
|             <v-btn |             <v-btn | ||||||
|  | @ -329,15 +333,13 @@ const router = useRouter(); | ||||||
|             :query-result="getQuestionsQuery" |             :query-result="getQuestionsQuery" | ||||||
|             v-slot="questionsResponse: { data: QuestionsResponse }" |             v-slot="questionsResponse: { data: QuestionsResponse }" | ||||||
|         > |         > | ||||||
|         <v-divider :thickness="6"></v-divider> |             <v-divider :thickness="6"></v-divider> | ||||||
|             <div class="question-header"> |             <div class="question-header"> | ||||||
|                 <span class="question-title">{{t("questions")}}</span> |                 <span class="question-title">{{ t("questions") }}</span> | ||||||
|                 <span class="discussion-link-text"> |                 <span class="discussion-link-text"> | ||||||
|                     {{t("view-questions")}} |                     {{ t("view-questions") }} | ||||||
|                     <router-link |                     <router-link :to="discussionLink"> | ||||||
|                         :to=discussionLink |                         {{ t("discussions") }} | ||||||
|                     > |  | ||||||
|                         {{t("discussions")}} |  | ||||||
|                     </router-link> |                     </router-link> | ||||||
|                 </span> |                 </span> | ||||||
|             </div> |             </div> | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Lint Action
						Lint Action