- Complete GGZ Ecademy Nuxt.js user portal - Learning products browser and management - Member management interface - User authentication and roles - Multi-language support (NL/EN) - Vuex store for state management - Component-based architecture
This commit is contained in:
520
pages/manager/learning/_product.vue
Normal file
520
pages/manager/learning/_product.vue
Normal file
@@ -0,0 +1,520 @@
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<h2 class="ma-4">{{ title }}</h2>
|
||||
<!-- <h2 class="ma-4">{{ title }} [{{ $store.getters.productHasChanges.toString() }}]</h2> -->
|
||||
|
||||
<div class="d-flex justify-space-between">
|
||||
<div class="d-flex">
|
||||
<small
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
@click="selectedTab = tab"
|
||||
class="ma-lg-4 ma-sm-2 dot"
|
||||
:class="{ 'accent--text activeTab': selectedTab === tab }"
|
||||
>{{ $t(`learning.product_overview.${tab}`) }}</small
|
||||
>
|
||||
</div>
|
||||
<current-date-time showIcon />
|
||||
</div>
|
||||
|
||||
<button ref="scrollStartingPoint" width="0.1px" height="0.1px"></button>
|
||||
|
||||
<basic v-if="isTabSelected('basic')" :editMode="canEdit" />
|
||||
<texts v-if="isTabSelected('texts')" :editMode="canEdit" />
|
||||
|
||||
<notifications
|
||||
v-if="isTabSelected('notifications')"
|
||||
:editMode="canEdit"
|
||||
:users="users"
|
||||
:isCreateMode="isNew"
|
||||
/>
|
||||
<organize v-if="isTabSelected('organize')" :editMode="canEdit" />
|
||||
<accreditation
|
||||
v-if="isTabSelected('accreditation')"
|
||||
:editMode="canEdit"
|
||||
:isCreateMode="isNew"
|
||||
/>
|
||||
<administration
|
||||
v-if="isTabSelected('administration')"
|
||||
:editMode="canEdit"
|
||||
/>
|
||||
<versions
|
||||
v-if="isTabSelected('version')"
|
||||
:editMode="canEdit"
|
||||
:isCreateMode="isNew"
|
||||
/>
|
||||
<links v-if="isTabSelected('links') && canEdit" :editMode="canEdit" />
|
||||
</v-col>
|
||||
|
||||
<v-footer
|
||||
fixed
|
||||
style="z-index: 4"
|
||||
height="90"
|
||||
color="primary"
|
||||
v-if="$store.getters.isAdmin || $store.getters.isOperator"
|
||||
>
|
||||
<v-btn title text small :to="localePath('/manager/learning')">
|
||||
<v-icon>icon-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<!-- <div class="mx-10" v-if="$store.getters.isLearningProductValidated"> -->
|
||||
<div class="mx-10">
|
||||
<v-btn
|
||||
v-cloak
|
||||
class="ma-2 white--text"
|
||||
color="accent"
|
||||
depressed
|
||||
v-if="!canEdit && !isNew"
|
||||
rounded
|
||||
nuxt
|
||||
:to="localePath(`${$nuxt.$route.path}?edit`)"
|
||||
>
|
||||
{{ $t('general.edit') }}</v-btn
|
||||
>
|
||||
<v-btn
|
||||
v-cloak
|
||||
class="ma-2 white--text"
|
||||
color="txt"
|
||||
depressed
|
||||
v-if="!canEdit && !isNew"
|
||||
rounded
|
||||
nuxt
|
||||
:to="localePath('/manager/learning')"
|
||||
>
|
||||
{{ $t('general.close') }}</v-btn
|
||||
>
|
||||
|
||||
<v-btn
|
||||
class="ma-2 white--text"
|
||||
:color="$vuetify.theme.dark ? 'secondary' : 'txt'"
|
||||
depressed
|
||||
@click="
|
||||
$router.push(localePath(`/manager/learning/${local.draft.slug}`))
|
||||
"
|
||||
v-if="canSave && local.draft"
|
||||
rounded
|
||||
>{{ $t('learning.product_overview.open_existing_draft') }}</v-btn
|
||||
>
|
||||
|
||||
<v-btn
|
||||
class="ma-2 white--text"
|
||||
:color="$vuetify.theme.dark ? 'secondary' : 'txt'"
|
||||
depressed
|
||||
@click="save(true)"
|
||||
v-if="canSave && !local.draft"
|
||||
rounded
|
||||
>{{ $t('general.save') }}</v-btn
|
||||
>
|
||||
<v-btn
|
||||
class="ma-2 white--text"
|
||||
:color="$vuetify.theme.dark ? 'secondary' : 'txt'"
|
||||
depressed
|
||||
@click="save(false)"
|
||||
v-if="canSave && !local.draft"
|
||||
rounded
|
||||
>{{ $t('general.save_and_close') }}</v-btn
|
||||
>
|
||||
|
||||
<v-btn
|
||||
v-cloak
|
||||
class="ma-2 white--text"
|
||||
color="accent"
|
||||
depressed
|
||||
@click="save(false, true)"
|
||||
v-if="canSave || isDraft"
|
||||
rounded
|
||||
>{{ $t('general.publish_and_close') }}</v-btn
|
||||
>
|
||||
</div>
|
||||
|
||||
<v-spacer />
|
||||
|
||||
<v-btn
|
||||
class="ma-2"
|
||||
tile
|
||||
text
|
||||
small
|
||||
v-if="remote.sharepoint_link"
|
||||
:href="remote.sharepoint_link"
|
||||
target="_blank"
|
||||
>
|
||||
<v-icon class="mx-2">icon-sharepoint</v-icon>
|
||||
{{ $t('footer_bar.documents') }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
class="ma-2"
|
||||
tile
|
||||
text
|
||||
small
|
||||
v-if="remote.support_link"
|
||||
:href="remote.support_link"
|
||||
target="_blank"
|
||||
>
|
||||
<v-icon class="mx-2">icon-info</v-icon>
|
||||
{{ $t('footer_bar.support_site') }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
class="ma-2"
|
||||
tile
|
||||
text
|
||||
small
|
||||
v-if="remote.support_tickets_link"
|
||||
:href="remote.support_tickets_link"
|
||||
target="_blank"
|
||||
>
|
||||
<v-icon class="mx-2">icon-support</v-icon>
|
||||
{{ $t('footer_bar.support_tickets') }}
|
||||
</v-btn>
|
||||
|
||||
<!-- <v-btn class="ma-2" tile text small>
|
||||
<v-icon class="mx-2" small>icon-share</v-icon>
|
||||
{{ $t('general.share_url') }}
|
||||
</v-btn> -->
|
||||
|
||||
<v-dialog v-model="dialog" persistent max-width="740">
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn
|
||||
class="ma-2"
|
||||
tile
|
||||
text
|
||||
small
|
||||
v-if="!isNew && canEdit"
|
||||
v-on="on"
|
||||
>
|
||||
<v-icon class="mx-2">icon-remove</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card class="primary pa-10" flat>
|
||||
<v-card-title class="headline">
|
||||
{{
|
||||
$t('learning.product_overview.delete_confirmation', {
|
||||
productName: local.title,
|
||||
})
|
||||
}}
|
||||
</v-card-title>
|
||||
<v-card-actions>
|
||||
<div class="ma-4">
|
||||
<v-btn
|
||||
color="accent"
|
||||
class="mx-2"
|
||||
@click="deleteProduct()"
|
||||
rounded
|
||||
depressed
|
||||
>{{ $t('general.delete') }}</v-btn
|
||||
>
|
||||
<v-btn
|
||||
class="mx-2"
|
||||
color="info"
|
||||
@click="close"
|
||||
rounded
|
||||
depressed
|
||||
>{{ $t('general.cancel') }}</v-btn
|
||||
>
|
||||
</div>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-footer>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import currentDateTime from '@/components/CurrentDateTime/CurrentDateTime'
|
||||
import basic from '@/components/Learning/ProductOverview/Basic'
|
||||
import texts from '@/components/Learning/ProductOverview/Texts'
|
||||
import notifications from '@/components/Learning/ProductOverview/Notifications'
|
||||
import organize from '@/components/Learning/ProductOverview/Organize'
|
||||
import accreditation from '@/components/Learning/ProductOverview/Accreditation'
|
||||
import administration from '@/components/Learning/ProductOverview/Administration'
|
||||
import versions from '@/components/Learning/ProductOverview/Versions'
|
||||
import links from '@/components/Learning/ProductOverview/Links'
|
||||
|
||||
export default {
|
||||
layout: `${process.env.CUSTOMER}Admin`,
|
||||
middleware: ['denyToOnlyMembers'],
|
||||
components: {
|
||||
currentDateTime,
|
||||
basic,
|
||||
texts,
|
||||
notifications,
|
||||
organize,
|
||||
accreditation,
|
||||
administration,
|
||||
versions,
|
||||
links,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: false,
|
||||
users: [],
|
||||
tabs: [
|
||||
'all',
|
||||
'basic',
|
||||
'texts',
|
||||
'notifications',
|
||||
'organize',
|
||||
'accreditation',
|
||||
'administration',
|
||||
'version',
|
||||
'links',
|
||||
],
|
||||
scrollFlag: false,
|
||||
selectedTab: 'all',
|
||||
}
|
||||
},
|
||||
|
||||
async asyncData({ $axios, store }) {
|
||||
try {
|
||||
let response = await $axios.get('/filters')
|
||||
await store.commit('learning/SORT_FILTERS', response.data)
|
||||
|
||||
response = await $axios.get('/synonyms')
|
||||
await store.commit('learning/SET_SYNONYMS_LIST', response.data)
|
||||
|
||||
response = await $axios.get('/checklist-categories')
|
||||
await store.commit('learning/SET_CHECKLIST', response.data)
|
||||
|
||||
if (store.getters.isAdmin || store.getters.isOperator) {
|
||||
response = await $axios.get('/admin/users/getList')
|
||||
return { users: response.data }
|
||||
}
|
||||
return { users: [] }
|
||||
} catch (error) {
|
||||
console.log('Data -> error', error)
|
||||
}
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
if (!this.isValidSlug) this.$router.push('/manager')
|
||||
if (this.isNew) return await this.$store.dispatch('learning/resetProduct')
|
||||
if (this.isSlugNumber) this.getItem(this.slug)
|
||||
},
|
||||
|
||||
updated() {
|
||||
if (!this.scrollFlag) {
|
||||
this.scrollToTop()
|
||||
this.scrollFlag = true
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
canEdit() {
|
||||
return (
|
||||
this.isNew ||
|
||||
(this.$route.query.edit === null &&
|
||||
(this.$store.getters.isAdmin || this.$store.getters.isOperator))
|
||||
)
|
||||
},
|
||||
remote() {
|
||||
return this.$store.state.learning.remote
|
||||
},
|
||||
local() {
|
||||
return this.$store.state.learning.local
|
||||
},
|
||||
title() {
|
||||
if (this.isNew) return 'New'
|
||||
return this.local.title || ''
|
||||
},
|
||||
slug() {
|
||||
return this.$route.params.product
|
||||
},
|
||||
isNew() {
|
||||
return this.slug.toLowerCase() === 'new'
|
||||
},
|
||||
isDraft() {
|
||||
return !this.local.published
|
||||
},
|
||||
isSlugNumber() {
|
||||
return !isNaN(this.slug.split(['-'], 1))
|
||||
},
|
||||
isValidSlug() {
|
||||
return this.isNew || this.isSlugNumber
|
||||
},
|
||||
isDownloaded() {
|
||||
return Object.keys(this.remote).length > 0
|
||||
},
|
||||
canSave() {
|
||||
if (this.isNew) return true
|
||||
return this.isSlugNumber && this.isDownloaded && this.hasChanges
|
||||
},
|
||||
hasChanges() {
|
||||
return this.$store.getters.productHasChanges
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
dialog(val) {
|
||||
val || this.close()
|
||||
},
|
||||
},
|
||||
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
await this.$store.dispatch('learning/resetProduct')
|
||||
next()
|
||||
},
|
||||
|
||||
methods: {
|
||||
scrollToTop() {
|
||||
this.$nextTick(() => this.$refs.scrollStartingPoint.focus())
|
||||
},
|
||||
|
||||
close() {
|
||||
this.dialog = false
|
||||
},
|
||||
|
||||
isTabSelected(tab) {
|
||||
return this.selectedTab === tab || this.selectedTab === 'all'
|
||||
},
|
||||
|
||||
async getItem(url) {
|
||||
if (!this.isSlugNumber) return
|
||||
|
||||
try {
|
||||
this.$nextTick(() => this.$nuxt.$loading.start())
|
||||
let response = await this.$axios.get(`/learning-products/${url}`)
|
||||
await this.$store.commit('learning/SET_PRODUCT', response.data)
|
||||
if (response.data.synonyms.length > 0) {
|
||||
const attachedSynonymsIds = response.data.synonyms.map(({ id }) => id)
|
||||
this.$store.commit('learning/SELECT_SYNONYM', attachedSynonymsIds)
|
||||
}
|
||||
|
||||
this.$nuxt.$loading.finish()
|
||||
} catch (error) {
|
||||
this.$nuxt.$loading.finish()
|
||||
this.$notifier.showMessage({
|
||||
content: `Error getting the learning product: ` + error.message,
|
||||
color: 'error',
|
||||
icon: 'icon-message',
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Scenarios
|
||||
* ---------------------------------------------------
|
||||
* Make draft without parent_id - Done
|
||||
* Make published - Done
|
||||
*
|
||||
* Edit draft without parent_id
|
||||
* Edit published without parent_id
|
||||
*
|
||||
* Make draft with parent_id (from published) - Done
|
||||
* Edit draft with parent_id (from published)
|
||||
|
||||
* Make published from draft with parent_id - backend
|
||||
*
|
||||
*/
|
||||
|
||||
async save(stay = false, published = false) {
|
||||
this.$nextTick(() => this.$nuxt.$loading.start())
|
||||
|
||||
try {
|
||||
const slug = await this.$store.dispatch(
|
||||
'learning/storeProduct',
|
||||
published
|
||||
)
|
||||
|
||||
await this.$store.dispatch('learning/pullProducts')
|
||||
|
||||
await this.$nuxt.$loading.finish()
|
||||
this.$notifier.showMessage({
|
||||
content: `Data saved successfully`,
|
||||
color: 'success',
|
||||
icon: 'mdi-check',
|
||||
})
|
||||
|
||||
if (!stay) {
|
||||
await this.$router.push(this.localePath('/manager/learning'))
|
||||
} else {
|
||||
await this.$router.push(
|
||||
this.localePath(`/manager/learning/${slug}?edit`)
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('save -> error', error)
|
||||
this.$nuxt.$loading.finish()
|
||||
this.$notifier.showMessage({
|
||||
content: `${
|
||||
this.slug.toLowerCase() === 'new' ? 'Creating' : 'Editing'
|
||||
} page: ${error.response ? error.response.data.message : error}.`,
|
||||
...(error.response && {
|
||||
errors: error.response.data.errors,
|
||||
}),
|
||||
color: 'error',
|
||||
icon: 'mdi-alert',
|
||||
})
|
||||
}
|
||||
this.close()
|
||||
},
|
||||
|
||||
async deleteProduct(productId = this.local.id) {
|
||||
if (!productId) return
|
||||
this.dialog = false
|
||||
await this.$store.dispatch('learning/deleteProduct', productId)
|
||||
this.$router.push(this.localePath('/manager/learning'))
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-card >>> .v-btn__content,
|
||||
.v-select .v-select__selection--comma,
|
||||
.v-chip__content,
|
||||
.v-list-item:not(.v-list-item--active):not(.v-list-item--disabled),
|
||||
.v-select.v-select--chips:not(.v-text-field--single-line).v-text-field--enclosed
|
||||
.v-select__selections,
|
||||
.v-list-item .v-list-item__title,
|
||||
.v-list-item .v-list-item__subtitle,
|
||||
.v-list-item span,
|
||||
.v-input--is-disabled input,
|
||||
.v-input--is-disable,
|
||||
.dot,
|
||||
h2,
|
||||
p {
|
||||
/* color: var(--v-txt-base); */
|
||||
}
|
||||
.v-card >>> .v-chip--disabled {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.v-card >>> .v-chip {
|
||||
/* padding-left: 0 !important; */
|
||||
background-color: var(--v-secondary-base) !important;
|
||||
font-size: 16px !important;
|
||||
}
|
||||
.v-card >>> .v-chip .mdi-close-circle::before {
|
||||
font-family: 'mijnggz';
|
||||
content: '\e907' !important;
|
||||
color: var(--v-secAccent-base) !important;
|
||||
font-size: 8px;
|
||||
margin-left: 2px;
|
||||
margin-right: 4px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.v-card >>> .v-chip .mdi-close-circle:hover::before {
|
||||
color: #bb1d28 !important;
|
||||
}
|
||||
|
||||
.dot {
|
||||
cursor: pointer;
|
||||
}
|
||||
.dot::before {
|
||||
content: '\A';
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background: #e54e0f;
|
||||
display: block;
|
||||
position: relative;
|
||||
top: 30px;
|
||||
left: 50%;
|
||||
opacity: 0;
|
||||
}
|
||||
.dot:hover::before {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
.dot.activeTab::before {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user