Initial Nuxt frontend import
Some checks failed
continuous-integration/drone/push Build is failing

- 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:
Joris Slagter
2025-12-02 17:48:48 +01:00
parent 0f691e83e3
commit 791aebc346
290 changed files with 113801 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
<template>
<div class="text-center">
<v-btn text x-small :disabled="isFirstPage" @click="changePage('previous')">
<v-icon x-small>icon-arrow-left</v-icon>
</v-btn>
<small class="mx-2">
{{ `${page} / ${totalPages}` }}
</small>
<v-btn text x-small :disabled="isLastPage" @click="changePage('next')">
<v-icon x-small>icon-arrow-right</v-icon>
</v-btn>
<!-- Debugging -->
<!-- <ul>
<li v-for="(column, i) in subset" :key="`column-dyn${i}`">
{{ column }}
</li>
</ul> -->
</div>
</template>
<script>
// Inspiration: https://github.com/byteboomers/vue-lpage
export default {
computed: {
page() {
return this.$store.state.columnBrowser.page
},
isFirstPage() {
return this.$store.getters['columnBrowser/isFirstPage']
},
isLastPage() {
return this.$store.getters['columnBrowser/isLastPage']
},
totalPages() {
return this.$store.getters['columnBrowser/totalPages']
},
subset() {
return this.$store.getters['columnBrowser/subset']
},
},
methods: {
changePage(direction) {
this.$store.dispatch('columnBrowser/changePage', direction)
},
},
}
</script>

View File

