Skip to content

Commit cee7a1d

Browse files
Merge pull request #6203 from christianbeeznest/GH-6200
Catalogue: Restore visual catalogue of courses and sessions in grid mode - refs #6200
2 parents 52fd00c + 0ea8e84 commit cee7a1d

12 files changed

+977
-566
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
<template>
2+
<div
3+
class="course-card hover:shadow-lg transition duration-300 rounded-2xl overflow-hidden border border-gray-300 bg-white flex flex-col"
4+
>
5+
<img
6+
:src="course.illustrationUrl"
7+
:alt="course.title"
8+
class="w-full h-40 object-cover"
9+
/>
10+
<div class="p-4 flex flex-col flex-grow gap-2">
11+
<h3 class="text-xl font-semibold text-gray-800">{{ course.title }}</h3>
12+
<p class="text-sm text-gray-600 line-clamp-3">{{ course.description }}</p>
13+
14+
<div
15+
v-if="course.duration"
16+
class="text-sm text-gray-700"
17+
>
18+
<strong>{{ $t("Duration") }}:</strong> {{ durationInHours }}
19+
</div>
20+
21+
<div
22+
v-if="course.dependencies?.length"
23+
class="text-sm text-gray-700"
24+
>
25+
<strong>{{ $t("Dependencies") }}:</strong>
26+
{{ course.dependencies.map((dep) => dep.title).join(", ") }}
27+
</div>
28+
29+
<div
30+
v-if="course.price !== undefined"
31+
class="text-sm text-gray-700"
32+
>
33+
<strong>{{ $t("Price") }}:</strong>
34+
{{ course.price > 0 ? "S/. " + course.price.toFixed(2) : $t("Free") }}
35+
</div>
36+
37+
<div
38+
v-if="course.categories?.length"
39+
class="flex flex-wrap gap-1"
40+
>
41+
<span
42+
v-for="cat in course.categories"
43+
:key="cat.id"
44+
class="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded-full"
45+
>
46+
{{ cat.title }}
47+
</span>
48+
</div>
49+
50+
<div class="text-sm text-gray-700">
51+
<strong>{{ $t("Language") }}:</strong> {{ course.courseLanguage }}
52+
</div>
53+
54+
<div
55+
v-if="course.teachers?.length"
56+
class="text-sm text-gray-700"
57+
>
58+
<strong>{{ $t("Teachers") }}:</strong>
59+
{{ course.teachers.map((t) => t.user.fullName).join(", ") }}
60+
</div>
61+
62+
<Rating
63+
:model-value="course.userVote?.vote || 0"
64+
:stars="5"
65+
:cancel="false"
66+
@change="emitRating"
67+
class="mt-2"
68+
/>
69+
<div
70+
class="text-xs text-gray-600 mt-1"
71+
v-if="course.popularity || course.userVote?.vote"
72+
>
73+
{{ course.popularity || 0 }} Vote<span v-if="course.popularity !== 1">s</span>
74+
|
75+
{{ course.nbVisits || 0 }} Visite<span v-if="course.nbVisits !== 1">s</span>
76+
<span v-if="course.userVote?.vote">
77+
|
78+
{{ $t("Your vote") }} [{{ course.userVote.vote }}]
79+
</span>
80+
</div>
81+
82+
<div class="mt-auto pt-2">
83+
<router-link
84+
v-if="course.visibility === 3 || (course.visibility === 2 && isUserInCourse)"
85+
:to="{ name: 'CourseHome', params: { id: course.id } }"
86+
>
87+
<Button
88+
:label="$t('Go to the course')"
89+
icon="pi pi-external-link"
90+
class="w-full"
91+
/>
92+
</router-link>
93+
94+
<Button
95+
v-else-if="course.visibility === 2 && course.subscribe && !isUserInCourse"
96+
:label="$t('Subscribe')"
97+
icon="pi pi-sign-in"
98+
class="w-full"
99+
@click="subscribeToCourse"
100+
/>
101+
102+
<Button
103+
v-else-if="course.visibility === 2 && !course.subscribe && !isUserInCourse"
104+
:label="$t('Subscription not allowed')"
105+
icon="pi pi-ban"
106+
disabled
107+
class="w-full"
108+
/>
109+
110+
<Button
111+
v-else-if="course.visibility === 1"
112+
:label="$t('Private course')"
113+
icon="pi pi-lock"
114+
disabled
115+
class="w-full"
116+
/>
117+
118+
<Button
119+
v-else
120+
:label="$t('Not available')"
121+
icon="pi pi-eye-slash"
122+
disabled
123+
class="w-full"
124+
/>
125+
</div>
126+
</div>
127+
</div>
128+
</template>
129+
<script setup>
130+
import Rating from "primevue/rating"
131+
import Button from "primevue/button"
132+
import { computed, ref } from "vue"
133+
import courseRelUserService from "../../services/courseRelUserService"
134+
import { useRouter } from "vue-router"
135+
import { useNotification } from "../../composables/notification"
136+
137+
const props = defineProps({
138+
course: Object,
139+
currentUserId: Number,
140+
})
141+
142+
const emit = defineEmits(["rate", "subscribed"])
143+
144+
const router = useRouter()
145+
const { showErrorNotification, showSuccessNotification } = useNotification()
146+
147+
const isUserInCourse = computed(() => {
148+
return props.course.users?.some((user) => user.user.id === props.currentUserId)
149+
})
150+
151+
const durationInHours = computed(() => {
152+
if (!props.course.duration) return "-"
153+
const duration = props.course.duration / 3600
154+
return props.course.durationExtra ? `${duration.toFixed(2)}+ h` : `${duration.toFixed(2)} h`
155+
})
156+
157+
const emitRating = (event) => {
158+
emit("rate", { value: event.value, course: props.course })
159+
}
160+
161+
const subscribing = ref(false)
162+
const subscribeToCourse = async () => {
163+
try {
164+
subscribing.value = true
165+
166+
const response = await courseRelUserService.subscribe({
167+
userId: props.currentUserId,
168+
courseId: props.course.id,
169+
})
170+
171+
emit("subscribed", { courseId: props.course.id, newUser: response })
172+
showSuccessNotification("You have successfully subscribed to this course.")
173+
router.push({ name: "CourseHome", params: { id: props.course.id } })
174+
} catch (e) {
175+
showErrorNotification("Failed to subscribe to the course.")
176+
} finally {
177+
subscribing.value = false
178+
}
179+
}
180+
</script>

0 commit comments

Comments
 (0)