Skip to content

Commit 9d97a99

Browse files
Course: Add support for resource sequencing in course and session views - refs BT#22586
Author: @christianbeeznest
1 parent 9d23b82 commit 9d97a99

24 files changed

+1222
-264
lines changed

assets/vue/components/basecomponents/ChamiloIcons.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,5 @@ export const chamiloIconToClass = {
144144
"clear-all": "mdi mdi-broom",
145145
"qrcode": "mdi mdi-qrcode",
146146
"minus": "mdi mdi-minus",
147+
"shield-check": "mdi mdi-shield-check",
147148
}

assets/vue/components/course/CatalogueCourseCard.vue

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@
121121

122122
<div class="mt-auto pt-2">
123123
<router-link
124-
v-if="course.visibility === 3 || (course.visibility === 2 && isUserInCourse)"
124+
v-if="isUserInCourse && (course.visibility === 2 || course.visibility === 3)"
125125
:to="{ name: 'CourseHome', params: { id: course.id } }"
126126
>
127127
<Button
@@ -132,15 +132,23 @@
132132
</router-link>
133133

134134
<Button
135-
v-else-if="course.visibility === 2 && course.subscribe && !isUserInCourse"
135+
v-else-if="isLocked && hasRequirements"
136+
:label="$t('Check requirements')"
137+
icon="mdi mdi-shield-check"
138+
class="w-full p-button-warning"
139+
@click="showDependenciesModal = true"
140+
/>
141+
142+
<Button
143+
v-else-if="course.subscribe && props.currentUserId"
136144
:label="$t('Subscribe')"
137145
icon="pi pi-sign-in"
138146
class="w-full"
139147
@click="subscribeToCourse"
140148
/>
141149