@@ -0,0 +1,505 @@
<template>
<div>
<a target="_blank" href="/" id="root_url" v-show="false"></a>
<v-data-table
:headers="headers"
:options="coursesTableOptions"
:items="products"
:footer-props="{ itemsPerPageOptions: [10, 25, 50, 100, -1] }"
@update:options="handleChangesOnOptions"
class="pa-4 secondary"
>
<!-- Translates dynamically headers -->
<template v-for="h in headers" v-slot:[`header.${h.value}`]="{ header }">
{{ $t(h.text) }}
<div
v-if="h.value === 'actions'"
:key="`column-browser-${h.text}`"
class="text-center"
>
<browser-columns />
</div>
</template>
<template v-slot:item.cover="{ item }">
<v-img
:alt="item.title"
lazy-src="/images/product-placeholder.jpg"
:src="computeImage(item)"
contain
height="60px"
width="110px"
/>
</template>
<template class="d-flex flex-column" v-slot:item.title="{ item }">
<div>
<span>
<span
v-if="!item.published && !item.deleted_at"
class="info--text font-weight-thin font-italic"
>{{ $t('general.draft') | capitalize }}</span
>
<span v-if="item.deleted_at" class="error--text font-italic">{{
$t('general.deleted') | capitalize
}}</span>
</span>
<span>
<filter-items-resolver
filterTitle="category"
:filters="item.filters"
/>
<filter-items-resolver gratisMode :filters="item.filters" />
</span>
</div>
<strong>{{ item.title | truncate(29) }}</strong>
</template>
<template v-slot:item.status="{ item }">
<filter-items-resolver filterTitle="status" :filters="item.filters" />
</template>
<template v-slot:item.product_type="{ item }">
<filter-items-resolver
filterTitle="product_type"
:filters="item.filters"
/>
</template>
<template v-slot:item.course="{ item }">
<filter-items-resolver filterTitle="course" :filters="item.filters" />
</template>
<template v-slot:item.actions="{ item }">
<v-btn
class="mx-4 white--text"
style="height: 100%"
:color="$vuetify.theme.dark ? 'info' : 'txt'"
rounded
depressed
nuxt
small
:to="localePath(`/manager/learning/${item.slug}`)"
>{{ $t('general.view') }}</v-btn
>
<v-menu
offset-y
close-on-content-click
v-if="$store.getters.isAdmin || $store.getters.isOperator"
>
<template v-slot:activator="{ on, attrs }">
<v-hover v-slot:default="{ hover }">
<v-btn
:color="hover ? 'info' : ''"
:outlined="hover"
depressed
fab
small
v-bind="attrs"
v-on="on"
class="menu-btn"
>
<v-icon>icon-options</v-icon>
</v-btn>
</v-hover>
</template>
<v-list width="200">
<v-list-item
:to="localePath(`/manager/learning/${item.slug}?edit`)"
nuxt
>
<v-list-item-icon class="mr-1">
<v-icon small>icon-edit</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 @click="clone(item.id)" v-if="publishedSelected">
<v-list-item-icon class="mr-1">
<v-icon small>icon-duplicate</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-subtitle>{{
$t('general.duplicate') | 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-download</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-subtitle>{{
$t('general.download') | capitalize
}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item
@click="shareUrl(localePath(`/manager/learning/${item.slug}`))"
>
<v-list-item-icon class="mr-1">
<v-icon small>icon-share</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-subtitle>{{
$t('general.share') | capitalize
}}</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="deleteProduct(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 Vue2Filters from 'vue2-filters'
import FilterItemsResolver from '~/components/Learning/FilterItemsResolver'
import BrowserColumns from '~/components/Learning/BrowserColumns'
export default {
mixins: [Vue2Filters.mixin],
props: {
published: {
type: Array,
required: true,
},
drafts: {
type: Array,
required: true,
},
deleted: {
type: Array,
required: true,
},
selector: {
type: String,
required: true,
},
},
components: {
FilterItemsResolver,
BrowserColumns,
},
data() {
return {
dialog: false,
page: 1,
}
},
watch: {
dialog(val) {
val || this.close()
},
},
computed: {
noImage() {
return require(`@/assets/img/no_image.png`)
},
headers() {
return this.$store.getters.columnsSortedSubset
},
columns() {
return this.$store.state.learning.columns
},
products() {
if (!['published', 'drafts', 'deleted'].includes(this.selector)) return []
return this[this.selector]
},
publishedSelected() {
return this.selector === 'published'
},
draftsSelected() {
return this.selector === 'drafts'
},
deletedSelected() {
return this.selector === 'deleted'
},
coursesTableOptions() {
return this.$store.state.learning.coursesTableOptions
},
},
async mounted() {
// If cols preferences saved in localstorage
if (localStorage.getItem('learning_products_cols')) {
// Get them
const colsLocalPreferences = JSON.parse(
localStorage.getItem('learning_products_cols')
)
// Local copy and extract values to sort columns
let colsRestored = [...this.columns]
const colsLocalPreferencesValues = colsLocalPreferences.map(
({ value }) => value
)
// Sort by local preferences
colsRestored.sort(
(a, b) =>
colsLocalPreferencesValues.indexOf(a.value) -
colsLocalPreferencesValues.indexOf(b.value)
)
// update visibility by local preferences
colsLocalPreferences.forEach((col) => {
if (col.hasOwnProperty('display')) {
const colIndex = colsRestored.findIndex(
(el) => el.value === col.value
)
colsRestored[colIndex] = {
...colsRestored[colIndex],
display: col.display,
}
}
})
// Send to Store
await this.$store.dispatch('learning/setColumns', colsRestored)
}
// Initialize columns ordered
if (this.$store.state.learning.columnsSorted.length <= 0) {
await this.$store.dispatch('learning/setColumns')
}
},
async created() {
// Get datatable options from localstorage if present
if (
localStorage.getItem(
`courses_table_options_user${this.$store.getters.loggedInUser.id || ''}`
)
) {
const options = JSON.parse(
localStorage.getItem(
`courses_table_options_user${
this.$store.getters.loggedInUser.id || ''
}`
)
)
this.$store.commit('learning/SET_COURSES_TABLE_OPTIONS', options)
}
},
methods: {
close() {
this.dialog = false
},
save() {
this.close()
},
computeImage(item) {
if (item.cover.full) return item.cover.full
if (item.video) return '/images/video-placeholder.jpg'
return '/images/product-placeholder.jpg'
},
async clone(productId) {
if (!productId) {
this.$notifier.showMessage({
content: `No product to clone selected`,
color: 'error',
icon: 'icon-message',
})
}
try {
this.$nextTick(() => this.$nuxt.$loading.start())
const response = await this.$axios.post('/learning-products/clone', {
product_id: productId,
})
this.$emit('reload-learning-products')
this.$notifier.showMessage({
content: `Product cloned in Drafts`,
color: 'success',
icon: 'icon-message',
})
this.$nuxt.$loading.finish()
} catch (error) {
console.log('clone -> error', error)
this.$nuxt.$loading.finish()
this.$notifier.showMessage({
content: `Error trying to clone the selected product`,
color: 'error',
icon: 'mdi-delete',
})
}
},
async deleteProduct(productId) {
if (!productId) return
this.dialog = false
this.$nextTick(() => this.$nuxt.$loading.start())
await this.$store.dispatch('learning/deleteProduct', productId)
this.$router.push(this.localePath('/manager/learning'))
},
async shareUrl(url) {
if (!url) return
const root_url = document.getElementById('root_url').href
// Remove last slash from root url
const full_url = `${root_url.slice(0, -1)}${url}`
try {
await navigator.clipboard.writeText(full_url)
this.$notifier.showMessage({
content: `Url Copied in clipboard `,
color: 'success',
icon: 'mdi-check',
})
} catch (error) {
console.log('shareUrl -> error', error)
}
},
async handleChangesOnOptions(options) {
this.$store.commit('learning/SET_COURSES_TABLE_OPTIONS', options)
await localStorage.setItem(
`courses_table_options_user${
this.$store.getters.loggedInUser.id || ''
}`,
JSON.stringify(options)
)
},
},
}
</script>
<style scoped>
table tr a.v-btn {
opacity: 0;
}
table tr:hover a.v-btn {
opacity: 1;
}
.v-list-item:hover >>> .v-list-item__icon i,
.v-list-item:hover >>> .v-list-item__content .v-list-item__subtitle {
color: var(--v-info-base) !important;
}
.v-data-table >>> .v-data-table-header th:last-child,
.v-data-table >>> .v-data-table-header th:nth-child(3) {
position: relative;
}
.v-data-table >>> .v-data-table-header th:last-child::before,
.v-data-table >>> .v-data-table-header th:nth-child(3)::before {
content: '';
position: absolute;
left: -1px;
height: 20px;
top: 50%;
transform: translateY(-50%);
border-left: 1px solid rgba(0, 0, 0, 0.12) !important;
}
.v-data-table >>> .v-data-table-header tr th {
border-bottom: none !important;
border-top: 1px solid rgba(0, 0, 0, 0.12);
}
.v-data-table td .menu-btn {
background-color: unset !important;
}
.v-data-table >>> td {
color: var(--v-txt-base) !important;
}
.v-data-table >>> thead th {
white-space: nowrap;
}
.v-data-table >>> td:first-child {
padding-right: 0px !important;
}
.v-data-table >>> td:last-child {
display: flex;
align-items: center;
justify-content: center;
}
.v-data-table >>> td:first-child .v-image {
margin-right: -18px;
}
.v-data-table >>> td:last-child,
.v-data-table >>> td:nth-child(3) {
position: relative;
}
.v-data-table >>> td:last-child::before,
.v-data-table >>> td:nth-child(3)::before {
content: '';
position: absolute;
left: -1px;
height: 60px;
top: 50%;
transform: translateY(-50%);
border-left: 1px solid rgba(0, 0, 0, 0.12) !important;
}
.v-data-table >>> .v-data-table__divider {
border-right: none !important;
}
</style>

View File

@@ -0,0 +1,73 @@
<template>
<span>
<span v-if="hasFilterItems && !gratisMode">
<small
v-for="({ filter_item }, i) in filterItemResolved"
:key="`column-filter_item-${filter_item.id}`"
>
<v-icon
:color="filter_item.color"
class="mx-1"
size="10"
v-if="filter_item.color"
>mdi-circle</v-icon
>
<span v-if="filter_item.title">{{ filter_item.title }}</span>
<span v-if="filter_item.subtitle">{{ filter_item.subtitle }}</span>
{{ filterItemResolved.length - 1 === i ? '' : '-' }}
</small>
</span>
<span v-if="gratisMode && isGratis">
<v-icon class="mx-1" color="accent" x-small>mdi-star</v-icon>
<span class="accent--text">Gratis</span>
</span>
</span>
</template>
<script>
export default {
props: {
filterTitle: {
type: String,
default: 'type',
},
filters: {
type: Array,
default: [],
},
gratisMode: {
type: Boolean,
default: false,
},
},
computed: {
filterItemResolved() {
// return this.$store.getters.filterItemsResolved(
// this.filterTitle,
// this.filters
// )
const filter = this.$store.getters.getFilterByTitle(this.filterTitle)
return this.filters.filter((f) => f.filter_item.filter_id === filter.id)
},
hasFilterItems() {
return this.filterItemResolved.length > 0
},
isGratis() {
return this.filterItemResolved.find(
(f) => f.filter_item.title === 'Gratis'
)
},
},
}
</script>
<style scoped>
small span {
font-size: 15.75px;
}
.mdi-star {
margin-right: 0 !important;
}
</style>

View File

@@ -0,0 +1,43 @@
<template>
<div class="mt-4">
<v-chip
class="mr-4 mb-2 txt--text font-weight-bold px-6"
color="primary"
v-for="filter in filters"
:key="filter.title"
@click.stop="
$store.commit('navigation/SWITCH_RIGHT_DRAWER', {
component: 'FiltersMenu',
subMenu: filter.title,
})
"
>
{{ $t(`learning.filters.${filter.title}`) | capitalize }}
</v-chip>
<v-chip
class="mr-4 mb-2 txt--text font-weight-bold px-6"
color="primary"
@click.stop="
$store.commit('navigation/SWITCH_RIGHT_DRAWER', {
component: 'Settings',
subMenu: 'filters',
})
"
>
{{ $t('learning.filters.more') }}
</v-chip>
</div>
</template>
<script>
export default {
mounted() {
this.$store.dispatch('learning/setFilters')
},
computed: {
filters() {
return this.$store.state.learning.filters.slice(0, 5)
},
},
}
</script>

View File

@@ -0,0 +1,25 @@
<template>
<div class="mt-4">
<v-btn
x-small
depressed
text
v-for="(filter, i) in $store.getters.filtersSelected"
:key="`filter-check${i}`"
@click="removeFilterItemFromSelected(filter)"
>
<v-icon class="mx-1" color="success">icon-checkmark</v-icon>
{{ $store.getters.getFilterItemById(filter).title | capitalize }}
</v-btn>
</div>
</template>
<script>
export default {
methods: {
removeFilterItemFromSelected(filterItemId) {
this.$store.commit('learning/REMOVE_SELECTED_FILTER', filterItemId)
},
},
}
</script>

View File

@@ -0,0 +1,53 @@
<template>
<v-card class="mx-auto" max-width="400" tile nuxt :to="slug">
<v-list-item>
<!-- <v-list-item-avatar color="grey"></v-list-item-avatar> -->
<v-list-item-content>
<v-list-item-subtitle>
<v-chip class="ma-2" color="primary">
{{ course }}
</v-chip>
</v-list-item-subtitle>
<v-list-item-title>{{ title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-img :src="cover" class="my-5" />
<!-- <v-card-title class="pb-0">{{ title }}</v-card-title> -->
<!-- <v-card-text class="primary--text">
<div>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Asperiores
officiis pariatur aliquid id ipsam magnam neque quo dicta veritatis
iusto.
</div>
</v-card-text> -->
<v-card-actions class="d-flex flex-column">
<v-btn color="orange" text> Share </v-btn>
<v-btn color="orange" text> Explore </v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
props: {
cover: {
type: String,
},
title: {
type: String,
},
course: {
type: String,
},
gratis: {},
slug: {
type: String,
},
},
}
</script>

View File

@@ -0,0 +1,604 @@
<template>
<accordion-card :title="$t('learning.product_overview.accreditation')">
<v-data-table
:headers="headers"
:items="accreditations"
:options="options"
hide-default-footer
item-key="accreditation"
flat
v-if="hasAccreditations"
>
<!-- Translates dynamically headers -->
<template v-for="h in headers" v-slot:[`header.${h.value}`]="{ header }">
{{ h.text ? $t(`learning.accreditation.${h.text}`) : '' }}
</template>
<template v-slot:item.register="{ item }">
{{ getFilterItem('register', item).title }}
</template>
<template v-slot:item.date_start="{ item }">
{{ formatDate(item.date_start) }}
</template>
<template v-slot:item.date_end="{ item }">
{{ formatDate(item.date_end) }}
</template>
<template v-slot:item.actions="{ item }">
<v-menu
offset-y
v-if="
editMode && ($store.getters.isAdmin || $store.getters.isOperator)
"
>
<template v-slot:activator="{ on }">
<v-hover v-slot:default="{ hover }">
<v-btn
:color="hover ? 'info' : ''"
:outlined="hover"
depressed
fab
small
v-on="on"
>
<v-icon>icon-options</v-icon>
</v-btn>
</v-hover>
</template>
<v-list width="200">
<v-list-item @click="editItem(item)">
<v-list-item-icon class="mr-1">
<v-icon small>icon-edit</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-dialog max-width="740" persistent v-model="dialogDelete">
<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.delete_accreditation_confirmation') }}
</v-card-title>
<v-card-actions>
<div class="ma-4">
<v-btn
@click="deleteItem(item)"
class="mx-2"
color="accent"
depressed
rounded
>{{ $t('general.delete') }}</v-btn
>
<v-btn
@click="dialogDelete = false"
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>
<v-dialog
v-model="dialogAccreditation"
max-width="75%"
v-if="editMode && ($store.getters.isAdmin || $store.getters.isOperator)"
>
<template v-slot:activator="{ on, attrs }">
<v-btn
class="cta-secondary my-10"
block
depressed
min-height="60px"
:disabled="isCreateMode"
v-on="on"
@click="resetAccreditationsFilters"
>
<v-icon x-small class="mx-4">icon-add</v-icon>
{{ $t('learning.add_new_accreditation') }}</v-btn
>
</template>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t(`learning.filters.register`)
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<universalFilterSelector
filterTitle="register"
:editMode="editMode"
target="accreditations"
/>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-text-field
v-model="editedItem.credits"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.accreditation_period')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="9" class="d-flex pl-2">
<v-menu
ref="menuStart"
v-model="menuStart"
:close-on-content-click="false"
transition="scale-transition"
offset-y
margin-top="20px"
min-width="290px"
>
<template v-slot:activator="{ on, attrs }">
<v-text-field
:name="`accreditation-start-date-${Math.random()}`"
v-model="computedDateFormattedStart"
append-icon="icon-events"
max-height="100px"
v-bind="attrs"
v-on="on"
readonly
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
>
<template v-slot:append>
<v-icon v-on="on">icon-events</v-icon>
</template>
</v-text-field>
</template>
<v-date-picker
v-model="editedItem.date_start"
no-title
scrollable
:disabled="!editMode"
:flat="!editMode"
locale="nl-NL"
>
<v-spacer></v-spacer>
<v-btn text @click="menuStart = false">Cancel</v-btn>
<v-btn
text
@click="$refs.menuStart.save(editedItem.date_start)"
>OK</v-btn
>
</v-date-picker>
</v-menu>
<span class="mx-5 my-3 font-weight-bold">
tot
</span>
<v-menu
ref="menuEnd"
v-model="menuEnd"
:close-on-content-click="false"
transition="scale-transition"
offset-y
min-width="290px"
>
<template v-slot:activator="{ on, attrs }">
<v-text-field
:name="`accreditation-end-date-${Math.random()}`"
v-model="computedDateFormattedEnd"
append-icon="icon-events"
readonly
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
max-height="100px"
v-bind="attrs"
v-on="on"
>
<template v-slot:append>
<v-icon v-on="on">icon-events</v-icon>
</template>
</v-text-field>
</template>
<v-date-picker
v-model="editedItem.date_end"
no-title
scrollable
:disabled="!editMode"
:flat="!editMode"
locale="nl-NL"
>
<v-spacer></v-spacer>
<v-btn text @click="menuEnd = false">Cancel</v-btn>
<v-btn text @click="$refs.menuEnd.save(editedItem.date_end)"
>OK</v-btn
>
</v-date-picker>
</v-menu>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-divider />
<v-card-actions v-if="editMode">
<v-btn
class="ma-2 white--text"
color="info"
depressed
rounded
:disabled="loading"
@click="save"
>{{ $t('general.save') }}</v-btn
>
<v-spacer />
<v-btn
class="ma-2 white--text"
color="txt"
depressed
text
:disabled="loading"
@click="close"
>{{ $t('general.cancel') }}</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
<p v-if="isCreateMode" class="text-center">
Bij het aanmaken van een nieuw product kiest u eerst voor 'Opslaan' om
deze functie te activeren.
</p>
</accordion-card>
</template>
<script>
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
import universalFilterSelector from '@/components/UniversalFilterSelector/UniversalFilterSelector'
export default {
components: {
accordionCard,
universalFilterSelector,
},
props: {
editMode: {
type: Boolean,
default: false,
},
isCreateMode: {
type: Boolean,
default: true,
},
},
data() {
return {
options: {
itemsPerPage: -1,
},
headers: [
{ text: 'register', value: 'register' },
{ text: 'credits', value: 'credits' },
{ text: 'date_start', value: 'date_start' },
{ text: 'date_end', value: 'date_end' },
{ text: '', value: 'actions' },
],
accreditationEditMode: false,
dialogAccreditation: false,
dialogDelete: false,
editedIndex: -1,
editedItem: {
date_start: '',
date_end: '',
credits: '',
},
defaultItem: {
date_start: '',
date_end: '',
credits: '',
},
loading: false,
date: null,
menuStart: false,
menuEnd: false,
}
},
computed: {
remote() {
return this.$store.state.learning.remote
},
local() {
return this.$store.state.learning.local
},
accreditations() {
if (!this.local || !Array.isArray(this.local.accreditations)) return []
return this.local.accreditations
},
hasAccreditations() {
if (!this.accreditations) return false
return this.accreditations.length > 0
},
formTitle() {
return this.editedIndex === -1
? this.$t('learning.new_accreditation')
: this.$t('learning.edit_accreditation')
},
accreditationsFiltersSelected() {
return this.$store.state.learning.accreditationsFiltersSelected
},
computedDateFormattedStart() {
return this.formatDate(this.editedItem.date_start)
},
computedDateFormattedEnd() {
return this.formatDate(this.editedItem.date_end)
},
},
watch: {
dialogAccreditation(val) {
val || this.close()
},
},
methods: {
getFilterItem(filterTitle, accreditation) {
const filter = this.$store.getters.getFilterByTitle(filterTitle)
const arrayTmp = []
if (!accreditation.filters) return {}
accreditation.filters.forEach((f) => {
const filterItemTmp = this.$store.getters.getFilterItemById(
f.filter_item_id
)
arrayTmp.push(filterItemTmp)
})
const filterItem = arrayTmp.find((e) => e.filter_id === filter.id)
return filterItem || {}
},
formatDate(date) {
if (!date) return null
const [year, month, day, time] = date.replace(' ', '-').split('-')
return `${day}/${month}/${year}`
},
resetAccreditationsFilters() {
this.$store.commit('learning/RESET_ACCREDITATIONS_FILTERS')
},
setAccreditationsFilters(item) {
item.filters.forEach(async (f) => {
const filterItem = this.$store.getters.getFilterItemById(
f.filter_item_id
)
await this.$store.commit('learning/UPDATE_FILTERS', {
filterId: filterItem.filter_id,
target: 'accreditations',
value: f.filter_item_id,
})
})
this.editedIndex = this.accreditations.indexOf(item)
this.editedItem = Object.assign({}, item)
this.$forceUpdate()
},
close() {
this.dialogAccreditation = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
})
},
setFiltersSelectedInEditedItem() {
let accreditationFiltersItemsIds = []
for (const [key, value] of Object.entries(
this.accreditationsFiltersSelected
)) {
accreditationFiltersItemsIds.push(value)
}
this.editedItem['filter_items'] = accreditationFiltersItemsIds
},
editItem(item) {
if (!this.editMode) return
this.setAccreditationsFilters(item)
this.dialogAccreditation = true
},
async deleteItem(item) {
this.$nextTick(() => this.$nuxt.$loading.start())
if (!item.id) {
this.$notifier.showMessage({
content: `No accredication to delete selected`,
color: 'error',
icon: 'icon-message',
})
}
try {
const response = await this.$axios.delete(`/accreditations/${item.id}`)
this.$store.commit('learning/DELETE_ACCREDITATION_LOCAL', item)
this.dialogDelete = false
this.dialogAccreditation = false
this.$nuxt.$loading.finish()
this.$notifier.showMessage({
content: `Accreditation deleted`,
color: 'success',
icon: 'mdi-delete',
})
} catch (error) {
this.$nuxt.$loading.finish()
this.$notifier.showMessage({
content: `Error trying to delete the selected accreditation`,
color: 'error',
icon: 'mdi-delete',
})
}
},
async save() {
this.loading = true
this.$nextTick(() => this.$nuxt.$loading.start())
// Edit mode
if (this.editedIndex > -1) {
try {
this.editedItem.learning_product_id = this.local.id
this.setFiltersSelectedInEditedItem()
const response = await this.$axios.post(
'/accreditations',
this.editedItem
)
this.$store.commit('learning/EDIT_ACCREDITATION_LOCAL', {
index: this.editedIndex,
accreditation: response.data,
})
this.dialogAccreditation = false
this.$notifier.showMessage({
content: `Accreditation stored`,
color: 'success',
icon: 'icon-checkmark',
})
} catch (error) {
console.log('save -> error', error)
this.dialogAccreditation = false
this.$nuxt.$loading.finish()
this.$notifier.showMessage({
content: `Error trying to store the accreditation`,
color: 'error',
icon: 'mdi-delete',
})
}
// Create new mode
} else {
try {
this.editedItem.learning_product_id = this.local.id
this.setFiltersSelectedInEditedItem()
const response = await this.$axios.post(
'/accreditations',
this.editedItem
)
this.dialog = false
this.$store.commit('learning/ADD_ACCREDITATION_LOCAL', response.data)
this.$notifier.showMessage({
content: `Accreditation stored`,
color: 'success',
icon: 'icon-checkmark',
})
} catch (error) {
console.log('save -> error', error)
this.dialogAccreditation = false
this.$notifier.showMessage({
content: `Error trying to store the accreditation`,
color: 'error',
icon: 'mdi-delete',
})
}
}
this.$nuxt.$loading.finish()
this.loading = false
this.close()
},
},
}
</script>
<style scoped>
.v-application >>> .v-menu__content{
top: 230px !important;
}
.v-card >>> table,
.v-card >>> .v-data-table-header tr,
.v-card >>> .v-data-footer {
background-color: var(--v-primary-base);
}
.v-card >>> .v-data-table-header th span {
color: var(--v-tertiary-base);
}
.v-card >>> .text-start,
.v-card >>> .v-icon,
.v-dialog__content >>> .v-subheader,
.v-dialog__content >>> i {
color: var(--v-txt-base);
font-weight: bold;
}
.v-dialog__content >>> .v-dialog {
border: 4px solid var(--v-secAccent-base);
/* box-shadow: inset 0px 0px 2px 2px var(--v-secAccent-base),
inset 6px 6px 14px -14px var(--v-secAccent-base) !important; */
}
.v-dialog .v-card {
background: var(--v-secondary-base);
}
.v-dialog__content >>> .v-input__slot {
background: var(--v-primary-base);
border: 1px solid var(--v-lines-base);
}
.v-menu__content >>> .v-date-picker-table {
height: 242px !important;
}
</style>

View File

@@ -0,0 +1,262 @@
<template>
<accordion-card :title="$t('learning.product_overview.administration')">
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black in_the_picture"
>Van leden voor leden</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="9" class="mb-4 d-flex flex-column">
<div class="pb-0 mb-0 d-flex">
<v-switch
:disabled="!editMode"
inset
class="my-3 toggle"
v-model="for_members"
/>
</div>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.product_overview.owner')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="owner"
:placeholder="$t('learning.product_overview.placeholder.description')"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
></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="txt--text font-weight-black">{{
$t('learning.product_overview.partner')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="partner"
:placeholder="$t('learning.product_overview.placeholder.description')"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
></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="txt--text font-weight-black">
Leverancier
</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="supplier"
:placeholder="$t('learning.product_overview.placeholder.description')"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
></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="txt--text font-weight-black">{{
$t('learning.product_overview.contract_agreements')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<quill-editor
v-model="contract_agreements"
:options="editorOption"
class="quill"
@focus="isEditorFocused = true"
@blur="isEditorFocused = false"
@change="onEditorChange()"
:class="{ focused: isEditorFocused }"
v-if="editMode"
/>
<span v-html="contract_agreements" v-else></span>
</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">
Prognose gebruik aantal leden
</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="prognosis_members"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
></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="txt--text font-weight-black">
Prognose gebruik aantal deelnemers
</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="prognosis_attendees"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3"> </v-col>
</v-row>
</accordion-card>
</template>
<script>
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
import { quillEditor } from 'vue-quill-editor'
export default {
components: {
accordionCard,
quillEditor,
},
props: {
editMode: {
type: Boolean,
default: false,
},
},
data() {
return {
isEditorFocused: false,
editorOption: {
modules: {
toolbar: [
[{ size: ['small', false, 'large'] }],
['bold', 'italic'],
[{ list: 'ordered' }, { list: 'bullet' }],
['link'],
],
},
},
}
},
computed: {
local() {
return this.$store.state.learning.local
},
owner: {
get() {
return this.local.owner || ''
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'owner',
value,
})
},
},
partner: {
get() {
return this.local.partner || ''
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'partner',
value,
})
},
},
supplier: {
get() {
return this.local.supplier || ''
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'supplier',
value,
})
},
},
contract_agreements: {
get() {
return this.local.contract_agreements || ''
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'contract_agreements',
value,
})
},
},
prognosis_members: {
get() {
return this.local.prognosis_members || ''
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'prognosis_members',
value,
})
},
},
prognosis_attendees: {
get() {
return this.local.prognosis_attendees || ''
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'prognosis_attendees',
value,
})
},
},
for_members: {
get() {
return this.local.for_members
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'for_members',
value,
})
},
},
},
methods: {
onEditorChange() {
if (!this.isEditorFocused) this.isEditorFocused = true
// console.log('editor change!', editor)
},
},
}
</script>

