feat(frontend): klas tonen bij assignment card
This commit is contained in:
		
							parent
							
								
									3423bd2724
								
							
						
					
					
						commit
						16f8aa449e
					
				
					 9 changed files with 321 additions and 289 deletions
				
			
		|  | @ -52,5 +52,6 @@ | |||
|     "title": "Titel", | ||||
|     "pick-class": "Wählen Sie eine klasse", | ||||
|     "choose-students": "Studenten auswählen", | ||||
|     "create-group": "Gruppe erstellen" | ||||
|     "create-group": "Gruppe erstellen", | ||||
|     "class": "klasse" | ||||
| } | ||||
|  |  | |||
|  | @ -52,5 +52,6 @@ | |||
|     "title": "Title", | ||||
|     "pick-class": "Pick a class", | ||||
|     "choose-students": "Select students", | ||||
|     "create-group": "Create group" | ||||
|     "create-group": "Create group", | ||||
|     "class": "class" | ||||
| } | ||||
|  |  | |||
|  | @ -52,5 +52,6 @@ | |||
|     "title": "Titre", | ||||
|     "pick-class": "Choisissez une classe", | ||||
|     "choose-students": "Sélectionnez des élèves", | ||||
|     "create-group": "Créer un groupe" | ||||
|     "create-group": "Créer un groupe", | ||||
|     "class": "classe" | ||||
| } | ||||
|  |  | |||
|  | @ -52,5 +52,6 @@ | |||
|     "title": "Titel", | ||||
|     "pick-class": "Kies een klas", | ||||
|     "choose-students": "Studenten selecteren", | ||||
|     "create-group": "Groep aanmaken" | ||||
|     "create-group": "Groep aanmaken", | ||||
|     "class": "klas" | ||||
| } | ||||
|  |  | |||
|  | @ -65,6 +65,7 @@ export const assignments: Assignment[] = Array.from({length: 4}, (_, i) => ({ | |||
|     id: `assignment${i}`, | ||||
|     title: `Assignment ${i}`, | ||||
|     learningPathHruid: 'lphruid', | ||||
|     class: `class 0${i+1}`, | ||||
|     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, " + | ||||
|  |  | |||
|  | @ -1,238 +0,0 @@ | |||
| <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 {classes} from "@/utils/tempData.ts"; | ||||
| 
 | ||||
|     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([]); | ||||
|     //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> | ||||
|     <div class="main-container"> | ||||
|         <h1 class="title">{{ t(`${sort}-assignment`) }}</h1> | ||||
|         <v-card class="form-card"> | ||||
|             <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-card-text> | ||||
| 
 | ||||
|                     <v-card-text> | ||||
|                         <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="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"/> | ||||
| 
 | ||||
|                     <v-card-text v-if="groups.length"> | ||||
|                         <strong>Created Groups: {{ groups.length }}</strong> | ||||
|                     </v-card-text> | ||||
| 
 | ||||
|                     <v-card-text> | ||||
|                         <v-textarea v-model="description" :label="t('description')" variant="outlined" density="compact" | ||||
|                                     auto-grow rows="3" :rules="descriptionRules"></v-textarea> | ||||
|                     </v-card-text> | ||||
| 
 | ||||
|                     <v-btn class="mt-2" color="secondary" type="submit" block>Submit</v-btn> | ||||
|                 </v-container> | ||||
|             </v-form> | ||||
|         </v-card> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped> | ||||
| .main-container { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     text-align: center; | ||||
| } | ||||
| 
 | ||||
| .title { | ||||
|     margin-bottom: 1%; | ||||
| } | ||||
| 
 | ||||
| .form-card { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     width: 55%; | ||||
| } | ||||
| 
 | ||||
| .form-container { | ||||
|     width: 100%; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
| } | ||||
| 
 | ||||
| .step-container { | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     flex-direction: column; | ||||
|     min-height: 200px; | ||||
| } | ||||
| 
 | ||||
| @media (max-width: 1000px) { | ||||
|     .form-card { | ||||
|         width: 70%; | ||||
|         padding: 1%; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @media (max-width: 650px) { | ||||
|     .form-card { | ||||
|         width: 95%; | ||||
|     } | ||||
| } | ||||
| </style> | ||||
|  | @ -1,20 +1,239 @@ | |||
| <script setup lang="ts"> | ||||
|     import AssignmentForm from "@/views/assignments/AssignmentForm.vue"; | ||||
|     const sort = "new"; | ||||
|     import {useI18n} from "vue-i18n"; | ||||
|     import {computed, onMounted, ref, watch} from "vue"; | ||||
|     import GroupSelector from "@/components/GroupSelector.vue"; | ||||
|     import {classes} from "@/utils/tempData.ts"; | ||||
|     import { | ||||
|         assignmentTitleRules, | ||||
|         classesRules, | ||||
|         descriptionRules, | ||||
|         learningPathRules, | ||||
|         submitForm | ||||
|     } from "@/utils/assignmentForm.ts"; | ||||
|     import DeadlineSelector from "@/components/DeadlineSelector.vue"; | ||||
| 
 | ||||
|     const handleSubmit = async (formData) => { | ||||
|         /*await fetch('/api/assignments', { | ||||
|             method: 'POST', | ||||
|             body: JSON.stringify(formData), | ||||
|             headers: { 'Content-Type': 'application/json' } | ||||
|         });*/ | ||||
|     const {t, locale} = useI18n(); | ||||
| 
 | ||||
|     const form = ref(); | ||||
| 
 | ||||
|     const language = ref(locale.value); | ||||
| 
 | ||||
|     const searchQuery = ref(''); | ||||
| 
 | ||||
|     const assignmentTitle = ref(''); | ||||
|     const deadline = ref(null); | ||||
|     const description = ref(''); | ||||
|     const allLearningPaths = ref([]); | ||||
|     const filteredLearningPaths = ref([]); | ||||
|     const selectedLearningPath = ref(null); | ||||
|     const allClasses = ref([...classes.map(cl => ({title: cl.displayName, value: cl.id}))]); | ||||
|     const selectedClass = ref(null); | ||||
|     const groups = ref<string[][]>([]); | ||||
| 
 | ||||
|     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 | ||||
|         })) : []; | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
|     // New group is added to the list | ||||
|     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 = []; | ||||
|     }); | ||||
| 
 | ||||
|     const searchResults = computed(() => { | ||||
|         return filteredLearningPaths.value.filter((lp: { hruid: string; title: string }) => | ||||
|             lp.title.toLowerCase().includes(searchQuery.value.toLowerCase()) | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     onMounted(fetchAllLearningPaths); | ||||
| 
 | ||||
|     const submitFormHandler = async () => { | ||||
|         const { valid } = await form.value.validate(); | ||||
|         // Don't submit thr form if all rules don't apply | ||||
|         if (!valid) return; | ||||
|         submitForm(assignmentTitle.value, selectedLearningPath.value, selectedClass.value, groups.value, deadline.value, description.value); | ||||
|     }; | ||||
| </script> | ||||
| 
 | ||||
| 
 | ||||
| <template> | ||||
|     <AssignmentForm :sort="sort" @submit="handleSubmit"></AssignmentForm> | ||||
|     <div class="main-container"> | ||||
|         <h1 class="title">{{ t("new-assignment") }}</h1> | ||||
|         <v-card class="form-card"> | ||||
|             <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-card-text> | ||||
| 
 | ||||
|                     <v-card-text> | ||||
|                         <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" | ||||
|                             :label="t('pick-class')" | ||||
|                             :rules="classesRules" | ||||
|                             variant="outlined" | ||||
|                             clearable | ||||
|                             hide-details | ||||
|                             density="compact" | ||||
|                             append-inner-icon="mdi-magnify" | ||||
|                             item-title="title" | ||||
|                             item-value="value" | ||||
|                             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" | ||||
|                     /> | ||||
| 
 | ||||
|                     <!-- Counter for created groups --> | ||||
|                     <v-card-text v-if="groups.length"> | ||||
|                         <strong>Created Groups: {{ groups.length }}</strong> | ||||
|                     </v-card-text> | ||||
| 
 | ||||
|                     <v-card-text> | ||||
|                         <v-textarea | ||||
|                             v-model="description" | ||||
|                             :label="t('description')" | ||||
|                             variant="outlined" | ||||
|                             density="compact" | ||||
|                             auto-grow | ||||
|                             rows="3" | ||||
|                             :rules="descriptionRules" | ||||
|                         ></v-textarea> | ||||
|                     </v-card-text> | ||||
| 
 | ||||
| 
 | ||||
|                     <v-btn class="mt-2" color="secondary" type="submit" block>Submit</v-btn> | ||||
|                 </v-container> | ||||
|             </v-form> | ||||
|         </v-card> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped> | ||||
| .main-container { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     text-align: center; | ||||
| } | ||||
| 
 | ||||
| .title { | ||||
|     margin-bottom: 1%; | ||||
| } | ||||
| 
 | ||||
| .form-card { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     width: 55%; | ||||
|     /*padding: 1%;*/ | ||||
| } | ||||
| 
 | ||||
| .form-container { | ||||
|     width: 100%; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
| } | ||||
| 
 | ||||
| .step-container { | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     flex-direction: column; | ||||
|     min-height: 200px; | ||||
| } | ||||
| 
 | ||||
| @media (max-width: 1000px) { | ||||
|     .form-card { | ||||
|         width: 70%; | ||||
|         padding: 1%; | ||||
|     } | ||||
| 
 | ||||
|     .step-container { | ||||
|         min-height: 300px; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @media (max-width: 650px) { | ||||
|     .form-card { | ||||
|         width: 95%; | ||||
|     } | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -50,6 +50,12 @@ | |||
|                 </v-btn> | ||||
|             </div> | ||||
|             <v-card-title class="text-h4">{{ assignment.title }}</v-card-title> | ||||
|             <v-container class="assignment-class"> | ||||
|                 {{ t('class') }}: | ||||
|                 <span class="class-name"> | ||||
|                     {{ assignment.class }} | ||||
|                   </span> | ||||
|             </v-container> | ||||
|             <v-card-subtitle> | ||||
|                 <v-btn | ||||
|                     :to="`/learningPath/${assignment.learningPathHruid}`" | ||||
|  | @ -77,8 +83,7 @@ | |||
| } | ||||
| 
 | ||||
| .assignment-card { | ||||
|     width: 90%; | ||||
|     max-width: 900px; | ||||
|     width: 85%; | ||||
|     padding: 2%; | ||||
|     border-radius: 12px; | ||||
| } | ||||
|  | @ -95,5 +100,14 @@ | |||
|     color: red; | ||||
| } | ||||
| 
 | ||||
| .assignment-class { | ||||
|     color: #666; | ||||
| } | ||||
| 
 | ||||
| .class-name { | ||||
|     font-weight: 500; | ||||
|     color: #333; | ||||
| } | ||||
| 
 | ||||
| </style> | ||||
| 
 | ||||
|  |  | |||
|  | @ -42,67 +42,99 @@ | |||
|     <div class="assignments-container"> | ||||
|         <h1>{{ t('assignments') }}</h1> | ||||
| 
 | ||||
|         <v-btn v-if="isTeacher" color="primary" class="mb-4" @click="goToCreateAssignment"> | ||||
|         <v-btn | ||||
|             v-if="isTeacher" | ||||
|             color="primary" | ||||
|             class="mb-4" | ||||
|             @click="goToCreateAssignment" | ||||
|         > | ||||
|             {{ t('new-assignment') }} | ||||
|         </v-btn> | ||||
| 
 | ||||
|         <v-container> | ||||
|             <v-row> | ||||
|                 <v-col v-for="assignment in allAssignments" :key="assignment.id" cols="12"> | ||||
|                 <v-col | ||||
|                     v-for="assignment in allAssignments" | ||||
|                     :key="assignment.id" | ||||
|                     cols="12" | ||||
|                 > | ||||
|                     <v-card class="assignment-card" variant="outlined"> | ||||
|                         <v-card-title class="title">{{ assignment.title }}</v-card-title> | ||||
|                         <v-divider></v-divider> | ||||
|                         <v-card-text class="description-text" >{{ assignment.description }}</v-card-text> | ||||
| 
 | ||||
|                         <v-card-actions> | ||||
|                             <v-btn color="primary" @click="goToAssignmentDetails(assignment.id)"> | ||||
|                                 {{ t('view-assignment') }} | ||||
|                             </v-btn> | ||||
|                             <v-btn v-if="isTeacher" color="secondary" @click="goToDeleteAssignment(assignment.id)"> | ||||
|                                 {{ t('delete') }} | ||||
|                             </v-btn> | ||||
|                         </v-card-actions> | ||||
|                         <v-card-text class="card-content"> | ||||
|                             <div class="left-content"> | ||||
|                                 <div class="assignment-title">{{ assignment.title }}</div> | ||||
|                                 <div class="assignment-class"> | ||||
|                                     {{ t('class') }}: | ||||
|                                     <span class="class-name"> | ||||
|                     {{ assignment.class }} | ||||
|                   </span> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                             <div class="right-content"> | ||||
|                                 <v-btn | ||||
|                                     color="primary" | ||||
|                                     @click="goToAssignmentDetails(assignment.id)" | ||||
|                                 > | ||||
|                                     {{ t('view-assignment') }} | ||||
|                                 </v-btn> | ||||
|                             </div> | ||||
|                         </v-card-text> | ||||
|                     </v-card> | ||||
|                 </v-col> | ||||
|             </v-row> | ||||
|         </v-container> | ||||
| 
 | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
| 
 | ||||
| <style scoped> | ||||
| .assignments-container { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     padding: 2%; | ||||
|     width: 100%; | ||||
|     margin: 0 auto; | ||||
|     padding: 2% 4%; | ||||
|     box-sizing: border-box; | ||||
| } | ||||
| 
 | ||||
| .assignment-card { | ||||
|     padding: 1%; | ||||
|     padding: 1rem; | ||||
| } | ||||
| 
 | ||||
| .card-content { | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     align-items: center; | ||||
|     flex-wrap: wrap; | ||||
|     gap: 1rem; | ||||
| } | ||||
| 
 | ||||
| .left-content { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: space-between; | ||||
|     flex: 1; | ||||
|     min-width: 200px; | ||||
|     max-width: 70%; | ||||
| } | ||||
| 
 | ||||
| h1 { | ||||
|     text-align: left; | ||||
|     width: 100%; | ||||
| } | ||||
| 
 | ||||
| .title { | ||||
|     flex-grow: 1; | ||||
|     white-space: normal; | ||||
|     overflow-wrap: break-word; | ||||
| .assignment-title { | ||||
|     font-weight: bold; | ||||
|     font-size: 1.5rem; | ||||
|     margin-bottom: 0.3rem; | ||||
|     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; | ||||
| .assignment-class { | ||||
|     color: #666; | ||||
|     font-size: 0.95rem; | ||||
| } | ||||
| 
 | ||||
| .class-name { | ||||
|     font-weight: 500; | ||||
|     color: #333; | ||||
| } | ||||
| 
 | ||||
| .right-content { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     justify-content: flex-end; | ||||
|     flex-shrink: 0; | ||||
| } | ||||
| </style> | ||||
|  |  | |||
		Reference in a new issue
	
	 Joyelle Ndagijimana
						Joyelle Ndagijimana