feat(frontend): assignment pagina toont titel, link naar lp en beschrijving
This commit is contained in:
		
							parent
							
								
									4f09d004ab
								
							
						
					
					
						commit
						f847f6d606
					
				
					 6 changed files with 299 additions and 134 deletions
				
			
		|  | @ -20,13 +20,13 @@ type Class = { | |||
|     students: Array<Student>; | ||||
| }; | ||||
| 
 | ||||
| const student01: Student = { username: "id01", firstName: "Mark", lastName: "Knopfler", classes: [] }; | ||||
| const student02: Student = { username: "id02", firstName: "John", lastName: "Hiat", classes: [] }; | ||||
| const student03: Student = { username: "id03", firstName: "Aaron", lastName: "Lewis", classes: [] }; | ||||
| const student01: Student = {username: "id01", firstName: "Mark", lastName: "Knopfler", classes: []}; | ||||
| const student02: Student = {username: "id02", firstName: "John", lastName: "Hiat", classes: []}; | ||||
| const student03: Student = {username: "id03", firstName: "Aaron", lastName: "Lewis", classes: []}; | ||||
| 
 | ||||
| const teacher01: Student = { username: "id11", firstName: "Mark", lastName: "Knopfler", classes: [] }; | ||||
| const teacher02: Student = { username: "id12", firstName: "John", lastName: "Hiat", classes: [] }; | ||||
| const teacher03: Student = { username: "id13", firstName: "Aaron", lastName: "Lewis", classes: [] }; | ||||
| const teacher01: Student = {username: "id11", firstName: "Mark", lastName: "Knopfler", classes: []}; | ||||
| const teacher02: Student = {username: "id12", firstName: "John", lastName: "Hiat", classes: []}; | ||||
| const teacher03: Student = {username: "id13", firstName: "Aaron", lastName: "Lewis", classes: []}; | ||||
| 
 | ||||