View File

@@ -0,0 +1,603 @@
<template>
<accordion-card :title="$t('learning.product_overview.basic')">
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.product_overview.title')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="title"
:rules="rules.title"
:placeholder="$t('learning.product_overview.placeholder.titel')"
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
required
error
/>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader v-if="editMode"></v-subheader>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.product_overview.code')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="code"
:placeholder="$t('learning.product_overview.placeholder.productcode')"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
:rules="rules.code"
required
error
></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="txt--text font-weight-black in_the_picture"
>Van leden voor leden</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="9" class="mb-4 d-flex flex-column">
<div class="pb-0 mb-0 d-flex">
<v-switch
:disabled="!editMode"
inset
class="my-3 toggle"
v-model="for_members"
/>
</div>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black"> Url </v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="url"
:placeholder="$t('learning.product_overview.placeholder.url')"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.product_overview.status')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<universalFilterSelector filterTitle="status" :editMode="editMode" />
</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">
Tegel afbeelding
</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6" class="pl-6">
<vue-dropzone
ref="imgDropZoneTile"
class="cta-secondary my-4"
id="customdropzoneTile"
:options="dropzoneOptions"
@vdropzone-complete="afterCompleteTile"
v-if="editMode"
/>
<v-img
:src="tileComputed || noImage"
:lazy-src="noImage"
contain
v-cloak
/>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader v-if="editMode" class="mt-12">
{{ $t('learning.product_overview.allowed') }} <br />
{{ $t('learning.product_overview.allowed2') }}
</v-subheader>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('general.image')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6" class="pl-6">
<vue-dropzone
ref="imgDropZoneCover"
class="cta-secondary my-4"
id="customdropzone"
:options="dropzoneOptions"
@vdropzone-complete="afterCompleteCover"
v-if="editMode"
/>
<v-img
:src="coverComputed || noImage"
:lazy-src="noImage"
contain
v-cloak
/>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader v-if="editMode" class="mt-12">
{{ $t('learning.product_overview.allowed') }} <br />
{{ $t('learning.product_overview.allowed2') }}
</v-subheader>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">Video</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6" class="pl-6">
<v-text-field
v-model="video"
prepend-inner-icon="mdi-youtube"
:placeholder="$t('learning.product_overview.placeholder.trailer')"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
v-if="editMode"
class="youtube"
/>
<iframe
v-if="video"
width="100%"
height="480"
frameborder="0"
scrolling="auto"
marginheight="0"
marginwidth="0"
:src="video"
allowfullscreen
msallowfullscreen
allow="fullscreen"
></iframe>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader v-if="editMode">
{{ $t('learning.product_overview.allowed_trailer') }}
</v-subheader>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.product_overview.duration')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="lead_time"
:placeholder="$t('learning.product_overview.duration')"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
></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="txt--text font-weight-black">{{
$t('learning.filters.level') | capitalize
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<universalFilterSelector
id="level"
filterTitle="level"
:editMode="editMode"
filterType="mixed"
/>
</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">
SEO Title
</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="seo_title"
:placeholder="$t('learning.product_overview.placeholder.seo_title')"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
:rules="rules.seo_title"
required
error
></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="txt--text font-weight-black">
Meta description
</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-textarea
v-model="meta_description"
:placeholder="
$t('learning.product_overview.placeholder.meta_description')
"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
></v-textarea>
</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">
Synoniemen
</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<synonyms-selector :editMode="editMode" />
</v-col>
<v-col cols="12" sm="12" md="3"> </v-col>
</v-row>
</accordion-card>
</template>
<script>
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
import universalFilterSelector from '@/components/UniversalFilterSelector/UniversalFilterSelector'
import synonymsSelector from '@/components/Learning/SynonymsSelector'
import vueDropzone from 'vue2-dropzone'
import 'vue2-dropzone/dist/vue2Dropzone.min.css'
export default {
components: {
accordionCard,
vueDropzone,
universalFilterSelector,
synonymsSelector,
},
props: {
editMode: {
type: Boolean,
default: false,
},
},
data() {
return {
date: new Date().toISOString().substr(0, 7),
menu: false,
modal: false,
dialog: false,
valid: true,
lazy: false,
loading: false,
dropzoneOptions: {
url: 'https://httpbin.org/post',
maxFilesize: 1.2, // MB
maxFiles: 1,
thumbnailWidth: 150,
thumbnailHeight: 150,
addRemoveLinks: true,
acceptedFiles: '.jpg, .jpeg, .png',
dictDefaultMessage: `<p><i class='icon icon-image mr-2'></i> Upload afbeelding</p>`,
},
images: [],
rules: {
// title: [(v) => 'De titel is verplicht'],
// code: [(v) => !!v || 'De productcode is verplicht'],
// seo_title: [(v) => !!v || 'De SEO-titel is verplicht'],
title: [(v) => 'De titel is verplicht'],
code: [(v) => 'De productcode is verplicht'],
seo_title: [(v) => 'De SEO-titel is verplicht'],
},
}
},
computed: {
local() {
return this.$store.getters.localProduct
},
title: {
get() {
return this.local.title
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', { field: 'title', value })
},
},
code: {
get() {
return this.local.code
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', { field: 'code', value })
},
},
for_members: {
get() {
return this.local.for_members
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'for_members',
value,
})
},
},
status: {
get() {
return this.local.status
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', { field: 'status', value })
},
},
video: {
get() {
return this.local.video
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', { field: 'video', value })
},
},
lead_time: {
get() {
return this.local.lead_time
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'lead_time',
value,
})
},
},
seo_title: {
get() {
return this.local.seo_title
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'seo_title',
value,
})
},
},
meta_description: {
get() {
return this.local.meta_description
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'meta_description',
value,
})
},
},
url: {
get() {
return this.local.url
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'url',
value,
})
},
},
level: {
get() {
return this.local.level
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'level',
value,
})
},
},
levelItems() {
if (!this.$store.getters.filters.length > 0) return []
return this.$store.getters.filters.find(
(filter) => filter.title === 'level'
).items
},
cover() {
return this.local.cover
},
coverComputed() {
if (this.$store.state.learning.cover.url)
return this.$store.state.learning.cover.url
if (this.local.cover) return this.local.cover.full
return null
},
tile() {
return this.local.tile
},
tileComputed() {
if (this.$store.state.learning.tile.url)
return this.$store.state.learning.tile.url
if (this.local.tile) return this.local.tile.full
return null
},
noImage() {
return require(`@/assets/img/no_image.png`)
},
},
methods: {
async afterCompleteTile(file) {
if (file === undefined) return
if (file.name.lastIndexOf('.') <= 0) return
if (file.size > 2000000) {
this.$notifier.showMessage({
content: `Exceeded Max filesize`,
color: 'error',
icon: 'mdi-alert-circle',
})
this.$store.commit('learning/RESET_TILE')
return
}
this.isLoading = true
try {
const fr = new FileReader()
fr.readAsDataURL(file)
fr.addEventListener('load', () => {
this.$store.commit('learning/UPDATE_TILE', {
name: file.name,
url: fr.result,
file: file,
})
})
this.isLoading = false
} catch (error) {
console.log(error)
}
this.$refs.imgDropZoneTile.removeFile(file)
},
async afterCompleteCover(file) {
if (file === undefined) return
if (file.name.lastIndexOf('.') <= 0) return
if (file.size > 2000000) {
this.$notifier.showMessage({
content: `Exceeded Max filesize`,
color: 'error',
icon: 'mdi-alert-circle',
})
this.$store.commit('learning/RESET_COVER')
return
}
this.isLoading = true
try {
const fr = new FileReader()
fr.readAsDataURL(file)
fr.addEventListener('load', () => {
this.$store.commit('learning/UPDATE_COVER', {
name: file.name,
url: fr.result,
file: file,
})
})
this.isLoading = false
} catch (error) {
console.log(error)
}
this.$refs.imgDropZoneCover.removeFile(file)
},
},
}
</script>
<style scoped>
#level >>> .v-input__slot .v-select__slot {
margin-left: -4px;
}
.image-div {
display: flex;
margin: 25px;
}
.image {
max-width: 250px;
margin: 15px;
}
#customdropzone {
margin-top: -10px;
}
#customdropzone >>> .icon {
position: relative;
top: 4px;
}
#customdropzone >>> p {
font-weight: bold;
}
.mt-12 {
margin-top: 35px !important;
}
.v-input >>> .v-text-field__slot input {
color: var(--v-txt-base) !important;
}
.v-input >>> .v-text-field__slot textarea {
color: var(--v-txt-base) !important;
}
.youtube >>> .v-input__icon--prepend-inner i {
color: #e64e0f !important;
font-size: 30px;
}
.youtube >>> .v-text-field__slot {
border-left: 1px solid var(--v-lines-base) !important;
padding-left: 15px;
}
.youtube >>> .v-input__icon--prepend-inner {
margin: auto 14px auto 10px;
}
</style>

