Chamilo is a learning management system focused on ease of use and accessibility
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
chamilo-lms/assets/vue/views/course/CatalogueCourses.vue

343 lines
9.2 KiB

<template>
<div class="card">
<DataTable
v-model:filters="filters"
:global-filter-fields="['title', 'description', 'category.title', 'courseLanguage']"
:loading="status"
:paginator="true"
:rows="9"
:value="courses"
class="p-datatable-courses p-datatable-lg"
data-key="id"
edit-mode="cell"
filter-display="menu"
responsive-layout="scroll"
striped-rows
>
<template #header>
<div class="table-header-container">
<div class="flex justify-content-end">
<Button
:label="$t('Clear filter results')"
class="p-button-outlined mr-2"
icon="pi pi-filter-slash"
type="button"
@click="clearFilter()"
/>
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText
v-model="filters['global'].value"
:placeholder="$t('Search')"
/>
</span>
</div>
</div>
</template>
<template #empty>
{{ $t("No course available") }}
</template>
<template #loading>
{{ $t("Loading courses. Please wait.") }}
</template>
<Column
header=""
style="min-width: 5rem"
>
<template #body="{ data }">
<img
:alt="data.title"
:src="data.illustrationUrl"
class="course-image"
/>
</template>
</Column>
<Column
:header="$t('Title')"
:sortable="true"
field="title"
style="min-width: 8rem; text-align: center"
>
<template #body="{ data }">
{{ data.title }}
</template>
</Column>
<Column
v-if="showCourseDuration"
:header="$t('Course description')"
:sortable="true"
field="description"
style="min-width: 8rem; text-align: center"
>
<template #body="{ data }">
{{ data.description }}
</template>
</Column>
<Column
:header="$t('Duration')"
:sortable="true"
field="duration"
style="min-width: 8rem; text-align: center"
>
<template #body="{ data }">
<div
v-if="data.duration"
class="course-duration"
>
{{ (data.duration / 60 / 60).toFixed(2) }} hours
</div>
</template>
</Column>
<Column
:header="$t('Teachers')"
:sortable="true"
field="teachers"
style="min-width: 10rem; text-align: center"
>
<template #body="{ data }">
<div v-if="data.teachers && data.teachers.length > 0">
{{ data.teachers.map((teacher) => teacher.user.fullName).join(", ") }}
</div>
</template>
</Column>
<Column
:header="$t('Language')"
:sortable="true"
field="courseLanguage"
style="min-width: 5rem; text-align: center"
>
<template #body="{ data }">
{{ data.courseLanguage }}
</template>
</Column>
<Column
:header="$t('Categories')"
:sortable="true"
field="categories"
style="min-width: 8rem; text-align: center"
>
<template #body="{ data }">
<span
v-for="category in data.categories"
:key="category.id"
>
<em class="pi pi-tag course-category-icon" />
<span class="course-category">{{ category.title }}</span
><br />
</span>
</template>
</Column>
<Column
:header="$t('Ranking')"
:sortable="true"
field="trackCourseRanking.realTotalScore"
style="min-width: 10rem; text-align: center"
>
<template #body="{ data }">
<Rating
:cancel="false"
:model-value="data.trackCourseRanking ? data.trackCourseRanking.realTotalScore : 0"
:stars="5"
class="pointer-events: none"
@change="onRatingChange($event, data.trackCourseRanking, data.id)"
/>
</template>
</Column>
<Column
field="link"
header=""
style="min-width: 10rem; text-align: center"
>
<template #body="{ data }">
<router-link
v-if="data.visibility === 3"
:to="{ name: 'CourseHome', params: { id: data.id } }"
>
<Button
:label="$t('Go to the course')"
class="btn btn--primary text-white"
icon="pi pi-external-link"
/>
</router-link>
<router-link
v-else-if="data.visibility === 2 && isUserInCourse(data)"
:to="{ name: 'CourseHome', params: { id: data.id } }"
>
<Button
:label="$t('Go to the course')"
class="btn btn--primary text-white"
icon="pi pi-external-link"
/>
</router-link>
<Button
v-else-if="data.visibility === 2 && !isUserInCourse(data)"
:label="$t('Not subscribed')"
class="btn btn--primary text-white"
disabled
icon="pi pi-times"
/>
<Button
v-else
:label="$t('Private course')"
class="btn btn--primary text-white"
disabled
icon="pi pi-lock"
/>
</template>
</Column>
<template #footer>
{{ $t("Total number of courses").concat(": ", courses ? courses.length.toString() : "0") }}
</template>
</DataTable>
</div>
</template>
<script setup>
import { ref } from "vue"
import { ENTRYPOINT } from "../../config/entrypoint"
import axios from "axios"
import { FilterMatchMode } from "primevue/api"
import Button from "primevue/button"
import DataTable from "primevue/datatable"
import Column from "primevue/column"
import Rating from "primevue/rating"
import { usePlatformConfig } from "../../store/platformConfig"
import { useSecurityStore } from "../../store/securityStore"
const securityStore = useSecurityStore()
const status = ref(null)
const courses = ref([])
const filters = ref(null)
const currentUserId = securityStore.user.id
const platformConfigStore = usePlatformConfig()
const showCourseDuration = "true" === platformConfigStore.getSetting("course.show_course_duration")
const load = function () {
status.value = true
axios
.get(ENTRYPOINT + "courses.json")
.then((response) => {
status.value = false
if (Array.isArray(response.data)) {
response.data.forEach((course) => {
course.courseLanguage = getOriginalLanguageName(course.courseLanguage)
if (course.duration) {
course.duration = course.duration
}
})
courses.value = response.data
}
})
.catch(function (error) {
console.log(error)
})
}
const updateRating = function (id, value) {
status.value = true
axios
.patch(
ENTRYPOINT + "track_course_rankings/" + id,
{ totalScore: value },
{ headers: { "Content-Type": "application/merge-patch+json" } },
)
.then((response) => {
courses.value.forEach((course) => {
if (course.trackCourseRanking && course.trackCourseRanking.id === id) {
course.trackCourseRanking.realTotalScore = response.data.realTotalScore
}
})
status.value = false
})
.catch(function (error) {
console.log(error)
})
}
const newRating = function (courseId, value) {
status.value = true
axios
.post(
ENTRYPOINT + "track_course_rankings",
{
totalScore: value,
course: ENTRYPOINT + "courses/" + courseId,
url_id: window.access_url_id,
sessionId: 0,
},
{ headers: { "Content-Type": "application/ld+json" } },
)
.then((response) => {
courses.value.forEach((course) => {
if (course.id === courseId) {
course.trackCourseRanking = response.data
}
})
status.value = false
})
.catch(function (error) {
console.log(error)
})
}
const isUserInCourse = (course) => {
return course.users.some((user) => user.user.id === currentUserId)
}
const clearFilter = function () {
initFilters()
}
const initFilters = function () {
filters.value = {
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
}
}
const getOriginalLanguageName = function (courseLanguage) {
const languages = window.languages
let language = languages.find((element) => element.isocode === courseLanguage)
if (language) {
return language.originalName
} else {
return ""
}
}
const onRatingChange = function (event, trackCourseRanking, courseId) {
let { value } = event
if (value > 0) {
if (trackCourseRanking) updateRating(trackCourseRanking.id, value)
else newRating(courseId, value)
} else {
event.preventDefault()
}
}
load()
initFilters()
</script>
<style scoped>
.p-datatable .p-datatable-thead > tr > th {
text-align: center !important;
}
.course-image {
width: 100px;
height: auto;
}
.course-duration {
text-align: center;
font-weight: bold;
}
.btn--primary {
background-color: #007bff;
border-color: #007bff;
color: #fff;
}
</style>