feat: leerpaden opzoeken en filteren in de create component werkt
This commit is contained in:
		
							parent
							
								
									aa33d32fa8
								
							
						
					
					
						commit
						9f55e62bcb
					
				
					 5 changed files with 150 additions and 26 deletions
				
			
		|  | @ -44,5 +44,8 @@ | ||||||
|     "next": "nächste", |     "next": "nächste", | ||||||
|     "previous": "vorherige", |     "previous": "vorherige", | ||||||
|     "groups": "Gruppen", |     "groups": "Gruppen", | ||||||
|     "learning-path": "Lernpfad" |     "learning-path": "Lernpfad", | ||||||
|  |     "choose-lp": "Einen lernpfad auswählen", | ||||||
|  |     "filter-themes": "Filter nach Themen", | ||||||
|  |     "search-lp": "Suche und Auswahl eines Lernpfads" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -44,5 +44,8 @@ | ||||||
|     "next": "next", |     "next": "next", | ||||||
|     "previous": "previous", |     "previous": "previous", | ||||||
|     "groups": "Groups", |     "groups": "Groups", | ||||||
|     "learning-path": "Learning path" |     "learning-path": "Learning path", | ||||||
|  |     "choose-lp": "Select a learning path", | ||||||
|  |     "filter-themes": "Filter by themes", | ||||||
|  |     "search-lp": "Search and select a learning path" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -44,5 +44,8 @@ | ||||||
|     "next": "suivant", |     "next": "suivant", | ||||||
|     "previous": "précédent", |     "previous": "précédent", | ||||||
|     "groups": "Groupes", |     "groups": "Groupes", | ||||||
|     "learning-path": "Parcours d'apprentissage" |     "learning-path": "Parcours d'apprentissage", | ||||||
|  |     "choose-lp": "Choisis un parcours d'apprentissage", | ||||||
|  |     "filter-themes": "Filtrer par thèmes", | ||||||
|  |     "search-lp": "Cherche et sélectionne un parcours d'apprentissage" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -44,5 +44,8 @@ | ||||||
|     "next": "volgende", |     "next": "volgende", | ||||||
|     "previous": "vorige", |     "previous": "vorige", | ||||||
|     "groups": "Groepen", |     "groups": "Groepen", | ||||||
|     "learning-path": "Leerpad" |     "learning-path": "Leerpad", | ||||||
|  |     "choose-lp": "Kies een leerpad", | ||||||
|  |     "filter-themes": "Filter op thema's", | ||||||
|  |     "search-lp": "Zoek en selecteer een leerpad" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,24 +1,129 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|     import {useI18n} from "vue-i18n"; |     import {useI18n} from "vue-i18n"; | ||||||
|     import {ref, shallowRef} from "vue"; |     import {computed, onMounted, ref, shallowRef, watch} from "vue"; | ||||||
|     import {THEMESITEMS} from "@/utils/constants.ts"; |     import {THEMESITEMS} from "@/utils/constants.ts"; | ||||||
| 
 | 
 | ||||||
|     const {t} = useI18n(); |     const {t, locale} = useI18n(); | ||||||
| 
 | 
 | ||||||
|     const step = ref(1); |     const step = ref(1); | ||||||
| 
 | 
 | ||||||
|     const loading = ref(false) |     const language = ref(locale.value); | ||||||
| 
 | 
 | ||||||
|     function onClick () { |     // If this value is set to true, the search bar will display a "loading" animation | ||||||
|         loading.value = true |     const loading = ref(false); | ||||||
|     } |  | ||||||
|     const searchQuery = ref(""); |     const searchQuery = ref(""); | ||||||
| 
 | 
 | ||||||
| 
 |     // These lists store all available and selected themes | ||||||
| 
 |  | ||||||
|     // Use a list with all themes so learning-paths can be filtered by theme |  | ||||||
|     const themeItems = ref(Object.keys(THEMESITEMS).slice(1)); |     const themeItems = ref(Object.keys(THEMESITEMS).slice(1)); | ||||||
|     const value = shallowRef([]); |     const selectedThemes = shallowRef<string[]>([]); | ||||||
|  | 
 | ||||||