View File

@@ -0,0 +1,181 @@
<template>
<accordion-card :title="$t('learning.product_overview.links')">
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('general.sharepoint')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
prepend-inner-icon="icon-sharepoint"
v-model="sharepoint_link"
placeholder="https://sharepoint.com/"
outlined
flat
class="links"
v-if="editMode"
></v-text-field>
<v-btn
v-if="!editMode && sharepoint_link"
:href="sharepoint_link"
target="_blank"
text
link
><v-icon left>icon-sharepoint</v-icon> {{ sharepoint_link }}</v-btn
>
</v-col>
<v-col cols="12" sm="12" md="3">
<!-- <v-subheader v-if="editMode">{{ $t('general.add_link') }}</v-subheader> -->
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('general.support_site')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="support_link"
prepend-inner-icon="icon-link"
placeholder="https://support.ggzecademy.nl/"
outlined
flat
class="links"
v-if="editMode"
/>
<v-btn
v-if="!editMode && support_link"
:href="support_link"
link
text
target="_blank"
><v-icon left>icon-link</v-icon>{{ support_link }}</v-btn
>
</v-col>
<v-col cols="12" sm="12" md="3">
<!-- <v-subheader v-if="editMode">{{ $t('general.add_link') }}</v-subheader> -->
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('general.support_tickets')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="support_tickets_link"
prepend-inner-icon="icon-link"
placeholder="https://support.ggzecademy.nl/"
outlined
flat
class="links"
v-if="editMode"
></v-text-field>
<v-btn
v-if="!editMode && support_tickets_link"
:href="support_tickets_link"
text
target="_blank"
link
><v-icon left>icon-link</v-icon>{{ support_tickets_link }}</v-btn
>
</v-col>
<v-col cols="12" sm="12" md="3">
<!-- <v-subheader v-if="editMode">{{ $t('general.add_link') }}</v-subheader> -->
</v-col>
</v-row>
</accordion-card>
</template>
<script>
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
export default {
components: {
accordionCard,
},
props: {
editMode: {
type: Boolean,
default: false,
}
},
computed: {
local() {
return this.$store.state.learning.local
},
sharepoint_link: {
get() {
return this.local.sharepoint_link || ''
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'sharepoint_link',
value,
})
},
},
support_link: {
get() {
return this.local.support_link || ''
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'support_link',
value,
})
},
},
support_tickets_link: {
get() {
return this.local.support_tickets_link || ''
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'support_tickets_link',
value,
})
},
},
},
}
</script>
<style scoped>
.v-input__icon--prepend .v-icon {
font-size: 15px !important;
}
.links >>> .v-input__icon--prepend-inner {
margin: auto 12px;
}
.links >>> .v-input__icon--prepend-inner i {
font-size: 22px;
font-weight: 600;
color: var(--v-txt-base) !important;
}
.links >>> .v-input__icon--prepend-inner .icon-sharepoint {
color: dodgerblue !important;
}
.links >>> .v-text-field__slot {
border-left: 1px solid var(--v-lines-base) !important;
padding-left: 15px;
}
.links >>> .v-input__slot:hover .v-text-field__slot {
border-color: var(--v-secAccent-base) !important;
}
.row{
overflow: hidden;
}
.row >>> div:nth-child(3) .v-subheader {
color: var(--v-tertiary-base) !important;
}
</style>

View File