142150
<Button
143-
v-else-if="course.visibility === 2 && !course.subscribe && !isUserInCourse"
151+
v-else-if="course.visibility === 2 && !course.subscribe && props.currentUserId"
144152
:label="$t('Subscription not allowed')"
145153
icon="pi pi-ban"
146154
disabled
@@ -165,6 +173,13 @@
165173
</div>
166174
</div>
167175
</div>
176+
<CatalogueRequirementModal
177+
v-model="showDependenciesModal"
178+
:course-id="course.id"
179+
:session-id="course.sessionId || 0"
180+
:requirements="requirementList"
181+
:graph-image="graphImage"
182+
/>
168183
<Dialog
169184
v-model:visible="showDescriptionDialog"
170185
:header="course.title"
@@ -179,21 +194,17 @@
179194
<script setup>
180195
import Rating from "primevue/rating"
181196
import Button from "primevue/button"
182-
import { computed, ref } from "vue"
183-
import courseRelUserService from "../../services/courseRelUserService"
197+
import Dialog from "primevue/dialog"
198+
import { computed, ref, onMounted } from "vue"
184199
import { useRoute, useRouter } from "vue-router"
185200
import { useNotification } from "../../composables/notification"
186-
import Dialog from "primevue/dialog"
187201
import { usePlatformConfig } from "../../store/platformConfig"
202+
import CatalogueRequirementModal from "./CatalogueRequirementModal.vue"
203+
import courseRelUserService from "../../services/courseRelUserService"
204+
import { useCourseRequirementStatus } from "../../composables/course/useCourseRequirementStatus"
188205
import { useLocale } from "../../composables/locale"
189-
const platformConfigStore = usePlatformConfig()
190-
const showDescriptionDialog = ref(false)
191206
const { getOriginalLanguageName } = useLocale()
192207
193-
const allowDescription = computed(
194-
() => platformConfigStore.getSetting("course.show_courses_descriptions_in_catalog") !== "false",
195-
)
196-
197208
const props = defineProps({
198209
course: Object,
199210
currentUserId: {
@@ -212,6 +223,14 @@ const emit = defineEmits(["rate", "subscribed"])
212223
const router = useRouter()
213224
const route = useRoute()
214225
const { showErrorNotification, showSuccessNotification } = useNotification()
226+
const platformConfigStore = usePlatformConfig()
227+
228+
const showDescriptionDialog = ref(false)
229+
const showDependenciesModal = ref(false)
230+
231+
const allowDescription = computed(
232+
() => platformConfigStore.getSetting("course.show_courses_descriptions_in_catalog") !== "false",
233+
)
215234
216235
const isUserInCourse = computed(() => {
217236
if (!props.currentUserId) return false
@@ -287,9 +306,7 @@ function routeExists(name) {
287306
288307
const linkSettings = computed(() => {
289308
const settings = platformConfigStore.getSetting("course.course_catalog_settings")
290-
const result = settings?.link_settings ?? {}
291-
console.log("Link settings:", result)
292-
return result
309+
return settings?.link_settings ?? {}
293310
})
294311
295312
const imageLink = computed(() => {
@@ -304,10 +321,6 @@ const imageLink = computed(() => {
304321
return { name: routeName, params: { id: props.course.id } }
305322
}
306323
307-
if (routeName) {
308-
console.warn(`[CatalogueCourseCard] Route '${routeName}' does not exist.`)
309-
}
310-
311324
return null
312325
})
313326
@@ -318,20 +331,21 @@ const titleLink = computed(() => {
318331
return { name: routeName, params: { id: props.course.id } }
319332
}
320333
321-
if (routeName) {
322-
console.warn(`[CatalogueCourseCard] Route '${routeName}' does not exist.`)
323-
}
324-
325334
return null
326335
})
327336
328337
const showInfoPopup = computed(() => {
329338
const allowed = ["course_description_popup"]
330339
const value = linkSettings.value.info_url
331-
if (value && !allowed.includes(value)) {
332-
console.warn(`[CatalogueCourseCard] info_url '${value}' is not a recognized option.`)
333-
return false
334-
}
335-
return value === "course_description_popup"
340+
return value && allowed.includes(value)
341+
})
342+
343+
const { isLocked, hasRequirements, requirementList, graphImage, fetchStatus } = useCourseRequirementStatus(
344+
props.course.id,
345+
props.course.sessionId || 0,
346+
)
347+
348+
onMounted(() => {
349+
fetchStatus()
336350
})
337351
</script>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<template>
2+
<Dialog
3+
v-model:visible="visible"
4+
modal
5+
:header="t('Session requirements and dependencies')"
6+
class="w-[30rem] z-[99999] !block !opacity-100"
7+
>
8+
<div v-if="hasData">
9+
<div
10+
v-for="section in requirements"
11+
:key="section.name"
12+
class="mb-4"
13+
>
14+
<h4 class="font-semibold text-gray-700 mb-2">
15+
{{ section.name }}
16+
</h4>
17+
<ul class="list-disc pl-5 text-sm text-gray-700">
18+
<li
19+
v-for="req in section.requirements"
20+
:key="req.name"
21+
class="flex items-center gap-2"
22+
>
23+
<i
24+
v-if="req.status !== null"
25+
:class="req.status
26+
? 'mdi mdi-check-circle text-green-500'
27+
: 'mdi mdi-alert-circle text-red-500'"
28+
></i>
29+
<span v-html="req.adminLink || req.name"></span>
30+
</li>
31+
</ul>
32+
</div>
33+
34+
<div
35+
v-if="graphImage"
36+
class="mt-4 text-center"
37+
>
38+
<img
39+
:src="graphImage"
40+
alt="Graph"
41+
class="max-w-full max-h-96 mx-auto border rounded"
42+
/>
43+
</div>
44+
</div>
45+
46+
<div
47+
v-else
48+
class="text-sm text-gray-500"
49+
>
50+
{{ t("No dependencies") }}
51+
</div>
52+
</Dialog>
53+
</template>
54+
55+
<script setup>
56+
import Dialog from "primevue/dialog"
57+
import { computed } from "vue"
58+
import { useI18n } from "vue-i18n"
59+
60+
const { t } = useI18n()
61+
62+
const props = defineProps({
63+
modelValue: Boolean,
64+
courseId: Number,
65+
sessionId: Number,
66+
requirements: Array,
67+
dependencies: Array,
68+
graphImage: String,
69+
})
70+
71+
const emit = defineEmits(["update:modelValue"])
72+
73+
const visible = computed({
74+
get: () => props.modelValue,
75+
set: (value) => emit("update:modelValue", value),
76+
})
77+
78+
const requirements = computed(() => props.requirements || [])
79+
const dependencies = computed(() => props.dependencies || [])
80+
81+
const hasData = computed(() => {
82+
return requirements.value.length > 0 || dependencies.value.length > 0
83+
})
84+
</script>

0 commit comments

Comments
 (0)