| const class01: Class = { | ||||
|     id: "class01", | ||||
|  | @ -61,13 +61,24 @@ type Assignment = { | |||
|     description: string; | ||||
| }; | ||||
| 
 | ||||
| export const assignments: Assignment[] = Array.from({ length: 4 }, (_, i) => ({ | ||||
| export const assignments: Assignment[] = Array.from({length: 4}, (_, i) => ({ | ||||
|     id: `assignment${i}`, | ||||
|     title: `Assignment ${i}`, | ||||
|     learningPathHruid: 'lphruid', | ||||
|     description: | ||||
|         "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. " + | ||||
|         "Aenean commodo ligula eget dolor. Aenean massa. " + | ||||
|         "Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus." | ||||
|         "Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, " + | ||||
|         "nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. " + | ||||
|         "Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. " + | ||||
|         "In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. " + | ||||
|         "Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. " + | ||||
|         "Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. " + | ||||
|         "Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. " + | ||||
|         "Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, " + | ||||
|         "sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. " + | ||||
|         "Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. " + | ||||
|         "Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. " + | ||||
|         "Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,", | ||||
| })); | ||||
| 
 | ||||
| export const classes: Array<Class> = [class01, class02, class03]; | ||||
|  |  | |||
|  | @ -1,87 +1,113 @@ | |||
| <script setup lang="ts"> | ||||
| import {useI18n} from "vue-i18n"; | ||||
| import {computed, onMounted, ref, watch, defineProps, defineEmits} from "vue"; | ||||
| import GroupSelector from "@/components/GroupSelector.vue"; | ||||
| import DeadlineSelector from "@/components/DeadlineSelector.vue"; | ||||
| import { | ||||
|     assignmentTitleRules, | ||||
|     classesRules, | ||||
|     descriptionRules, | ||||
|     learningPathRules | ||||
| } from "@/utils/assignmentForm.ts"; | ||||
|     import {useI18n} from "vue-i18n"; | ||||
|     import {computed, onMounted, ref, watch, defineProps, defineEmits} from "vue"; | ||||
|     import GroupSelector from "@/components/GroupSelector.vue"; | ||||
|     import DeadlineSelector from "@/components/DeadlineSelector.vue"; | ||||
|     import { | ||||
|         assignmentTitleRules, | ||||
|         classesRules, | ||||
|         descriptionRules, | ||||
|         learningPathRules | ||||
|     } from "@/utils/assignmentForm.ts"; | ||||
|     import {classes} from "@/utils/tempData.ts"; | ||||
| 
 | ||||
| const {t, locale} = useI18n(); | ||||
| const emit = defineEmits(["submit"]); | ||||
|     const {t, locale} = useI18n(); | ||||
|     const emit = defineEmits(["submit"]); | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|     sort: {type: String}, | ||||
|     initialTitle: {type: String, default: ""}, | ||||
|     initialDeadline: {type: Date, default: null}, | ||||
|     initialDescription: {type: String, default: ""}, | ||||
|     initialLearningPath: {type: Object, default: null}, | ||||
|     initialClass: {type: Object, default: null}, | ||||
|     initialGroups: {type: Array, default: () => []} | ||||
| }); | ||||
| 
 | ||||
| const form = ref(); | ||||
| const language = ref(locale.value); | ||||
| const searchQuery = ref(""); | ||||
| const assignmentTitle = ref(props.initialTitle); | ||||
| const deadline = ref(props.initialDeadline); | ||||
| const description = ref(props.initialDescription); | ||||
| const selectedLearningPath = ref(props.initialLearningPath); | ||||
| const selectedClass = ref(props.initialClass); | ||||
| const groups = ref(props.initialGroups); | ||||
| const allLearningPaths = ref([]); | ||||
| const filteredLearningPaths = ref([]); | ||||
| const allClasses = ref([]); | ||||
| 
 | ||||
| const availableClass = computed(() => selectedClass.value); | ||||
| const allStudents = computed(() => selectedClass.value ? selectedClass.value.students : []); | ||||
| 
 | ||||
| const addGroupToList = (students: string[]) => { | ||||
|     if (students.length) { | ||||
|         groups.value = [...groups.value, students]; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| async function fetchAllLearningPaths() { | ||||
|     try { | ||||
|         //TODO: replace by function from controller | ||||
|         const response = await fetch(`http://localhost:3000/api/learningPath?language=${language.value}`); | ||||
|         if (!response.ok) throw new Error("Failed to fetch learning paths"); | ||||
|         const data = await response.json(); | ||||
|         allLearningPaths.value = data.map((lp: { hruid: string; title: string }) => ({ | ||||
|             hruid: lp.hruid, | ||||
|             name: lp.title | ||||
|         })); | ||||
|         filteredLearningPaths.value = [...allLearningPaths.value]; | ||||
|     } catch (error) { | ||||
|         console.error(error); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| watch(() => locale.value, fetchAllLearningPaths, {immediate: true}); | ||||
| watch(selectedClass, () => groups.value = []); | ||||
| 
 | ||||
| const searchResults = computed(() => filteredLearningPaths.value.filter((lp) => | ||||
|     lp.title.toLowerCase().includes(searchQuery.value.toLowerCase()) | ||||
| )); | ||||
| 
 | ||||
| onMounted(fetchAllLearningPaths); | ||||
| 
 | ||||
| const submitFormHandler = async () => { | ||||
|     const {valid} = await form.value.validate(); | ||||
|     if (!valid) return; | ||||
|     emit("submit", { | ||||
|         assignmentTitle: assignmentTitle.value, | ||||
|         selectedLearningPath: selectedLearningPath.value, | ||||
|         selectedClass: selectedClass.value, | ||||
|         groups: groups.value, | ||||
|         deadline: deadline.value, | ||||
|         description: description.value | ||||
|     const props = defineProps({ | ||||
|         sort: {type: String}, | ||||
|         initialTitle: {type: String, default: ""}, | ||||
|         initialDeadline: {type: Date, default: null}, | ||||
|         initialDescription: {type: String, default: ""}, | ||||
|         initialLearningPath: {type: Object, default: null}, | ||||
|         initialClass: {type: Object, default: null}, | ||||
|         initialGroups: {type: Array, default: () => []} | ||||
|     }); | ||||
| }; | ||||
| 
 | ||||
|     const form = ref(); | ||||
|     const language = ref(locale.value); | ||||
|     const searchQuery = ref(""); | ||||
|     const assignmentTitle = ref(props.initialTitle); | ||||
|     const deadline = ref(props.initialDeadline); | ||||
|     const description = ref(props.initialDescription); | ||||
|     const selectedLearningPath = ref(props.initialLearningPath); | ||||
|     const selectedClass = ref(props.initialClass); | ||||
|     const groups = ref(props.initialGroups); | ||||
|     const allLearningPaths = ref([]); | ||||
|     const filteredLearningPaths = ref([]); | ||||
|     //TODO: replace by real data | ||||
|     const allClasses = ref([...classes.map(cl => ({title: cl.displayName, value: cl.id}))]); | ||||
| 
 | ||||
|     const availableClass = computed(() => { | ||||
|         //TODO: replace by real data | ||||
|         return classes.find(cl => selectedClass.value?.value === cl.id) || null; | ||||
|     }); | ||||
| 
 | ||||
|     const allStudents = computed(() => { | ||||
|         //TODO: replace by real data | ||||
|         if (!selectedClass.value) return []; | ||||
|         const cl = classes.find(c => c.id === selectedClass.value.value); | ||||
|         return cl ? cl.students.map(st => ({ | ||||
|             title: `${st.firstName} ${st.lastName}`, | ||||
|             value: st.username, | ||||
|             classes: cl | ||||
|         })) : []; | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
|     const addGroupToList = (students: string[]) => { | ||||
|         if (students.length) { | ||||
|             groups.value = [...groups.value, students]; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     async function fetchAllLearningPaths() { | ||||
|         try { | ||||
|             //TODO: replace by function from controller | ||||
|             const response = await fetch(`http://localhost:3000/api/learningPath?language=${language.value}`); | ||||
|             if (!response.ok) throw new Error("Failed to fetch learning paths"); | ||||
|             const data = await response.json(); | ||||
|             allLearningPaths.value = data.map((lp: { hruid: string; title: string }) => ({ | ||||
|                 hruid: lp.hruid, | ||||
|                 title: lp.title | ||||
|             })); | ||||
|             filteredLearningPaths.value = [...allLearningPaths.value]; | ||||
|         } catch (error) { | ||||
|             console.error(error); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     watch( | ||||
|         () => locale.value, | ||||
|         (newLocale) => { | ||||
|             if (!["nl", "en"].includes(newLocale)) { | ||||
|                 language.value = "en"; | ||||
|             } | ||||
|             fetchAllLearningPaths(); | ||||
|         }, | ||||
|         {immediate: true} | ||||
|     ); | ||||
|     watch(selectedClass, () => groups.value = []); | ||||
| 
 | ||||
|     // Seach queries for the dropdown search bar | ||||
|     const searchResults = computed(() => filteredLearningPaths.value.filter((lp) => | ||||
|         lp.title.toLowerCase().includes(searchQuery.value.toLowerCase()) | ||||
|     )); | ||||
| 
 | ||||
|     onMounted(fetchAllLearningPaths); | ||||
| 
 | ||||
|     const submitFormHandler = async () => { | ||||
|         const {valid} = await form.value.validate(); | ||||
|         if (!valid) return; | ||||
|         emit("submit", { | ||||
|             title: assignmentTitle.value, | ||||
|             description: description, | ||||
|             learningPathHruid: selectedLearningPath.value, | ||||
|             class: selectedClass.value, | ||||
|             groups: groups.value, | ||||
|             deadline: deadline.value | ||||
|         }); | ||||
|     }; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|  | @ -91,47 +117,60 @@ const submitFormHandler = async () => { | |||
|             <v-form ref="form" class="form-container" validate-on="submit lazy" @submit.prevent="submitFormHandler"> | ||||
|                 <v-container class="step-container"> | ||||
|                     <v-card-text> | ||||
|                         <v-text-field v-model="assignmentTitle" :label="t('title')" :rules="assignmentTitleRules" | ||||
|                                       density="compact" variant="outlined" clearable required></v-text-field> | ||||
|                         <v-text-field | ||||
|                             v-model="assignmentTitle" | ||||
|                             :label="t('title')" | ||||
|                             :rules="assignmentTitleRules" | ||||
|                             density="compact" | ||||
|                             variant="outlined" | ||||
|                             clearable | ||||
|                             required | ||||
|                         ></v-text-field> | ||||
|                     </v-card-text> | ||||
| 
 | ||||
|                     <v-card-text> | ||||
|                         <v-combobox v-model="selectedLearningPath" | ||||
|                                     :items="searchResults" | ||||
|                                     item-title="name" | ||||
|                                     item-value="hruid" | ||||
|                                     :label="t('choose-lp')" | ||||
|                                     :rules="learningPathRules" | ||||
|                                     variant="outlined" | ||||
|                                     clearable | ||||
|                                     hide-details density="compact" | ||||
|                                     append-inner-icon="mdi-magnify" | ||||
|                                     required></v-combobox> | ||||
|                         <v-combobox | ||||
|                             v-model="selectedLearningPath" | ||||
|                             :items="searchResults" | ||||
|                             :label="t('choose-lp')" | ||||
|                             :rules="learningPathRules" | ||||
|                             variant="outlined" | ||||
|                             clearable | ||||
|                             hide-details | ||||
|                             density="compact" | ||||
|                             append-inner-icon="mdi-magnify" | ||||
|                             item-title="title" | ||||
|                             item-value="value" | ||||
|                             required | ||||
|                             :filter="(item, query: string) => item.title.toLowerCase().includes(query.toLowerCase())" | ||||
|                         ></v-combobox> | ||||
|                     </v-card-text> | ||||
| 
 | ||||
|                     <v-card-text> | ||||
|                         <v-combobox v-model="selectedClass" | ||||
|                                     :items="allClasses" | ||||
|                                     item-title="name" | ||||
|                                     item-value="id" | ||||
|                                     :label="t('pick-class')" | ||||
|                                     :rules="classesRules" | ||||
|                                     variant="outlined" | ||||
|                                     clearable | ||||
|                                     hide-details | ||||
|                                     density="compact" | ||||
|                                     append-inner-icon="mdi-magnify" | ||||
|                                     required></v-combobox> | ||||
|                         <v-combobox | ||||
|                             v-model="selectedClass" | ||||
|                             :items="allClasses" | ||||
|                             item-title="title" | ||||
|                             item-value="value" | ||||
|                             :label="t('pick-class')" | ||||
|                             :rules="classesRules" | ||||
|                             variant="outlined" | ||||
|                             clearable | ||||
|                             hide-details | ||||
|                             density="compact" | ||||
|                             append-inner-icon="mdi-magnify" | ||||
|                             required></v-combobox> | ||||
|                     </v-card-text> | ||||
| 
 | ||||
|                     <DeadlineSelector v-model:deadline="deadline"/> | ||||
| 
 | ||||
|                     <h3>{{ t('create-groups') }}</h3> | ||||
| 
 | ||||
|                     <GroupSelector :students="allStudents" | ||||
|                                    :availableClass="availableClass" | ||||
|                                    :groups="groups" | ||||
|                                    @groupCreated="addGroupToList"/> | ||||
|                     <GroupSelector | ||||
|                         :students="allStudents" | ||||
|                         :availableClass="availableClass" | ||||
|                         :groups="groups" | ||||
|                         @groupCreated="addGroupToList"/> | ||||
| 
 | ||||
|                     <v-card-text v-if="groups.length"> | ||||
|                         <strong>Created Groups: {{ groups.length }}</strong> | ||||
|  |  | |||
|  | @ -1,11 +1,19 @@ | |||
| <script setup lang="ts"> | ||||
|     import AssignmentForm from "@/views/assignments/AssignmentForm.vue"; | ||||
| 
 | ||||
|     const sort = "new"; | ||||
| 
 | ||||
|     const handleSubmit = async (formData) => { | ||||
|         /*await fetch('/api/assignments', { | ||||
|             method: 'POST', | ||||
|             body: JSON.stringify(formData), | ||||
|             headers: { 'Content-Type': 'application/json' } | ||||
|         });*/ | ||||
|     }; | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|     <AssignmentForm :sort="sort"></AssignmentForm> | ||||
|     <AssignmentForm :sort="sort" @submit="handleSubmit"></AssignmentForm> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped> | ||||
|  |  | |||
|  | @ -1,12 +1,20 @@ | |||
| <script setup lang="ts"> | ||||
| import AssignmentForm from "@/views/assignments/AssignmentForm.vue"; | ||||
|     import AssignmentForm from "@/views/assignments/AssignmentForm.vue"; | ||||
|     import {ref} from "vue"; | ||||
|     import {useRoute} from "vue-router"; | ||||
| 
 | ||||
|     const sort = "edit"; | ||||
|     //TODO: use the id param to extract info about the assignment and pass them as props | ||||
|     const route = useRoute(); | ||||
|     const assignmentId = ref(route.params.id as string); | ||||
| 
 | ||||
|     const handleUpdate = async (formData) => { | ||||
|     }; | ||||
| 
 | ||||
| const sort = "edit"; | ||||
| //TODO: use the id param to extract info about the assignment | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|     <AssignmentForm :sort="sort"></AssignmentForm> | ||||
|     <AssignmentForm :sort="sort" @submit="handleUpdate"></AssignmentForm> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped> | ||||
|  |  | |||
|  | @ -1,7 +1,90 @@ | |||
| <script setup lang="ts"></script> | ||||
| <script setup lang="ts"> | ||||
|     import { useRoute } from "vue-router"; | ||||
|     import {ref, onMounted, computed} from "vue"; | ||||
|     import {useI18n} from "vue-i18n"; | ||||
|     import {assignments} from "@/utils/tempData.ts"; | ||||
|     import auth from "@/services/auth/auth-service.ts"; | ||||
| 
 | ||||
|     const {t} = useI18n(); | ||||
|     const route = useRoute(); | ||||
|     const assignmentId = ref(route.params.id as string); | ||||
|     const assignment = ref(null); | ||||
| 
 | ||||
|     const role = auth.authState.activeRole; | ||||
|     const isTeacher = computed(() => role === 'teacher'); | ||||
| 
 | ||||
|     onMounted(async () => { | ||||
|         try { | ||||
|             // TODO: Replace with real data | ||||
|             assignment.value = assignments[0]; | ||||
|         } catch (error) { | ||||
|             console.error(error); | ||||
|         } | ||||
|     }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|     <main></main> | ||||
|     <div class="container"> | ||||
|         <v-card v-if="assignment" class="assignment-card"> | ||||
|             <div class="top-buttons"> | ||||
|                 <v-btn | ||||
|                     icon | ||||
|                     variant="text" | ||||
|                     class="back-btn" | ||||
|                     to="user/assignment" | ||||
|                 > | ||||
|                     <v-icon>mdi-arrow-left</v-icon> | ||||
|                 </v-btn> | ||||
| 
 | ||||
|                 <v-btn | ||||
|                     v-if="isTeacher" | ||||
|                     icon | ||||
|                     variant="text" | ||||
|                     class="edit-btn" | ||||
|                     :to="`/assignment/${assignmentId}/edit`" | ||||
|                 > | ||||
|                     <v-icon>mdi-pencil</v-icon> | ||||
|                 </v-btn> | ||||
|             </div> | ||||
|             <v-card-title class="text-h4">{{ assignment.title }}</v-card-title> | ||||
|             <v-card-subtitle> | ||||
|                 <v-btn | ||||
|                     :to="`/learningPath/${assignment.learningPathHruid}`" | ||||
|                     variant="tonal" | ||||
|                     color="primary" | ||||
|                 > | ||||
|                     {{ t("learning-path") }} | ||||
|                 </v-btn> | ||||
|             </v-card-subtitle> | ||||
| 
 | ||||
|             <v-card-text class="description"> | ||||
|                 {{ assignment.description }} | ||||
|             </v-card-text> | ||||
|         </v-card> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped></style> | ||||
| <style scoped> | ||||
| .container { | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|     padding: 2%; | ||||
|     min-height: 100vh; | ||||
| } | ||||
| 
 | ||||
| .assignment-card { | ||||
|     width: 90%; | ||||
|     max-width: 900px; | ||||
|     padding: 2%; | ||||
|     border-radius: 12px; | ||||
| } | ||||
| 
 | ||||
| .description { | ||||
|     margin-top: 2%; | ||||
|     line-height: 1.6; | ||||
|     font-size: 1.1rem; | ||||
| } | ||||
| 
 | ||||
| </style> | ||||
| 
 | ||||
|  |  | |||
|  | @ -24,11 +24,11 @@ | |||
|         router.push('/assignment/create'); | ||||
|     }; | ||||
| 
 | ||||
|     const goToEditAssignment = (id: number) => { | ||||
|         router.push(`/assignment/edit/${id}`); | ||||
|     const goToEditAssignment = (id: string) => { | ||||
|         router.push(`/assignment/${id}/edit`); | ||||
|     }; | ||||
| 
 | ||||
|     const goToAssignmentDetails = (id: number) => { | ||||
|     const goToAssignmentDetails = (id: string) => { | ||||
|         router.push(`/assignment/${id}`); | ||||
|     }; | ||||
| </script> | ||||
|  | @ -45,9 +45,9 @@ | |||
|             <v-row> | ||||
|                 <v-col v-for="assignment in allAssignments" :key="assignment.id" cols="12"> | ||||
|                     <v-card class="assignment-card" variant="outlined"> | ||||
|                         <v-card-title>{{ assignment.title }}</v-card-title> | ||||
|                         <v-card-title class="title">{{ assignment.title }}</v-card-title> | ||||
|                         <v-divider></v-divider> | ||||
|                         <v-card-text>{{ assignment.description }}</v-card-text> | ||||
|                         <v-card-text class="description-text" >{{ assignment.description }}</v-card-text> | ||||
| 
 | ||||
|                         <v-card-actions> | ||||
|                             <v-btn color="primary" @click="goToAssignmentDetails(assignment.id)"> | ||||
|  | @ -70,11 +70,11 @@ | |||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     padding: 20px; | ||||
|     padding: 2%; | ||||
| } | ||||
| 
 | ||||
| .assignment-card { | ||||
|     padding: 16px; | ||||
|     padding: 1%; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: space-between; | ||||
|  | @ -84,4 +84,20 @@ h1 { | |||
|     text-align: left; | ||||
|     width: 100%; | ||||
| } | ||||
| 
 | ||||
| .title { | ||||
|     flex-grow: 1; | ||||
|     white-space: normal; | ||||
|     overflow-wrap: break-word; | ||||
|     word-break: break-word; | ||||
| } | ||||
| 
 | ||||
| .description-text { | ||||
|     display: -webkit-box; | ||||
|     -webkit-line-clamp: 3;  /* Limit to 3 lines */ | ||||
|     -webkit-box-orient: vertical; | ||||
|     overflow: hidden; | ||||
|     text-overflow: ellipsis; | ||||
| } | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
		Reference in a new issue
	
	 Joyelle Ndagijimana
						Joyelle Ndagijimana