@@ -0,0 +1,859 @@
<template>
<accordion-card :title="$t('learning.product_overview.notifications')">
<v-data-table
:headers="headers"
:items="notifications"
:options="options"
hide-default-footer
item-key="notification"
flat
v-if="hasNotifications"
>
<template v-slot:item.date="{ item }">{{
formatDate(item.date)
}}</template>
<template v-slot:item.sent="{ item }">
<v-icon color="teal" class="icon-checkmark" v-if="item.sent"></v-icon>
<v-icon color="red" class="icon-close" size="8" v-else></v-icon>
</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
small
@click="viewItem(item)"
>{{ $t('general.view') }}</v-btn
>
<v-menu
offset-y
v-if="
editMode && ($store.getters.isAdmin || $store.getters.isOperator)
"
>
<template v-slot:activator="{ on }">
<v-hover v-slot:default="{ hover }">
<v-btn
:color="hover ? 'info' : ''"
:outlined="hover"
depressed
fab
small
v-on="on"
>
<v-icon>icon-options</v-icon>
</v-btn>
</v-hover>
</template>
<v-list width="200">
<v-list-item @click="editItem(item)">
<v-list-item-icon class="mr-1">
<v-icon small>icon-edit</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-dialog max-width="740" persistent v-model="dialogDelete">
<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.subject,
})
}}
</v-card-title>
<v-card-actions>
<div class="ma-4">
<v-btn
@click="deleteItem(item)"
class="mx-2"
color="accent"
depressed
rounded
>{{ $t('general.delete') }}</v-btn
>
<v-btn
@click="dialogDelete = false"
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>
<v-dialog
v-model="dialogNotification"
max-width="75%"
v-if="$store.getters.isAdmin || $store.getters.isOperator"
>
<template v-slot:activator="{ on, attrs }">
<v-btn
v-if="editMode"
class="my-10 cta-secondary"
block
depressed
min-height="60px"
:disabled="isCreateMode"
v-on="on"
@click="notificationEditMode = true"
>
<v-icon x-small class="mx-4">icon-add</v-icon>
{{ $t('learning.add_new_notification') }}
</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" sm="12" md="2">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.actions.date_time')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="8" class="d-flex">
<v-menu
ref="menuDate"
v-model="menuDate"
:close-on-content-click="false"
transition="scale-transition"
offset-y
min-width="290px"
>
<template v-slot:activator="{ on, attrs }">
<v-text-field
:name="`notification-date-${Math.random()}`"
v-model="computedDateFormatted"
max-height="100px"
v-bind="attrs"
v-on="on"
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
readonly
>
<template v-slot:append>
<v-icon v-on="on">icon-events</v-icon>
</template>
</v-text-field>
</template>
<v-date-picker
v-model="editedItem.date"
no-title
scrollable
:disabled="!editModeComputed"
:flat="!editModeComputed"
locale="nl-NL"
>
<v-spacer></v-spacer>
<v-btn text @click="menuDate = false">Cancel</v-btn>
<v-btn text @click="$refs.menuDate.save(editedItem.date)"
>OK</v-btn
>
</v-date-picker>
</v-menu>
<span class="mx-5 my-3 font-weight-bold">{{
$t('learning.actions.at')
}}</span>
<v-menu
ref="menuTime"
v-model="menuTime"
:close-on-content-click="false"
:nudge-right="40"
:return-value.sync="editedItem.time"
transition="scale-transition"
offset-y
max-width="290px"
min-width="290px"
>
<template v-slot:activator="{ on, attrs }">
<v-text-field
:name="`notification-time-${Math.random()}`"
v-model="editedItem.time"
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
readonly
v-bind="attrs"
v-on="on"
></v-text-field>
</template>
<v-time-picker
v-if="menuTime"
v-model="editedItem.time"
full-width
format="24hr"
@click:minute="$refs.menuTime.save(editedItem.time)"
></v-time-picker>
</v-menu>
</v-col>
<v-col cols="12" sm="12" md="2">
<v-subheader class="txt--text font-weight-black"></v-subheader>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="2">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.actions.mail_addresses')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="8" class="d-flex">
<v-combobox
v-model="editedItem.emails"
hide-selected
label="Add emails here"
multiple
chips
solo
:disabled="!editModeComputed"
>
<template
v-slot:selection="{ attrs, item, parent, selected }"
>
<v-chip
v-bind="attrs"
:input-value="selected"
small
:close="editModeComputed"
@click:close="parent.selectItem(item)"
>
<span class="pr-2">{{ item }}</span>
</v-chip>
</template>
</v-combobox>
</v-col>
<v-col cols="12" sm="12" md="2">
<v-subheader class="txt--text font-weight-black"></v-subheader>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="2">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.actions.users')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="8" class="d-flex users">
<v-autocomplete
v-model="editedItem.users"
:items="users"
chips
color="blue-grey lighten-2"
item-text="fullName"
item-value="id"
multiple
hide-selected
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
>
<template v-slot:selection="data">
<v-chip
v-bind="data.attrs"
:input-value="data.id"
:close="editModeComputed"
@click="data.select"
@click:close="removeUser(data.item.id)"
>{{ data.item.fullName }}</v-chip
>
</template>
<template v-slot:item="data">
<template>
<v-list-item-content>
<v-list-item-title
v-html="data.item.fullName"
></v-list-item-title>
<!-- <v-list-item-subtitle
v-html="data.item.group"
></v-list-item-subtitle>-->
</v-list-item-content>
</template>
</template>
</v-autocomplete>
</v-col>
<v-col cols="12" sm="12" md="2"></v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="2">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.actions.subject')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="8" class="d-flex">
<v-text-field
v-model="editedItem.subject"
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="2">
<v-subheader class="txt--text font-weight-black"></v-subheader>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="2">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.actions.message')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="8">
<quill-editor
class="quill primary"
:options="editorOption"
v-model="editedItem.message"
style="width: 100%"
v-if="editModeComputed"
/>
<span v-html="editedItem.message" v-else></span>
</v-col>
<v-col cols="12" sm="12" md="2">
<v-subheader class="txt--text font-weight-black"></v-subheader>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-divider />
<v-card-actions v-if="editModeComputed">
<v-btn
class="ma-2 white--text"
color="info"
depressed
rounded
:disabled="loading"
@click="save"
>{{ $t('general.save') }}</v-btn
>
<v-spacer />
<v-btn
class="ma-2 white--text"
color="txt"
depressed
text
:disabled="loading"
@click="close"
>{{ $t('general.cancel') }}</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
<p v-if="isCreateMode" class="text-center">
Bij het aanmaken van een nieuw product kiest u eerst voor 'Opslaan' om
deze functie te activeren.
</p>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black in_the_picture"
>In the picture</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="9" class="mb-4 d-flex flex-column">
<div class="pb-0 mb-0 d-flex">
<v-switch
:disabled="!editMode"
inset
class="toggle my-3"
v-model="in_the_picture"
/>
<v-text-field
:value="computedInThePicDateFormattedStart"
readonly
prepend-inner-icon="icon-events"
:outlined="editMode"
:solo="!editMode"
:flat="!editMode"
/>
<span class="mx-5 my-3 font-weight-bold"> tot </span>
<v-text-field
:value="computedInThePicDateFormattedEnd"
readonly
:outlined="editMode"
:solo="!editMode"
:flat="!editMode"
append-icon="icon-events"
/>
</div>
<v-date-picker
v-if="$store.getters.isSuperAdminOrAdmin"
v-model="dates"
no-title
:disabled="!editMode"
flat
first-day-of-week="1"
range
full-width
locale="nl-NL"
/>
</v-col>
</v-row>
</accordion-card>
</template>
<script>
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
import { quillEditor } from 'vue-quill-editor'
export default {
components: {
accordionCard,
quillEditor,
},
props: {
editMode: {
type: Boolean,
default: false,
},
isCreateMode: {
type: Boolean,
default: true,
},
users: {
type: Array,
default: [],
},
},
data() {
return {
picker: new Date(Date.now() - new Date().getTimezoneOffset() * 60000)
.toISOString()
.substr(0, 10),
options: {
itemsPerPage: -1,
},
headers: [
{ text: 'subject', value: 'subject' },
{ text: 'date', value: 'date' },
{ text: 'time', value: 'time' },
{ text: 'sent', value: 'sent' },
{ text: '', value: 'actions' },
],
loading: false,
dialogNotification: false,
dialogDelete: false,
editedIndex: -1,
editedItem: {
date: '',
time: null,
emails: [],
users: [],
subject: '',
message: '',
},
defaultItem: {
date: '',
time: null,
emails: [],
users: [],
subject: '',
message: '',
},
content: 'Write here your content...',
editorOption: {
modules: {
toolbar: [
[{ size: ['small', false, 'large'] }],
['bold', 'italic'],
[{ list: 'ordered' }, { list: 'bullet' }],
['link'],
],
},
},
notificationEditMode: false,
autoUpdate: true,
isUpdating: false,
activator: null,
attach: null,
editing: null,
index: -1,
date: null,
menu: false,
menuStart: false,
menuEnd: false,
menuDate: false,
menuTime: false,
menu2: false,
x: 0,
y: 0,
dates: [],
}
},
watch: {
dialogNotification(val) {
val || this.close()
},
dates(val) {
// If the second date is earlier than the first, switch them
if (val.length === 2 && val[1] < val[0]) this.dates.reverse()
this.$store.commit('learning/UPDATE_FIELD', {
field: 'in_the_picture_start',
value: val[0] || null,
})
this.$store.commit('learning/UPDATE_FIELD', {
field: 'in_the_picture_end',
value: val[1] || null,
})
},
},
computed: {
local() {
return this.$store.getters.localProduct
},
notifications() {
if (!this.local || !Array.isArray(this.local.notifications)) return []
return this.local.notifications
},
hasNotifications() {
return this.notifications.length > 0
},
formTitle() {
return this.editedIndex === -1
? 'Nieuwe notificatie'
: 'Bewerken notificatie'
},
editModeComputed() {
return this.notificationEditMode && this.editMode
},
computedDateFormatted() {
return this.formatDate(this.editedItem.date)
},
computedInThePicDateFormattedStart() {
return this.formatDate(this.in_the_picture_start)
},
computedInThePicDateFormattedEnd() {
return this.formatDate(this.in_the_picture_end)
},
in_the_picture: {
get() {
return this.local.in_the_picture
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'in_the_picture',
value,
})
},
},
in_the_picture_start() {
return this.local.in_the_picture_start
},
in_the_picture_end() {
return this.local.in_the_picture_end
},
},
methods: {
assignItemToEdited(item) {
this.editedIndex = this.notifications.indexOf(item)
this.editedItem = Object.assign({}, item)
if (typeof this.editedItem.users === 'string') {
this.editedItem.users = JSON.parse(this.editedItem.users)
}
if (typeof this.editedItem.emails === 'string') {
this.editedItem.emails = JSON.parse(this.editedItem.emails)
}
},
formatDate(date) {
if (!date) return null
const [year, month, day, time] = date.replace(' ', '-').split('-')
return `${day}/${month}/${year}`
},
viewItem(item) {
this.editedIndex = this.notifications.indexOf(item)
this.notificationEditMode = false
this.assignItemToEdited(item)
this.dialogNotification = true
},
editItem(item) {
if (!this.editMode) return
this.notificationEditMode = true
this.assignItemToEdited(item)
this.dialogNotification = true
},
async deleteItem(item) {
this.$nextTick(() => this.$nuxt.$loading.start())
if (!item.id) {
this.$notifier.showMessage({
content: `No notificaion to delete selected`,
color: 'error',
icon: 'icon-message',
})
}
try {
const response = await this.$axios.delete(
`/learning-products/notifications/${item.id}`
)
this.$store.commit('learning/DELETE_COURSE_NOTIFICATION_LOCAL', item)
this.dialogDelete = false
this.dialogNotification = false
this.$nuxt.$loading.finish()
this.$notifier.showMessage({
content: `Notification deleted`,
color: 'success',
icon: 'mdi-delete',
})
} catch (error) {
this.$nuxt.$loading.finish()
this.$notifier.showMessage({
content: `Error trying to delete the selected notification`,
color: 'error',
icon: 'mdi-delete',
})
}
},
async save() {
this.loading = true
this.$nextTick(() => this.$nuxt.$loading.start())
// Edit mode
if (this.editedIndex > -1) {
try {
this.editedItem.learning_product_id = this.local.id
this.editedItem.sent = 0
const response = await this.$axios.post(
'/learning-products/notifications',
this.editedItem
)
this.$store.commit('learning/EDIT_COURSE_NOTIFICATION_LOCAL', {
index: this.editedIndex,
notification: response.data,
})
this.dialogAccreditation = false
this.$notifier.showMessage({
content: `Notification stored`,
color: 'success',
icon: 'icon-checkmark',
})
} catch (error) {
console.log('save -> error', error)
this.dialogNotification = false
this.$nuxt.$loading.finish()
this.$notifier.showMessage({
content: `Error trying to store the notification`,
color: 'error',
icon: 'mdi-delete',
})
}
// Create new mode
} else {
try {
this.editedItem.learning_product_id = this.local.id
let data = { ...this.editedItem }
data['emails'] = JSON.stringify(data['emails'])
data['users'] = JSON.stringify(data['users'])
const response = await this.$axios.post(
'/learning-products/notifications',
data
)
this.dialog = false
this.$store.commit(
'learning/ADD_COURSE_NOTIFICATION_LOCAL',
response.data
)
this.$notifier.showMessage({
content: `Notifican stored`,
color: 'success',
icon: 'icon-checkmark',
})
} catch (error) {
console.log('save -> error', error)
this.dialogAccreditation = false
this.$nuxt.$loading.finish()
this.$notifier.showMessage({
content: `Error trying to store the notification`,
color: 'error',
icon: 'mdi-delete',
})
}
}
this.$nuxt.$loading.finish()
this.loading = false
this.close()
},
close() {
this.dialogNotification = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
})
},
edit(index, item) {
if (!this.editing) {
this.editing = item
this.index = index
} else {
this.editing = null
this.index = -1
}
},
removeUser(userId) {
const index = this.editedItem.users.indexOf(userId)
if (index >= 0) {
let arrayUsers = [...this.editedItem.users]
arrayUsers.splice(index, 1)
this.editedItem.users = [...arrayUsers]
}
},
},
}
</script>
<style scoped>
.v-card >>> table,
.v-card >>> .v-data-table-header tr,
.v-card >>> .v-data-footer {
background-color: var(--v-primary-base);
}
.v-card >>> .v-data-table-header th span {
color: var(--v-tertiary-base);
}
.v-card >>> .text-start,
.v-card >>> .v-icon,
.v-dialog__content >>> .v-subheader,
.v-dialog__content >>> i,
.font-weight-bold {
color: var(--v-txt-base);
font-weight: bold;
}
.v-card >>> .in_the_picture {
padding-left: 4px;
}
.v-dialog__content >>> .v-dialog {
border: 4px solid var(--v-secAccent-base);
max-height: 68% !important;
/* box-shadow: inset 0px 0px 2px 2px var(--v-secAccent-base),
inset 6px 6px 14px -14px var(--v-secAccent-base) !important; */
}
.v-dialog .v-card {
background: var(--v-secondary-base);
}
.v-dialog__content >>> .v-input__slot {
background: var(--v-primary-base);
border: 1px solid var(--v-lines-base);
}
table tr .button-hover.v-btn {
opacity: 0;
}
table tr:hover .button-hover.v-btn {
opacity: 1;
}
.v-autocomplete__content {
top: 532px !important;
left: 507px !important;
}
.v-autocomplete__content >>> .v-list .v-list-item:first-child {
pointer-events: auto !important;
cursor: pointer !important;
}
.v-autocomplete__content
.v-list
.v-list-item:first-child
.v-list-item__content {
border-bottom: 0px solid var(--v-search-base) !important;
padding-bottom: unset !important;
}
.v-autocomplete__content .v-list {
padding: 6px !important;
}
.v-autocomplete__content .v-list .v-list-item:first-child .v-list-item__title {
font-weight: 400 !important;
}
.v-text-field.v-text-field--solo >>> .v-input__append-inner {
align-self: baseline;
}
.v-text-field.v-text-field--solo >>> .v-input__slot {
padding: 8px 12px !important;
}
.v-text-field.v-text-field--solo >>> .v-input__icon--append {
margin-top: 8px;
}
</style>

