- 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>
|
||||
324
pages/manager/learning/filters/_filter.vue
Normal file
324
pages/manager/learning/filters/_filter.vue
Normal file
@@ -0,0 +1,324 @@
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<div class="d-flex justify-space-between">
|
||||
<h2 class="ml-6 txt--text">
|
||||
{{ $t(`learning.filters.${filter.title}`) | capitalize }}
|
||||
({{ filter.items.length }})
|
||||
</h2>
|
||||
<current-date-time showIcon class="mr-6" />
|
||||
</div>
|
||||
|
||||
<accordion-card :title="$t('general.list') | capitalize">
|
||||
<v-row v-for="(item, index) in filter.items" :key="item.title + index">
|
||||
<v-col cols="12" sm="3" md="3">
|
||||
<v-subheader class="ml-10 txt--text font-weight-black"
|
||||
>{{ index + 1 }}.
|
||||
</v-subheader>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="6">
|
||||
<v-text-field
|
||||
outlined
|
||||
:name="item.title + index"
|
||||
:value="item.title"
|
||||
append-icon="icon-close"
|
||||
@change="setDynamicTitle($event, index)"
|
||||
@click:append="askForDelete(item)"
|
||||
:append-outer-icon="
|
||||
hasItemChanges(index) ? 'mdi-content-save' : ''
|
||||
"
|
||||
@click:append-outer="update(item.id, index)"
|
||||
:error="hasItemChanges(index)"
|
||||
>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="3" md="3">
|
||||
<!-- <v-btn color="success" :to="`/manager/learning/filters/${slug}/${item.id}`">edit</v-btn> -->
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" sm="12" md="3">
|
||||
<v-subheader class="ml-10 txt--text font-weight-black">
|
||||
Nieuw
|
||||
</v-subheader>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="6">
|
||||
<v-text-field v-model="newItem" outlined> </v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="3"> </v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" sm="12" md="3"> </v-col>
|
||||
<v-col cols="12" sm="12" md="6">
|
||||
<v-btn
|
||||
class="cta-secondary"
|
||||
block
|
||||
depressed
|
||||
@click="addFilterItem"
|
||||
min-height="60px"
|
||||
>
|
||||
<v-icon x-small class="mx-4">icon-add</v-icon>
|
||||
|
||||
{{ $t('general.add') | capitalize }}</v-btn
|
||||
>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="3"> </v-col>
|
||||
</v-row>
|
||||
</accordion-card>
|
||||
|
||||
<v-dialog v-model="dialog" persistent max-width="740">
|
||||
<v-card class="primary pa-10" flat>
|
||||
<v-card-title class="headline">
|
||||
{{
|
||||
$t('learning.filters.delete_item_confirmation', {
|
||||
itemName: itemSelected.title,
|
||||
})
|
||||
}}
|
||||
</v-card-title>
|
||||
<v-card-actions>
|
||||
<div class="ma-4">
|
||||
<v-btn
|
||||
color="accent"
|
||||
class="mx-2"
|
||||
@click="deleteItem(itemSelected.id)"
|
||||
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-col>
|
||||
|
||||
<v-footer fixed style="z-index: 4" height="90" color="primary">
|
||||
<v-btn text nuxt :to="localePath('/manager/learning/filters')">
|
||||
<v-icon>icon-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<!-- <v-btn
|
||||
v-cloak
|
||||
class="ma-2 white--text"
|
||||
color="accent"
|
||||
depressed
|
||||
rounded
|
||||
v-if="itemsHaveChanges"
|
||||
@click="saveAll"
|
||||
>
|
||||
SAVE</v-btn
|
||||
> -->
|
||||
</v-footer>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
|
||||
import currentDateTime from '@/components/CurrentDateTime/CurrentDateTime'
|
||||
import PageHeader from '~/components/UI/PageHeader/PageHeader'
|
||||
|
||||
import Vue from 'vue'
|
||||
|
||||
export default {
|
||||
layout: `${process.env.CUSTOMER}Admin`,
|
||||
middleware: 'adminsOrOperators',
|
||||
components: {
|
||||
accordionCard,
|
||||
PageHeader,
|
||||
currentDateTime,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newItem: '',
|
||||
filter: { items: [] },
|
||||
items: [],
|
||||
itemSelected: {},
|
||||
dialog: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (!this.isSlugNumber) this.$router.push('/manager')
|
||||
if (this.isNew) return
|
||||
if (this.isSlugNumber) this.getFilter(this.slug)
|
||||
},
|
||||
|
||||
computed: {
|
||||
slug() {
|
||||
return this.$route.params.filter
|
||||
},
|
||||
isSlugNumber() {
|
||||
return !isNaN(this.slug.split(['-'], 1))
|
||||
},
|
||||
remoteItemsTitles() {
|
||||
if (!this.filter || !(this.filter.items.length > 0)) return []
|
||||
return this.filter.items.map(({ title }) => title)
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
async getFilter(url) {
|
||||
if (!this.isSlugNumber) return
|
||||
|
||||
this.$nextTick(() => this.$nuxt.$loading.start())
|
||||
|
||||
try {
|
||||
const response = await this.$axios.get(`/filters/${url}`)
|
||||
|
||||
this.filter = { ...response.data }
|
||||
|
||||
this.items = response.data.items.map(({ title }) => title)
|
||||
|
||||
this.$nuxt.$loading.finish()
|
||||
} catch (error) {
|
||||
this.$nuxt.$loading.finish()
|
||||
this.$notifier.showMessage({
|
||||
content: `Error getting the filter: ` + error.message,
|
||||
color: 'error',
|
||||
icon: 'icon-message',
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
async getFilters() {
|
||||
const response = await this.$axios.get('/filters')
|
||||
const filters = response.data
|
||||
await this.$store.commit('learning/SORT_FILTERS', filters)
|
||||
},
|
||||
|
||||
async addFilterItem() {
|
||||
const data = {
|
||||
filter_id: this.filter.id,
|
||||
title: this.newItem,
|
||||
}
|
||||
this.$nextTick(() => this.$nuxt.$loading.start())
|
||||
|
||||
try {
|
||||
await this.$axios.post(`/filter-items`, data)
|
||||
await this.getFilters()
|
||||
this.getFilter(this.slug)
|
||||
this.newItem = ''
|
||||
this.$nuxt.$loading.finish()
|
||||
} catch (error) {
|
||||
this.$nuxt.$loading.finish()
|
||||
this.$notifier.showMessage({
|
||||
content: `Error creating the filter item: ` + error.message,
|
||||
color: 'error',
|
||||
icon: 'icon-message',
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
setDynamicTitle(title, index) {
|
||||
Vue.set(this.items, index, title)
|
||||
},
|
||||
|
||||
hasItemChanges(index) {
|
||||
return this.items[index] !== this.remoteItemsTitles[index]
|
||||
},
|
||||
|
||||
close() {
|
||||
this.dialog = false
|
||||
},
|
||||
|
||||
save() {
|
||||
this.close()
|
||||
},
|
||||
|
||||
async update(id, index) {
|
||||
if (
|
||||
id === null ||
|
||||
id === undefined ||
|
||||
index === null ||
|
||||
index === undefined ||
|
||||
!this.items[index]
|
||||
)
|
||||
return
|
||||
|
||||
const data = {
|
||||
id: id,
|
||||
title: this.items[index],
|
||||
}
|
||||
this.$nextTick(() => this.$nuxt.$loading.start())
|
||||
|
||||
try {
|
||||
await this.$axios.post(`/filter-items`, data)
|
||||
|
||||
await this.getFilters()
|
||||
this.getFilter(this.slug)
|
||||
this.$nuxt.$loading.finish()
|
||||
this.$notifier.showMessage({
|
||||
content: `Filter Item updated successfully`,
|
||||
color: 'success',
|
||||
icon: 'icon-message',
|
||||
})
|
||||
} catch (error) {
|
||||
this.$nuxt.$loading.finish()
|
||||
this.$notifier.showMessage({
|
||||
content: `Error updating the filter item: ` + error.message,
|
||||
color: 'error',
|
||||
icon: 'icon-message',
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
askForDelete(item) {
|
||||
this.itemSelected = item
|
||||
this.dialog = true
|
||||
},
|
||||
|
||||
async deleteItem(itemId) {
|
||||
if (!itemId) {
|
||||
this.$notifier.showMessage({
|
||||
content: `No item to delete selected`,
|
||||
color: 'error',
|
||||
icon: 'icon-message',
|
||||
})
|
||||
}
|
||||
|
||||
this.$nextTick(() => this.$nuxt.$loading.start())
|
||||
|
||||
try {
|
||||
await this.$axios.delete(`/filter-items/${itemId}`)
|
||||
|
||||
this.dialog = false
|
||||
this.itemSelected = {}
|
||||
|
||||
await this.getFilter(this.slug)
|
||||
await this.getFilters()
|
||||
|
||||
this.$nuxt.$loading.finish()
|
||||
|
||||
this.$notifier.showMessage({
|
||||
content: `Filter Item deleted`,
|
||||
color: 'success',
|
||||
icon: 'mdi-delete',
|
||||
})
|
||||
} catch (error) {
|
||||
this.dialog = false
|
||||
this.$nuxt.$loading.finish()
|
||||
|
||||
this.$notifier.showMessage({
|
||||
content: `Error trying to delete the selected Filter Item`,
|
||||
color: 'error',
|
||||
icon: 'mdi-delete',
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-dialog >>> .v-cardcard__title {
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
136
pages/manager/learning/filters/index.vue
Normal file
136
pages/manager/learning/filters/index.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div>
|
||||
<page-header class="d-flex justify-space-between">
|
||||
<span>
|
||||
{{ $t('learning.filters.title') }}
|
||||
</span>
|
||||
|
||||
<!-- <v-btn
|
||||
depressed
|
||||
:to="localePath('/manager/learning/filters/new')"
|
||||
color="accent"
|
||||
rounded
|
||||
>
|
||||
<v-icon left>mdi-plus</v-icon>
|
||||
<small>{{ $t('general.add') }}</small>
|
||||
</v-btn> -->
|
||||
</page-header>
|
||||
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="filters"
|
||||
:items-per-page="100"
|
||||
hide-default-footer
|
||||
class="pa-4 secondary"
|
||||
>
|
||||
<!-- @click:row="handleClick" -->
|
||||
<!-- Translates dynamically headers -->
|
||||
<template v-for="h in headers" v-slot:[`header.${h.value}`]="{ header }">
|
||||
{{ $t(h.text) }}
|
||||
</template>
|
||||
|
||||
<template v-slot:item.title="{ item }">
|
||||
<strong class="txt--text">{{
|
||||
$t(`learning.filters.${item.title}`) | capitalize
|
||||
}}</strong>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-btn
|
||||
class="mx-4 white--text button-hover"
|
||||
style="height: 100%"
|
||||
:color="$vuetify.theme.dark ? 'info' : 'txt'"
|
||||
rounded
|
||||
depressed
|
||||
nuxt
|
||||
small
|
||||
@click="handleClick(item)"
|
||||
>{{ $t('general.edit') }}</v-btn
|
||||
>
|
||||
|
||||
<v-menu offset-y>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-hover v-slot:default="{ hover }">
|
||||
<v-btn
|
||||
class="dots"
|
||||
v-on="on"
|
||||
depressed
|
||||
:outlined="hover"
|
||||
small
|
||||
fab
|
||||
:color="hover ? 'info' : ''"
|
||||
>
|
||||
<v-icon>mdi-dots-horizontal</v-icon>
|
||||
</v-btn>
|
||||
</v-hover>
|
||||
</template>
|
||||
<v-list width="200">
|
||||
<v-list-item @click="handleClick(item)">
|
||||
<v-list-item-icon class="mr-1">
|
||||
<v-icon small>mdi-pencil</v-icon>
|
||||
</v-list-item-icon>
|
||||
|
||||
<v-list-item-content>
|
||||
<v-list-item-subtitle>
|
||||
{{ $t('general.edit') | capitalize }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageHeader from '~/components/UI/PageHeader/PageHeader'
|
||||
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
|
||||
|
||||
export default {
|
||||
layout: `${process.env.CUSTOMER}Admin`,
|
||||
middleware: 'adminsOrOperators',
|
||||
components: {
|
||||
PageHeader,
|
||||
accordionCard,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
headers: [
|
||||
{ text: 'naam', value: 'title' },
|
||||
{ text: 'aantal', value: 'items_count' },
|
||||
// { text: 'sinds', value: 'created_at' },
|
||||
// { text: 'update', value: 'last_updated_at' },
|
||||
{ text: '', value: 'actions', sortable: false },
|
||||
],
|
||||
filters: [],
|
||||
}
|
||||
},
|
||||
async asyncData({ $axios }) {
|
||||
const { data } = await $axios.get('/filters-with-count')
|
||||
|
||||
const filtersFiltered = data.filter((f) => f.title !== 'quality_standards')
|
||||
|
||||
return { filters: filtersFiltered }
|
||||
},
|
||||
methods: {
|
||||
handleClick(item) {
|
||||
this.$router.push(
|
||||
this.localePath(`/manager/learning/filters/${item.slug}`)
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
table tr .button-hover.v-btn {
|
||||
opacity: 0;
|
||||
}
|
||||
table tr:hover .button-hover.v-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
.dots {
|
||||
background-color: unset !important;
|
||||
}
|
||||
</style>
|
||||
328
pages/manager/learning/index.vue
Normal file
328
pages/manager/learning/index.vue
Normal file
@@ -0,0 +1,328 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="pa-lg-6">
|
||||
<page-header class="d-flex justify-space-between">
|
||||
<div class="subtitle-1">
|
||||
<span
|
||||
:class="{ 'display-1': selector === 'published' }"
|
||||
:style="{ cursor: 'pointer' }"
|
||||
@click="selector = 'published'"
|
||||
class="mr-lg-4"
|
||||
>
|
||||
{{ $t('learning.products') }}
|
||||
<small class="font-weight-light">({{ published.length }})</small>
|
||||
</span>
|
||||
|
||||
<span
|
||||
:class="{ 'display-1': selector === 'drafts' }"
|
||||
:style="{ cursor: 'pointer' }"
|
||||
@click="selector = 'drafts'"
|
||||
class="mx-lg-4"
|
||||
v-if="$store.getters.isAdmin || $store.getters.isOperator"
|
||||
>
|
||||
{{ $t('learning.drafts') }}
|
||||
<small class="font-weight-light">({{ drafts.length }})</small>
|
||||
</span>
|
||||
<span
|
||||
:class="{ 'display-1': selector === 'deleted' }"
|
||||
:style="{ cursor: 'pointer' }"
|
||||
@click="selector = 'deleted'"
|
||||
class="mx-lg-4"
|
||||
v-if="$store.getters.isAdmin || $store.getters.isOperator"
|
||||
>
|
||||
{{ $t('general.deleted') }}
|
||||
<small class="font-weight-light">({{ deleted.length }})</small>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<settings />
|
||||
|
||||
<v-btn
|
||||
color="accent"
|
||||
depressed
|
||||
rounded
|
||||
to="/manager/learning/new"
|
||||
v-if="$store.getters.isAdmin || $store.getters.isOperator"
|
||||
>
|
||||
<v-icon left x-small>icon-add</v-icon>
|
||||
<small>{{ $t('general.add') }}</small>
|
||||
</v-btn>
|
||||
</div>
|
||||
</page-header>
|
||||
|
||||
<div class="d-flex justify-space-between">
|
||||
<filters />
|
||||
<filters-selected />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<courses-table
|
||||
:deleted="deleted"
|
||||
:drafts="drafts"
|
||||
:published="published"
|
||||
:selector="selector"
|
||||
@reload-learning-products="getProducts()"
|
||||
/>
|
||||
|
||||
<v-footer
|
||||
fixed
|
||||
style="z-index: 4"
|
||||
height="90"
|
||||
color="primary"
|
||||
v-if="$store.getters.isAdmin || $store.getters.isOperator"
|
||||
>
|
||||
<v-btn text nuxt :to="localePath('/manager/learning')">
|
||||
<v-icon>icon-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<div class="mx-10">
|
||||
<v-btn
|
||||
color="accent"
|
||||
depressed
|
||||
rounded
|
||||
to="/manager/learning/new"
|
||||
class="ml-10"
|
||||
>
|
||||
<v-icon left size="8">icon-add</v-icon>
|
||||
<small>{{ $t('general.add') }}</small>
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<v-spacer />
|
||||
|
||||
<!-- <v-btn
|
||||
class="ma-2"
|
||||
tile
|
||||
text
|
||||
small
|
||||
color="success"
|
||||
nuxt
|
||||
target="_blank"
|
||||
:to="`${this.localePath(
|
||||
'/manager/learning'
|
||||
)}?filters=${sharedUrlSuffix}`"
|
||||
id="url_to_share"
|
||||
v-show="showCopiedButton"
|
||||
>
|
||||
<v-icon class="mx-2" small>icon-selectionbox-checked</v-icon>
|
||||
URL gekopieerd
|
||||
</v-btn>
|
||||
|
||||
<v-tooltip top v-if="!showCopiedButton">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn
|
||||
class="ma-2"
|
||||
tile
|
||||
text
|
||||
small
|
||||
@click="copyUrl"
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
>
|
||||
<v-icon class="mx-2" small>icon-share</v-icon>
|
||||
Delen Lijst
|
||||
</v-btn>
|
||||
</template>
|
||||
<span> {{ $t('general.tooltip_share') }}</span>
|
||||
</v-tooltip> -->
|
||||
|
||||
<v-btn class="ma-2" tile text small @click="exportCsv">
|
||||
<v-icon class="mx-2" x-small>icon-export</v-icon>
|
||||
{{ $t('general.export_csv') | capitalize }}
|
||||
</v-btn>
|
||||
<settings class="ml-10" />
|
||||
</v-footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Util from '@/util'
|
||||
import PageHeader from '~/components/UI/PageHeader/PageHeader'
|
||||
import Settings from '~/components/Learning/Settings'
|
||||
import Filters from '~/components/Learning/Filters'
|
||||
import FiltersSelected from '~/components/Learning/FiltersSelected'
|
||||
import CoursesTable from '~/components/Learning/CoursesTable'
|
||||
|
||||
export default {
|
||||
layout: `${process.env.CUSTOMER}Admin`,
|
||||
middleware: ['denyToOnlyMembers'],
|
||||
components: {
|
||||
PageHeader,
|
||||
Settings,
|
||||
Filters,
|
||||
CoursesTable,
|
||||
FiltersSelected,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selector: 'published',
|
||||
showCopiedButton: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
learningProducts() {
|
||||
return this.$store.getters.learningProducts
|
||||
},
|
||||
hasProducts() {
|
||||
return this.$store.getters.hasLearningProducts
|
||||
},
|
||||
|
||||
published() {
|
||||
return this.learningProductsFiltered.filter(
|
||||
(product) => product.published && !product.deleted_at
|
||||
)
|
||||
},
|
||||
drafts() {
|
||||
return this.learningProductsFiltered.filter(
|
||||
(product) => !product.deleted_at && !product.published
|
||||
)
|
||||
},
|
||||
deleted() {
|
||||
return this.learningProductsFiltered.filter(
|
||||
(product) => product.deleted_at
|
||||
)
|
||||
},
|
||||
filtersSelected() {
|
||||
return this.$store.getters.filtersSelected
|
||||
},
|
||||
learningProductsFiltered() {
|
||||
if (!this.filtersSelected.length > 0) return this.learningProducts
|
||||
|
||||
return this.learningProducts.filter((product) => {
|
||||
return Util.findItemsWithExactValuesInArray(
|
||||
this.filtersSelected,
|
||||
product.filterItemsSelected
|
||||
)
|
||||
})
|
||||
},
|
||||
|
||||
// sharedUrlSuffix() {
|
||||
// let filtersSelectedIdsStringyfied = ''
|
||||
// this.filtersSelected.map(
|
||||
// (id) => (filtersSelectedIdsStringyfied += `${id},`)
|
||||
// )
|
||||
// return filtersSelectedIdsStringyfied
|
||||
// },
|
||||
// sharedUrlParsed() {
|
||||
// // if is a shared Url
|
||||
// if (!this.$route.query.filters) return null
|
||||
|
||||
// // parse content, convert every element to number
|
||||
// let filterItemIds = this.$route.query.filters.split(',').map(Number)
|
||||
|
||||
// // validate it and return
|
||||
// return filterItemIds.filter(
|
||||
// (el) => !isNaN(el) && this.$store.getters.filtersItemsMap.has(el)
|
||||
// )
|
||||
// },
|
||||
},
|
||||
|
||||
async asyncData({ $axios, store }) {
|
||||
try {
|
||||
if (!store.getters.hasLearningProducts) {
|
||||
await store.dispatch('learning/pullProducts')
|
||||
}
|
||||
|
||||
const response = await $axios.get('/filters')
|
||||
const filters = response.data
|
||||
await store.commit('learning/SORT_FILTERS', filters)
|
||||
} catch (error) {
|
||||
console.log('Data -> error', error)
|
||||
}
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
if (this.sharedUrlParsed) {
|
||||
this.$store.commit('learning/SELECT_FILTERS', this.sharedUrlParsed)
|
||||
}
|
||||
|
||||
// If filters sorting preferences saved in localstorage
|
||||
if (localStorage.getItem('learning_products_filters')) {
|
||||
// Get them
|
||||
const filtersOrder = JSON.parse(
|
||||
localStorage.getItem('learning_products_filters')
|
||||
)
|
||||
|
||||
// Get filters from store
|
||||
const filters = [...this.$store.state.learning.filters]
|
||||
|
||||
// Sort by preferences
|
||||
const filtersSorted = filters.sort(
|
||||
(a, b) => filtersOrder.indexOf(a.id) - filtersOrder.indexOf(b.id)
|
||||
)
|
||||
|
||||
// send to store, sorted
|
||||
await this.$store.commit('learning/SORT_FILTERS', filtersSorted)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async getProducts() {
|
||||
try {
|
||||
const response = await this.$axios.get('/learning-products')
|
||||
await this.$store.commit('learning/STORE_PRODUCTS', response.data)
|
||||
} catch (error) {
|
||||
console.log('getProducts -> error', error)
|
||||
}
|
||||
},
|
||||
async copyUrl() {
|
||||
if (this.showCopiedButton) return
|
||||
|
||||
// Create <a> link with v-show false and Copy from there to have the full path?
|
||||
const url = document.getElementById('url_to_share').href
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(url)
|
||||
|
||||
this.showCopiedButton = true
|
||||
setTimeout(() => (this.showCopiedButton = false), 5000)
|
||||
} catch (error) {
|
||||
console.log('copyUrl -> error', error)
|
||||
}
|
||||
},
|
||||
|
||||
exportCsv() {
|
||||
const data = [...this.learningProductsFiltered]
|
||||
|
||||
const headersNeeded = [
|
||||
'title',
|
||||
'code',
|
||||
'partner',
|
||||
'owner',
|
||||
'status',
|
||||
'lead_time',
|
||||
'product_type',
|
||||
'theme',
|
||||
'course',
|
||||
]
|
||||
|
||||
const dataFiltered = this.$store.getters[
|
||||
'utils/filterArrayObjsByArrayOfProperties'
|
||||
](data, headersNeeded)
|
||||
|
||||
let dataCsv =
|
||||
this.$store.getters['utils/arrayOfObjectToCsv'](dataFiltered)
|
||||
|
||||
// Translate every entry in 'headersNeeded'
|
||||
let headersTranslated =
|
||||
headersNeeded.map((h) => this.$t(`csv.learning.${h}`)).join(',') + '\n'
|
||||
|
||||
// Remove 1st row (string)
|
||||
dataCsv = dataCsv.split('\n').slice(1).join('\n')
|
||||
|
||||
// Add as 1st row the one translated
|
||||
dataCsv = headersTranslated + dataCsv
|
||||
|
||||
// Simulate click to download file
|
||||
const universalBOM = '\uFEFF'
|
||||
const blob = new Blob([universalBOM + dataCsv], { type: 'text/csv' })
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.setAttribute('hidden', '')
|
||||
link.setAttribute('download', 'producten.csv')
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
272
pages/manager/learning/quality-standards/_filter.vue
Normal file
272
pages/manager/learning/quality-standards/_filter.vue
Normal file
@@ -0,0 +1,272 @@
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<div class="d-flex justify-space-between">
|
||||
<h2 class="ml-6 txt--text">{{ title }}</h2>
|
||||
<current-date-time showIcon class="mr-6" />
|
||||
</div>
|
||||
|
||||
<accordion-card disableAccordion>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="12" md="3">
|
||||
<v-subheader class="ml-10 txt--text font-weight-black">
|
||||
Kwaliteitsstandaard
|
||||
</v-subheader>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="6">
|
||||
<v-text-field v-model="item.title" outlined> </v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="3"> </v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" sm="12" md="3">
|
||||
<v-subheader class="ml-10 txt--text font-weight-black">
|
||||
Externe link
|
||||
</v-subheader>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="6">
|
||||
<v-text-field
|
||||
v-model="item.link"
|
||||
outlined
|
||||
prepend-inner-icon="icon-link"
|
||||
>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="3"> Voeg link toe</v-col>
|
||||
</v-row>
|
||||
</accordion-card>
|
||||
</v-col>
|
||||
|
||||
<v-footer fixed style="z-index: 4" height="90" color="primary">
|
||||
<v-btn text nuxt :to="localePath('/manager/learning/quality-standards')">
|
||||
<v-icon>icon-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
v-cloak
|
||||
class="ma-2 white--text"
|
||||
color="txt"
|
||||
depressed
|
||||
rounded
|
||||
@click="save(true)"
|
||||
v-if="canSave && !isNew"
|
||||
>
|
||||
Opslaan</v-btn
|
||||
>
|
||||
|
||||
<v-btn
|
||||
v-cloak
|
||||
class="ma-2 white--text"
|
||||
color="accent"
|
||||
depressed
|
||||
rounded
|
||||
@click="save(false)"
|
||||
v-if="canSave"
|
||||
>
|
||||
Opslaan en sluiten</v-btn
|
||||
>
|
||||
|
||||
<v-spacer />
|
||||
|
||||
<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" 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: item.title,
|
||||
})
|
||||
}}
|
||||
</v-card-title>
|
||||
<v-card-actions>
|
||||
<div class="ma-4">
|
||||
<v-btn
|
||||
color="accent"
|
||||
class="mx-2"
|
||||
@click="deleteItem(item.id)"
|
||||
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 accordionCard from '@/components/UI/AccordionCard/AccordionCard'
|
||||
import currentDateTime from '@/components/CurrentDateTime/CurrentDateTime'
|
||||
import PageHeader from '~/components/UI/PageHeader/PageHeader'
|
||||
|
||||
export default {
|
||||
layout: `${process.env.CUSTOMER}Admin`,
|
||||
middleware: 'adminsOrOperators',
|
||||
components: {
|
||||
accordionCard,
|
||||
PageHeader,
|
||||
currentDateTime,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
remoteItem: {},
|
||||
item: { title: null },
|
||||
dialog: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.isSlugNumber) this.getFilter(this.slug)
|
||||
// if (!this.isNew) this.$router.push('/manager')
|
||||
},
|
||||
|
||||
computed: {
|
||||
slug() {
|
||||
return this.$route.params.filter
|
||||
},
|
||||
isSlugNumber() {
|
||||
return !isNaN(this.slug.split(['-'], 1))
|
||||
},
|
||||
isNew() {
|
||||
return this.slug.toLowerCase() === 'new'
|
||||
},
|
||||
|
||||
title() {
|
||||
if (!this.item || !this.item.title) return 'Nieuw'
|
||||
return this.item.title || ''
|
||||
},
|
||||
canSave() {
|
||||
if (this.isNew && this.itemFilled) return true
|
||||
if (this.itemFilled && this.itemHasChanges) return true
|
||||
return false
|
||||
},
|
||||
|
||||
itemFilled() {
|
||||
if (!this.item || !this.item.title) return false
|
||||
return true
|
||||
},
|
||||
|
||||
itemHasChanges() {
|
||||
return JSON.stringify(this.remoteItem) !== JSON.stringify(this.item)
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
async getFilter(url) {
|
||||
if (!this.isSlugNumber) return
|
||||
|
||||
this.$nextTick(() => this.$nuxt.$loading.start())
|
||||
|
||||
try {
|
||||
const { data } = await this.$axios.get(`/filter-items/${url}`)
|
||||
|
||||
this.remoteItem = { ...data }
|
||||
this.item = { ...data }
|
||||
|
||||
this.$nuxt.$loading.finish()
|
||||
} catch (error) {
|
||||
this.$nuxt.$loading.finish()
|
||||
this.$notifier.showMessage({
|
||||
content: `Error getting the filter: ` + error.message,
|
||||
color: 'error',
|
||||
icon: 'icon-message',
|
||||
})
|
||||
this.$router.push('/manager/learning/quality-standards/')
|
||||
}
|
||||
},
|
||||
|
||||
close() {
|
||||
this.dialog = false
|
||||
},
|
||||
|
||||
async save(stay = false) {
|
||||
if (this.dialog) this.close()
|
||||
|
||||
this.$nextTick(() => this.$nuxt.$loading.start())
|
||||
|
||||
const QUALITY_STANDARD_ID = 14
|
||||
|
||||
const data = {
|
||||
...this.item,
|
||||
filter_id: this.item.filter_id || QUALITY_STANDARD_ID,
|
||||
}
|
||||
|
||||
try {
|
||||
await this.$axios.post(`/filter-items`, data)
|
||||
|
||||
this.remoteItem = { ...data }
|
||||
|
||||
this.$nuxt.$loading.finish()
|
||||
|
||||
if (!stay) this.$router.push('/manager/learning/quality-standards/')
|
||||
} catch (error) {
|
||||
this.$nuxt.$loading.finish()
|
||||
this.$notifier.showMessage({
|
||||
content: `Error creating the filter item: ` + error.message,
|
||||
color: 'error',
|
||||
icon: 'icon-message',
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
async deleteItem(itemId) {
|
||||
if (!itemId) {
|
||||
this.$notifier.showMessage({
|
||||
content: `No item to delete selected`,
|
||||
color: 'error',
|
||||
icon: 'icon-message',
|
||||
})
|
||||
}
|
||||
|
||||
this.$nextTick(() => this.$nuxt.$loading.start())
|
||||
|
||||
try {
|
||||
await this.$axios.delete(`/filter-items/${itemId}`)
|
||||
|
||||
this.dialog = false
|
||||
|
||||
this.$nuxt.$loading.finish()
|
||||
|
||||
this.$notifier.showMessage({
|
||||
content: `Filter Item deleted`,
|
||||
color: 'success',
|
||||
icon: 'mdi-delete',
|
||||
})
|
||||
|
||||
this.$router.push('/manager/learning/quality-standards/')
|
||||
} catch (error) {
|
||||
this.dialog = false
|
||||
this.$nuxt.$loading.finish()
|
||||
|
||||
this.$notifier.showMessage({
|
||||
content: `Error trying to delete the selected Filter Item`,
|
||||
color: 'error',
|
||||
icon: 'mdi-delete',
|
||||
})
|
||||
|
||||
this.$router.push('/manager/learning/quality-standards/')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-dialog >>> .v-cardcard__title {
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
202
pages/manager/learning/quality-standards/index.vue
Normal file
202
pages/manager/learning/quality-standards/index.vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<div>
|
||||
<page-header class="d-flex justify-space-between">
|
||||
<span>
|
||||
{{ $t('learning.quality_standards') }}
|
||||
</span>
|
||||
|
||||
<v-btn
|
||||
depressed
|
||||
:to="localePath('/manager/learning/quality-standards/new')"
|
||||
color="accent"
|
||||
rounded
|
||||
>
|
||||
<v-icon left>mdi-plus</v-icon>
|
||||
<small>{{ $t('general.add') }}</small>
|
||||
</v-btn>
|
||||
</page-header>
|
||||
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="filter.items"
|
||||
:items-per-page="10"
|
||||
class="pa-4 secondary"
|
||||
>
|
||||
<!-- Translates dynamically headers -->
|
||||
<template v-for="h in headers" v-slot:[`header.${h.value}`]="{ header }">
|
||||
{{ $t(h.text) }}
|
||||
</template>
|
||||
|
||||
<template v-slot:item.name="{ item }">
|
||||
<strong class="txt--text" v-text="item.name"></strong>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-menu offset-y>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-hover v-slot:default="{ hover }">
|
||||
<v-btn
|
||||
v-on="on"
|
||||
depressed
|
||||
:outlined="hover"
|
||||
small
|
||||
fab
|
||||
:color="hover ? 'info' : ''"
|
||||
>
|
||||
<v-icon>mdi-dots-horizontal</v-icon>
|
||||
</v-btn>
|
||||
</v-hover>
|
||||
</template>
|
||||
<v-list width="200">
|
||||
<v-list-item
|
||||
nuxt
|
||||
:to="localePath(`/manager/learning/quality-standards/${item.id}`)"
|
||||
>
|
||||
<v-list-item-icon class="mr-1">
|
||||
<v-icon small>mdi-pencil</v-icon>
|
||||
</v-list-item-icon>
|
||||
|
||||
<v-list-item-content>
|
||||
<v-list-item-subtitle>
|
||||
{{ $t('general.edit') | capitalize }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item>
|
||||
<v-list-item-icon class="mr-1">
|
||||
<v-icon small>icon-link</v-icon>
|
||||
</v-list-item-icon>
|
||||
|
||||
<v-list-item-content>
|
||||
<v-list-item-subtitle>
|
||||
Link naar informatie
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-dialog max-width="740" persistent v-model="dialog">
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-list-item v-on="on">
|
||||
<v-list-item-icon class="mr-1">
|
||||
<v-icon small>icon-remove</v-icon>
|
||||
</v-list-item-icon>
|
||||
|
||||
<v-list-item-content>
|
||||
<v-list-item-subtitle>{{
|
||||
$t('general.delete') | capitalize
|
||||
}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<v-card class="primary pa-10" flat>
|
||||
<v-card-title class="headline">
|
||||
{{
|
||||
$t('learning.product_overview.delete_confirmation', {
|
||||
productName: item.title,
|
||||
})
|
||||
}}
|
||||
</v-card-title>
|
||||
<v-card-actions>
|
||||
<div class="ma-4">
|
||||
<v-btn
|
||||
@click="deleteItem(item.id)"
|
||||
class="mx-2"
|
||||
color="accent"
|
||||
depressed
|
||||
rounded
|
||||
>{{ $t('general.delete') }}</v-btn
|
||||
>
|
||||
<v-btn
|
||||
@click="close"
|
||||
class="mx-2"
|
||||
color="info"
|
||||
depressed
|
||||
rounded
|
||||
>{{ $t('general.cancel') }}</v-btn
|
||||
>
|
||||
</div>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageHeader from '~/components/UI/PageHeader/PageHeader'
|
||||
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
|
||||
|
||||
export default {
|
||||
layout: `${process.env.CUSTOMER}Admin`,
|
||||
components: {
|
||||
PageHeader,
|
||||
accordionCard,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
headers: [
|
||||
{ text: 'naam', sortable: false, value: 'title' },
|
||||
{ text: '', value: 'actions' },
|
||||
],
|
||||
filter: {},
|
||||
dialog: false,
|
||||
}
|
||||
},
|
||||
async asyncData({ $axios }) {
|
||||
try {
|
||||
const { data } = await $axios.get(`/filters`)
|
||||
|
||||
const qualityStandards = data.find((f) => f.title === 'quality_standards')
|
||||
|
||||
return { filter: { ...qualityStandards } }
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.dialog = false
|
||||
},
|
||||
async deleteItem(itemId) {
|
||||
if (!itemId) {
|
||||
this.$notifier.showMessage({
|
||||
content: `No item to delete selected`,
|
||||
color: 'error',
|
||||
icon: 'icon-message',
|
||||
})
|
||||
}
|
||||
|
||||
this.$nextTick(() => this.$nuxt.$loading.start())
|
||||
|
||||
try {
|
||||
await this.$axios.delete(`/filter-items/${itemId}`)
|
||||
|
||||
this.dialog = false
|
||||
|
||||
this.filter.items = this.filter.items.filter((i) => i.id !== itemId)
|
||||
|
||||
this.$nuxt.$loading.finish()
|
||||
|
||||
this.$notifier.showMessage({
|
||||
content: `Filter Item deleted`,
|
||||
color: 'success',
|
||||
icon: 'mdi-delete',
|
||||
})
|
||||
} catch (error) {
|
||||
this.dialog = false
|
||||
this.$nuxt.$loading.finish()
|
||||
|
||||
this.$notifier.showMessage({
|
||||
content: `Error trying to delete the selected Filter Item`,
|
||||
color: 'error',
|
||||
icon: 'mdi-delete',
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
179
pages/manager/learning/synonyms/index.vue
Normal file
179
pages/manager/learning/synonyms/index.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<div>
|
||||
<page-header class="d-flex justify-space-between">
|
||||
<span>
|
||||
{{ $t('learning.synonyms') }}
|
||||
</span>
|
||||
</page-header>
|
||||
|
||||
<accordion-card disableAccordion>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="12" md="3"></v-col>
|
||||
<v-col cols="12" sm="12" md="6">
|
||||
<v-chip
|
||||
v-for="(synonym, i) in synonyms"
|
||||
:key="`synonym-key${i}`"
|
||||
@click:close="askForDelete(synonym)"
|
||||
class="ma-2"
|
||||
color="secondary"
|
||||
close
|
||||
>
|
||||
{{ synonym.title }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="3"> </v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" sm="12" md="3">
|
||||
<v-subheader class="txt--text font-weight-black ml-10">
|
||||
Nieuw
|
||||
</v-subheader>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="6">
|
||||
<v-text-field v-model="newSynonym" outlined v-on:keyup.enter="add">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="3"> </v-col>
|
||||
</v-row>
|
||||
</accordion-card>
|
||||
|
||||
<v-dialog v-model="dialog" persistent max-width="740" v-if="itemSelected">
|
||||
<v-card class="primary pa-10" flat>
|
||||
<v-card-title class="headline">
|
||||
{{
|
||||
$t('learning.filters.delete_item_confirmation', {
|
||||
itemName: itemSelected.title || null,
|
||||
})
|
||||
}}
|
||||
</v-card-title>
|
||||
<v-card-actions>
|
||||
<div class="ma-4">
|
||||
<v-btn
|
||||
color="accent"
|
||||
class="mx-2"
|
||||
@click="deleteItem(itemSelected)"
|
||||
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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageHeader from '~/components/UI/PageHeader/PageHeader'
|
||||
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
|
||||
|
||||
export default {
|
||||
layout: `${process.env.CUSTOMER}Admin`,
|
||||
middleware: 'adminsOrOperators',
|
||||
components: {
|
||||
PageHeader,
|
||||
accordionCard,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
synonyms: [],
|
||||
newSynonym: '',
|
||||
dialog: false,
|
||||
itemSelected: null,
|
||||
}
|
||||
},
|
||||
async asyncData({ $axios }) {
|
||||
const response = await $axios.get('/synonyms')
|
||||
return { synonyms: response.data }
|
||||
},
|
||||
methods: {
|
||||
async add() {
|
||||
if (!this.newSynonym) return
|
||||
if (this.synonyms.includes(this.newSynonym)) return
|
||||
|
||||
this.$nextTick(() => this.$nuxt.$loading.start())
|
||||
|
||||
try {
|
||||
const response = await this.$axios.post(`/synonyms`, {
|
||||
title: this.newSynonym,
|
||||
})
|
||||
this.synonyms.push(response.data)
|
||||
this.newSynonym = ''
|
||||
this.$nuxt.$loading.finish()
|
||||
this.$notifier.showMessage({
|
||||
content: `Synonym '${this.newSynonym}' added`,
|
||||
color: 'success',
|
||||
icon: 'icon-message',
|
||||
})
|
||||
} catch (error) {
|
||||
console.log('add -> error', error)
|
||||
this.$nuxt.$loading.finish()
|
||||
this.$notifier.showMessage({
|
||||
content: `Error creating the filter item: ` + error.message,
|
||||
color: 'error',
|
||||
icon: 'icon-message',
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
close() {
|
||||
this.dialog = false
|
||||
},
|
||||
|
||||
askForDelete(item) {
|
||||
this.itemSelected = item
|
||||
this.dialog = true
|
||||
},
|
||||
|
||||
async deleteItem(synonym) {
|
||||
if (!synonym) {
|
||||
this.$notifier.showMessage({
|
||||
content: `No synonym to delete passed`,
|
||||
color: 'error',
|
||||
icon: 'icon-message',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const indexSynonym = this.synonyms.findIndex(
|
||||
(s) => s.title === synonym.title
|
||||
)
|
||||
|
||||
if (indexSynonym === -1) return
|
||||
|
||||
this.$nextTick(() => this.$nuxt.$loading.start())
|
||||
|
||||
try {
|
||||
await this.$axios.delete(`/synonyms/${synonym.id}`)
|
||||
|
||||
this.dialog = false
|
||||
this.itemSelected = null
|
||||
|
||||
this.synonyms.splice(indexSynonym, 1)
|
||||
|
||||
this.$nuxt.$loading.finish()
|
||||
|
||||
this.$notifier.showMessage({
|
||||
content: `Synonym deleted`,
|
||||
color: 'success',
|
||||
icon: 'mdi-delete',
|
||||
})
|
||||
} catch (error) {
|
||||
this.dialog = false
|
||||
console.log('remove -> error', error)
|
||||
this.$nuxt.$loading.finish()
|
||||
|
||||
this.$notifier.showMessage({
|
||||
content: `Error trying to delete the selected Synonym`,
|
||||
color: 'error',
|
||||
icon: 'mdi-delete',
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user