Add option for students to mark courses as favorites
This commit is contained in:
69
components/CourseList.vue
Normal file
69
components/CourseList.vue
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<v-row>
|
||||||
|
<v-col v-for="(course, id) in courses" :key="id" cols="12" lg="6">
|
||||||
|
<v-card width="100%" @click="openCourse(id)">
|
||||||
|
<v-row no-gutters class="flex-nowrap">
|
||||||
|
<v-col cols="auto">
|
||||||
|
<v-img
|
||||||
|
src="https://www.onlinecollegeplan.com/wp-content/uploads/2018/05/computer-programming.jpg"
|
||||||
|
:width="imgSize"
|
||||||
|
:height="imgSize"
|
||||||
|
class="rounded-l-lg"
|
||||||
|
></v-img>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="auto" class="flex-grow-1 flex-shrink-0">
|
||||||
|
<v-card-title class="text--secondary text-subtitle-1">{{ id.toUpperCase() }}</v-card-title>
|
||||||
|
<v-card-subtitle class="text--primary text-h6 text-wrap">{{ course.name }}</v-card-subtitle>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="auto">
|
||||||
|
<v-btn icon @click.stop="isFavoriteCourse(id) ? addToFavorites(id, false) : addToFavorites(id, true)">
|
||||||
|
<v-icon>{{ isFavoriteCourse(id) ? 'mdi-heart' : 'mdi-heart-outline' }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { doc, updateDoc, arrayUnion, arrayRemove } from 'firebase/firestore'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CourseList',
|
||||||
|
props: {
|
||||||
|
courses: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
imgSize: 125
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openCourse (courseID) {
|
||||||
|
this.$store.commit('setSelectedCourse', courseID)
|
||||||
|
this.$router.push(`courses/${courseID}`)
|
||||||
|
},
|
||||||
|
isFavoriteCourse (courseID) {
|
||||||
|
return this.$store.state.user.courses && this.$store.state.user.courses.includes(courseID)
|
||||||
|
},
|
||||||
|
addToFavorites (courseID, add) {
|
||||||
|
const gameRef = doc(this.$db, `benutzer/${this.$auth.currentUser.uid}`)
|
||||||
|
|
||||||
|
// Atomically add a new answer to the "player[ID]answers" array field.
|
||||||
|
updateDoc(gameRef, {
|
||||||
|
kurse: add ? arrayUnion(courseID) : arrayRemove(courseID)
|
||||||
|
}).then((empty) => {
|
||||||
|
// Query execution was successful
|
||||||
|
add ? this.$store.commit('addFavoriteCourse', courseID) : this.$store.commit('removeFavoriteCourse', courseID)
|
||||||
|
}).catch((error) => {
|
||||||
|
// Failed to update the game; display error message
|
||||||
|
this.$toast({ content: error, color: 'error' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,26 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container fluid>
|
<v-container v-if="!coursesLoaded" fluid fill-height>
|
||||||
|
<v-card color="transparent" class="mx-auto">
|
||||||
|
<v-progress-circular indeterminate color="primary" size="32"></v-progress-circular>
|
||||||
|
</v-card>
|
||||||
|
</v-container>
|
||||||
|
<v-container v-else fluid>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col v-if="$store.getters.isAdmin" cols="12">
|
<v-col cols="12">
|
||||||
|
<v-tabs v-model="tab" color="primary" background-color="transparent" centered>
|
||||||
|
<v-tab>Meine Kurse ({{ Object.keys(userCourses).length }})</v-tab>
|
||||||
|
<v-tab>Alle Kurse ({{ Object.keys(allCourses).length }})</v-tab>
|
||||||
|
</v-tabs>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-tabs-items v-model="tab" style="background-color: transparent !important;">
|
||||||
|
<v-tab-item>
|
||||||
|
<CourseList :courses="userCourses" />
|
||||||
|
</v-tab-item>
|
||||||
|
<v-tab-item>
|
||||||
|
<v-row v-if="$store.getters.isAdmin">
|
||||||
|
<v-col cols="12">
|
||||||
<AddCourse />
|
<AddCourse />
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col v-for="(course, id) in courses" :key="id" cols="12" lg="6">
|
|
||||||
<v-card flat width="100%" class="rounded-lg" @click="openCourse(id)">
|
|
||||||
<v-row no-gutters class="flex-nowrap">
|
|
||||||
<v-col cols="auto">
|
|
||||||
<v-img
|
|
||||||
src="https://www.onlinecollegeplan.com/wp-content/uploads/2018/05/computer-programming.jpg"
|
|
||||||
:width="imgSize"
|
|
||||||
:height="imgSize"
|
|
||||||
class="rounded-l-lg"
|
|
||||||
></v-img>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="auto">
|
|
||||||
<v-card-title class="text--secondary text-subtitle-1">{{ id.toUpperCase() }}</v-card-title>
|
|
||||||
<v-card-subtitle class="text--primary text-h6 text-wrap">{{ course.name }}</v-card-subtitle>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-card>
|
<CourseList :courses="allCourses" />
|
||||||
|
</v-tab-item>
|
||||||
|
</v-tabs-items>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-container>
|
</v-container>
|
||||||
@@ -29,6 +34,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import _cloneDeep from 'lodash-es/cloneDeep'
|
import _cloneDeep from 'lodash-es/cloneDeep'
|
||||||
import { collection, getDocs } from 'firebase/firestore'
|
import { collection, getDocs } from 'firebase/firestore'
|
||||||
|
import _isEmpty from 'lodash-es/isEmpty'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DashboardPage',
|
name: 'DashboardPage',
|
||||||
@@ -38,17 +44,22 @@ export default {
|
|||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
imgSize: 125
|
coursesLoaded: false,
|
||||||
|
tab: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
courses () {
|
allCourses () {
|
||||||
return this.$store.state.courses
|
return this.$store.state.courses
|
||||||
|
},
|
||||||
|
userCourses () {
|
||||||
|
return this.$store.getters.getFavoriteCourses
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
// Load all courses from the database and convert to JS object
|
// Load all courses from the database and convert to JS object
|
||||||
getDocs(collection(this.$db, 'kurse')).then(querySnapshot => {
|
getDocs(collection(this.$db, 'kurse'))
|
||||||
|
.then(querySnapshot => {
|
||||||
const courses = {}
|
const courses = {}
|
||||||
querySnapshot.forEach((doc) => {
|
querySnapshot.forEach((doc) => {
|
||||||
// doc.data() is never undefined for query doc snapshots
|
// doc.data() is never undefined for query doc snapshots
|
||||||
@@ -57,13 +68,15 @@ export default {
|
|||||||
|
|
||||||
// Save courses in Vuex store
|
// Save courses in Vuex store
|
||||||
this.$store.commit('setCourses', _cloneDeep(courses))
|
this.$store.commit('setCourses', _cloneDeep(courses))
|
||||||
|
|
||||||
|
// Switch to the "All Courses" tab (index = 1) if the student doesn't have any favorite courses
|
||||||
|
this.tab = _isEmpty(this.userCourses) ? 1 : 0
|
||||||
|
this.coursesLoaded = true
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
// Couldn't get list of courses from the db; display error message
|
||||||
|
this.$toast({ content: error, color: 'error' })
|
||||||
})
|
})
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
openCourse (courseID) {
|
|
||||||
this.$store.commit('setSelectedCourse', courseID)
|
|
||||||
this.$router.push(`courses/${courseID}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export class User {
|
export class User {
|
||||||
constructor (displayName, gamesStarted) {
|
constructor (displayName, courses, gamesStarted) {
|
||||||
this.displayName = displayName
|
this.displayName = displayName
|
||||||
|
this.courses = courses
|
||||||
this.gamesStarted = []
|
this.gamesStarted = []
|
||||||
|
|
||||||
if (gamesStarted && gamesStarted.length > 0) {
|
if (gamesStarted && gamesStarted.length > 0) {
|
||||||
@@ -21,11 +22,12 @@ export const UserConverter = {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
anzeigename: user.displayName,
|
anzeigename: user.displayName,
|
||||||
|
kurse: user.courses,
|
||||||
spieleBegonnen
|
spieleBegonnen
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fromFirestore: (snapshot, options) => {
|
fromFirestore: (snapshot, options) => {
|
||||||
const data = snapshot.data(options)
|
const data = snapshot.data(options)
|
||||||
return new User(data.anzeigename, data.spieleBegonnen)
|
return new User(data.anzeigename, data.kurse, data.spieleBegonnen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import _pick from 'lodash-es/pick'
|
||||||
|
|
||||||
export const state = () => ({
|
export const state = () => ({
|
||||||
firebaseInitialized: false,
|
firebaseInitialized: false,
|
||||||
idTokenResult: null,
|
idTokenResult: null,
|
||||||
@@ -12,6 +14,10 @@ export const getters = {
|
|||||||
},
|
},
|
||||||
getCourseByID: (state) => (courseID) => {
|
getCourseByID: (state) => (courseID) => {
|
||||||
return state.courses[courseID]
|
return state.courses[courseID]
|
||||||
|
},
|
||||||
|
getFavoriteCourses (state) {
|
||||||
|
// Ref: https://lodash.com/docs/#pick
|
||||||
|
return _pick(state.courses, state.user.courses)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +37,13 @@ export const mutations = {
|
|||||||
setCourses (state, courses) {
|
setCourses (state, courses) {
|
||||||
state.courses = courses
|
state.courses = courses
|
||||||
},
|
},
|
||||||
|
addFavoriteCourse (state, courseID) {
|
||||||
|
state.user.courses.push(courseID)
|
||||||
|
},
|
||||||
|
removeFavoriteCourse (state, courseID) {
|
||||||
|
const index = state.user.courses.findIndex(e => e === courseID)
|
||||||
|
state.user.courses.splice(index, 1)
|
||||||
|
},
|
||||||
setCourse (state, { courseID, courseData }) {
|
setCourse (state, { courseID, courseData }) {
|
||||||
this._vm.$set(state.courses, courseID, courseData)
|
this._vm.$set(state.courses, courseID, courseData)
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user