View File

@@ -0,0 +1,168 @@
<template>
<accordion-card
:title="$t('learning.product_overview.organize')"
id="organize"
>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.filters.type') | capitalize
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<universalFilterSelector
filterTitle="type"
filterType="checkbox"
:editMode="editMode"
/>
<!-- v-if="isFilterTypeEmpty" -->
<small class="error--text">Het type is verplicht</small>
<br />
<br />
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader v-if="editMode">
{{ $t('learning.more_options_selectable') }}
</v-subheader>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.filters.product_type') | capitalize
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<universalFilterSelector
class="producttype"
filterTitle="product_type"
filterType="mixed"
:editMode="editMode"
/>
</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">{{
$t('learning.filters.category') | capitalize
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<universalFilterSelector
filterTitle="category"
:editMode="editMode"
filterType="mixed"
/>
</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">{{
$t('learning.filters.theme') | capitalize
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<universalFilterSelector
filterTitle="theme"
:editMode="editMode"
filterType="mixed"
/>
</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">{{
$t('learning.filters.course') | capitalize
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<universalFilterSelector
filterTitle="course"
:editMode="editMode"
filterType="mixed"
/>
</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">{{
$t('learning.filters.audience') | capitalize
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<universalFilterSelector
filterTitle="audience"
:editMode="editMode"
filterType="mixed"
/>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader v-if="editMode">
{{ $t('learning.more_options_selectable') }}
</v-subheader>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.filters.quality_standards') | capitalize
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<universalFilterSelector
filterTitle="quality_standards"
:editMode="editMode"
filterType="mixed"
/>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader v-if="editMode">
{{ $t('learning.more_options_selectable') }}
</v-subheader>
</v-col>
</v-row>
</accordion-card>
</template>
<script>
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
import universalFilterSelector from '@/components/UniversalFilterSelector/UniversalFilterSelector'
// import { mapGetters } from 'vuex'
export default {
components: {
accordionCard,
universalFilterSelector,
},
props: {
editMode: {
type: Boolean,
default: false,
},
},
computed: {
isFilterTypeEmpty() {
return this.$store.getters.isFilterTypeEmpty
},
},
}
</script>
<style scoped>
#organize >>> .v-input__slot .v-select__slot {
margin-left: -4px;
}
#organize >>> .producttype .v-input__slot .v-select__slot {
margin-left: 0;
}
</style>

View File

@@ -0,0 +1,304 @@
<template>
<accordion-card :title="$t('learning.product_overview.texts')">
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.product_overview.short_description')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6" class="pl-6">
<quill-editor
v-model="short_description"
id="short_description"
:options="editorOption"
class="quill"
:class="{ focused: selectedEditor === 'short_description' }"
@focus="onEditorFocus($event, 'short_description')"
@blur="onEditorBlur($event)"
@change="onEditorChange($event, 'short_description')"
v-if="editMode"
/>
<!-- @change="onEditorChange($event)" -->
<span v-html="short_description" v-else></span>
</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">{{
$t('learning.product_overview.learning_goals')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6" class="pl-6">
<quill-editor
v-model="learning_goals"
:options="editorOption"
class="quill"
:class="{ focused: selectedEditor === 'learning_goals' }"
@focus="onEditorFocus($event, 'learning_goals')"
@blur="onEditorBlur($event)"
@change="onEditorChange($event, 'learning_goals')"
v-if="editMode"
/>
<span v-html="learning_goals" v-else></span>
</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">{{
$t('learning.product_overview.review')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6" class="pl-6">
<quill-editor
v-model="review"
:options="editorOption"
class="quill"
:class="{ focused: selectedEditor === 'review' }"
@focus="onEditorFocus($event, 'review')"
@blur="onEditorBlur($event)"
@change="onEditorChange($event, 'review')"
v-if="editMode"
/>
<span v-html="review" v-else></span>
</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">{{
$t('learning.product_overview.certification')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6" class="pl-6">
<quill-editor
v-model="certification"
:options="editorOption"
class="quill"
:class="{ focused: selectedEditor === 'certification' }"
@focus="onEditorFocus($event, 'certification')"
@blur="onEditorBlur($event)"
@change="onEditorChange($event, 'certification')"
v-if="editMode"
/>
<span v-html="certification" v-else></span>
</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">{{
$t('learning.product_overview.extra_information')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6" class="pl-6">
<quill-editor
v-model="extra_information"
:options="editorOption"
class="quill"
:class="{ focused: selectedEditor === 'extra_information' }"
@focus="onEditorFocus($event, 'extra_information')"
@blur="onEditorBlur($event)"
@change="onEditorChange($event, 'extra_information')"
v-if="editMode"
/>
<span v-html="extra_information" v-else></span>
</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">{{
$t('learning.product_overview.target_audience')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6" class="pl-6">
<quill-editor
v-model="target_audience"
:options="editorOption"
class="quill"
:class="{ focused: selectedEditor === 'target_audience' }"
@focus="onEditorFocus($event, 'target_audience')"
@blur="onEditorBlur($event)"
@change="onEditorChange($event, 'target_audience')"
v-if="editMode"
/>
<span v-html="target_audience" v-else></span>
</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">
Kwaliteitsstandaarden
</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6" class="pl-6">
<quill-editor
v-model="quality_standards"
:options="editorOption"
class="quill"
:class="{ focused: selectedEditor === 'quality_standards' }"
@focus="onEditorFocus($event, 'quality_standards')"
@blur="onEditorBlur($event)"
@change="onEditorChange($event, 'quality_standards')"
v-if="editMode"
/>
<span v-html="quality_standards" v-else></span>
</v-col>
<v-col cols="12" sm="12" md="3"> </v-col>
</v-row>
</accordion-card>
</template>
<script>
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
import { quillEditor } from 'vue-quill-editor'
export default {
components: {
accordionCard,
quillEditor,
},
props: {
editMode: {
type: Boolean,
default: false,
},
},
data() {
return {
selectedEditor: '',
editorOption: {
modules: {
toolbar: [
[{ size: ['small', false, 'large'] }],
['bold', 'italic'],
[{ list: 'ordered' }, { list: 'bullet' }],
['link'],
],
},
},
}
},
computed: {
local() {
return this.$store.state.learning.local
},
short_description: {
get() {
return this.local.short_description
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'short_description',
value,
})
},
},
learning_goals: {
get() {
return this.local.learning_goals
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'learning_goals',
value,
})
},
},
review: {
get() {
return this.local.review
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'review',
value,
})
},
},
certification: {
get() {
return this.local.certification
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'certification',
value,
})
},
},
extra_information: {
get() {
return this.local.extra_information
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'extra_information',
value,
})
},
},
target_audience: {
get() {
return this.local.target_audience
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'target_audience',
value,
})
},
},
quality_standards: {
get() {
return this.local.quality_standards
},
set(value) {
this.$store.commit('learning/UPDATE_FIELD', {
field: 'quality_standards',
value,
})
},
},
},
methods: {
onEditorBlur(editor) {
this.selectedEditor = ''
// console.log('editor blur!', editor)
},
onEditorFocus(editor, nameEditor) {
this.selectedEditor = nameEditor
},
onEditorReady(editor) {
// console.log('editor ready!', editor)
},
onEditorChange(editor, nameEditor) {
if (this.selectedEditor !== nameEditor) this.selectedEditor = nameEditor
// console.log('editor change!', editor)
},
},
}
</script>
<style scoped>
.v-subheader {
display: unset;
}
</style>