|  |     // Store all learning paths | ||||||
|  |     const allLearningPaths = ref([]); | ||||||
|  | 
 | ||||||
|  |     // Filtered learning paths that will be displayed in the search bar dropdown | ||||||
|  |     const filteredLearningPaths = ref([]); | ||||||
|  | 
 | ||||||
|  |     // The hruid and title of the currently selected learning path(TODO: use for post req) | ||||||
|  |     const selectedLearningPath = ref(null); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     // Fetch all learning paths initially | ||||||
|  |     async function fetchAllLearningPaths() { | ||||||
|  |         //TODO: replace by function from controller | ||||||
|  |         try { | ||||||
|  |             const response = await fetch(`http://localhost:3000/api/learningPath?language=${language.value}`); | ||||||
|  | 
 | ||||||
|  |             // Error | ||||||
|  |             if (!response.ok) throw new Error("Failed to fetch learning paths"); | ||||||
|  | 
 | ||||||
|  |             // Collect all the learning paths and store them in a list by hruid and title | ||||||
|  |             const data = await response.json(); | ||||||
|  |             allLearningPaths.value = data.map((lp: { hruid: string; title: string }) => ({ | ||||||
|  |                 hruid: lp.hruid, | ||||||
|  |                 title: lp.title | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |             // Get all the learning paths in the filtered list | ||||||
|  |             filteredLearningPaths.value = [...allLearningPaths.value]; | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error(error); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     watch( | ||||||
|  |         () => locale.value, | ||||||
|  |         (newLocale) => { | ||||||
|  |             // Check if the language is valid | ||||||
|  |             if (!["nl", "en"].includes(newLocale)) { | ||||||
|  |                 language.value = "en"; | ||||||
|  |             } | ||||||
|  |             fetchAllLearningPaths(); // Re-fetch the learning path data when language changes | ||||||
|  |         }, | ||||||
|  |         { immediate: true } | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     // Filter the learning paths based on selected themes | ||||||
|  |     async function filterLearningPathsByThemes() { | ||||||
|  |         if (selectedThemes.value.length === 0) { | ||||||
|  |             // Show all the learning paths if no themes are selected | ||||||
|  |             filteredLearningPaths.value = [...allLearningPaths.value]; | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const learningPathHruids = new Set(); | ||||||
|  | 
 | ||||||
|  |         // Collect all themes categories that are selected | ||||||
|  |         const themeCategories = new Set(); | ||||||
|  |         selectedThemes.value.forEach(theme => { | ||||||
|  |             THEMESITEMS[theme]?.forEach(category => themeCategories.add(category)); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         //TODO: replace by function from controller | ||||||
|  |         try { | ||||||
|  |             // Fetch all theme data in parallel and wait for all to complete | ||||||
|  |             const responses = await Promise.all( | ||||||
|  |                 Array.from(themeCategories).map(category => | ||||||
|  |                     fetch(`http://localhost:3000/api/theme/${category}?language=${language.value}`) | ||||||
|  |                         .then(response => { | ||||||
|  |                             if (!response.ok) throw new Error(`Error fetching ${category}`); | ||||||
|  |                             return response.json(); | ||||||
|  |                         }) | ||||||
|  |                         .catch(error => { | ||||||
|  |                             console.error(error); | ||||||
|  |                             return []; // Return empty array on failure | ||||||
|  |                         }) | ||||||
|  |                 ) | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             // Combine all received hruids and add them in a set | ||||||
|  |             responses.forEach(data => { | ||||||
|  |                 data.forEach((lp: string ) => learningPathHruids.add(lp)); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |             // Update filteredLearningPaths only after all requests complete | ||||||
|  |             filteredLearningPaths.value = allLearningPaths.value.filter(lp => | ||||||
|  |                 learningPathHruids.has(lp.hruid) | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |         } catch (error) { | ||||||
|  |             console.error("Error fetching themes:", error); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const searchResults = computed(() => { | ||||||
|  |         return filteredLearningPaths.value.filter(lp => | ||||||
|  |             lp.title.toLowerCase().includes(searchQuery.value.toLowerCase()) | ||||||
|  |         ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     // Fetch all learning paths on mount | ||||||
|  |     onMounted(fetchAllLearningPaths); | ||||||
|  | 
 | ||||||
|  |     // Watch for theme selection changes and filter lerning paths per theme | ||||||
|  |     watch(selectedThemes, filterLearningPathsByThemes); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  | @ -28,14 +133,14 @@ | ||||||
|             <v-stepper class="stepper-container" alt-labels :items="[t('learning-path'), t('classes'), t('groups')]" |             <v-stepper class="stepper-container" alt-labels :items="[t('learning-path'), t('classes'), t('groups')]" | ||||||
|                        v-model="step" show-actions> |                        v-model="step" show-actions> | ||||||
|                 <template v-slot:item.1> |                 <template v-slot:item.1> | ||||||
|                     <v-card title="Select a learning path" flat> |                     <v-card :title="t('choose-lp')" flat> | ||||||
|                         <v-container class="step-container"> |                         <v-container class="step-container"> | ||||||
|                             <v-card-text> |                             <v-card-text> | ||||||
|                                 <v-select |                                 <v-select | ||||||
|                                     v-model="value" |                                     v-model="selectedThemes" | ||||||
|                                     :items="themeItems.map(theme => ({ title: t(`theme-options.${theme}`), value: theme }))" |                                     :items="themeItems.map(theme => ({ title: t(`theme-options.${theme}`), value: theme }))" | ||||||
|                                     variant="solo" |                                     variant="solo" | ||||||
|                                     label="Filter by themes" |                                     :label="t('filter-themes')" | ||||||
|                                     chips |                                     chips | ||||||
|                                     multiple |                                     multiple | ||||||
|                                     deletable-chips |                                     deletable-chips | ||||||
|  | @ -44,17 +149,24 @@ | ||||||
|                             </v-card-text> |                             </v-card-text> | ||||||
| 
 | 
 | ||||||
|                             <v-card-text> |                             <v-card-text> | ||||||
|                                 <v-text-field |                                 <v-combobox | ||||||
|  |                                     v-model="selectedLearningPath" | ||||||
|  |                                     :items="searchResults" | ||||||
|  |                                     :label="t('search-lp')" | ||||||
|  |                                     variant="solo" | ||||||
|  |                                     clearable | ||||||
|  |                                     hide-details | ||||||
|  |                                     density="compact" | ||||||
|                                     :loading="loading" |                                     :loading="loading" | ||||||
|                                     append-inner-icon="mdi-magnify" |                                     append-inner-icon="mdi-magnify" | ||||||
|                                     density="compact" |                                     item-title="title" | ||||||
|                                     label="Search" |                                     item-value="value" | ||||||
|                                     variant="solo" |                                     :filter="(item, query: string) => item.title.toLowerCase().includes(query.toLowerCase())" | ||||||
|                                     hide-details |                                 ></v-combobox> | ||||||
|                                     single-line |  | ||||||
|                                     @click:append-inner="onClick" |  | ||||||
|                                 ></v-text-field> |  | ||||||
|                             </v-card-text> |                             </v-card-text> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|                         </v-container> |                         </v-container> | ||||||
|                     </v-card> |                     </v-card> | ||||||
|                 </template> |                 </template> | ||||||
|  | @ -99,7 +211,7 @@ | ||||||
|     align-items: center; |     align-items: center; | ||||||
|     justify-content: center; |     justify-content: center; | ||||||
|     width: 70%; |     width: 70%; | ||||||
|     padding: 1%; |     /*padding: 1%;*/ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .stepper-container { | .stepper-container { | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Joyelle Ndagijimana
						Joyelle Ndagijimana