View File

@@ -0,0 +1,955 @@
<template>
<accordion-card :title="$t('learning.product_overview.version')">
<v-data-table
:headers="headers"
:items="versions"
:options="options"
hide-default-footer
item-key="version"
v-if="hasVersions"
flat
>
<template v-slot:item.made_by="{ item }">
{{ getFilterItem('made_by', item).title }}
</template>
<template v-slot:item.dev_environment="{ item }">
{{ getFilterItem('dev_environment', item).title }}
</template>
<template v-slot:item.status="{ item }">
<v-icon
x-small
class="mr-2"
v-if="getFilterItem('status', item).color"
:color="getFilterItem('status', item).color"
>mdi-circle</v-icon
>
{{ getFilterItem('status', item).title }}
</template>
<template v-slot:item.release_start="{ item }">
{{ formatDate(item.release_start) }}
</template>
<template v-slot:item.release_end="{ item }">
{{ formatDate(item.release_end) }}
</template>
<template v-slot:item.actions="{ item }">
<v-btn
class="mx-4 white--text view"
style="height: 100%"
:color="$vuetify.theme.dark ? 'info' : 'txt'"
rounded
depressed
small
@click="viewItem(item)"
>{{ $t('general.view') }}</v-btn
>
<v-menu
offset-y
v-if="
editMode && ($store.getters.isAdmin || $store.getters.isOperator)
"
>
<template v-slot:activator="{ on }">
<v-hover v-slot:default="{ hover }">
<v-btn
:color="hover ? 'info' : ''"
:outlined="hover"
depressed
fab
small
v-on="on"
>
<v-icon>icon-options</v-icon>
</v-btn>
</v-hover>
</template>
<v-list width="200">
<v-list-item @click="editItem(item)">
<v-list-item-icon class="mr-1">
<v-icon small>icon-edit</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-dialog max-width="740" persistent v-model="dialogDelete">
<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.version_number,
})
}}
</v-card-title>
<v-card-actions>
<div class="ma-4">
<v-btn
@click="deleteItem(item)"
class="mx-2"
color="accent"
depressed
rounded
>{{ $t('general.delete') }}</v-btn
>
<v-btn
@click="dialogDelete = false"
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>
<v-dialog
v-model="dialogVersion"
max-width="75%"
v-if="$store.getters.isAdmin || $store.getters.isOperator"
>
<template v-slot:activator="{ on, attrs }">
<v-btn
v-if="editMode"
class="cta-secondary my-10"
block
depressed
min-height="60px"
:disabled="isCreateMode"
v-on="on"
@click="resetVersionsFilters"
>
<v-icon x-small class="mx-4">icon-add</v-icon>
{{ $t('learning.add_new_version') }}</v-btn
>
</template>
<v-form ref="form" v-model="valid" lazy-validation>
<v-card ref="card">
<v-card-title>
<span class="headline">{{ formTitle }}</span>
<!-- <input ref="input" /> -->
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.versions.version_number')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="9">
<v-text-field
v-model="editedItem.version_number"
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
:rules="rules.version_number"
required
error
></v-text-field>
</v-col>
</v-row>
<v-row
v-for="(filterTitle, i) in filterInputs"
:key="`row-filter-${i}`"
>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t(`learning.filters.${filterTitle}`)
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="9">
<universalFilterSelector
:filterTitle="filterTitle"
:editMode="editModeComputed"
target="versions"
:key="`universal-filter-version-${i}`"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">
{{ $t('learning.versions.release_date.from') }}
</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="9" class="d-flex">
<v-menu
ref="menuStart"
v-model="menuStart"
:close-on-content-click="false"
transition="scale-transition"
offset-y
min-width="290px"
>
<template v-slot:activator="{ on, attrs }">
<!-- <input type="text" v-model="startData" /> -->
<v-text-field
:name="`release-start-date-${Math.random()}`"
v-model="computedDateFormattedStart"
max-height="100px"
v-bind="attrs"
v-on="on"
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
:rules="rules.release_start"
required
error
readonly
>
<template v-slot:append>
<v-icon v-on="on">icon-events</v-icon>
</template>
</v-text-field>
</template>
<v-date-picker
v-model="editedItem.release_start"
no-title
scrollable
:disabled="!editModeComputed"
:flat="!editModeComputed"
locale="nl-NL"
>
<v-spacer></v-spacer>
<v-btn text @click="menuStart = false">Cancel</v-btn>
<v-btn
text
@click="$refs.menuStart.save(editedItem.release_start)"
>OK</v-btn
>
</v-date-picker>
</v-menu>
<span class="mx-5 my-3 font-weight-bold">
{{ $t('learning.versions.release_date.to') }}
</span>
<v-menu
ref="menuEnd"
v-model="menuEnd"
:close-on-content-click="false"
transition="scale-transition"
offset-y
min-width="290px"
>
<template v-slot:activator="{ on, attrs }">
<v-text-field
:name="`release-end-date-${Math.random()}`"
v-model="computedDateFormattedEnd"
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
max-height="100px"
v-bind="attrs"
v-on="on"
clearable
readonly
>
<template v-slot:append>
<v-icon v-on="on">icon-events</v-icon>
</template>
</v-text-field>
</template>
<v-date-picker
v-model="editedItem.release_end"
no-title
scrollable
:disabled="!editModeComputed"
:flat="!editModeComputed"
locale="nl-NL"
>
<v-spacer></v-spacer>
<v-btn text @click="menuEnd = false">Cancel</v-btn>
<v-btn
text
@click="$refs.menuEnd.save(editedItem.release_end)"
>OK</v-btn
>
</v-date-picker>
</v-menu>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.versions.release_planning')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="9">
<quill-editor
class="quill primary"
:options="editorOption"
v-model="editedItem.release_planning_description"
v-if="editModeComputed"
/>
<span
v-html="editedItem.release_planning_description"
v-else
></span>
</v-col>
</v-row>
<v-row class="mb-10">
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
$t('learning.versions.technical_information')
}}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="9">
<quill-editor
class="quill primary"
v-model="editedItem.technical_information"
:options="editorOption"
v-if="editModeComputed"
/>
<div v-html="editedItem.technical_information" v-else></div>
</v-col>
</v-row>
<!-- Checklist Summary -->
<v-row class="mb-10">
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">
Checklist
</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="9">
<v-text-field
v-model="checklist_summary"
solo
disabled
flat
max-height="100px"
></v-text-field>
</v-col>
</v-row>
<!-- Checklist -->
<v-row v-for="checklist in checklists" :key="checklist.id">
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">{{
checklist.title
}}</v-subheader>
</v-col>
<v-col
cols="12"
sm="12"
md="9"
class="d-flex flex-column justify-center"
>
<!-- -->
<v-list
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
input-value="true"
>
<v-list-item-group v-model="checklistsSelected" multiple>
<template>
<v-list-item
v-for="(item, i) in checklist.items"
:key="`item-${i}`"
:value="item.id"
active-class="secondary"
two-line
>
<template v-slot:default="{ active, toggle }">
<v-list-item-action>
<v-checkbox
:input-value="active"
:true-value="item.id"
:click="toggle"
on-icon="icon-selectionbox-checked"
off-icon="icon-selectionbox"
></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title
v-text="item.title"
></v-list-item-title>
<v-list-item-subtitle
v-if="item.subtitle"
v-text="item.subtitle"
></v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
{{ showChecklistInfo(item.id) }}
</v-list-item-action>
</template>
</v-list-item>
</template>
</v-list-item-group>
</v-list>
<!-- -->
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-divider />
<v-card-actions v-if="editModeComputed">
<v-btn
class="ma-2 white--text"
color="info"
depressed
rounded
:disabled="loading"
@click="validate"
>{{ $t('general.save') }}</v-btn
>
<v-spacer />
<v-btn
class="ma-2 white--text"
color="txt"
depressed
text
:disabled="loading"
@click="close"
>{{ $t('general.cancel') }}</v-btn
>
</v-card-actions>
</v-card>
</v-form>
</v-dialog>
<p v-if="isCreateMode" class="text-center">
Bij het aanmaken van een nieuw product kiest u eerst voor 'Opslaan' om
deze functie te activeren.
</p>
</accordion-card>
</template>
<script>
import Util from '@/util'
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
import universalFilterSelector from '@/components/UniversalFilterSelector/UniversalFilterSelector'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
import { quillEditor } from 'vue-quill-editor'
export default {
components: {
accordionCard,
quillEditor,
universalFilterSelector,
},
props: {
editMode: {
type: Boolean,
default: false,
},
isCreateMode: {
type: Boolean,
default: true,
},
},
data() {
return {
options: {
itemsPerPage: -1,
},
content: 'Write here your content...',
editorOption: {
modules: {
toolbar: [
[{ size: ['small', false, 'large'] }],
['bold', 'italic'],
[{ list: 'ordered' }, { list: 'bullet' }],
['link'],
],
},
},
headers: [
{ text: 'versienummer', value: 'version_number' },
{ text: 'gemaakt door', value: 'made_by' },
{ text: 'ontwikkelomgeving', value: 'dev_environment' },
{ text: 'status', value: 'status' },
{ text: 'beschikbaar van', value: 'release_start' },
{ text: 'beschikbaar tot', value: 'release_end' },
{ text: '', value: 'actions' },
],
rules: {
version_number: [(v) => !!v || 'Het versienummer is verplicht.'],
release_start: [
(v) => !!v || 'De startdatum van de release is verplicht.',
],
},
valid: true,
loading: false,
date: null,
menuPlanning: false,
menuStart: false,
menuEnd: false,
modal: false,
menu2: false,
filterInputs: ['status', 'made_by', 'dev_environment', 'format_version'],
version: {},
versionEditMode: false,
dialogVersion: false,
dialogDelete: false,
editedIndex: -1,
editedItem: {
version_number: '',
release_start: '',
release_end: '',
release_planning_description: '',
technical_information: '',
},
defaultItem: {
version_number: '',
release_start: '',
release_end: '',
release_planning_description: '',
technical_information: '',
},
// startData: null,
}
},
computed: {
remote() {
return this.$store.state.learning.remote
},
local() {
return this.$store.state.learning.local
},
versions() {
if (!this.local || !Array.isArray(this.local.versions)) return []
return this.local.versions
},
versionsFiltersSelected() {
return this.$store.state.learning.versionsFiltersSelected
},
hasVersions() {
if (!this.versions) return false
return this.versions.length > 0
},
hasTmpVersion() {
return Util.isNotEmptyObj(this.version)
},
checklists() {
return this.$store.state.learning.checklists || null
},
checklistsSelected: {
get() {
return this.$store.state.learning.checklistsSelected
},
set(value) {
this.$store.commit('learning/SELECT_CHECKLIST', value)
},
},
canEdit() {
return (
this.$route.query.edit === null &&
(this.$store.getters.isAdmin || this.$store.getters.isOperator)
)
},
formTitle() {
return this.editedIndex === -1 ? 'Nieuwe versie' : 'Bewerken versie'
},
editModeComputed() {
return this.versionEditMode && this.editMode
},
checklist_summary() {
let totalChecklists = 0
this.checklists.forEach((category) => {
totalChecklists += category.items.length
})
return `${this.checklistsSelected.length} van ${totalChecklists} voltooid`
},
// computedDateFormattedStart() {
// return this.formatDate(this.editedItem.release_start)
// },
// computedDateFormattedEnd() {
// return this.formatDate(this.editedItem.release_end)
// },
computedDateFormattedStart: {
get() {
return this.formatDate(this.editedItem.release_start)
},
set(value) {
this.editedItem.release_start = value
// if (this.isValidDate(value)) {
// this.editedItem.release_start = value
// }
},
},
computedDateFormattedEnd: {
get() {
return this.formatDate(this.editedItem.release_end)
},
set(value) {
this.editedItem.release_end = value
},
},
},
watch: {
dialogVersion(val) {
val || this.close()
},
// startData(val) {
// console.log(': val', val)
// if (this.isValidDate(val)) {
// this.editedItem.release_start = this.parseDate(val)
// }
// },
},
methods: {
validate() {
if (!this.$refs.form.validate()) return
this.save()
},
resetVersionsFilters() {
this.versionEditMode = true
this.$store.commit('learning/RESET_CHECKLIST_SELECTED')
this.$store.commit('learning/RESET_VERSIONS_FILTERS')
},
formatDate(date) {
if (!date) return null
const [year, month, day, time] = date.replace(' ', '-').split('-')
return `${day}/${month}/${year}`
},
parseDate(date) {
if (!date || !this.isValidDate(date)) return null
const [day, month, year] = date.split('/')
if (day && day.length < 2) return null
if (month && month.length < 2) return null
if (year && year.length < 4) return null
return `${year}-${month}-${day}`
},
isValidDate(date) {
return new Date(date) !== 'Invalid Date' && !isNaN(new Date(date))
},
showChecklistInfo(checklistId) {
if (
!this.editedItem ||
!this.editedItem.checklists ||
!this.editedItem.checklists.length > 0
)
return null
const checklistFound = this.editedItem.checklists.find(
(checklist) => checklist.checklist_id === checklistId
)
if (!checklistFound) return null
return `${checklistFound.created_at}, ${checklistFound.user.fullName}`
},
setVersionFiltersAndChecklist(item) {
// Iterate item.filters and set in store versionsFiltersSelected filter_name: filter_item_id
item.filters.forEach(async (f) => {
const filterItem = this.$store.getters.getFilterItemById(
f.filter_item_id
)
await this.$store.commit('learning/UPDATE_FILTERS', {
filterId: filterItem.filter_id,
target: 'versions',
value: f.filter_item_id,
})
})
const arrayChecklistIdsSelected = []
item.checklists.forEach(async (checklist) => {
arrayChecklistIdsSelected.push(checklist.checklist_id)
this.$store.commit(
'learning/SET_CHECKED_CHECKLISTS',
arrayChecklistIdsSelected
)
})
this.editedIndex = this.versions.indexOf(item)
this.editedItem = Object.assign({}, item)
this.$forceUpdate()
},
getFilterItem(filterTitle, version) {
const filter = this.$store.getters.getFilterByTitle(filterTitle)
const arrayTmp = []
if (!version.filters) return {}
version.filters.forEach((f) => {
const filterItemTmp = this.$store.getters.getFilterItemById(
f.filter_item_id
)
arrayTmp.push(filterItemTmp)
})
const filterItem = arrayTmp.find((e) => e.filter_id === filter.id)
return filterItem || {}
},
viewItem(item) {
this.versionEditMode = false
this.setVersionFiltersAndChecklist(item)
this.dialogVersion = true
},
editItem(item) {
if (!this.editMode) return
this.versionEditMode = true
this.setVersionFiltersAndChecklist(item)
this.dialogVersion = true
// this.$refs.input.focus()
},
async deleteItem(item) {
this.$nextTick(() => this.$nuxt.$loading.start())
if (!item.id) {
this.$notifier.showMessage({
content: `No version to delete selected`,
color: 'error',
icon: 'icon-message',
})
}
try {
const response = await this.$axios.delete(`/versions/${item.id}`)
this.$store.commit('learning/DELETE_VERSION_LOCAL', item)
this.dialogDelete = false
this.dialogVersion = false
this.$nuxt.$loading.finish()
this.$notifier.showMessage({
content: `Version deleted`,
color: 'success',
icon: 'mdi-delete',
})
} catch (error) {
this.$nuxt.$loading.finish()
this.$notifier.showMessage({
content: `Error trying to delete the selected version`,
color: 'error',
icon: 'mdi-delete',
})
}
},
close() {
this.dialogVersion = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
this.$refs.card.$el.scrollIntoView(true)
})
},
setFiltersSelectedInEditedItem() {
let versionFiltersItemsIds = []
for (const [key, value] of Object.entries(this.versionsFiltersSelected)) {
versionFiltersItemsIds.push(value)
}
this.editedItem['filter_items'] = versionFiltersItemsIds
},
setChecklistsSelectedInEditedItem() {
this.editedItem['checklists_selected'] = [
...this.$store.state.learning.checklistsSelected,
]
},
async save() {
this.$nextTick(() => {
this.$refs.card.$el.scrollIntoView(true)
})
this.loading = true
this.$nextTick(() => this.$nuxt.$loading.start())
// Edit mode
if (this.editedIndex > -1) {
try {
this.editedItem.learning_product_id = this.local.id
this.setFiltersSelectedInEditedItem()
this.setChecklistsSelectedInEditedItem()
const response = await this.$axios.post('/versions', this.editedItem)
this.$store.commit('learning/EDIT_VERSION_LOCAL', {
index: this.editedIndex,
version: response.data,
})
this.dialogVersion = false
this.$notifier.showMessage({
content: `Version stored`,
color: 'success',
icon: 'icon-checkmark',
})
} catch (error) {
console.log('save -> error', error)
this.dialogVersion = false
this.$nuxt.$loading.finish()
this.$notifier.showMessage({
content: `Error trying to store the version`,
color: 'error',
icon: 'mdi-delete',
})
}
// Create new mode
} else {
try {
this.editedItem.learning_product_id = this.local.id
this.setFiltersSelectedInEditedItem()
this.setChecklistsSelectedInEditedItem()
const response = await this.$axios.post('/versions', this.editedItem)
this.dialog = false
this.$store.commit('learning/ADD_VERSION_LOCAL', response.data)
this.$notifier.showMessage({
content: `Version stored`,
color: 'success',
icon: 'icon-checkmark',
})
} catch (error) {
console.log('save -> error', error)
this.dialogVersion = false
this.$nuxt.$loading.finish()
this.$notifier.showMessage({
content: `Error trying to store the version`,
color: 'error',
icon: 'mdi-delete',
})
}
}
this.$nuxt.$loading.finish()
this.loading = false
this.close()
},
},
}
</script>
<style scoped>
.v-card >>> table,
.v-card >>> .v-data-table-header tr,
.v-card >>> .v-data-footer {
background-color: var(--v-primary-base);
}
.v-card >>> .v-data-table-header th span {
color: var(--v-tertiary-base);
}
.v-card >>> .text-start,
.v-card >>> .v-icon,
.v-dialog__content >>> .v-subheader,
.v-dialog__content >>> i {
color: var(--v-txt-base);
font-weight: bold;
}
.v-card >>> p {
margin: 0;
color: var(--v-txt-base);
}
.v-dialog .v-card {
background: var(--v-secondary-base);
}
table tr button.view {
opacity: 0;
}
table tr:hover button.view {
opacity: 1;
}
.v-dialog__content >>> .v-dialog {
border: 4px solid var(--v-secAccent-base);
max-height: 68%;
/* box-shadow: inset 0px 0px 2px 2px var(--v-secAccent-base),
inset 6px 6px 14px -14px var(--v-secAccent-base) !important; */
}
.v-dialog__content >>> .v-input__slot {
background: var(--v-primary-base);
border: 1px solid var(--v-lines-base);
}
.v-list-item >>> .v-input__slot {
background: unset;
border: none;
}
.v-list-item--active.secondary {
background-color: var(--v-primary-base) !important;
}
.v-list-item--active::before {
background-color: unset;
}
.v-list {
padding: 0;
}
.v-menu__content >>> .v-date-picker-table {
height: 242px !important;
}
</style>

View File

@@ -0,0 +1,18 @@
<template>
<v-icon
class="mx-4"
@click.stop="
$store.commit('navigation/SWITCH_RIGHT_DRAWER', {
component: 'Settings',
subMenu: 'columns',
})
"
>icon-settings</v-icon
>
</template>
<script>
export default {
}
</script>

View File

@@ -0,0 +1,65 @@
<template>
<v-select
v-model="synonymsSelected"
:items="allSynonyms"
attach
chips
multiple
item-text="title"
item-value="id"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
append-icon="icon-dropdown"
>
<template v-slot:selection="{ attrs, item, select, selected }">
<v-chip
v-bind="attrs"
:input-value="selected"
color="secondary"
@click="select"
>
<!-- close -->
<!-- @click:close="remove(item)" -->
<v-icon class="mx-2"> mdi-tag</v-icon>
<strong>{{ item.title }}</strong
>&nbsp;
</v-chip>
</template>
</v-select>
</template>
<script>
export default {
props: {
editMode: {
type: Boolean,
default: false,
},
},
computed: {
synonymsSelected: {
get() {
return this.$store.state.learning.synonymsSelected
},
set(value) {
this.$store.commit('learning/SELECT_SYNONYM', value)
},
},
allSynonyms() {
return this.$store.getters.synonyms
},
},
// methods: {
// remove(synonym) {
// if (!synonym || !synonym.id) return
// this.$store.dispatch('learning/removeSynonym', synonym.id)
// },
// },
}
</script>