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,18 @@
<template>
<v-footer app class="grey--text overline" id="admin-footer">
<span class="mr-2">3110</span>
<span>&copy; Turning thougts into pixels</span>
<!-- <span>{{ new Date().getFullYear() }}</span> -->
<v-spacer />
<v-btn text small class="grey--text">General terms</v-btn>
<v-btn text small class="grey--text">Privacy Settings</v-btn>
<v-btn text small class="grey--text">Help</v-btn>
</v-footer>
</template>
<style>
#admin-footer {
border-top: 1px solid #dfdbe3 !important;
z-index: 4
}
</style>

View File

@@ -0,0 +1,213 @@
<template>
<v-list class="pa-4">
<div class="mt-4 mb-10 d-flex">
<v-btn fab text class="mr-6" @click="$emit('close-left-menu')">
<v-icon x-small>icon-close</v-icon>
</v-btn>
<router-link :to="localePath('/manager')">
<app-logo width="180" />
</router-link>
<v-spacer />
</div>
<v-list-group
v-for="item in items"
:key="item.title"
v-model="item.active"
:prepend-icon="item.icon"
no-action
color="accent"
append-icon=""
>
<template v-slot:activator>
<v-list-item-content>
<v-list-item-subtitle v-text="item.title"></v-list-item-subtitle>
</v-list-item-content>
</template>
<template v-for="subItem in item.items">
<v-list-item
nuxt
@click
exact
:key="subItem.title"
:to="localePath(subItem.to)"
>
<v-list-item-content>
<v-list-item-subtitle v-text="subItem.title"></v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</template>
</v-list-group>
</v-list>
</template>
<script>
import Util from '~/util'
import AppLogo from '~/components/Logo'
export default {
components: {
AppLogo,
},
data() {
return {
drawer: null,
item: 1,
roles: [],
}
},
mounted() {
this.roles = this.$auth.user.roles.map(({ name }) => name);
},
methods: {
shouldShowItemByAllowRules(allowRules) {
for (const allowRule of allowRules) {
if (!this.checkAllowRule(allowRule)) {
return false;
}
}
return true;
},
checkAllowRule(allowRule) {
if (Array.isArray(allowRule)) {
return Util.findCommonValuesInArray(allowRule, this.roles);
} else if ('boolean' === typeof allowRule) {
return allowRule;
} else {
throw new Error('Unexpected allow rule');
}
},
},
computed: {
userIsOnlyMemberEditor() {
return this.$store.getters.isOnlyMemberEditor;
},
items() {
const itemsArray = [
{
icon: 'icon-learningproducts',
title: this.$t('learning.products'),
allowed: [['super_admin', 'admin', 'operator']],
items: [
{
title: this.$t('learning.all_products'),
to: '/manager/learning/',
allowed: [['super_admin', 'admin', 'operator', 'user']],
},
{
title: this.$t('learning.filters.title'),
to: '/manager/learning/filters',
allowed: [['super_admin', 'admin', 'operator']],
},
{
title: this.$t('learning.synonyms'),
to: '/manager/learning/synonyms/',
allowed: [['super_admin', 'admin', 'operator']],
},
{
title: this.$t('learning.quality_standards'),
to: '/manager/learning/quality-standards/',
allowed: [['super_admin', 'admin', 'operator']],
},
],
},
{
icon: 'icon-members',
title: this.$t('auth.account.management'),
allowed: [['super_admin']],
items: [
{
title: this.$t('learning.manage'),
to: '/manager/accounts/',
allowed: [['super_admin', 'admin']],
},
],
},
{
icon: 'icon-members',
title: this.$t('members.management'),
allowed: [
['super_admin', 'admin', 'operator', 'user'],
!this.userIsOnlyMemberEditor,
],
items: [
{
title: 'Ledencontrole',
to: '/manager/members/control',
allowed: [['super_admin', 'admin']],
},
{
title: this.$store.getters.isSuperAdminOrAdmin
? this.$t('learning.manage')
: 'Alle leden',
to: '/manager/members/',
allowed: [['super_admin', 'admin', 'operator', 'user']],
},
{
title: 'Branches',
to: '/manager/members/branches',
allowed: [['super_admin', 'admin']],
},
],
},
{
icon: 'icon-members',
title: this.$t('members.managementinfo'),
allowed: [['super_admin', 'admin', 'operator']],
items: [
{
title: this.$t('members.report'),
to: '/manager/members/report',
allowed: [['member']],
},
{
title: this.$t('members.manage'),
to: '/manager/members/managementinfo',
allowed: [['super_admin', 'admin']],
},
],
},
{
icon: 'icon-attributes',
title: 'Lidmaatschapgegevens',
allowed: [this.userIsOnlyMemberEditor],
items: [{
title: 'Bekijken',
to: '/manager/members',
}],
},
{
icon: 'icon-managementinfo',
title: 'Managementinformatie',
allowed: [this.userIsOnlyMemberEditor],
items: [{
title: this.$t('members.report'),
to: '/manager/members/report',
}],
},
];
return itemsArray.filter(item => {
if (!('allowed' in item)) {
return true;
} else if (Array.isArray(item.allowed)) {
return this.shouldShowItemByAllowRules(item.allowed);
} else {
throw new Error('Expected allow ruleset to be an array');
}
});
},
},
}
</script>

View File

@@ -0,0 +1,85 @@
<template>
<v-card class="pa-6" :color="$vuetify.theme.dark ? 'primary' : 'txt'" flat>
<v-card-title class="white--text lighten-1 font-weight-bold"
>{{ $t('general.hi') }}
{{ $auth.user.first_name | capitalize }}!</v-card-title
>
<v-card-text class="white--text text font-weight-light">
<span v-if="hasNotifications">
{{ $t('general.dashboard.current') }}
<span
class="warning--text mx-1"
@click.stop="
$store.commit('navigation/SWITCH_RIGHT_DRAWER', {
component: 'Notifications',
})
"
:style="{ cursor: 'pointer' }"
>{{ notifications.length }} {{ $t('rightMenu.notes') }}</span
>
{{ $t('general.dashboard.require') }}<br
/></span>
<span v-if="$store.getters.isMember">
Goed om je te zien. Update de lidmaatschapgegevens of bekijk de management informatie via de tegels hieronder of via het menu.</span>
<span v-else>Welkom in je dashboard van MijnGGZEcademy. Klik op onderstaande tegels of navigeer via het menu linksboven in je scherm.<br /><br />
Succes!</span>
<span v-if="$auth.user.last_login_at">
De laatste keer heb je ingelogd op
{{ formatDate($auth.user.last_login_at) }}. Fijne dag!
</span>
</v-card-text>
<v-img
class="img plant"
:src="require(`@/assets/img/plant.png`)"
contain
></v-img>
<v-img
class="img cat"
:src="require(`@/assets/img/cat.png`)"
contain
></v-img>
</v-card>
</template>
<script>
import dayjs from 'dayjs'
import nl from 'dayjs/locale/nl'
export default {
computed: {
notifications() {
return this.$store.getters.notifications
},
hasNotifications() {
return this.$store.getters.hasNotifications
},
},
methods: {
formatDate(date) {
dayjs.locale('nl')
return dayjs(date).format('dddd D MMMM YYYY').toLowerCase()
},
},
}
</script>
<style lang="scss" scoped>
.text {
width: 650px;
color: white !important;
}
.img {
position: absolute;
}
.plant {
right: -35px;
top: -21px;
}
.cat {
right: 150px;
top: -96px;
}
</style>

View File

@@ -0,0 +1,6 @@
<template class="m-12">
<span class="ml-19" style="text-transform: none !important; letter-spacing: normal !important; font-size: 12px; color: #bac7cb;"
>&copy; {{ new Date().getFullYear() }} MijnGGZEcademy is onderdeel van GGZ
Ecademy Coöperatie U.A.</span
>
</template>

View File

@@ -0,0 +1,21 @@
<template>
<v-footer app class="overline" color="primary" height="60">
<span class="grey--text"> <Copyright /> </span>
<v-spacer />
<v-btn text x-small href="https://www.ggzecademy.nl" target="_blank"
>ggzecademy.nl</v-btn
>
<v-btn text x-small href="https://portaal.ggzecademy.nl" target="_blank">centraal leerplatform</v-btn>
<v-btn text x-small href="https://support.ggzecademy.nl" target="_blank">support platform</v-btn>
</v-footer>
</template>
<script>
import Copyright from '@/components/Admin/ggz/Footer/Copyright'
export default {
components: {
Copyright,
},
}
</script>

285
components/Auth/Auth.vue Normal file
View File

@@ -0,0 +1,285 @@
<template>
<v-form ref="form" v-model="valid" lazy-validation>
<v-card tile flat class="mb-6 d-flex flex-column fill-height" width="400">
<v-card-title class="txt--text">
<h2 class="mt-4" v-if="isLogin">{{ $t('auth.login.title') }}</h2>
<h2 class="mt-4" v-if="isForgotten">
{{ $t('auth.password_forgotten.title') }}
</h2>
<h2 class="mt-4" v-if="isReset">
{{ $t('auth.password_reset.title') }}
</h2>
</v-card-title>
<v-card-text class="txt--text">
<span v-if="isLogin">{{ $t('auth.login.text') }}</span>
<span v-if="isForgotten">{{ $t('auth.password_forgotten.text') }}</span>
<span v-if="isReset">{{ $t('auth.password_reset.text') }}</span>
</v-card-text>
<v-card-text class="secondary--text" v-if="errors">
<errors-list :errors="errors" />
</v-card-text>
<v-card-text>
<v-text-field
name="email"
prepend-inner-icon="icon-user"
type="text"
color="accent"
outlined
required
lines
:placeholder="$t('auth.email')"
v-model="email"
:rules="rules.email"
/>
<v-text-field
id="password"
name="password"
:placeholder="$t('auth.password')"
prepend-inner-icon="icon-password"
type="password"
color="accent"
outlined
v-model="password"
:rules="rules.password"
v-if="isLogin || isReset"
/>
<v-text-field
id="password_confirmation"
name="password_confirmation"
placeholder="Wachtwoord bevestiging"
prepend-inner-icon="icon-password"
type="password"
outlined
v-model="passwordConfirmation"
:rules="rules.password_confirmation"
v-if="isReset"
/>
</v-card-text>
<v-card-actions>
<v-btn
block
color="accent"
rounded
large
depressed
v-if="isLogin"
@click="validate"
:disabled="!valid"
>{{ $t('auth.login.cta') }}</v-btn
>
<v-btn
block
color="accent"
rounded
large
depressed
v-if="isForgotten"
@click="validate"
:disabled="!valid"
>{{ $t('auth.password_forgotten.cta') }}</v-btn
>
<v-btn
block
color="accent"
rounded
large
depressed
v-if="isReset"
@click="validate"
:disabled="!valid || !isValidToken"
>{{ $t('auth.password_reset.cta') }}</v-btn
>
</v-card-actions>
<v-card-actions class="d-flex justify-space-between" v-if="isLogin">
<!-- <v-checkbox
on-icon="icon-selectionbox-checked"
off-icon="icon-selectionbox"
style="margin-left: 10px;"
v-model="check"
:label="$t('auth.keep_logged')"
value="secondary"
rounded
></v-checkbox> -->
<nuxt-link
class="info--text"
:to="localePath('/auth/password-forgotten')"
>{{ $t('auth.password_forgotten.question') }}</nuxt-link
>
</v-card-actions>
<!-- <v-card-actions class="d-flex justify-space-between" v-if="!isLogin">
<nuxt-link
class="info--text"
:to="localePath('/auth/login')"
>{{ $t('auth.account.question') }}</nuxt-link>
</v-card-actions> -->
</v-card>
</v-form>
</template>
<script>
import errorsList from '@/components/UI/ErrorsList/ErrorsList'
export default {
components: {
errorsList,
},
props: {
mode: {
type: String,
default: 'login',
},
},
data() {
return {
valid: true,
check: true,
email: '',
errors: null,
password: '',
passwordConfirmation: '',
rules: {
email: [
(v) => !!v || this.$t('auth.validation.email.required'),
(v) => v.length <= 50 || this.$t('auth.validation.email.max_length'),
(v) =>
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
v
) || this.$t('auth.validation.email.invalid'),
],
password: [
(v) => !!v || this.$t('auth.validation.password.required'),
(v) =>
(v && v.length >= 8) ||
this.$t('auth.validation.password.min_length'),
(v) =>
(v && v.length <= 20) ||
this.$t('auth.validation.password.max_min_length'),
],
password_confirmation: [
(v) =>
v === this.password ||
this.$t('auth.validation.password.confirmation'),
],
},
}
},
computed: {
isLogin() {
return this.mode === 'login'
},
isForgotten() {
return this.mode === 'password-forgotten'
},
isReset() {
return this.mode === 'password-reset'
},
isValidToken() {
return this.$route.query.token.length === 64 && this.$route.query.token
},
hasErrors() {
return this.backendErrors.length > 0
},
},
methods: {
async validate() {
if (!this.$refs.form.validate()) return
if (this.isLogin) await this.login()
if (this.isForgotten) await this.askToResetPassword()
if (this.isReset) await this.resetPassword()
},
// reset() {
// this.$refs.form.reset()
// },
// resetValidation() {
// this.$refs.form.resetValidation()
// },
async login() {
try {
await this.$auth.loginWith('local', {
data: {
email: this.email,
password: this.password,
},
})
this.$router.push('/manager')
} catch (error) {
this.errors = error.response.data.errors
this.$notifier.showMessage({
content: error.response.data.message,
color: 'error',
icon: 'icon-message',
})
}
},
async askToResetPassword() {
if (!this.isForgotten) return
try {
const response = await this.$axios.post('auth/password/email', {
email: this.email,
})
this.$notifier.showMessage({
content: this.$t('auth.notifications.request_accepted'),
color: 'success',
icon: 'icon-checkmark',
})
} catch (error) {
this.errors = error.response.data.errors
this.$notifier.showMessage({
content: error.response.data.error,
color: 'error',
icon: 'icon-message',
})
}
},
async resetPassword() {
if (!this.isReset) return
if (!this.isValidToken) return
try {
const response = await this.$axios.post('auth/password/reset', {
email: this.email,
password: this.password,
password_confirmation: this.passwordConfirmation,
token: this.$route.query.token,
})
this.$notifier.showMessage({
content: this.$t('auth.notifications.password_changed'),
color: 'success',
icon: 'icon-checkmark',
})
this.$router.push('/auth/login')
} catch (error) {
this.errors = error.response.data.errors
this.$notifier.showMessage({
content: error.response.data.error,
color: 'error',
icon: 'icon-message',
})
}
},
},
}
</script>
<style lang="scss" scoped>
.info--text {
text-decoration: none;
}
.v-card__title,
.v-card__text {
word-break: normal;
}
</style>

57
components/Card.vue Normal file
View File

@@ -0,0 +1,57 @@
<template>
<v-hover v-slot:default="{ hover }">
<v-card flat :elevation="hover ? 8 : 0" @click.prevent="$router.push(route)" color="primary">
<v-card-text class="d-flex justify-space-between">
<div class="d-flex">
<v-avatar
size="60"
:color="hover ? 'accent' : 'deep-orange lighten-5'"
>
<v-icon :color="hover ? 'white' : 'accent'">{{ icon }}</v-icon>
</v-avatar>
<div v-if="count" class="mx-6 txt--text">
<v-card-title class="font-weight-bold my-0 pa-0">
{{ count }}
</v-card-title>
<v-card-subtitle v-if="count" class="my-0 pa-0">
{{ label }}
</v-card-subtitle>
</div>
<div v-else class="mx-6 txt--text d-flex align-center">
<v-card-title class="my-0 pa-0 text-subtitle-1">
{{ label }}
</v-card-title>
</div>
</div>
<slot></slot>
</v-card-text>
</v-card>
</v-hover>
</template>
<script>
export default {
props: {
count: {
type: Number,
default: 0,
},
label: {
type: String,
default: '',
},
icon: {
type: String,
default: '',
},
route: {
type: String,
default: '',
},
},
}
</script>

View File

@@ -0,0 +1,66 @@
<template>
<v-toolbar dense flat>
<v-overflow-btn
:items="components"
item-text="name"
return-object
v-model="componentSelected"
label="Select Component"
hide-details
class="pa-0"
flat
></v-overflow-btn>
<v-btn text @click="addComponent">
<v-icon small>icon-add</v-icon>
</v-btn>
</v-toolbar>
</template>
<script>
export default {
props: {
id: {
type: Number
},
model: {
type: String
}
},
data() {
return {
componentSelected: '',
components: []
}
},
async mounted() {
await this.getComponents()
},
methods: {
async getComponents() {
try {
const response = await this.$axios.get(`/components`)
this.components = response.data
} catch (error) {
console.log('TCL: getComponents -> error', error)
}
},
async addComponent() {
const data = {
model: this.model,
model_id: this.id,
component_id: this.componentSelected.id
}
try {
const response = await this.$axios.post('/components/attach', data)
const modelReturned = response.data;
this.componentSelected = null
} catch (error) {
console.log('TCL: addComponent -> error', error)
}
}
}
}
</script>

View File

@@ -0,0 +1,108 @@
<template>
<v-card class="mx-auto" tile>
<v-toolbar color="error" dark flat>
<v-toolbar-title>Components</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn depressed color="white" light :to="localePath('/manager/components/create')" text>
<v-icon small>icon-add</v-icon>
</v-btn>
</v-toolbar>
<v-card-text>
<v-list>
<v-list-item-group
v-model="selected"
multiple
active-class="pink--text"
@change="syncComponents"
>
<template v-for="(item, index) in components">
<v-list-item :key="item.name" :value="item.id">
<template v-slot:default="{ active, toggle }">
<v-list-item-content>
<v-list-item-title v-text="item.name"></v-list-item-title>
</v-list-item-content>
<v-list-item-action>
<v-switch :value="isAdded(item.id)" :disabled="loading" />
</v-list-item-action>
</template>
</v-list-item>
<v-divider v-if="index + 1 < components.length" :key="index"></v-divider>
</template>
</v-list-item-group>
</v-list>
</v-card-text>
</v-card>
</template>
<script>
export default {
props: {
model_id: {
type: Number
},
model: {
type: String
},
componentsAttachedIds: {
type: Array
}
},
data() {
return {
components: [],
selected: [],
loading: true
}
},
watch: {
componentsAttachedIds() {
this.copyComponentsIds()
}
},
async mounted() {
await this.getComponents()
this.copyComponentsIds()
},
methods: {
copyComponentsIds() {
this.selected = [...this.componentsAttachedIds]
},
isAdded(componentId) {
return this.componentsAttachedIds.includes(componentId)
},
async getComponents() {
try {
this.loading = true
const response = await this.$axios.get(`/components`)
this.components = response.data
this.loading = false
} catch (error) {
console.log('TCL: getComponents -> error', error)
}
},
async syncComponents() {
this.loading = true
const data = {
model: this.model,
model_id: this.model_id,
components_ids: this.selected
}
try {
const response = await this.$axios.post('/components/sync', data)
this.$emit('reload-resource')
this.loading = false
} catch (error) {
console.log('TCL: syncComponents -> error', error)
}
}
}
}
</script>

View File

@@ -0,0 +1,58 @@
<template>
<div class="d-flex">
<small class="my-4 font-weight-light mr-4 txt--text">
<v-icon size="12" color="teal" class="icon-checkmark mr-2" v-if="showIcon"></v-icon>
{{ timestamp }}</small
>
</div>
</template>
<script>
export default {
props: {
showIcon: {
type: Boolean,
default: false,
},
},
data() {
return {
timestamp: '',
}
},
mounted() {
setInterval(this.getNow, 1000)
},
methods: {
getNow() {
const months = [
this.$t('general.date.january'),
this.$t('general.date.february'),
this.$t('general.date.march'),
this.$t('general.date.april'),
this.$t('general.date.may'),
this.$t('general.date.june'),
this.$t('general.date.july'),
this.$t('general.date.august'),
this.$t('general.date.september'),
this.$t('general.date.october'),
this.$t('general.date.november'),
this.$t('general.date.december'),
]
const now = new Date()
const day = now.getDate()
const month = now.getMonth()
const year = now.getFullYear()
let h = now.getHours() < 10 ? '0' + now.getHours() : now.getHours()
let m = now.getMinutes() < 10 ? '0' + now.getMinutes() : now.getMinutes()
const date = `${day} ${months[month]} ${year}`
const time = `${h}:${m}`
this.timestamp = `${date}`
},
},
}
</script>

View File

@@ -0,0 +1,123 @@
<template>
<div>
<v-sheet tile height="54" color="grey lighten-3" class="d-flex">
<v-btn icon class="ma-2" @click="$refs.calendar.prev()">
<v-icon x-small>icon-dropdown-left</v-icon>
</v-btn>
<v-select v-model="type" :items="types" dense outlined hide-details class="ma-2" label="type"></v-select>
<v-select
v-model="mode"
:items="modes"
dense
outlined
hide-details
label="event-overlap-mode"
class="ma-2"
></v-select>
<v-select
v-model="weekday"
:items="weekdays"
dense
outlined
hide-details
label="weekdays"
class="ma-2"
></v-select>
<v-spacer></v-spacer>
<v-btn icon class="ma-2" @click="$refs.calendar.next()">
<v-icon x-small>icon-dropdown-right</v-icon>
</v-btn>
</v-sheet>
<v-sheet height="600">
<v-calendar
ref="calendar"
v-model="value"
:weekdays="weekday"
:type="type"
:events="events"
:event-overlap-mode="mode"
:event-overlap-threshold="30"
:event-color="getEventColor"
@change="getEvents"
></v-calendar>
</v-sheet>
</div>
</template>
<script>
export default {
data: () => ({
type: 'month',
types: ['month', 'week', 'day', '4day'],
mode: 'stack',
modes: ['stack', 'column'],
weekday: [0, 1, 2, 3, 4, 5, 6],
weekdays: [
{ text: 'Sun - Sat', value: [0, 1, 2, 3, 4, 5, 6] },
{ text: 'Mon - Sun', value: [1, 2, 3, 4, 5, 6, 0] },
{ text: 'Mon - Fri', value: [1, 2, 3, 4, 5] },
{ text: 'Mon, Wed, Fri', value: [1, 3, 5] }
],
value: '',
events: [],
colors: [
'blue',
'indigo',
'deep-purple',
'cyan',
'green',
'orange',
'grey darken-1'
],
names: [
'Meeting',
'Holiday',
'PTO',
'Travel',
'Event',
'Birthday',
'Conference',
'Party'
]
}),
methods: {
getEvents({ start, end }) {
const events = []
const min = new Date(`${start.date}T00:00:00`)
const max = new Date(`${end.date}T23:59:59`)
const days = (max.getTime() - min.getTime()) / 86400000
const eventCount = this.rnd(days, days + 20)
for (let i = 0; i < eventCount; i++) {
const allDay = this.rnd(0, 3) === 0
const firstTimestamp = this.rnd(min.getTime(), max.getTime())
const first = new Date(firstTimestamp - (firstTimestamp % 900000))
const secondTimestamp = this.rnd(2, allDay ? 288 : 8) * 900000
const second = new Date(first.getTime() + secondTimestamp)
events.push({
name: this.names[this.rnd(0, this.names.length - 1)],
start: this.formatDate(first, !allDay),
end: this.formatDate(second, !allDay),
color: this.colors[this.rnd(0, this.colors.length - 1)]
})
}
this.events = events
},
getEventColor(event) {
return event.color
},
rnd(a, b) {
return Math.floor((b - a + 1) * Math.random()) + a
},
formatDate(a, withTime) {
return withTime
? `${a.getFullYear()}-${a.getMonth() +
1}-${a.getDate()} ${a.getHours()}:${a.getMinutes()}`
: `${a.getFullYear()}-${a.getMonth() + 1}-${a.getDate()}`
}
}
}
</script>

View File

@@ -0,0 +1,30 @@
<template>
<v-card class="mx-auto my-12" max-width="374">
<v-img height="250" :src="data.image" cover></v-img>
<v-card-title>{{data.title}}</v-card-title>
<v-card-text>
<v-row align="center" class="mx-0">
<v-rating :value="4.5" color="amber" dense half-increments readonly size="14"></v-rating>
<div class="grey--text ml-4">4.5 (413)</div>
</v-row>
<div class="my-4 subtitle-1 black--text">$ Italian, Cafe</div>
<div>Small plates, salads & sandwiches - an intimate setting with 12 indoor seats plus patio seating.</div>
</v-card-text>
</v-card>
</template>
<script>
export default {
props: {
data: Object
},
data: () => ({}),
methods: {}
}
</script>

View File

@@ -0,0 +1,47 @@
<template>
<v-carousel>
<v-carousel-item
v-for="(item,i) in items"
:key="i"
:src="item"
reverse-transition="fade-transition"
transition="fade-transition"
>
<v-row class="fill-height" align="center" justify="center">
<div class="display-3">Title</div>
</v-row>
<!-- <div class="display-3 pa-4">Title</div> -->
</v-carousel-item>
</v-carousel>
</template>
<script>
export default {
props: {
data: Object
},
computed: {
items() {
// Returns an array with all the imageX fields
// Iterate data where key === image__ and return the value;
let filtered_keys = (obj, filter) => {
let key,
keys = []
for (key in obj)
if (obj.hasOwnProperty(key) && filter.test(key)) keys.push(key)
return keys
}
const keys = filtered_keys(this.data, /img_carousel_/)
return keys.map(key => {
if (this.data[key]) return this.data[key]
return null
})
}
}
}
</script>

View File

@@ -0,0 +1,59 @@
<template>
<v-card class="mx-auto my-12" max-width="374">
<v-img height="250" src="https://cdn.vuetifyjs.com/images/cards/cooking.png"></v-img>
<v-card-title>Cafe Badilico</v-card-title>
<v-card-text>
<v-row align="center" class="mx-0">
<v-rating :value="4.5" color="amber" dense half-increments readonly size="14"></v-rating>
<div class="grey--text ml-4">4.5 (413)</div>
</v-row>
<div class="my-4 subtitle-1 black--text">$ Italian, Cafe</div>
<div>Small plates, salads & sandwiches an inteimate setting with 12 indoor seats plus patio seating.</div>
</v-card-text>
<v-divider class="mx-4"></v-divider>
<v-card-title>Tonight's availability</v-card-title>
<v-card-text>
<v-chip-group v-model="selection" active-class="deep-purple accent-4 white--text" column>
<v-chip>5:30PM</v-chip>
<v-chip>7:30PM</v-chip>
<v-chip>8:00PM</v-chip>
<v-chip>9:00PM</v-chip>
</v-chip-group>
</v-card-text>
<v-card-actions>
<v-btn color="deep-purple accent-4" text @click="reserve">Reserve</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
data: () => ({
loading: false,
selection: 1
}),
methods: {
reserve() {
this.loading = true
setTimeout(() => (this.loading = false), 2000)
}
}
}
</script>
<style lang="scss" scoped>
</style>

42
components/Hero/Hero.vue Normal file
View File

@@ -0,0 +1,42 @@
<template>
<v-container fluid>
<v-card tile flat>
<v-img
class="white--text"
gradient="to bottom, rgba(0,0,0,.1), rgba(0,0,0,.5)"
height="480px"
:lazy-src="require('@/assets/img/newsletter.jpg')"
>
<div class="d-flex justify-space-between flex-column fill-height">
<span class="display-1 ma-4 pa-4">In the mood for black and yellow..</span>
<div class="d-flex justify-space-between ma-4 pa-4">
<v-btn
v-for="(btn, i) in buttons"
:key="i"
tile
depressed
light
class="px-6"
color="#ffcc00"
x-large
>{{btn.label}}</v-btn>
</div>
</div>
</v-img>
</v-card>
</v-container>
</template>
<script>
export default {
data: () => ({
buttons: [
{ label: 'Sierkussens', link: '' },
{ label: 'Vloerkleden', link: '' },
{ label: 'Salontafels', link: '' },
{ label: 'Banken', link: '' },
{ label: 'Leeslampen', link: '' }
]
})
}
</script>

100
components/Info/Cookies.vue Normal file
View File

@@ -0,0 +1,100 @@
<template>
<div class="text">
<h2>Wat zijn cookies?</h2>
<p>Een cookie is een simpel, klein bestand dat met paginas van deze wordt meegestuurd. Een cookie wordt door uw browser op de harde schrijf van uw apparaat opgeslagen. Sommige van deze cookies zijn nodig om de site te laten werken; andere helpen ons de website te verbeteren en gebruiksvriendelijker te maken. De met behulp van de cookies opgeslagen informatie kan bij een volgend bezoek weer naar onze servers teruggestuurd worden.</p>
<p>Cookies hebben veel verschillende functies. Ze helpen ons bijvoorbeeld om jouw gegevens (zoals authenticatiegegevens), andere essentiële informatie en voorkeuren te onthouden. Cookies kunnen ons ook helpen om onze website te analyseren en kunnen ons in staat stellen om jou bepaalde inhoud aan te bevelen waarvan wij denken dat deze voor jou het meest relevant is.</p>
<p>GGZ Ecademy maakt ook gebruik van technieken die vergelijkbaar zijn met cookies, zoals link-tracking. Bij link-tracking worden bepaalde elementen toegevoegd aan een URL, zodat wij u kunnen herkennen als u vanuit onze e-mails doorklikt naar onze website. Hierna zullen cookies en vergelijkbare technieken samen (Cookies) worden genoemd.</p>
<p>
Bepaalde cookies bevatten persoonsgegevens. Meer informatie over welke persoonsgegevens we precies voor welke doeleinden verwerken, kun je vinden in onze
<a
href="https://ggzecademy.nl/privacy/"
>privacyverklaring</a>.
</p>
<h2>Wat voor soort cookies en andere trackingtechnologieën gebruiken wij?</h2>
<p>Over het algemeen voeren onze cookies en soortgelijke volgtechnologieën de volgende verschillende functies uit:</p>
<h3>Essentiële / functionele cookies</h3>
<p>Deze cookies zijn essentieel voor de kernactiviteit van onze website en worden automatisch ingeschakeld wanneer je de website gebruikt. We slaan je machtigingsinstelling ook op in een cookie om dit te onthouden voor toekomstige bezoeken.</p>
<p>Concreet gebruiken wij de volgende essentiële /functionele cookies:</p>
<h4>
<em>
<strong>GGZ Ecademy</strong>
</em>
</h4>
<p>
Wie: ggzecademy.nl
<br />Doel: het onthouden van een gegeven akkoord op de cookieverklaring, reeds gesloten pop-ups en waar de gebruiker zich bevindt op niveau hoofdstructuur (scholen, instellingen, lid worden).
<br />Cookies: Cookie_notice_accepted, Popup-extra-info-closed, Ggzroot
<br />Welke persoonsgegevens: met deze cookie worden geen persoonsgegevens verwerkt.
<br />Bewaartermijn: 7 dagen
<br />Type cookie: functioneel
</p>
<h3>Analytische cookies</h3>
<p>Deze cookies helpen de prestaties van de website te verbeteren en zorgen voor een betere gebruikerservaring. Dit stelt ons in staat om je een hoogwaardige ervaring te bieden door onze website en inhoud aan te passen en snel problemen te identificeren en op te lossen. We gebruiken bijvoorbeeld prestatiecookies om bij te houden welke paginas het populairst zijn, welke methode van koppeling tussen paginas het meest effectief is en om te bepalen waarom sommige paginas foutmeldingen ontvangen. Je kunt ervoor kiezen deze cookies uit te schakelen. De statistieken en overige rapportages zijn niet te herleiden tot individuele personen.</p>
<p>Concreet gebruiken wij de volgende analytische cookies:</p>
<p>
<em>
<strong>Google Analytics</strong>
</em>
<br />Wie: ggzecademy.nl
<br />Doel: het analyseren van het gebruik van onze website en diensten en het verbeteren van onze website en diensten.
<br />Cookies: _utma, _utmb, _utmc, _utmv, _ga, _gat_[tracker]
<br />Welke persoonsgegevens: wij zien enkel globale statistieken en geanonimiseerde gegevens. Google Analytics software verwerkt uw gegevens om tot deze statistieken te komen. Zo verwerkt Google Analytics o.a. van welke website u afkomstig bent, naar welke pagina of website u surft, uw klikgedrag en gebruik van onze website en gegevens over het apparaat waarmee u onze website bezoekt (zoals besturingssysteem, schermresolutie, algemene locatie (tot op stadsniveau), geslacht, taal, sessieduur, browserinformatie). Uw data wordt niet met Google gedeeld.
<br />Bewaartermijn: 24 maanden
<br />Type cookie: analytisch
</p>
<h3>Marketing en tracking cookies</h3>
<p>We werken samen met derden om advertenties op onze website weer te geven of om onze advertenties op andere sites te beheren. Indien je ons toestemming geeft voor het plaatsen van marketing en/of tracking cookies, dan kan die derde technologieën zoals cookies of vergelijkbare technologieën gebruiken om informatie te verzamelen over jouw activiteiten op deze website en andere sites om je reclame te bieden op basis van jouw browse-activiteiten en interesses. Je kan gegeven toestemming altijd intrekken door je cookie instellingen aan te passen.</p>
<p>Concreet gebruiken wij de volgende marketing en tracking cookies:</p>
<p>
<em>
<strong>Facebook &amp; Instagram</strong>
</em>
<br />Wie: ggzecademy.nl
<br />Doel: Facebook gebruikt deze cookies om conversie van posts en advertenties op onze website te meten.
<br />Cookies: fbp, fbc, locale, c_user, csm, datr, fr, lu, xs, pk, s, p, act, x-src, presence
<br />Welke persoonsgegevens: wij verwerken uw User ID, IP-adres en de datum dat u onze website heeft bezocht. De cookie wordt geplaatst via Google Tag Manager.
<br />Bewaartermijn: tot 1 jaar na uw bezoek aan onze website
<br />Type cookie: tracking pixel
</p>
<p>
<em>
<strong>LinkedIn</strong>
</em>
<br />Wie: ggzecademy.nl
<br />Doel: LinkedIn gebruikt deze cookies om conversie van posts en advertenties op onze website te meten.
<br />Cookies: lidc, bcookie, bscookie, L1c, BizoID, BizoData, BizoUserMatchHistory, BizoNetworkPartnerIndex
<br />Welke persoonsgegevens: wij verwerken uw browser ID en hoe u met advertenties van/op LinkedIn omgaat.
<br />Bewaartermijn: tot 1 jaar na uw bezoek aan onze website
<br />Type cookie: tracking
</p>
<p>
<em>
<strong>Twitter</strong>
</em>
<br />Wie: ggzecademy.nl
<br />Doel: deze cookie worden gebruikt om conversie van tweets en advertenties op onze website te meten.
<br />Cookies: muc
<br />Welke persoonsgegevens: wij verwerken enkel anonieme conversie informatie over u.
<br />Bewaartermijn: tot 2 jaar na uw bezoek aan onze website
<br />Type cookie: tracking
</p>
<h2>Derden</h2>
<p>Onze websites kunnen knoppen, widgets, hulpmiddelen of inhoud bevatten die linken naar services van andere bedrijven (bijvoorbeeld een Facebook-knop Vind ik leuk of de knop Delen). We kunnen informatie verzamelen over jouw gebruik van deze functies. Wanneer je deze knoppen, gereedschappen of inhoud ziet of gebruikt, of als je een webpagina bekijkt die deze bevat, kan bovendien bepaalde informatie uit je browser automatisch naar het andere bedrijf worden verzonden. Lees het privacybeleid van dat bedrijf voor meer informatie.</p>
<h2>Hoe kan ik cookies beheren?</h2>
<p>Als je cookies wilt accepteren of weigeren, dan kan je dat regelen via het hulpmiddel voor toestemmingsinstellingen op onze website. Deze tool wordt automatisch geladen bij het eerste bezoek van de site.</p>
<p>Wij geven u de keuze om Cookies te accepteren of weigeren (met uitzondering van functionele cookies). Daarnaast kunt u ook uw web browser instellingen aanpassen om cookies te weigeren. Hierdoor kan het zijn dat onze website niet meer volledig functioneert.</p>
<p>We stellen de essentiële / vereiste cookies in, zodat we jouw keuzes kunnen onthouden wanneer je de website de volgende keer vanuit dezelfde browser bezoekt.</p>
<p>Versie 1.0 03 oktober 2019</p>
</div>
</template>
<script>
export default {}
</script>
<style lang="scss" scoped>
.text {
text-align: justify;
text-justify: inter-word;
}
</style>

View File

@@ -0,0 +1,28 @@
<template>
<div class="text">
<p>GGZ Ecademy Coöperatie U.A. (Kamer van Koophandel: 59018062), hierna te noemen GGZ Ecademy, verleent u hierbij toegang tot www.ggzecademy.nl (de Website).</p>
<p>Op het gebruik van deze website (www.ggzecademy.nl) zijn onderstaande gebruiksvoorwaarden van toepassing. Door gebruik te maken van deze website, wordt u geacht kennis te hebben genomen van de gebruiksvoorwaarden en deze te hebben aanvaard.</p>
<h2>Gebruik van informatie</h2>
<p>GGZ Ecademy streeft ernaar op deze website altijd juiste en actuele informatie aan te bieden. Hoewel deze informatie met de grootst mogelijke zorgvuldigheid is samengesteld, staat GGZ Ecademy niet in voor de volledigheid, juistheid of actualiteit van de informatie. De juridische informatie op de website is van algemene aard en kan niet worden beschouwd als een vervangen van juridisch advies.</p>
<p>Aan de informatie kunnen geen rechten worden ontleend. GGZ Ecademy aanvaardt geen aansprakelijkheid voor schade die voortvloeit uit het gebruik van de informatie of de website en evenmin voor het niet goed functioneren van de website. Op basis van het verzenden en ontvangen van informatie via de website of via e-mail kan niet zonder meer een relatie tussen GGZ Ecademy en de gebruiker van de website ontstaan.</p>
<h2>Auteursrechten</h2>
<p>Alle rechten van intellectuele eigendom betreffende deze materialen liggen bij GGZ Ecademy of haar licentiegevers. Kopiëren, verspreiden en elk ander gebruik van deze materialen is niet toegestaan zonder schriftelijke toestemming van GGZ Ecademy, behoudens en slechts voor zover anders bepaald in regelingen van dwingend recht (zoals citaatrecht), tenzij bij specifieke materialen anders aangegeven is.</p>
<h2>E-mail</h2>
<p>GGZ Ecademy garandeert niet dat aan haar gezonden e-mails worden ontvangen of verwerkt, omdat tijdige ontvangst van e-mails niet kan worden gegarandeerd. Ook de veiligheid van het e-mailverkeer kan niet volledig worden gegarandeerd door de hieraan verbonden veiligheidsrisicos. Door zonder encryptie of wachtwoordbeveiliging per e-mail met GGZ Ecademy te corresponderen, accepteert u dit risico.</p>
<h2>Hyperlinks</h2>
<p>Deze website kan hyperlinks bevatten naar websites van derden. GGZ Ecademy heeft geen invloed op websites van derden en is niet verantwoordelijk voor de beschikbaarheid of inhoud daarvan. GGZ Ecademy aanvaardt dan ook geen aansprakelijkheid voor schade die voortvloeit uit het gebruik van websites van derden.</p>
<h2>Overig</h2>
<p>Deze disclaimer kan van tijd tot tijd wijzigen. De laatste wijziging was op 9 juni 2019.</p>
</div>
</template>
<script>
export default {}
</script>
<style lang="scss" scoped>
.text {
text-align: justify;
text-justify: inter-word;
}
</style>

338
components/Info/Privacy.vue Normal file
View File

@@ -0,0 +1,338 @@
<template>
<div class="text">
<h2>1. Wie zijn wij?</h2>
<p>GGZ Ecademy Coöperatie U.A. (GGZ Ecademy) is een Nederlands bedrijf. Wij zijn actief in de Europese Economische Ruimte (EER) en wij bewaren onze gegevens op servers in de EER, tenzij anders aangegeven.</p>
<p>
Wij verwerken uw persoonsgegevens als u gebruik maakt van onze dienstverlening, applicaties, websites en software. Dit noemen wij hierna de Dienst. In deze privacyverklaring vatten wij samen wanneer en hoe wij uw persoonsgegevens verzamelen, gebruiken en beveiligen. Wij zullen een onderscheid maken tussen persoonsgegevens die we verzamelen voor ons Centrale Leerplatform (het CLP) en de persoonsgegevens die we verzamelen voor de algemene website
<a
href="https://www.ggzecademy.nl"
>https://www.ggzecademy.nl</a>.
</p>
<h2>2. Algemeen</h2>
<p>Wij kunnen bepalingen van deze privacyverklaring wijzigen. Als wij dat doen, dan laten wij u dat weten. Toch raden wij u aan af en toe zelf te controleren of de privacyverklaring is gewijzigd.</p>
<h2>3. Welke persoonsgegevens verzamelen we en voor welke doeleinden?</h2>
<p>Er zijn een aantal manieren waarop wij uw persoonsgegevens kunnen verzamelen. In deze paragraaf leggen wij u uit welke persoonsgegevens wij van u kunnen verzamelen. De persoonsgegevens zijn gesorteerd naar de website en het CLP en vervolgens nader gesorteerd naar de verschillende verwerkingsdoelen. Naast ieder doel staat hoe lang de persoonsgegevens voor dat doel worden bewaard. Indien zich wijzigingen voordoen in wettelijke bewaartermijnen, dan gaan die voor op de bewaartermijnen die in deze privacyverklaring worden genoemd.</p>
<h3>
3.1 Persoonsgegevens die we verwerken indien u
<a
href="https://www.ggzecademy.nl"
>https://www.ggzecademy.nl</a> bezoekt
</h3>
<p>De volgende persoonsgegevens zijn rechtstreeks door ons verzameld of rechtstreeks door u aan ons aangeleverd bij uw gebruik van onze website.</p>
<p>
<u>a. Verwerkingen die noodzakelijk zijn voor leveren van onze dienstverlening en het uitvoeren van overeenkomsten</u>
</p>
<ol>
<li>
<u>Indien u deelneemt aan de redactieraad: wij bewaren de persoonsgegevens voor dit doeleinde zolang u deelneemt aan de redactieraad en tot 2 jaar nadat u de redactieraad hebt verlaten</u>
<br /> uw naam
<br /> uw privé en/of zakelijk e-mailadres (o.a. voor het opnemen van contact)
<br /> uw telefoonnummer (werk of privé)
<br /> uw werkgever
<br /> uw werk-gerelateerde functie
<br /> uw curriculum vitae
<br /> uw specialisme (optioneel)
<br /> uw toelichting in het open vlak
</li>
<li>
<u>Indien u zich aanmeldt voor een training: wij bewaren de persoonsgegevens voor dit doeleinde tot 2 jaar nadat de training is afgerond</u>
<br /> uw naam
<br /> uw e-mailadres
<br /> uw telefoonnummer
<br /> de organisatie waar u werkzaam bent
<br /> overige persoonsgegevens die u invult bij het aanmelden voor een training
</li>
</ol>
<p>Indien u gebruik wilt maken van de betreffende dienst, moet u deze persoonsgegevens verplicht aan ons verstrekken of door ons laten verzamelen. De reden hiervoor is dat wij deze gegevens nodig hebben om de dienst te leveren.</p>
<p>
<u>b. Verwerkingen voor het behartigen van onze gerechtvaardigde belangen</u>
</p>
<ol>
<li>
<u>Het verbeteren van onze dienstverlening: wij bewaren de persoonsgegevens voor dit doeleinde maximaal gedurende 24 maanden nadat zij zijn verzameld</u>
<br /> een nummer om u te identificeren op onze diensten (user- of sessie-ID)
<br /> gegevens over uw browser en apparaat waarmee u onze website bezoekt, zoals besturingssysteem en schermgrootte
<br /> hoe lang u onze website bezoekt
<br /> datum en tijdstip van bezoek
<br /> welke zoekopdracht u onze website geeft
<br /> hoe u op onze website terecht bent gekomenDe gegevens die wij verwerken voor het verbeteren van onze dienstverlening zijn in de regel niet te herleiden tot een individu.
</li>
<li>
<u>Het veilig houden van de Dienst: wij bewaren de persoonsgegevens voor dit doeleinde maximaal 6 maanden nadat zij zijn verzameld</u>
<br /> uw IP-adres
<br /> een nummer om uw apparaat te identificeren op onze diensten (device-ID)
<br /> een nummer om u te identificeren op onze diensten (user- of sessie-ID)
<br /> het door u gebruikte besturingssysteem
</li>
<li>
<u>Indien u bij ons solliciteert (ook indien u solliciteert voor de redactieraad): wij bewaren de persoonsgegevens voor dit doeleinde tot maximaal 4 weken na het eindigen van de sollicitatieprocedure, of tot maximaal 1 jaar na het eindigen van de sollicitatieprocedure indien u daarom verzoekt</u> uw naam
<br /> uw adres
<br /> uw e-mailadres
<br /> uw telefoonnummer
<br /> uw werkgever en werk-gerelateerde functie (indien u solliciteert voor de redactieraad)
<br /> uw motivatiebrief en curriculum vitae en de daarin verwerkte gegevens (zoals uw foto, opleidingen, stages, werkervaring en trainingen)
<br /> gegevens over uw beschikbaarheid
<br /> overige gegevens die u in het kader van uw sollicitatie hebt verstrekt
</li>
</ol>
<p>Wij verwerken deze persoonsgegevens op basis van een belangenafweging. Wilt u de gegevens onder b, onder 1, 2 of 3 niet verstrekken, dan vragen wij u ons dit te laten weten onder vermelding van uw motivering. Wij zullen uw motivering meenemen en nogmaals de belangenafweging maken. Indien wij na de nieuwe belangenafweging tot de conclusie komen dat u uw persoonsgegevens toch dient te verstrekken, dan kunt u onze Dienst niet gebruiken indien u weigert de persoonsgegevens te verstrekken. Meer informatie over uw rechten kunt u hieronder onder het kopje Uw rechten lezen.</p>
<p>
<u>c. Verwerking met uw toestemming</u>
</p>
<ol>
<li>
<u>Om u (op uw verzoek) te informeren over de door u gevolgde dienst of andere diensten van GGZ Ecademy door het sturen van nieuwsbrieven: wij bewaren de persoonsgegevens voor dit doeleinde totdat u zich uitschrijft</u>
<br /> uw naam
<br /> uw e-mailadres
</li>
<li>
<u>Om u op uw verzoek te informeren en te antwoorden op uw vragen: wij bewaren de persoonsgegevens voor dit doeleinde tot maximaal twee jaar nadat wij voor het laatst e-mailcontact met u hebben gehad</u>
<br /> uw naam
<br /> de organisatie waar u werkzaam bent
<br /> uw e-mailadres
<br /> overige persoonsgegevens die u invult bij het opnemen van contact met GGZ Ecademy
</li>
<li>
<u>Om advertenties over onze diensten te bieden op (andere) websites en om met advertentiepartners af te rekenen door conversie te meten: wij bewaren de persoonsgegevens voor dit doeleinde tot twee jaar nadat u voor het laatste onze website heeft bezocht</u>
<br /> uw user-ID
<br /> uw browser-ID
<br /> uw IP-adres
</li>
</ol>
<p>Wij kunnen tevens in algemene zin zien uit welke plaatsen onze bezoekers komen, welk geslacht ze hebben, tot welke inkomenscategorie ze behoren etc. Onderstaande gegevens zijn in beginsel geanonimiseerd en kunnen wij niet tot u persoonlijk herleiden. Het gaat om de volgende gegevens:</p>
<ul>
<li>uw locatie (tot op het niveau van de stad)</li>
<li>uw taalinstellingen</li>
<li>uw geslacht</li>
<li>het bedrijf waar u werkt en gegevens over uw bedrijf</li>
<li>uw functie</li>
<li>tot welke inkomenscategorie u behoort</li>
<li>wanneer u onze website heeft bezocht</li>
<li>hoe u met onze advertenties omgaat</li>
</ul>
<p>U bent niet verplicht om deze persoonsgegevens aan ons te verstrekken. Indien u de persoonsgegevens niet aan ons verstrekt, dan heeft dit geen negatieve gevolgen voor uw gebruik van de Dienst. U kunt de Dienst dus gewoon blijven gebruiken. Wij verwerken deze persoonsgegevens alleen als u daarvoor toestemming heeft gegeven en ze worden ook pas verwerkt als u daadwerkelijk toestemming heeft gegeven of zelf de persoonsgegevens heeft verstrekt.</p>
<h3>3.2 Persoonsgegevens die we verwerken indien u gebruik maakt van het CLP</h3>
<p>
De volgende persoonsgegevens zijn rechtstreeks door ons verzameld of rechtstreeks door u aan ons aangeleverd indien u het CLP gebruikt.
<br />
<u></u>
</p>
<p>
<u>a. Administratieverplichtingen (wettelijke grondslag): wij bewaren de persoonsgegevens voor dit doeleinde maximaal 10 jaar</u>
</p>
<ul>
<li>uw naam (enkel van leden)</li>
<li>uw adres (enkel van leden)</li>
<li>btw-identificatienummer (enkel van leden)</li>
</ul>
<p>Indien u gebruik wilt maken van onze Dienst, moet u deze persoonsgegevens verplicht aan ons verstrekken. De reden hiervoor is dat wij deze gegevens nodig hebben om te voldoen aan onze wettelijke verplichtingen.</p>
<p>
<u>b. Verwerkingen die noodzakelijk zijn voor het uitvoeren van een overeenkomst die u gesloten heeft (het aanbieden van het CLP),</u>
</p>
<ol>
<li>
<u>Algemeen: wij bewaren de persoonsgegevens voor dit doeleinde maximaal tot 2 jaar nadat u voor het laatst het CLP heeft gebruikt</u>
<br /> uw naam
<br /> uw privé en/of zakelijk e-mailadres (o.a. voor het opnemen van contact over het CLP)
<br /> uw wachtwoord
<br /> uw telefoonnummer (werk en privé)
<br /> uw werkgever
<br /> uw werk-gerelateerde functie
<br /> uw systeemrol (bijvoorbeeld auteur, ontwerper, begeleider, student, beheerder)
<br /> uw geboortedatum
<br /> uw leerresultaten
<br /> uw redenen voor een verzoek om toegang tot een specifieke leeromgeving te krijgen
<br /> de overige persoonsgegevens die u invult bij het opnemen van contact met GGZ Ecademy
<br /> mogelijke video-opnames bij events (wij zullen proberen u zo min mogelijk in beeld te brengen)
</li>
</ol>
<p>Indien u gebruik wilt maken van onze Dienst, moet u deze persoonsgegevens verplicht aan ons verstrekken of door ons laten verzamelen. De reden hiervoor is dat deze persoonsgegevens nodig zijn om de Dienst aan u te kunnen leveren.</p>
<p>
<u>c. Verwerkingen voor het behartigen van onze gerechtvaardigde belangen</u>
</p>
<ol>
<li>
<u>Het verbeteren van onze dienstverlening, indien u deelneemt aan een vorm van evaluatie wij bewaren de persoonsgegevens voor dit doeleinde maximaal tot 2 jaar nadat de evaluatie heeft plaatsgevonden</u>
<br /> het door u gevolgde leertraject
<br /> (overige) ingevulde informatie bij het evaluatieformulier of vermeld tijdens een evaluatiegesprek
<br /> uw werkgever
</li>
<li>
<u>Het veilig houden van het CLP: wij bewaren de persoonsgegevens voor dit doeleinde maximaal 6 maanden nadat zij zijn verzameld</u>
<br /> uw IP-adres
<br /> een nummer om uw apparaat te identificeren op onze diensten (device-ID)
<br /> een nummer om u te identificeren op onze diensten (user- of sessie-ID)
<br /> het door u gebruikte besturingssysteem
<br /> tijd, datum en plaats van inloggen
</li>
<li>
<u>Om u te informeren over andere diensten van GGZ Ecademy: wij bewaren de persoonsgegevens voor dit doeleinde totdat u zich uitschrijft</u>
<br /> uw e-mailadres (indien u dit verschaft bij het invullen van het evaluatieformulier)
</li>
</ol>
<p>Wij verwerken deze persoonsgegevens op basis van een belangenafweging. Wilt u de gegevens onder c, onder 1 of 2 niet verstrekken, dan vragen wij u ons dit te laten weten onder vermelding van uw motivering. Wij zullen uw motivering meenemen en nogmaals de belangenafweging maken. Indien wij na de nieuwe belangenafweging tot de conclusie komen dat u uw persoonsgegevens toch dient te verstrekken, dan kunt u onze Dienst niet gebruiken indien u weigert de persoonsgegevens te verstrekken. Meer informatie over uw rechten kunt u hieronder onder het kopje Uw rechten lezen.</p>
<p>
U kunt uzelf afmelden voor de verwerking genoemd onder c, onder 3 door de afmeldinginstructies te volgen die bij elke marketing e-mail zijn opgenomen. Als u zich afmeldt, dan heeft dit geen gevolg voor onze mogelijkheden om u belangrijke e-mails te sturen over de Dienst en uw account. Daarnaast heeft het geen gevolg voor onze mogelijkheid om uw persoonsgegevens te gebruiken zoals wij hebben beschreven in deze privacyverklaring.
<br />
<u></u>
</p>
<p>
<u>d. Verwerking met uw toestemming</u>
</p>
<ol>
<li>
<u>Om u (op uw verzoek) te informeren over de door u gevolgde dienst of andere diensten van GGZ Ecademy: wij bewaren de persoonsgegevens voor dit doeleinde totdat u zich uitschrijft</u>
<br /> uw naam
<br /> de organisatie waar u werkzaam bent
<br /> uw e-mailadres
</li>
<li>
<u>Om u op uw verzoek te informeren en te antwoorden op uw vragen: wij bewaren de persoonsgegevens voor dit doeleinde tot maximaal twee jaar nadat wij voor het laatst e-mailcontact met u hebben gehad</u>
<br /> uw naam
<br /> de organisatie waar u werkzaam bent
<br /> uw e-mailadres
<br /> overige persoonsgegevens die u invult bij het opnemen van contact met GGZ Ecademy
</li>
<li>
<u>Vrijwillig ingevulde informatie in onze leeromgeving(en)/op onze websites om in contact te komen met andere cursisten of om beter te kunnen communiceren (ten behoeve van social-learning): wij bewaren de persoonsgegevens voor dit doeleinde maximaal tot 2 jaar nadat u voor het laatst het CLP heeft gebruikt</u>
<br /> uw Twitter profiellink
<br /> uw Skypenaam
<br /> uw LinkedIn profiellink
<br /> uw Facebook profiellink
<br /> uw adres
<br /> uw taal
<br /> uw biografie en overige achtergrondinformatie
<br /> uw foto
<br /> door u opgegeven tags
</li>
<li>
<u>Opleidingsportfolio (gebruik van het opleidingsportfolio is geheel vrijblijvend): wij bewaren de persoonsgegevens voor dit doeleinde maximaal tot 2 jaar nadat u voor het laatst het CLP heeft gebruikt</u>
<br /> uw naam
<br /> de door u gevolgde opleidingen
<br /> overige ingevulde informatie
</li>
</ol>
<p>U bent niet verplicht om deze persoonsgegevens aan ons te verstrekken. Indien u de persoonsgegevens niet aan ons verstrekt, dan heeft dit geen negatieve gevolgen voor uw gebruik van de Dienst. U kunt de Dienst dus gewoon blijven gebruiken. Wij verwerken deze persoonsgegevens alleen als u daarvoor toestemming heeft gegeven en ze worden ook pas verwerkt als u daadwerkelijk toestemming heeft gegeven of zelf de persoonsgegevens heeft verstrekt.</p>
<h2>4. Delen van persoonsgegevens</h2>
<p>Wij verkopen of verhandelen geen persoonlijke informatie van u aan anderen. Dit is alleen anders als wij dit hebben aangegeven in deze privacyverklaring.</p>
<h3>4.1 Delen met verwerkers</h3>
<p>Wij kunnen anderen vragen om te helpen bij het verlenen van de Dienst. Het kan voorkomen dat deze derden hierdoor uw persoonsgegevens verwerken. Deze derden worden in deze privacyverklaring verder Verwerker genoemd. Met deze Verwerkers sluiten wij verwerkersovereenkomsten.</p>
<p>Wij maken gebruik van de volgende soorten Verwerkers:</p>
<ul>
<li>opslag van (persoons)gegevens en database beheer en onderhoud;</li>
<li>website en software ontwikkelaars die onze producten maken, verbeteren, uitbreiden en onderhouden;</li>
<li>onderzoeksbureaus en analytische software om onze dienstverlening te verbeteren (o.a. privacy-vriendelijk ingesteld Google Analytics waardoor geen persoonsgegevens met Google gedeeld worden);</li>
<li>hosting provider(s);</li>
<li>marketingbureau(s) om de website te monitoren;</li>
<li>aanbieders van relatiebeheer software, en;</li>
<li>beheerders van videos en opslag daarvan.</li>
</ul>
<p>In sommige gevallen kan de Verwerker uw persoonsgegevens namens ons verzamelen. Wij informeren Verwerkers dat zij persoonsgegevens die zij van ons verkrijgen alleen mogen gebruiken om het verlenen van de Dienst mogelijk te maken. Verwerkers mogen deze gegevens niet gebruiken om reclame te maken.</p>
<p>Als u zelf aanvullende informatie aan deze verwerkers verstrekt, dan zijn wij hiervoor niet verantwoordelijk. Het is verstandig om uzelf goed te informeren over de Verwerker en zijn bedrijf, voordat u uw persoonsgegevens verstrekt.</p>
<h3>4.2 Delen met uw toestemming</h3>
<p>Wij kunnen ook persoonsgegevens delen met anderen als u ons daarvoor toestemming geeft. We kunnen bijvoorbeeld met andere partijen samenwerken om u specifieke diensten of aanbiedingen aan te bieden. Als u zich inschrijft voor deze diensten of marketingaanbiedingen, dan kunnen wij uw naam of contactgegevens verstrekken als die nodig zijn om die dienst te verlenen of contact met u op te nemen.</p>
<h3>4.3 Onze wettelijke verantwoordelijkheid</h3>
<p>Wij mogen ook persoonsgegevens met derden delen indien dit:</p>
<ol>
<li>redelijkerwijs noodzakelijk of passend is om te voldoen aan wettelijke verplichtingen;</li>
<li>nodig is om te voldoen aan wettelijke verzoeken van autoriteiten;</li>
<li>nodig is om op eventuele aanspraken te reageren;</li>
<li>nodig is om de rechten, eigendom of veiligheid van ons, onze gebruikers, onze medewerkers of het publiek te</li>
<li>beschermen;</li>
<li>nodig is om onszelf of onze gebruikers te beschermen tegen frauduleus, beledigend, ongepast of onwettig gebruik van de Dienst.</li>
</ol>
<p>Wij zullen u onmiddellijk op de hoogte stellen indien een overheidsinstantie een verzoek doet dat betrekking heeft op uw persoonsgegevens, tenzij wij dit niet mogen op grond van de wet.</p>
<h3>4.4 Fusie of verkoop (deel) van de onderneming</h3>
<p>Het kan voorkomen dat wij uw persoonsgegevens openbaar maken, delen of overdragen als wij een gedeelte van ons bedrijf overdragen. Voorbeelden hiervan zijn (onderhandelingen over) een fusie, verkoop van onderdelen van de onderneming of het verkrijgen van financiering. Wij zullen uiteraard proberen de impact voor u zoveel mogelijk te beperken door persoonsgegevens alleen over te dragen als dat noodzakelijk is.</p>
<h2>5. Bescherming van persoonsgegevens</h2>
<p>Wij vinden het belangrijk om uw persoonsgegevens zorgvuldig te behandelen en beveiligingen. Wij hebben daarom passende technische en organisatorische beveiligingsmaatregelen genomen om uw persoonsgegevens te beveiligen. Wij hebben in ieder geval de volgende maatregelen genomen:</p>
<ul>
<li>We hebben fysieke en elektronische maatregelen ingevoerd die zijn ontworpen om onbevoegde toegang, verlies of misbruik van persoonsgegevens zoveel mogelijk te voorkomen.</li>
<li>We gebruiken TLS (Transport Layer Security)-technologie om transmissie van gevoelige informatie of persoonsgegevens naar ons te versleutelen, zoals accountwachtwoorden en andere identificeerbare informatie over betalingen.</li>
<li>Waar redelijkerwijs mogelijk worden back-ups van persoonsgegevens gemaakt.</li>
<li>Gevoelige informatie wordt versleuteld opgeslagen als dat mogelijk is.</li>
<li>Kwetsbaarheden in de software worden zo snel als redelijkerwijs mogelijk aangepakt.</li>
</ul>
<p>Wij willen u er wel graag op wijzen dat absolute veiligheid voor het verzenden van persoonsgegevens via het internet of het opslaan van persoonsgegevens niet altijd gegarandeerd kan worden.</p>
<h2>6. Links naar sites van derden</h2>
<p>Onze Diensten kunnen links bevatten naar andere websites en diensten. Daarnaast kan onze Dienst ook zijn voorzien van advertenties van derden. Websites en diensten van derden kunnen informatie over u bijhouden. We hebben geen controle over deze sites of hun activiteiten. Als u uw persoonsgegevens verstrekt aan derden, dan zijn wij hierbij niet betrokken. In dat geval is het privacybeleid van deze derde partij van toepassing. Wij zijn niet verantwoordelijk voor de inhoud van het privacybeleid van deze partijen en de manier waarop deze partijen omgaan met persoonsgegevens. Wij raden u aan om hun privacy- en beveiligingspraktijken en hun beleid te bestuderen voordat u persoonsgegevens aan ze verstrekt.</p>
<h2>7. Uw rechten</h2>
<p>Privacywetgeving geeft u bepaalde rechten met betrekking tot uw eigen persoonsgegevens. De rechten die wij hieronder omschrijven zijn geen absolute rechten. Wij zullen altijd een afweging maken of wij redelijkerwijs aan uw verzoek kunnen voldoen. Lukt dit niet, of zou het bijvoorbeeld ten koste gaan van de privacy van anderen, dan kunnen wij uw verzoek weigeren. Indien wij een verzoek weigeren, laten wij dit gemotiveerd weten.</p>
<p>
<u>Recht op inzage</u>
</p>
<p>U heeft het recht om op te vragen welke persoonsgegevens wij van u verwerken. U kunt ons ook vragen om inzicht te geven in de verwerkingsdoeleinden, betrokken categorieën van persoonsgegevens, de (categorieën van) ontvangers van persoonsgegevens, de bewaartermijn, de bron van de gegevens en of wij wel of niet gebruikmaken van geautomatiseerde besluitvorming.</p>
<p>U mag ook vragen om een kopie van uw persoonsgegevens die door ons worden verwerkt. Wilt u bijkomende kopieën? Dan kunnen wij daarvoor een redelijke vergoeding in rekening brengen.</p>
<p>
<u>Recht op rectificatie</u>
</p>
<p>
Indien de door ons over u verwerkte persoonsgegevens onjuist of onvolledig zijn, kan u ons verzoeken de persoonsgegevens aan te passen of aan te vullen.
<br />Indien wij uw verzoek inwilligen, zullen wij, voor zover dat redelijkerwijs mogelijk is, de partijen aan wie wij gegevens verstrekken daarover inlichten.
</p>
<p>
<u>Recht op het wissen van gegevens</u>
</p>
<p>Wilt u niet meer dat wij bepaalde persoonsgegevens van u verwerken? Dan kunt u ons verzoeken om bepaalde (of alle) persoonsgegevens over u te wissen. Of wij gegevens zullen wissen, hangt af van het verwerkingsdoeleinde. Gegevens die wij verwerken op grond van een wettelijke plicht of voor het uitvoeren van de overeenkomst, wissen wij alleen indien de persoonsgegevens niet langer noodzakelijk zijn. Indien wij gegevens verwerken op grond van het gerechtvaardigd belang, dan wissen wij gegevens alleen indien uw belang zwaarder weegt dan dat van ons. Wij zullen deze afweging maken. Verwerken we de gegevens op grond van toestemming, dan wissen wij de gegevens slechts indien u de toestemming intrekt. Hebben wij per ongeluk gegevens onrechtmatig verwerkt of schrijft een specifieke wet voor dat wij gegevens moeten wissen? Dan zullen wij de gegevens wissen. Als de gegevens nodig zijn voor de afhandeling van een gerechtelijke procedure of een (juridisch) geschil, dan wissen wij de persoonsgegevens pas na afloop van de procedure of het geschil.</p>
<p>Indien wij uw verzoek inwilligen, zullen wij, voor zover dat redelijkerwijs mogelijk is, de partijen aan wie wij gegevens verstrekken daarover inlichten.</p>
<p>
<u>Beperking van de verwerking</u>
</p>
<p>Als u de juistheid van door ons verwerkte persoonsgegevens betwist, als u meent dat wij uw persoonsgegevens onrechtmatig hebben verwerkt, indien wij de gegevens niet meer nodig hebben of indien u bezwaar heeft gemaakt tegen de verwerking, kunt u ons tevens verzoeken om de verwerking van die persoonsgegevens te beperken. Bijvoorbeeld gedurende de tijd die wij nodig hebben om uw betwisting of bezwaar te beoordelen, of indien reeds duidelijk is dat voor verdere verwerking van die persoonsgegevens geen rechtmatige grondslag (meer) bestaat, maar u er toch belang bij heeft dat wij de persoonsgegevens nog niet wissen. Als wij de verwerking van uw persoonsgegevens op uw verzoek beperken, mogen wij die gegevens nog wel gebruiken voor de afhandeling van een gerechtelijke procedure of een (juridisch) geschil.</p>
<p>
<u>Recht op overdraagbaarheid</u>
</p>
<p>Op uw verzoek kunnen wij de gegevens die wij verwerken voor het uitvoeren van de overeenkomst of op grond van uw toestemming en die automatisch verwerkt worden, overdragen aan u of een andere door u aan te wijzen partij. Een dergelijk verzoek kunt u met redelijke tussenpozen doen.</p>
<p>
<u>Geautomatiseerde individuele besluitvorming</u>
</p>
<p>Wij nemen geen besluiten die uitsluitend zijn gebaseerd op geautomatiseerde verwerkingen.</p>
<p>
<u>Recht van bezwaar en het intrekken van toestemming</u>
</p>
<p>Indien wij gegevens verwerken op grond van een gerechtvaardigd belang, mag u bezwaar maken tegen de verwerking. Indien wij gegevens verwerken op grond van uw toestemming, dan mag u die toestemming intrekken. Voor meer informatie verwijzen wij u graag naar de betreffende verwerkingsdoeleinden hierboven.</p>
<p>
<u>Het uitoefenen van uw rechten</u>
</p>
<p>
U kunt een verzoek tot inzage, correctie, verwijdering, gegevensoverdraging van uw persoonsgegevens of verzoek tot intrekking van uw toestemming of bezwaar op de verwerking van uw persoonsgegevens sturen naar
<a
href="https://mail.google.com/mail/?view=cm&amp;fs=1&amp;tf=1&amp;to=privacy@ggzecademy.nl"
target="_blank"
>privacy@ggzecademy.nl</a>.
</p>
<p>Om misbruik te voorkomen vragen wij u, bij een schriftelijk verzoek tot inzage, aanpassing of verwijdering, u adequaat te identificeren. Dit kunt u doen door een kopie van een geldig legitimatiebewijs mee te sturen. Vergeet niet om op de kopie uw BSN én pasfoto af te schermen.</p>
<p>We streven ernaar om uw verzoek, klacht of bezwaar binnen een maand te verwerken. Als het niet mogelijk is om binnen een maand een beslissing te nemen, zullen we u op de hoogte brengen van de redenen voor de vertraging en van het tijdstip waarop de beslissing naar verwachting zal worden verstrekt (niet langer dan 3 maanden na ontvangst).</p>
<p>
<u>Autoriteit Persoonsgegevens</u>
</p>
<p>
Heeft u een klacht over onze verwerking van uw persoonsgegevens? Neem dan contact met ons op. Wij helpen u natuurlijk graag verder. Mocht u er desondanks toch niet samen met ons uitkomen, dan heeft u op grond van de privacywetgeving ook het recht om een klacht in te dienen bij de privacytoezichthouder, de Autoriteit Persoonsgegevens. U kunt hiervoor contact opnemen met de Autoriteit Persoonsgegevens via
<a
href="https://autoriteitpersoonsgegevens.nl/"
target="_blank"
rel="noopener"
>https://autoriteitpersoonsgegevens.nl/</a>.
</p>
<h2>8. Contact</h2>
<p>
Als u vragen, problemen of opmerkingen heeft over deze privacyverklaring of onze gegevensverwerkingen, dan kunt u contact met ons opnemen via e-mail op
<a
href="https://mail.google.com/mail/?view=cm&amp;fs=1&amp;tf=1&amp;to=privacy@ggzecademy.nl"
target="_blank"
>privacy@ggzecademy.nl</a>.
</p>
<p>Versie 2.1 d.d. 1 oktober 2019</p>
</div>
</template>
<script>
export default {}
</script>
<style lang="scss" scoped>
.text {
text-align: justify;
text-justify: inter-word;
}
</style>

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>

View File

@@ -0,0 +1,34 @@
<template>
<span></span>
</template>
<script>
export default {
// #warning Laravel Echo has been manually disabled until broadcasting issue is fixed.
// async mounted() {
// this.$echo
// .channel('updates')
// .listen(`.updated-user-${this.$auth.user.id}`, async (e) => {
// await this.$auth.fetchUser()
// this.$notifier.showMessage({
// content: 'Notification received',
// color: 'success',
// icon: 'icon-message',
// })
// })
// this.$echo
// .channel('updates')
// .listen(`.products-catalog-updated`, async (e) => {
// await this.$store.dispatch('learning/pullProducts')
// this.$notifier.showMessage({
// content: 'Products Catalog updated',
// color: 'success',
// icon: 'icon-message',
// })
// })
// },
}
</script>

View File

@@ -0,0 +1,43 @@
<template>
<v-menu offset-y>
<template v-slot:activator="{ on }">
<v-avatar size="24" v-on="on" tile class="mx-2">
<img
:src="require(`@/assets/img/flags/svg/${currentLocale.flag}`)"
:alt="currentLocale.name"
/>
</v-avatar>
</template>
<v-list>
<v-list-item
v-for="locale in availableLocales"
:key="locale.code"
nuxt
:to="switchLocalePath(locale.code)"
exact
>
<v-list-item-avatar tile>
<img :src="require(`@/assets/img/flags/svg/${locale.flag}`)" />
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="locale.name"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-menu>
</template>
<script>
export default {
computed: {
currentLocale() {
return this.$i18n.locales.filter((i) => i.code === this.$i18n.locale)[0]
},
availableLocales() {
return this.$i18n.locales.filter((i) => i.code !== this.$i18n.locale)
},
},
}
</script>

25
components/Logo.vue Normal file
View File

@@ -0,0 +1,25 @@
<template>
<component :is="logo" />
</template>
<script>
export default {
props: {
theme: {
type: String,
},
},
computed: {
computedTheme() {
if (this.theme) return this.theme
if (this.$vuetify.theme.dark) return 'dark'
return 'light'
},
logo() {
const customerLowercase = process.env.CUSTOMER.toLowerCase()
return require(`@/assets/img/${customerLowercase}/logo-${this.computedTheme}.svg?inline`)
},
},
}
</script>

View File

@@ -0,0 +1,714 @@
<template>
<accordion-card title="Adressen">
<v-data-table
:headers="headers"
:items="addresses"
:options="options"
hide-default-footer
item-key="address"
flat
v-if="hasAddresses"
>
<template v-slot:item.type="{ item }">
{{ $t(`members.types.${item.type}`).toLowerCase() }}
</template>
<template v-slot:item.contact="{ item }">
{{
`${item.first_name_contact_person} ${item.last_name_contact_person}`
}}
</template>
<template v-slot:item.address="{ item }">{{
`${item.address ? item.address : ''} ${
item.postal ? ', ' + item.postal : ''
} ${item.city ? item.city : ''}`
}}</template>
<template v-slot:item.profiel="{ item }">
<v-icon :color="!item.approved_at ? 'accent' : 'success'">
{{
!item.approved_at ? 'mdi-alert-circle-outline' : 'icon-checkmark'
}}
</v-icon>
</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)"
v-if="$store.getters.hasCharges"
>{{ $t('general.view') }}</v-btn
>
<v-menu offset-y v-if="editMode && $store.getters.isSuperAdminOrAdmin">
<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="editModeComputed"
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_addressmembers_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="dialogAddresses"
max-width="75%"
v-if="$store.getters.isSuperAdminOrAdmin"
>
<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="addressEditMode = true"
>
<v-icon x-small class="mx-4">icon-add</v-icon>Nieuw adres toevoegen
</v-btn>
</template>
<v-form ref="form" v-model="valid" lazy-validation>
<v-card ref="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="6">
<v-subheader class="txt--text font-weight-black"
>Type</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-select
v-model="editedItem.type"
:items="types"
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-if="isCreatedModeAddress"
:rules="rules.type"
required
error
/>
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
disabled
:flat="!editModeComputed"
v-model="editedItem.type"
v-else
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Onder vermelding van</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.indicating"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Aanhef</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.title_contact_person"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Voorletters</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.initials_contact_person"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>T.a.v. voornaam</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.first_name_contact_person"
:rules="rules.first_name"
required
error
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Tussenvoegsel</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.middle_name_contact_person"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>T.a.v. achternaam</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.last_name_contact_person"
:rules="rules.last_name"
required
error
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Mailadres</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.email"
type="email"
:rules="rules.email"
required
error
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Functie</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.job_title_contact_person"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Telefoonnummer</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.phone"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Straatnaam</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.address"
:rules="rules.address"
required
error
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Huisnummer</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.house_number"
:rules="rules.house_number"
required
error
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Postcode</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.postal"
:rules="rules.postal"
required
error
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Plaats</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.city"
:rules="rules.city"
required
error
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Land</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.country"
></v-text-field>
</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() && save()"
v-if="
$store.getters.isSuperAdmin ||
$store.getters.isAdmin ||
isUserDelegated
"
>{{ $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 lid kiest u eerst voor 'Tussentijds
Opslaan' om deze functie te activeren.
</p>
</accordion-card>
</template>
<script>
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
export default {
components: {
accordionCard,
},
props: {
editMode: {
type: Boolean,
default: false,
},
isCreateMode: {
type: Boolean,
default: true,
},
},
data() {
return {
options: {
itemsPerPage: -1,
},
headers: [
{ text: 'type', value: 'type' },
{ text: 't.a.v.', value: 'contact' },
{ text: 'adres', value: 'address' },
{ text: 'mailadres', value: 'email' },
{ text: 'telefoonnummer', value: 'phone' },
{ text: 'profiel', value: 'profiel', sortable: false },
{ text: '', value: 'actions' },
],
typeKeys: ['main', 'visiting', 'invoice', 'other'],
addressEditMode: false,
dialogAddresses: false,
dialogDelete: false,
editedIndex: -1,
editedItem: {},
defaultItem: {},
loading: false,
rules: {
type: [(v) => !!v || 'Het type is verplicht.'],
address: [(v) => !!v || 'Straatnaam is verplicht.'],
house_number: [(v) => !!v || 'Huisnummer is verplicht.'],
postal: [
(v) => !!v || 'Postcode is verplicht.',
(v) =>
/^[1-9][0-9]{3} ?(?!sa|sd|ss)[a-z]{2}$/i.test(v) ||
'Ongeldige postcode',
],
city: [(v) => !!v || 'Plaats is verplicht.'],
first_name: [(v) => !!v || 'De voornaam is verplicht.'],
last_name: [(v) => !!v || 'De achternaam is verplicht.'],
email: [(v) => !!v || 'Het e-mailadres is verplicht.'],
},
valid: true,
}
},
computed: {
local() {
return this.$store.state.members.local
},
addresses() {
if (!this.local || !Array.isArray(this.local.addresses)) return []
return this.local.addresses
},
hasAddresses() {
if (!this.addresses) return false
return this.addresses.length > 0
},
formTitle() {
return this.editedIndex === -1 ? 'new' : 'edit'
},
isCreatedModeAddress() {
return this.editedIndex === -1
},
editModeComputed() {
return this.addressEditMode && this.editMode
},
isUserDelegated() {
return this.local.user_id === this.$store.getters.loggedInUser.id
},
types() {
return this.typeKeys.map((key) => ({
text: this.$t(`members.types.${key}`).toLowerCase(),
value: key,
}))
},
},
watch: {
dialogAddresses(val) {
val || this.close()
},
},
methods: {
close() {
this.dialogAddresses = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
this.$refs.card.$el.scrollIntoView(true)
})
},
viewItem(item) {
this.addressEditMode = false
this.editedIndex = this.addresses.indexOf(item)
this.editedItem = Object.assign({}, item)
this.$forceUpdate()
this.dialogAddresses = true
},
editItem(item) {
if (!this.editMode) return
this.addressEditMode = true
this.setItemToEdit(item)
this.dialogAddresses = true
},
setItemToEdit(item) {
this.editedIndex = this.addresses.indexOf(item)
this.editedItem = Object.assign({}, item)
this.$forceUpdate()
},
validate() {
return this.$refs.form.validate()
},
async save() {
this.$nextTick(() => {
this.$refs.card.$el.scrollIntoView(true)
})
this.loading = true
this.$nextTick(() => this.$nuxt.$loading.start())
this.dialogAddresses = false
if (this.isCreatedModeAddress) {
try {
await this.$store.dispatch('members/storeAddress', this.editedItem)
this.loading = false
} catch (error) {
console.log('save -> error', error)
this.loading = false
}
} else {
// Edit mode
try {
await this.$store.dispatch('members/storeAddress', this.editedItem)
this.loading = false
this.dialogAddresses = false
} catch (error) {
this.loading = false
console.log('save -> error', error)
}
}
this.close()
this.$nuxt.$loading.finish()
},
async deleteItem(item) {
if (!item.id) {
this.$notifier.showMessage({
content: `No address to delete selected`,
color: 'error',
icon: 'icon-message',
})
}
this.$nextTick(() => this.$nuxt.$loading.start())
try {
await this.$store.dispatch('members/deleteAddress', item)
this.dialogDelete = false
this.dialogAddresses = false
this.$nuxt.$loading.finish()
} catch (error) {
console.log('deleteItem -> error', error)
this.$nuxt.$loading.finish()
}
},
},
}
</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 >>> .v-subheader {
padding: 0px !important;
}
.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);
}
.v-menu__content >>> .v-date-picker-table {
height: 285px !important;
}
table tr button.view {
opacity: 0;
}
table tr:hover button.view {
opacity: 1;
}
</style>

View File

@@ -0,0 +1,835 @@
<template>
<accordion-card :title="$t('learning.product_overview.basic')">
<v-row v-if="!isCreateMode">
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">Lid nummer</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field :value="local.id" hide-details solo disabled flat />
</v-col>
<v-col cols="12" sm="12" md="3"></v-col>
</v-row>
<v-row v-if="false">
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">Gebruiker</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6" class="d-flex users">
<v-select
v-model="user_id"
:items="users"
color="blue-grey lighten-2"
item-text="fullName"
item-value="id"
hide-selected
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!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('members.fields.type') }}</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-select
v-model="type"
:items="validTypes"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
item-text="title"
item-value="id"
:append-icon="$store.getters.isMember ? '' : 'mdi-menu-down'"
/>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader class="justify-center d-flex flex-column align-start">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(type, $store.getters['members/revision'].type)
"
/>
</v-subheader>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black" append-icon>Hoofdbranche</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-select
v-model="branch_id"
:items="branches"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
item-text="title"
item-value="id"
:append-icon="$store.getters.isMember ? '' : 'mdi-menu-down'"
/>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader v-if="editMode">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
branch_id,
$store.getters['members/revision'].branch_id
)
"
/>
</v-subheader>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">Subbranche</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-select
:items="branches"
v-model="sub_branches"
attach
chips
multiple
item-text="title"
item-value="id"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
:append-icon="$store.getters.isMember ? '' : 'icon-dropdown'"
>
<template v-slot:selection="{ attrs, item, select, selected }">
<v-chip v-bind="attrs" :input-value="selected" @click="select">
<v-icon class="mx-2">mdi-source-branch</v-icon>
<strong>{{ item.title }}</strong>&nbsp;
</v-chip>
</template>
</v-select>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader class="d-flex flex-column justify-space-between align-start">
<span>
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
sub_branches,
$store.getters['members/revision'].sub_branches
)
"
/>
</span>
<span>Meerdere keuzes mogelijk</span>
</v-subheader>
</v-col>
</v-row>
<!-- {{ sub_branches }}
{{ $store.getters['members/revision'].sub_branches }}-->
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">Naam</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="informal_name"
:rules="rules.informal_name"
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
informal_name,
$store.getters['members/revision'].informal_name
)
"
>
<span
class="caption accent--text"
>{{ $store.getters['members/revision'].informal_name }}</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader>
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
informal_name,
$store.getters['members/revision'].informal_name
)
"
/>
</v-subheader>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">Juridische tenaamstelling</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="formal_name"
:rules="rules.formal_name"
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
formal_name,
$store.getters['members/revision'].formal_name
)
"
>
<span class="caption accent--text">{{ $store.getters['members/revision'].formal_name }}</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader v-if="editMode">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
formal_name,
$store.getters['members/revision'].formal_name
)
"
/>
</v-subheader>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">Aanvang lidmaatschap</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<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"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
readonly
>
<template v-slot:append>
<v-icon v-on="on">icon-events</v-icon>
</template>
</v-text-field>
</template>
<v-date-picker
v-model="start_membership"
no-title
scrollable
:disabled="!editMode"
:flat="!editMode"
locale="nl-NL"
/>
</v-menu>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader>
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
start_membership,
$store.getters['members/revision'].start_membership
)
"
/>
</v-subheader>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">Einde lidmaatschap</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-menu
ref="menuStart"
v-model="menuEnd"
: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-end-date-${Math.random()}`"
v-model="computedDateFormattedEnd"
append-icon="icon-events"
max-height="100px"
v-bind="attrs"
v-on="on"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
readonly
clearable
>
<template v-slot:append>
<v-icon v-on="on">icon-events</v-icon>
</template>
</v-text-field>
</template>
<v-date-picker
v-model="end_membership"
no-title
scrollable
:disabled="!editMode"
:flat="!editMode"
locale="nl-NL"
/>
</v-menu>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader>
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
end_membership,
$store.getters['members/revision'].end_membership
)
"
/>
</v-subheader>
</v-col>
</v-row>
<!-- <v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black"
>Contributie</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="contribution"
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
contribution,
$store.getters['members/revision'].contribution
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].contribution }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader>
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
contribution,
$store.getters['members/revision'].contribution
)
"
/>
</v-subheader>
</v-col>
</v-row>-->
<!-- <v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black"
>Factuurnummer</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="invoice_number"
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
invoice_number,
$store.getters['members/revision'].invoice_number
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].invoice_number }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader>
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
invoice_number,
$store.getters['members/revision'].invoice_number
)
"
/>
</v-subheader>
</v-col>
</v-row>-->
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">KVK-nummer</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="kvk_number"
:rules="rules.kvk_number"
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
type="number"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
kvk_number,
$store.getters['members/revision'].kvk_number
)
"
>
<span class="caption accent--text">{{ $store.getters['members/revision'].kvk_number }}</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader>
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
kvk_number,
$store.getters['members/revision'].kvk_number
)
"
/>
</v-subheader>
</v-col>
</v-row>
<!-- <v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">Website</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:rules="rules.website"
v-model="website"
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
website,
$store.getters['members/revision'].website
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].website }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader>
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
website,
$store.getters['members/revision'].website
)
"
/>
</v-subheader>
</v-col>
</v-row>-->
<v-row class="mb-14" v-if="$store.getters.isAdmin || $store.getters.isOperator">
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black">Logo</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<vue-dropzone
ref="imgDropZoneLogo"
class="my-4 cta-secondary"
id="customdropzone"
:options="dropzoneOptions"
@vdropzone-complete="afterCompleteLogo"
v-if="editMode"
/>
<v-img :src="logoComputed || noImage" :lazy-src="noImage" contain v-cloak />
<!-- <button @click="removeLogo()">
<h1>Delete logo</h1>
</button> -->
</v-col>
<v-col cols="12" sm="12" md="3">
<v-subheader>
Het logo mag maximaal 160x90px zijn. Zorg dat er minimaal 10px
witruimte om het logo zit. Upload in svg, png of jpg.
</v-subheader>
</v-col>
</v-row>
</accordion-card>
</template>
<script>
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
import fieldHasChanges from '@/components/Members/FieldHasChanges'
import vueDropzone from 'vue2-dropzone'
import 'vue2-dropzone/dist/vue2Dropzone.min.css'
export default {
components: {
accordionCard,
vueDropzone,
fieldHasChanges,
},
props: {
editMode: {
type: Boolean,
default: false,
},
isCreateMode: {
type: Boolean,
default: false,
},
users: {
type: Array,
default: [],
},
},
data() {
return {
rules: {
informal_name: [(v) => !!v || 'informal name is required'],
formal_name: [(v) => !!v || 'formal name is required'],
kvk_number: [(v) => !!v || 'kvk number is required'],
// website: [(v) => !!v || 'website is required'],
},
loading: false,
showDropzone: false,
dropzoneOptions: {
url: 'https://httpbin.org/post',
maxFilesize: 1.2, // MB
maxFiles: 1,
thumbnailWidth: 320,
thumbnailHeight: 180,
addRemoveLinks: true,
acceptedFiles: '.jpg, .jpeg, .png',
dictDefaultMessage: `<p><i class='mr-2 icon icon-image'></i> Upload afbeelding</p>`,
},
images: [],
img: {
name: '',
url: '',
file: null,
},
menuStart: false,
menuEnd: false,
}
},
computed: {
local() {
return this.$store.state.members.local
},
branches() {
return this.$store.state.members.branches
},
logo() {
return this.local.logo
},
curLogo() {
return this.$store.state.members.logo
},
logoComputed() {
if (this.$store.state.members.logo.url)
return this.$store.state.members.logo.url
if (this.local.logo) return this.local.logo.full
return null
},
noImage() {
return require(`@/assets/img/no_image.png`)
},
branch_id: {
get() {
return this.local.branch_id
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'branch_id',
value,
})
},
},
user_id: {
get() {
return this.local.user_id
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'user_id',
value,
})
},
},
sub_branches: {
get() {
return this.local.sub_branches
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'sub_branches',
value,
})
},
},
type: {
get() {
return this.local.type
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'type',
value,
})
},
},
informal_name: {
get() {
return this.local.informal_name
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'informal_name',
value,
})
},
},
formal_name: {
get() {
return this.local.formal_name
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'formal_name',
value,
})
},
},
kvk_number: {
get() {
return this.local.kvk_number
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'kvk_number',
value,
})
},
},
// website: {
// get() {
// return this.local.website
// },
// set(value) {
// this.$store.commit('members/UPDATE_FIELD', {
// field: 'website',
// value,
// })
// },
// },
start_membership: {
get() {
return this.local.start_membership
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'start_membership',
value,
})
},
},
end_membership: {
get() {
return this.local.end_membership
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'end_membership',
value,
})
},
},
validTypes() {
return this.$store.state.members.types;
},
computedDateFormattedStart() {
return this.formatDate(this.start_membership)
},
computedDateFormattedEnd: {
get() {
return this.formatDate(this.end_membership);
},
set(value) {
this.end_membership = value;
},
}
},
methods: {
areEqualInputs(input1, input2) {
return this.$store.getters['utils/areEquals'](input1, input2)
},
// async removeLogo(){
// // this.$store.commit('members/RESET_LOGO')
// this.$store.commit('members/UPDATE_LOGO', {
// name: '',
// url: '',
// file: null,
// })
// console.log("file deleted");
// },
formatDate(date) {
if (!date) return null
const [year, month, day, time] = date.replace(' ', '-').split('-')
return `${day}/${month}/${year}`
},
async afterCompleteLogo(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('members/RESET_LOGO')
return
}
this.isLoading = true
try {
const fr = new FileReader()
fr.readAsDataURL(file)
fr.addEventListener('load', () => {
this.$store.commit('members/UPDATE_LOGO', {
name: file.name,
url: fr.result,
file: file,
})
})
this.isLoading = false
} catch (error) {
console.log(error)
}
this.$refs.imgDropZoneLogo.removeFile(file)
},
},
}
</script>
<style scoped>
.v-card >>> .v-subheader {
padding: 0px !important;
}
</style>

View File

@@ -0,0 +1,685 @@
<template>
<accordion-card title="Contactpersonen">
<v-data-table
:headers="headers"
:items="contacts"
:options="options"
hide-default-footer
item-key="contact"
flat
v-if="hasContacts"
>
<template v-slot:item.contact_person="{ item }">
{{ determineNameFullByContactPersonItem(item) }}
</template>
<template v-slot:item.ledencontrole="{ item }">
<v-icon
:color="!item.approved_at ? 'accent' : 'success'"
>
{{
!item.approved_at
? 'mdi-alert-circle-outline'
: 'icon-checkmark'
}}
</v-icon>
</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)"
v-if="$store.getters.hasCharges"
>{{ $t('general.view') }}</v-btn
>
<v-menu
offset-y
v-if="
editMode && $store.getters['members/isSuperAdminAdminOrDelegated']
"
>
<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_contactsmembers_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="dialogContacts"
:persistent="editModeComputed"
max-width="75%"
v-if="$store.getters['members/isSuperAdminAdminOrDelegated']"
>
<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="contactEditMode = true"
>
<v-icon x-small class="mx-4">icon-add</v-icon>Nieuw contactperson
toevoegen
</v-btn>
</template>
<v-card ref="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="6">
<v-subheader class="txt--text font-weight-black"
>Rol</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-select
:items="roles"
v-model="editedItem.function"
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Locatie</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<!-- :items="formatAddresses(addresses)" -->
<v-select
:items="addresses"
item-text="indicating"
item-value="id"
v-model="editedItem.address_id"
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
>
<template v-slot:item="{ item, attrs, on }">
{{
`
${item.indicating || ''}
${item.house_number + ',' || ''}
${item.postal + ',' || ''}
${item.city + ',' || ''}
${item.country || ''}
`
}}
</template>
</v-select>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Aanhef</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-select
:items="salutations"
v-model="editedItem.salutation"
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Voorletters</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.initials"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Voornaam</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.firstname"
:rules="rules.firstname"
required
error
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Tussenvoegsel</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.middlename"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Achternaam</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.lastname"
:rules="rules.lastname"
required
error
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Functie</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.job_title"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Mailadres</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.email"
type="email"
required
error
:rules="[rules.email.required]"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Mailadres 2</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.email2"
type="email"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Mailadres 3</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.email3"
type="email"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Telefoonnummer</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.phone"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Mobiel</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:outlined="editModeComputed"
:solo="!editModeComputed"
:disabled="!editModeComputed"
:flat="!editModeComputed"
v-model="editedItem.mobile"
></v-text-field>
</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"
v-if="
$store.getters.isAdmin ||
$store.getters.isOperator ||
isUserDelegated
"
>{{ $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 lid kiest u eerst voor 'Tussentijds
Opslaan' om deze functie te activeren.
</p>
</accordion-card>
</template>
<script>
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
import Name from '@/util/Name'
export default {
components: {
accordionCard,
},
props: {
editMode: {
type: Boolean,
default: false,
},
isCreateMode: {
type: Boolean,
default: true,
},
},
data() {
return {
options: {
itemsPerPage: -1,
},
headers: [
{ text: 'rol', value: 'function' },
{ text: 'contactpersoon', value: 'contact_person' },
{ text: 'ledencontrole', value: 'ledencontrole', sortable: false },
{ text: '', value: 'actions' },
],
roles: [
'Bestuurder',
'Klankbordgroeplid cc',
'Klankbordgroeplid cp',
'Algemene vergadering cp',
'Algemene vergadering cc',
'Vertegenwoordiger',
],
salutations: ['Mevrouw', 'De heer'],
contactEditMode: false,
dialogContacts: false,
dialogDelete: false,
editedIndex: -1,
editedItem: {},
defaultItem: {},
loading: false,
rules: {
firstname: [(v) => !!v || 'De voornaam is verplicht.'],
lastname: [(v) => !!v || 'De achternaam is verplicht.'],
email: {
required: (value) => !!value || 'Het mailadres is verplicht.',
},
},
}
},
computed: {
local() {
return this.$store.state.members.local
},
addresses() {
if (!this.local || !Array.isArray(this.local.addresses)) return []
return this.local.addresses
},
hasAddresses() {
if (!this.addresses) return false
return this.addresses.length > 0
},
contacts() {
if (!this.local || !Array.isArray(this.local.contacts)) return []
return this.local.contacts
},
hasContacts() {
if (!this.contacts) return false
return this.contacts.length > 0
},
formTitle() {
return this.editedIndex === -1 ? 'new' : 'edit'
},
isCreatedModeContact() {
return this.editedIndex === -1
},
editModeComputed() {
return this.contactEditMode && this.editMode
},
isUserDelegated() {
return this.local.user_id === this.$store.getters.loggedInUser.id
},
},
watch: {
dialogContacts(val) {
val || this.close()
},
},
methods: {
close() {
this.dialogContacts = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
this.$refs.card.$el.scrollIntoView(true)
})
},
viewItem(item) {
this.contactEditMode = false
this.editedIndex = this.contacts.indexOf(item)
this.editedItem = Object.assign({}, item)
this.$forceUpdate()
this.dialogContacts = true
},
editItem(item) {
if (!this.editMode) return
this.setItemToEdit(item)
this.dialogContacts = true
},
setItemToEdit(item) {
this.contactEditMode = true
this.editedIndex = this.contacts.indexOf(item)
this.editedItem = Object.assign({}, item)
this.$forceUpdate()
},
async save() {
this.$nextTick(() => {
this.$refs.card.$el.scrollIntoView(true)
})
this.loading = true
this.$nextTick(() => this.$nuxt.$loading.start())
this.dialogContacts = false
if (this.isCreatedModeContact) {
try {
await this.$store.dispatch('members/storeContact', this.editedItem)
this.loading = false
} catch (error) {
console.log('save -> error', error)
this.loading = false
}
} else {
// Edit mode
try {
await this.$store.dispatch('members/storeContact', this.editedItem)
this.loading = false
this.dialogContacts = false
} catch (error) {
console.log('save -> error', error)
this.loading = false
}
}
this.close()
this.$nuxt.$loading.finish()
},
async deleteItem(item) {
if (!item.id) {
this.$notifier.showMessage({
content: `No contact to delete selected`,
color: 'error',
icon: 'icon-message',
})
}
this.$nextTick(() => this.$nuxt.$loading.start())
try {
await this.$store.dispatch('members/deleteContact', item)
this.dialogDelete = false
this.dialogContacts = false
this.$nuxt.$loading.finish()
} catch (error) {
console.log('deleteItem -> error', error)
this.$nuxt.$loading.finish()
}
},
determineNameFullByContactPersonItem(item) {
return Name.fromContactPersonItem(item).nameFull
},
formatAddresses(addresses) {
return addresses.map((x) => this.formatAddress(x)).filter((x) => x)
},
formatAddress(address) {
const addressParts = [
address.indicating || '',
address.house_number || '',
address.postal || '',
address.city || '',
address.country || '',
]
return addressParts
.filter((x) => x)
.map((x) => x.trim())
.join(', ')
.trimRight()
},
},
}
</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 >>> .v-subheader {
padding: 0px !important;
}
.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);
}
.v-menu__content >>> .v-date-picker-table {
height: 285px !important;
}
table tr button.view {
opacity: 0;
}
table tr:hover button.view {
opacity: 1;
}
</style>

View File

@@ -0,0 +1,438 @@
<template>
<accordion-card title="Contributie">
<v-data-table
:headers="headers"
:items="contributions"
:options="options"
sort-by="year"
sort-desc
hide-default-footer
item-key="accreditation"
flat
v-if="hasContributions"
>
<template v-slot:item.contribution="{ item }"> {{new Intl.NumberFormat().format(item.contribution)}},00</template>
<template v-slot:item.profiel="{ item }">
<v-icon
:color="!item.approved_at ? 'accent' : 'success'"
>{{
!item.approved_at
? 'mdi-alert-circle-outline'
: 'icon-checkmark'
}}</v-icon
>
</template>
<template v-slot:item.actions="{ item }">
<v-menu
offset-y
v-if="editMode && $store.getters.isSuperAdminOrAdmin"
>
<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="editMode"
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_contributionmembers_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="dialogContribution"
max-width="75%"
v-if="editMode && $store.getters.isSuperAdminOrAdmin"
>
<template v-slot:activator="{ on, attrs }">
<v-btn
v-if="editMode && ($store.getters.isSuperAdmin || $store.getters.isAdmin)"
class="my-10 cta-secondary"
block
depressed
min-height="60px"
:disabled="isCreateMode"
v-on="on"
>
<v-icon x-small class="mx-4">icon-add</v-icon>
Voeg jaartal toe</v-btn
>
</template>
<v-card ref="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="6">
<v-subheader class="txt--text font-weight-black"
>Jaar</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-select
:items="yearsComputed"
v-model="editedItem.year"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode || !$store.getters.isSuperAdminOrAdmin"
:flat="!editMode"
v-if="isCreatedModeContribution"
/>
<v-text-field
v-model="editedItem.year"
:outlined="editMode"
:solo="!editMode"
disabled
:flat="!editMode"
v-else
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Contributie
</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="editedItem.contribution"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
type="number"
prepend-inner-icon="mdi-currency-eur"
></v-text-field>
</v-col>
</v-row>
<v-row v-if="isCreatedModeContribution && editMode && $store.getters.isSuperAdminOrAdmin">
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Pas toe op alle leden
</v-subheader>
</v-col>
<v-col class="checkbox" cols="12" sm="12" md="1">
<v-checkbox
v-model="editedItem.toAll"
:disabled="!editMode"
></v-checkbox>
</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 lid kiest u eerst voor 'Tussentijds
Opslaan' om deze functie te activeren.
</p>
</accordion-card>
</template>
<script>
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
export default {
components: {
accordionCard,
},
props: {
editMode: {
type: Boolean,
default: false,
},
isCreateMode: {
type: Boolean,
default: true,
},
},
data() {
return {
options: {
itemsPerPage: -1,
},
headers: [
{ text: 'jaar', value: 'year' },
{ text: 'contributie' , value: 'contribution' },
{ text: 'profiel', value: 'profiel', sortable: false },
{ text: '', value: 'actions' },
],
dialogContribution: false,
dialogDelete: false,
editedIndex: -1,
editedItem: {},
defaultItem: {},
loading: false,
}
},
computed: {
local() {
return this.$store.state.members.local
},
isCreatedModeContribution() {
return this.editedIndex === -1
},
formTitle() {
return this.isCreatedModeContribution ? 'Nieuw' : 'Bewerk'
},
contributions() {
if (!this.local || !Array.isArray(this.local.contributions)) return []
return this.local.contributions
},
hasContributions() {
if (!this.contributions) return false
return this.contributions.length > 0
},
years() {
const year = new Date().getFullYear()
return Array.from({ length: 51 }, (value, index) => 2010 + index)
},
yearsTaken() {
if (!this.hasContributions) return []
return this.contributions.map(({ year }) => year)
},
yearsComputed() {
return this.years.filter((y) => !this.yearsTaken.includes(y))
},
},
watch: {
dialogContribution(val) {
val || this.close()
},
},
methods: {
close() {
this.dialogContribution = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
this.$refs.card.$el.scrollIntoView(true);
})
},
editItem(item) {
if (!this.editMode) return
this.setSummaryToEdit(item)
this.dialogContribution = true
},
setSummaryToEdit(item) {
this.editedIndex = this.contributions.indexOf(item)
this.editedItem = Object.assign({}, item)
this.$forceUpdate()
},
close() {
this.dialogContribution = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
})
},
async deleteItem(item) {
if (!item.id) {
this.$notifier.showMessage({
content: `No summary to delete selected`,
color: 'error',
icon: 'icon-message',
})
}
this.$nextTick(() => this.$nuxt.$loading.start())
try {
await this.$store.dispatch('members/deleteContribution', item)
this.dialogDelete = false
this.dialogContribution = false
this.$nuxt.$loading.finish()
} catch (error) {
console.log('deleteItem -> error', error)
this.$nuxt.$loading.finish()
}
this.$forceUpdate()
},
async save() {
this.$nextTick(() => {
this.$refs.card.$el.scrollIntoView(true);
})
this.loading = true
this.$nextTick(() => this.$nuxt.$loading.start())
this.dialogContribution = false
if (this.isCreatedModeContribution) {
try {
await this.$store.dispatch(
'members/storeContribution',
this.editedItem
)
this.loading = false
} catch (error) {
console.log('save -> error', error)
this.loading = false
}
} else {
// Edit mode
try {
await this.$store.dispatch(
'members/storeContribution',
this.editedItem
)
this.dialogContribution = false
this.loading = false
} catch (error) {
console.log('save -> error', error)
this.loading = false
}
}
this.close()
this.$nuxt.$loading.finish()
},
},
}
</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 >>> .v-subheader {
padding: 0px !important;
}
.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: 285px !important;
}
</style>

View File

@@ -0,0 +1,543 @@
<template>
<accordion-card title="Werknemers">
<v-data-table
:headers="headers"
:items="summaries"
:options="options"
sort-by="year"
sort-desc
hide-default-footer
item-key="accreditation"
flat
v-if="hasSummaries"
>
<template v-slot:item.profiel="{ item }">
<v-icon :color="!item.approved_at ? 'accent' : 'success'">{{
!item.approved_at ? 'mdi-alert-circle-outline' : 'icon-checkmark'
}}</v-icon>
</template>
<template v-slot:item.actions="{ item }">
<v-menu
offset-y
v-if="
(editing && $store.getters.isSuperAdminOrAdmin) ||
(editing && $store.getters.isOnlyMemberEditor && !item.approved_at)
"
>
<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="editing"
v-model="dialogDelete"
v-if="!$store.getters.isOnlyMemberEditor"
>
<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_employeesmembers_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="dialogEmployees"
max-width="75%"
v-if="editing && $store.getters['members/isSuperAdminAdminOrDelegated']"
>
<template v-slot:activator="{ on, attrs }">
<v-btn
v-if="editing && $store.getters.isSuperAdminOrAdmin"
class="my-10 cta-secondary"
block
depressed
min-height="60px"
:disabled="isCreateMode"
v-on="on"
>
<v-icon x-small class="mx-4">icon-add</v-icon>
Voeg jaartal toe</v-btn
>
</template>
<v-form ref="form" v-model="valid" lazy-validation>
<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="6">
<v-subheader class="txt--text font-weight-black"
>Jaar</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-select
:items="yearsComputed"
v-model.number="editedItem.year"
:outlined="editing"
:solo="!editing"
:disabled="!editing || !$store.getters.isSuperAdminOrAdmin"
:flat="!editing"
v-if="isCreatedModeSummary"
:rules="rules.year"
required
error
/>
<v-text-field
v-model.number="editedItem.year"
:outlined="editing"
:solo="!editing"
disabled
:flat="!editing"
v-else
:rules="rules.year"
required
error
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Werkelijk aantal medewerkers per 31-12-{{
editedItem.year - 1 || '__'
}}</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model.number="editedItem.real_number_last_year"
:outlined="editing"
:solo="!editing"
:disabled="!editing"
:flat="!editing"
type="number"
:rules="rules.real_number_last_year"
required
error
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Schatting aantal medewerkers per 31-12-{{
editedItem.year || '__'
}}</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="editedItem.estimated_number_next_year"
:outlined="editing"
:solo="!editing"
:disabled="!editing"
:flat="!editing"
type="number"
:rules="rules.estimated_number_next_year"
required
error
></v-text-field>
</v-col>
</v-row>
<v-row
v-if="
isCreatedModeSummary &&
editing &&
$store.getters.isSuperAdminOrAdmin
"
>
<v-col cols="12" sm="12" md="6">
<v-subheader class="txt--text font-weight-black"
>Pas toe op alle leden
</v-subheader>
</v-col>
<v-col class="checkbox" cols="12" sm="12" md="1">
<v-checkbox
v-model="editedItem.toAll"
:disabled="!editing"
></v-checkbox>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="12">
<small> * Personeel in loondienst </small>
</v-col>
<v-col cols="12" sm="12" md="12">
<small
>Stuur de onderbouwing van het werknemersaantal van vorig
jaar (bijvoorbeeld een pagina uit een jaarverslag) naar
<a href="mailto:info@ggzecademy.nl"
>info@ggzecademy.nl</a
></small
>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-divider />
<v-card-actions v-if="editing">
<v-btn
class="ma-2 white--text"
color="info"
depressed
rounded
:disabled="loading"
v-if="isFormFilled"
@click="validate() && save()"
>{{
!isCreatedModeSummary && $store.getters.isSuperAdminOrAdmin
? 'Goedkeuren'
: $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 lid kiest u eerst voor 'Tussentijds
Opslaan' om deze functie te activeren.
</p>
</accordion-card>
</template>
<script>
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
export default {
components: {
accordionCard,
},
props: {
editMode: {
type: Boolean,
default: false,
},
isCreateMode: {
type: Boolean,
default: true,
},
},
data() {
return {
rules: {
year: [(v) => !!v || 'Het jaar is vereist.'],
real_number_last_year: [
(v) => +v >= 0 || 'Het werkelijk aantal werknemers is vereist.',
],
estimated_number_next_year: [
(v) => +v >= 0 || 'De schatting aantal werknemers is vereist.',
],
},
options: {
itemsPerPage: -1,
},
headers: [
{ text: 'jaar', value: 'year' },
{ text: 'opgave medewerkers', value: 'real_number_last_year' },
{
text: 'schatting medewerkers komende jaar',
value: 'estimated_number_next_year',
},
{ text: 'ledencontrole', value: 'profiel', sortable: false },
{ text: '', value: 'actions' },
],
employersEditMode: false,
dialogEmployees: false,
dialogDelete: false,
editedIndex: -1,
editedItem: {},
loading: false,
valid: true,
}
},
computed: {
editing() {
return (
this.$store.getters.isOnlyMemberEditor ||
(this.editMode && this.$store.getters.isSuperAdminOrAdmin)
)
},
local() {
return this.$store.state.members.local
},
isCreatedModeSummary() {
return this.editedIndex === -1
},
formTitle() {
return this.isCreatedModeSummary ? 'Nieuw' : 'Bewerken'
},
summaries() {
if (!this.local || !Array.isArray(this.local.summaries)) return []
// Years older than the current are made hidden for Users delegated to edit members
if (this.$store.getters.isOnlyMemberEditor) {
const yearValid = new Date().getFullYear()
const summary = this.local.summaries.find(
(summary) => summary.year === yearValid
)
return summary ? [summary] : []
}
return this.local.summaries
},
hasSummaries() {
if (!this.summaries) return false
return this.summaries.length > 0
},
years() {
const year = new Date().getFullYear()
return Array.from({ length: 51 }, (value, index) => 2010 + index)
},
yearsTaken() {
if (!this.hasSummaries) return []
return this.summaries.map(({ year }) => year)
},
yearsComputed() {
return this.years.filter((y) => !this.yearsTaken.includes(y))
},
isFormFilled() {
if (this.editedItem['year'] === null) return false
if (this.editedItem['year'] === undefined) return false
if (this.editedItem['year'] === '') return false
if (this.editedItem['real_number_last_year'] === null) return false
if (this.editedItem['real_number_last_year'] === undefined) return false
if (this.editedItem['real_number_last_year'] === '') return false
if (this.editedItem['estimated_number_next_year'] === null) return false
if (this.editedItem['estimated_number_next_year'] === undefined)
return false
if (this.editedItem['estimated_number_next_year'] === '') return false
return true
},
defaultItem() {
return {
year: this.yearsTaken.includes(new Date().getFullYear())
? this.yearsComputed[0]
: new Date().getFullYear(),
real_number_last_year: 0,
estimated_number_next_year: 0,
}
},
},
watch: {
dialogEmployees(val) {
val || this.close()
if (Object.entries(this.editedItem).length === 0) {
this.editedItem = Object.assign({}, this.defaultItem)
}
},
},
methods: {
close() {
this.dialogEmployees = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
})
},
editItem(item) {
if (!this.editing) return
this.setSummaryToEdit(item)
},
setSummaryToEdit(item) {
this.editedIndex = this.summaries.indexOf(item)
this.editedItem = Object.assign({}, item)
this.$forceUpdate()
this.dialogEmployees = true
},
async deleteItem(item) {
if (!item.id) {
this.$notifier.showMessage({
content: `No summary to delete selected`,
color: 'error',
icon: 'icon-message',
})
}
this.$nextTick(() => this.$nuxt.$loading.start())
try {
await this.$store.dispatch('members/deleteSummary', item)
this.dialogDelete = false
this.dialogEmployees = false
this.$nuxt.$loading.finish()
} catch (error) {
console.log('deleteItem -> error', error)
this.$nuxt.$loading.finish()
}
this.$forceUpdate()
},
validate() {
return this.$refs.form.validate()
},
async save() {
this.loading = true
this.$nextTick(() => this.$nuxt.$loading.start())
this.dialogEmployees = false
if (this.isCreatedModeSummary) {
try {
await this.$store.dispatch('members/storeSummary', this.editedItem)
this.loading = false
} catch (error) {
console.log('save -> error', error)
this.loading = false
}
} else {
// Edit mode
try {
await this.$store.dispatch('members/storeSummary', this.editedItem)
this.dialogEmployees = false
this.loading = false
} catch (error) {
console.log('save -> error', error)
this.loading = false
}
}
this.close()
this.$nuxt.$loading.finish()
},
},
}
</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 >>> .v-subheader {
padding: 0px !important;
}
.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: 285px !important;
}
</style>

View File

@@ -0,0 +1,5 @@
<template>
<v-avatar color="info" class="mx-2" size="26">
<v-icon dark> mdi-check </v-icon>
</v-avatar>
</template>

View File

@@ -0,0 +1,114 @@
<template>
<div>
<v-data-table
:headers="headers"
:items-per-page="10"
:items="items"
sort-by="updated_at"
:sort-desc="true"
class="pa-4 secondary"
>
<template v-slot:item.informal_name="{ item }">
<v-img
:alt="item.informal_name"
lazy-src="/images/product-placeholder.jpg"
:src="computeImage(item)"
contain
height="60px"
width="110px"
/>
{{ item.informal_name }}
</template>
<template v-slot:item.updated_at="{ item }">
{{ formatDate(item.updated_at) }}
</template>
<template v-slot:item.delegated="{ item }">
{{ findUserById(item.user_id) }}
</template>
<template v-slot:item.actions="{ item }">
<div class="view-container">
<v-btn
class="mx-4 white--text"
style="height: 100%"
:color="$vuetify.theme.dark ? 'info' : 'txt'"
rounded
depressed
nuxt
small
:to="localePath(`/manager/members/${item.slug}`)"
>{{ $t('general.view') }}</v-btn
>
</div>
</template>
</v-data-table>
</div>
</template>
<script>
import dayjs from 'dayjs'
export default {
props: {
items: {
type: Array,
required: true,
default: [],
},
users: {
type: Array,
required: true,
default: [],
},
},
data() {
return {
headers: [
{
text: 'lid',
value: 'informal_name',
},
{
text: 'bijgewerkt op',
value: 'updated_at',
},
{
text: 'door',
value: 'delegated',
},
{
text: 'aangepast',
value: 'changes',
},
{
text: '',
value: 'actions',
},
],
}
},
methods: {
formatDate(date) {
return dayjs(date).format('D MMM YYYY, HH:mm').toLowerCase()
},
computeImage(item) {
if (item.logo.thumb) return item.logo.thumb
return '/images/product-placeholder.jpg'
},
findUserById(id) {
const unassigned = 'unassigned'
if (!id) return unassigned
const user = this.users.find((u) => u.id === id)
if (user) return user.fullName
return unassigned
},
},
}
</script>

View File

@@ -0,0 +1,310 @@
<template>
<div>
<v-data-table
:headers="headers"
:items-per-page="10"
:items="members"
class="pa-4 secondary"
>
<template v-slot:item.informal_name="{ item }">
<v-img
:alt="item.informal_name"
lazy-src="/images/mijnggz-placeholder-members.png"
:src="computeImage(item)"
contain
height="60px"
width="110px"
/>
{{ item.informal_name }}
</template>
<template v-slot:item.main_branch="{ item }">
{{ getBranchTitleById(item.branch_id) }}
</template>
<template v-slot:item.start_membership="{ item }">
{{ formatDate(item.start_membership || item.created_at) }}
</template>
<template v-slot:item.updated_at="{ item }">
{{ formatDate(item.updated_at) }}
</template>
<template v-slot:item.changes="{ item }">
<v-icon
v-if="item.revision"
:color="item.revision.hasChanges ? 'accent' : 'success'"
>{{
item.revision.hasChanges
? 'mdi-alert-circle-outline'
: 'icon-checkmark'
}}</v-icon
>
</template>
<template v-slot:item.actions="{ item }">
<div class="view-container">
<v-btn
class="mx-4 white--text"
style="height: 100%"
:color="$vuetify.theme.dark ? 'info' : 'txt'"
rounded
depressed
nuxt
small
:to="localePath(`/manager/members/${item.slug}`)"
>{{ $t('general.view') }}</v-btn
>
<v-menu
offset-y
v-if="canEditMember"
>
<template v-slot:activator="{ on }">
<v-hover v-slot:default="{ hover }">
<v-btn
:color="hover ? 'info' : ''"
:outlined="hover"
depressed
fab
small
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/members/${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-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.informal_name,
})
}}
</v-card-title>
<v-card-actions>
<div class="ma-4">
<v-btn
@click="deleteMember(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>
</div>
</template>
</v-data-table>
</div>
</template>
<script>
import dayjs from 'dayjs'
export default {
props: {
members: {
type: Array,
required: true,
},
},
data() {
return {
dialog: false,
page: 1,
headers: [
{
text: '#',
value: 'id',
},
{
text: this.$t('members.table.headers.member'),
value: 'informal_name',
// sortable: false,
},
{
text: this.$t('members.table.headers.type'),
value: 'type',
},
{
text: this.$t('members.table.headers.industry'),
value: 'main_branch',
},
{
text: this.$t('members.table.headers.location'),
value: 'info_city',
},
{
text: this.$t('members.table.headers.since'),
value: 'start_membership',
},
{
text: this.$t('members.table.headers.updated'),
value: 'updated_at',
},
{
text: this.$t('members.table.headers.check'),
value: 'changes',
},
{
text: this.$t('members.table.headers.action'),
value: 'actions',
},
],
}
},
watch: {
dialog(val) {
val || this.close()
},
},
computed: {
canEditMember() {
return this.$store.getters['members/isSuperAdminAdminOrDelegated']
},
},
methods: {
close() {
this.dialog = false
},
async deleteMember(memberId) {
if (!memberId) return
this.dialog = false
await this.$store.dispatch('members/deleteMember', memberId)
this.$router.push(this.localePath('/manager/members'))
},
formatDate(date) {
return dayjs(date).format('D MMM YYYY').toLowerCase()
},
computeImage(item) {
if (item.logo.thumb) return item.logo.thumb
return '/images/mijnggz-placeholder-members.png'
},
getBranchTitleById(id) {
const branch = this.$store.getters['members/getBranchById'](id);
return branch ? branch.title : '';
},
},
}
</script>
<style scoped>
.view-container{
display:flex;
}
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);
}
.v-data-table >>> th {
white-space: nowrap;
}
.v-data-table >>> td:first-child {
padding-right: 0px !important;
}
.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,49 @@
<template>
<accordion-card title="Overige">
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black ml-10">
Leeromgeving
</v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field outlined> </v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3"> </v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3"> </v-col>
<v-col cols="12" sm="12" md="6">
<v-btn
class="cta-secondary"
block
depressed
@click=""
min-height="60px"
>
<v-icon x-small class="mx-4">icon-add</v-icon>
{{ $t('general.add') | capitalize }}</v-btn
>
</v-col>
<v-col cols="12" sm="12" md="3"> </v-col>
</v-row>
</accordion-card>
</template>
<script>
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
export default {
components: {
accordionCard,
},
props: {
editMode: {
type: Boolean,
default: false,
},
},
computed: {},
}
</script>

View File

@@ -0,0 +1,896 @@
<template>
<accordion-card title="Ledenpagina">
<v-row v-if="$store.getters.isAdmin || $store.getters.isOperator">
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text font-weight-black in_the_picture"
>Gegevens tonen</v-subheader
>
</v-col>
<v-col cols="12" sm="12" md="6" class="d-flex">
<v-switch
:disabled="!editMode"
inset
class="my-3 toggle"
v-model="show_on_website"
/>
<small class="my-4"
><strong>
{{
show_on_website
? 'Aan (Met het aanzetten van deze optie worden onderstaande gegevens op de website van GGZ Ecademy gepubliceerd. Ik verklaar daarvoor toestemming te hebben van degene wiens gegevens het betreft.)'
: 'Uit'
}}
</strong>
</small>
</v-col>
<v-col class="my-4" cols="12" sm="12" md="3">
Bij 'Uit' wordt alleen het logo en de website link getoond
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="12">
<v-subheader class="txt--text font-weight-black in_the_picture"
>Helpdesk voor cursisten</v-subheader
>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text"> Afdeling </v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
v-model="helpdesk_department"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
helpdesk_department,
$store.getters['members/revision'].helpdesk_department
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].helpdesk_department }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
helpdesk_department,
$store.getters['members/revision'].helpdesk_department
)
"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text"> Contactpersoon </v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
v-model="helpdesk_contact_person"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
helpdesk_contact_person,
$store.getters['members/revision'].helpdesk_contact_person
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].helpdesk_contact_person }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
helpdesk_contact_person,
$store.getters['members/revision'].helpdesk_contact_person
)
"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text"> Emailadres </v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
v-model="helpdesk_email"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
helpdesk_email,
$store.getters['members/revision'].helpdesk_email
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].helpdesk_email }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
helpdesk_email,
$store.getters['members/revision'].helpdesk_email
)
"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text"> Telefoonnummer </v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
v-model="helpdesk_phone"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
helpdesk_phone,
$store.getters['members/revision'].helpdesk_phone
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].helpdesk_phone }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
helpdesk_phone,
$store.getters['members/revision'].helpdesk_phone
)
"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="12">
<v-subheader class="txt--text font-weight-black in_the_picture"
>Informatie over scholing</v-subheader
>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text"> Afdeling </v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
v-model="info_department"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_department,
$store.getters['members/revision'].info_department
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].info_department }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_department,
$store.getters['members/revision'].info_department
)
"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text"> Contactpersoon </v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
v-model="info_contacteperson"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_contacteperson,
$store.getters['members/revision'].info_contacteperson
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].info_contacteperson }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_contacteperson,
$store.getters['members/revision'].info_contacteperson
)
"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text"> Emailadres </v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
v-model="info_email"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_email,
$store.getters['members/revision'].info_email
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].info_email }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_email,
$store.getters['members/revision'].info_email
)
"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text"> Telefoonnummer </v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
v-model="info_phone"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_phone,
$store.getters['members/revision'].info_phone
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].info_phone }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_phone,
$store.getters['members/revision'].info_phone
)
"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text"> Straat </v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
v-model="info_address"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_address,
$store.getters['members/revision'].info_address
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].info_address }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_address,
$store.getters['members/revision'].info_address
)
"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text"> Huisnummer </v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
v-model="info_housenumber"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_housenumber,
$store.getters['members/revision'].info_housenumber
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].info_housenumber }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_housenumber,
$store.getters['members/revision'].info_housenumber
)
"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text"> Postcode </v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
v-model="info_postal"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_postal,
$store.getters['members/revision'].info_postal
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].info_postal }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_postal,
$store.getters['members/revision'].info_postal
)
"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text"> Plaats </v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
v-model="info_city"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_city,
$store.getters['members/revision'].info_city
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].info_city }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_city,
$store.getters['members/revision'].info_city
)
"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text"> Land </v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
v-model="info_country"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_country,
$store.getters['members/revision'].info_country
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].info_country }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_country,
$store.getters['members/revision'].info_country
)
"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text"> Link </v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
v-model="info_link"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_link,
$store.getters['members/revision'].info_link
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].info_link }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
info_link,
$store.getters['members/revision'].info_link
)
"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="12">
<v-subheader class="txt--text font-weight-black in_the_picture"
>Meer informatie</v-subheader
>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-subheader class="txt--text"> Website url </v-subheader>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
:hide-details="!editMode"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
v-model="more_info_link"
>
<template
slot="append"
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
more_info_link,
$store.getters['members/revision'].more_info_link
)
"
>
<span class="caption accent--text">
{{ $store.getters['members/revision'].more_info_link }}
</span>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<field-has-changes
v-if="
$store.getters['members/revision'] &&
!areEqualInputs(
more_info_link,
$store.getters['members/revision'].more_info_link
)
"
/>
</v-col>
</v-row>
</accordion-card>
</template>
<script>
import accordionCard from '@/components/UI/AccordionCard/AccordionCard'
import fieldHasChanges from '@/components/Members/FieldHasChanges'
export default {
components: {
accordionCard,
fieldHasChanges,
},
props: {
editMode: {
type: Boolean,
default: false,
},
},
computed: {
local() {
return this.$store.state.members.local
},
show_on_website: {
get() {
return this.local.show_on_website
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'show_on_website',
value,
})
},
},
helpdesk_department: {
get() {
return this.local.helpdesk_department
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'helpdesk_department',
value,
})
},
},
helpdesk_contact_person: {
get() {
return this.local.helpdesk_contact_person
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'helpdesk_contact_person',
value,
})
},
},
helpdesk_email: {
get() {
return this.local.helpdesk_email
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'helpdesk_email',
value,
})
},
},
helpdesk_phone: {
get() {
return this.local.helpdesk_phone
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'helpdesk_phone',
value,
})
},
},
info_department: {
get() {
return this.local.info_department
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'info_department',
value,
})
},
},
info_contacteperson: {
get() {
return this.local.info_contacteperson
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'info_contacteperson',
value,
})
},
},
info_email: {
get() {
return this.local.info_email
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'info_email',
value,
})
},
},
info_phone: {
get() {
return this.local.info_phone
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'info_phone',
value,
})
},
},
info_address: {
get() {
return this.local.info_address
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'info_address',
value,
})
},
},
info_housenumber: {
get() {
return this.local.info_housenumber
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'info_housenumber',
value,
})
},
},
info_postal: {
get() {
return this.local.info_postal
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'info_postal',
value,
})
},
},
info_city: {
get() {
return this.local.info_city
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'info_city',
value,
})
},
},
info_country: {
get() {
return this.local.info_country
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'info_country',
value,
})
},
},
info_link: {
get() {
return this.local.info_link
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'info_link',
value,
})
},
},
more_info_link: {
get() {
return this.local.more_info_link
},
set(value) {
this.$store.commit('members/UPDATE_FIELD', {
field: 'more_info_link',
value,
})
},
},
},
methods: {
areEqualInputs(input1, input2) {
return this.$store.getters['utils/areEquals'](input1, input2)
},
},
}
</script>
<style scoped>
.v-card >>> .v-subheader {
padding: 0px !important;
}
</style>

View File

@@ -0,0 +1,45 @@
<template>
<section id="newsletter" class="my-0">
<img :src="require(`@/assets/img/newsletter.jpg`)" />
<img :src="require(`@/assets/img/triangle_pattern.png`)" class="pattern"/>
<div class="row">
<div class="col-12 col-md-6"></div>
<div class="col-12 col-md-4 mx-auto text">
<h2>Nieuwsbrief</h2>Je ontvangt een kortingscode van 10,- bij besteding vanaf 75,-. Deze is meteen in te wisselen.
<v-text-field outlined label="Inschrijven" append-icon="mdi-arrow-right" class="my-4"></v-text-field>
</div>
</div>
</section>
</template>
<script>
export default {}
</script>
<style lang="scss" scoped>
#newsletter {
clear: both;
position: relative;
background-color: #f5f5f5;
margin-bottom: 20px;
img {
position: absolute;
object-fit: cover;
width: 50%;
height: 100%;
left: 0px;
// z-index: 1;
}
.pattern {
right: 0px;
left: unset;
width: 100%;
}
.text {
padding-top: 100px;
padding-bottom: 100px;
// z-index: 2;
}
}
</style>

View File

@@ -0,0 +1,24 @@
<template>
<component :is="componentRendered" :data="componentProp.content" />
</template>
<script>
export default {
props: {
componentProp: {
type: Object
}
},
computed: {
componentRendered() {
return this.componentProp.component_type.name
? () =>
import(
`@/components/DynamicComponents/${this.componentProp.component_type.name}`
)
: null
}
}
}
</script>

54
components/Post/Post.vue Normal file
View File

@@ -0,0 +1,54 @@
<template>
<v-card max-width="300" class="mx-auto">
<v-list-item>
<v-list-item-content>
<v-list-item-title class="subtitle-2">{{title}}</v-list-item-title>
<v-list-item-subtitle>{{author}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-img src="https://cdn.vuetifyjs.com/images/cards/mountain.jpg" height="180"></v-img>
<v-card-text v-html="body" v-if="!noText"/>
<v-card-actions>
<v-btn nuxt :to="{ name: 'blog-slug', params: { slug, id } }">Read more</v-btn>
<v-spacer></v-spacer>
<v-btn icon>
<v-icon>icon-share</v-icon>
</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
props: {
id: {
type: Number,
required: true
},
title: {
type: String,
required: true
},
author: {
type: String,
},
body: {
type: String,
required: true
},
image: {
required: false
},
slug: {
type: String,
required: true
},
noText: {
type: Boolean,
}
}
}
</script>

View File

@@ -0,0 +1,14 @@
<template>
<div class="d-flex flex-column">
<v-sheet class="mx-4 pa-4" max-height="600" max-width="500">
</v-sheet>
</div>
</template>
<script>
export default {}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,48 @@
<template>
<v-expansion-panels flat accordion tile v-model="panel">
<v-expansion-panel v-for="filter in filtersSearchable" :key="filter.title">
<v-expansion-panel-header>{{
$t(`learning.filters.${filter.title}`) | capitalize
}}</v-expansion-panel-header>
<v-expansion-panel-content>
<universalFilterSelector
:filterTitle="filter.title"
:editMode="true"
filterType="menu"
/>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
</template>
<script>
import universalFilterSelector from '@/components/UniversalFilterSelector/UniversalFilterSelector'
export default {
components: {
universalFilterSelector,
},
computed: {
filtersSearchable() {
return this.$store.getters.filtersSearchable
},
subMenu() {
return this.$store.getters.rightDrawer.subMenu
},
panel: {
get() {
return this.filtersSearchable.findIndex(
(filter) => filter.title === this.subMenu
)
},
set(v) {},
},
},
}
</script>
<style scoped>
.v-item-group >>> .v-list-item--active.secondary {
background-color: var(--v-primary-base) !important;
}
</style>

View File

@@ -0,0 +1,165 @@
<template>
<div>
<div class="mx-10 my-4">
<div class="d-flex">
<chip-user-logged v-if="$auth.loggedIn" displayName />
<v-spacer />
<v-btn
fab
text
@click="$store.commit('navigation/SWITCH_RIGHT_DRAWER', false)"
>
<v-icon x-small>icon-close</v-icon>
</v-btn>
</div>
<v-divider class="lines" />
<h3 v-if="hasNotifications" class="my-6">
{{ $t('rightMenu.notes') }}
<span class="font-weight-light">({{ notifications.length }})</span>
</h3>
</div>
<v-list two-line v-if="hasNotifications">
<v-list-item-group v-model="selected" multiple active-class="secondary">
<template v-for="(notification, index) in notifications">
<v-divider
v-if="!index && hasNotifications"
:key="index + notification.title"
class="lines"
/>
<v-list-item
:key="`user-notification-${notification.id}`"
@click="markAsRead(notification.id)"
>
<template v-slot:default="{ active, toggle }">
<v-list-item-avatar>
<!-- <v-icon x-small v-if="active" color="accent">mdi-circle</v-icon>
<v-icon x-small v-else>mdi-circle-outline</v-icon> -->
<v-icon x-small v-if="!notification.read_at" color="accent"
>mdi-circle</v-icon
>
<v-icon x-small v-else>mdi-circle-outline</v-icon>
</v-list-item-avatar>
<v-list-item-content class="font-weight-bold">
<v-list-item-subtitle
v-text="notification.data.subject"
></v-list-item-subtitle>
<v-list-item-subtitle
class="terziary--text"
v-text="notification.data.message"
></v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
<v-list-item-action-text>{{
formatDate(notification.created_at)
}}</v-list-item-action-text>
</v-list-item-action>
</template>
</v-list-item>
<v-divider
v-if="index + 1 < notifications.length"
:key="index"
class="lines"
/>
</template>
</v-list-item-group>
</v-list>
<v-card v-else flat>
<v-card-text> No notifications </v-card-text>
</v-card>
<!-- <v-container class="text-center d-flex flex-column">
<v-btn
color="success"
@click="testNotification"
class="my-2"
v-if="$store.getters.isAdmin || $store.getters.isOperator"
>Test Notification</v-btn
>
<v-btn
color="red"
@click="testMailNotifications"
class="my-2"
v-if="$store.getters.isAdmin || $store.getters.isOperator"
>Test Email Notifications
</v-btn>
</v-container> -->
</div>
</template>
<script>
import dayjs from 'dayjs'
import PageHeader from '~/components/UI/PageHeader/PageHeader'
import chipUserLogged from '~/components/UI/ChipUserLogged/ChipUserLogged'
export default {
components: {
PageHeader,
chipUserLogged,
},
data() {
return {
selected: [2],
}
},
computed: {
notifications() {
return this.$store.getters.notifications
},
hasNotifications() {
return this.$store.getters.hasNotifications
},
},
methods: {
formatDate(date) {
return dayjs(date).format('D MMM').toLowerCase()
},
async markAsRead(notificationId) {
if (!this.$store.getters.isReadNotification(notificationId)) return
try {
const response = await this.$axios.post('notifications/mark-as-read', {
id: notificationId,
})
await this.$auth.setUser(response.data)
} catch (error) {
console.log('markAsRead -> error', error)
}
},
async delete(notificationId) {
if (!this.$store.getters.isReadNotification(notificationId)) return
try {
const response = await this.$axios.post('notifications/delete', {
id: notificationId,
})
await this.$auth.setUser(response.data)
} catch (error) {
console.log('markAsRead -> error', error)
}
},
async testNotification() {
if (!this.$auth.loggedIn) return
try {
await this.$axios.post('notifications/test')
} catch (error) {
console.log('testNotification -> error', error)
}
},
async testMailNotifications() {
if (!this.$auth.loggedIn) return
try {
await this.$axios.get('notifications/test-mail-notifications')
} catch (error) {
console.log('testNotification -> error', error)
}
},
},
}
</script>

View File

@@ -0,0 +1,156 @@
<template>
<v-expansion-panels flat accordion tile v-model="panel" multiple>
<v-expansion-panel>
<v-expansion-panel-header>Table</v-expansion-panel-header>
<v-expansion-panel-content>
<draggable v-model="columnsSorted">
<transition-group>
<div v-for="(column, i) in columnsSorted" :key="column.value">
<v-card class="mx-auto" outlined tile flat v-if="!column.fixed">
<v-card-text
class="d-flex justify-space-between font-weight-black"
>
<small class="mr-4">{{ i - 1 + ':' }}</small>
<small>{{ $t(column.text) }}</small>
<v-spacer></v-spacer>
<v-btn
icon
x-small
class="mr-4"
@click="toggleVisibility(column.value)"
>
<v-icon x-small>{{
column.display
? 'icon-visible-true'
: 'icon-visible-false'
}}</v-icon>
</v-btn>
<v-btn icon x-small class="mr-2">
<v-icon>icon-dragdrop</v-icon>
</v-btn>
</v-card-text>
</v-card>
</div>
</transition-group>
</draggable>
</v-expansion-panel-content>
</v-expansion-panel>
<v-expansion-panel>
<v-expansion-panel-header>Filters </v-expansion-panel-header>
<v-expansion-panel-content>
<draggable v-model="filters">
<transition-group>
<div v-for="(filter, i) in filters" :key="filter.title">
<v-card class="mx-auto" outlined tile flat v-if="!filter.fixed">
<v-card-text
class="d-flex justify-space-between font-weight-black"
>
<small class="mr-4">{{ i + 1 + ':' }}</small>
<small>
{{ $t(`learning.filters.${filter.title}`) }}
</small>
<v-spacer></v-spacer>
<!-- <v-btn icon x-small class="mr-4">
<v-icon>icon-visible-true</v-icon>
</v-btn> -->
<v-btn icon x-small class="mr-2">
<v-icon>icon-dragdrop</v-icon>
</v-btn>
</v-card-text>
</v-card>
<template v-if="i === 4">
<div class="my-4">Meer filters:</div>
</template>
</div>
</transition-group>
</draggable>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
</template>
<script>
import draggable from 'vuedraggable'
export default {
components: {
draggable,
},
computed: {
columns() {
return this.$store.state.learning.columns
},
subMenu() {
return this.$store.getters.rightDrawer.subMenu
},
panel: {
get() {
const submenus = ['columns', 'filters']
const index = submenus.indexOf(this.subMenu)
return [index]
},
set(v) {},
},
columnsSorted: {
get() {
return this.$store.state.learning.columnsSorted
},
async set(v) {
await this.$store.commit('learning/SORT_COLUMNS', v)
await this.storeLocallyColumnsSorted()
},
},
filters: {
get() {
return this.$store.state.learning.filters
},
async set(v) {
await this.$store.commit('learning/SORT_FILTERS', v)
await this.storeLocallyFiltersSorted()
},
},
},
methods: {
async storeLocallyColumnsSorted() {
await localStorage.setItem(
'learning_products_cols',
JSON.stringify(this.columnsSorted)
)
},
async storeLocallyFiltersSorted() {
const filtersIdsSorted = this.filters.map(({ id }) => id)
await localStorage.setItem(
'learning_products_filters',
JSON.stringify(filtersIdsSorted)
)
},
async toggleVisibility(value) {
// if no columns sorted, make a copy from columns
if (this.columnsSorted.length <= 0) {
await this.$store.dispatch('learning/setColumns')
}
// Find column index
const columnIndex = this.columnsSorted.findIndex(
(element) => element.value === value
)
// find the column to change and edit
let colsSortedCopy = [...this.columnsSorted]
colsSortedCopy[columnIndex] = {
...colsSortedCopy[columnIndex],
display: !colsSortedCopy[columnIndex].display,
}
// store in colSsorted
await this.$store.commit('learning/SORT_COLUMNS', colsSortedCopy)
// copy in localStorage
this.storeLocallyColumnsSorted()
},
},
}
</script>

View File

@@ -0,0 +1,78 @@
<template>
<v-expansion-panels flat tile v-model="panel" multiple>
<v-expansion-panel>
<v-expansion-panel-header
class="font-weight-bold"
expand-icon="icon-dropdown-up"
>{{ $t('rightMenu.my_account') }}
</v-expansion-panel-header>
<v-expansion-panel-content>
<v-list-item
nuxt
exact
:to="localePath(`/manager/accounts/${$auth.user.id}`)"
@click="$store.commit('navigation/SWITCH_RIGHT_DRAWER', false)"
>
<v-list-item-content>
<v-list-item-subtitle> Profiel bewerken </v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<!--<v-list-item
nuxt
:to="localePath('/manager/accounts')"
exact
@click="$store.commit('navigation/SWITCH_RIGHT_DRAWER', false)"
v-if="$store.getters.isSuperAdmin"
>
<v-list-item-content>
<v-list-item-subtitle>
{{ $t('rightMenu.users_manager') }}
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item>
<v-list-item-content>
<v-list-item-subtitle>
{{ $t('rightMenu.notification_manager') }}
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item> -->
<v-divider class="my-2 lines" />
<v-list-item @click="logout">
<v-list-item-content>
<v-list-item-title>
{{ $t('rightMenu.logout') }}
<!-- <localeSwitch /> -->
</v-list-item-title>
</v-list-item-content>
<v-list-item-avatar>
<v-btn text>
<v-icon small>icon-logout</v-icon>
</v-btn>
</v-list-item-avatar>
</v-list-item>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
</template>
<script>
// import localeSwitch from '~/components/LocaleSwitch/LocaleSwitch'
export default {
data: () => ({
panel: [0],
}),
// components: {
// localeSwitch,
// },
methods: {
async logout() {
await this.$auth.logout()
},
},
}
</script>

View File

@@ -0,0 +1,75 @@
<template>
<div>
<div class="searchbar">
<input
v-model="search"
@keyup.enter="query"
class="search_input"
type="text"
autofocus
name
placeholder="Search Image"
/>
<a href="#" @click="query" class="search_icon">
<v-icon>icon-search</v-icon>
</a>
</div>
</div>
</template>
<script>
export default {
middleware: 'auth',
layout: `${process.env.CUSTOMER}Admin`,
data() {
return {
search: 'value'
}
},
methods: {
query() {}
}
}
</script>
<style scoped>
@import 'https://getbootstrap.com/docs/4.0/examples/cover/cover.css';
.searchbar {
/* margin-bottom: auto;
margin-top: auto;
height: 60px; */
background-color: #353b48;
border-radius: 30px;
padding: 10px;
}
.search_input {
color: white;
border: 0;
outline: 0;
background: none;
width: 0;
caret-color: transparent;
line-height: 40px;
transition: width 0.3s linear;
}
.searchbar:hover > .search_input {
padding: 0 10px;
width: 250px;
caret-color: red;
transition: width 0.3s linear;
}
.searchbar:hover > .search_icon {
background: white;
color: #e74c3c;
}
.search_icon {
height: 40px;
width: 40px;
float: right;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
color: white;
}
</style>

View File

@@ -0,0 +1,54 @@
<template>
<section class="mx-auto trending">
<v-container fluid class="pa-4">
<h1 class="pa-4">TRENDING</h1>
<v-slide-group v-model="model" class="pa-4" show-arrows>
<v-slide-item v-for="n in 7" :key="n" v-slot:default="{ active, toggle }">
<div class="d-flex flex-column ma-4 pa-4">
<v-sheet height="320" width="290" class="pa-4" color="white" tile>
<v-img
src="https://gusta-online.nl/media/catalog/product/cache/4/image/544000d3f0f3eb3fd09c51e2c6034a96/0/4/04125370.jpg"
contain
/>
</v-sheet>
<span class="title mt-6">Linteloo</span>
<span class="subtitle">Isola salontafel van Roderick Vos, large</span>
<span class="mt-6 font-weight-black">4125,-</span>
</div>
<!-- <v-card color="white" class="ma-4 pa-4" height="320" width="290" tile flat>
<v-card-text>
</v-card-text>
</v-card>-->
</v-slide-item>
</v-slide-group>
<v-expand-transition>
<v-sheet v-if="model != null" color="grey lighten-4" height="200" tile>
<v-row class="fill-height" align="center" justify="center">
<h3 class="title">Selected {{ model }}</h3>
</v-row>
</v-sheet>
</v-expand-transition>
</v-container>
</section>
</template>
<script>
import Product from '@/components/Product/Product'
export default {
components: { Product },
data: () => ({
model: null
})
}
</script>
<style scoped>
.trending {
background-color: #f5f5f5;
}
</style>

View File

@@ -0,0 +1,50 @@
<template>
<v-card flat class="my-4" color="primary">
<v-card-title primary-title v-if="disableAccordion"/>
<v-card-title primary-title v-else @click="expanded = !expanded">
<v-icon x-small class="mx-4">{{
expanded ? 'icon-dropdown' : 'icon-dropdown-up'
}}</v-icon>
{{ title }}
</v-card-title>
<v-card-text v-if="expanded" class="accordion-body">
<div class="addresses-info" v-if="title == 'Adressen'">Wil je een adres wijzigen of toevoegen? Mail dan naar <a href="mailto:info@ggzecademy.nl">info@ggzecademy.nl</a></div>
<v-divider v-if="!disableAccordion" class="mb-10"/>
<slot></slot>
</v-card-text>
</v-card>
</template>
<script>
export default {
props: {
title: {
type: String,
default: 'Title',
},
companyMail: {
type: String,
default: 'info@ggzecademy.nl',
},
disableAccordion: {
type: Boolean,
default: false,
},
},
data() {
return {
expanded: true,
}
},
}
</script>
<style lang="scss">
.addresses-info{
padding-left: 45px;
margin-bottom: 16px;
}
</style>

View File

@@ -0,0 +1,45 @@
<template>
<div>
<v-badge
color="accent"
dot
overlap
class="mx-4"
:value="$store.getters.hasUnreadNotifications"
>
<v-avatar size="50" class="secondary has-outline">
<img
:src="$auth.user.image.thumb || noImage"
:alt="$auth.user.fullName"
/>
</v-avatar>
</v-badge>
<small v-if="displayName" class="font-weight-bold txt--text">{{
$auth.user.fullName
}}</small>
</div>
</template>
<script>
export default {
props: {
displayName: {
type: Boolean,
default: false,
},
},
computed: {
noImage() {
return require(`@/assets/img/no_image.png`)
},
}
}
</script>
<style scoped>
.has-outline {
box-shadow: 0 0 0 3px #eef7f9;
}
</style>

View File

@@ -0,0 +1,58 @@
<template>
<v-btn fab text @click="switchDarkMode">
<v-tooltip bottom>
{{ $t('general.theme')}}
<template
v-slot:activator="{ on, attrs }"
color="#FFCCCC"
max-width="10"
>
<v-icon :color="`${$vuetify.theme.dark && 'yellow'}`" v-bind="attrs" v-on="on">icon-sun</v-icon>
</template>
</v-tooltip>
</v-btn>
</template>
<script>
export default {
mounted() {
if (localStorage.getItem('dark') == 'true') {
setTimeout(() => (this.$vuetify.theme.dark = true), 0)
}
},
methods: {
switchDarkMode() {
this.$vuetify.theme.dark = !this.$vuetify.theme.dark
localStorage.setItem('dark', this.$vuetify.theme.dark.toString())
},
},
}
</script>
<style lang="scss" scoped>
.v-tooltip__content.v-tooltip__content--fixed {
max-width: 272px;
padding: 15px 20px;
line-height: 18px;
top: 75px !important;
left: 1020px !important;
position: absolute !important;
// box-shadow: 2px 10px 2px 1px rgba(0, 0, 0, .15);
filter: drop-shadow(5px 10px 0.35rem rgba(0, 0, 0, 0.15));
&:before {
content: '';
display: block;
width: 0;
height: 0;
position: absolute;
border-top: 13px solid transparent;
border-bottom: 13px solid transparent;
border-right: 13px solid #30b7cd;
left: 130px;
top: -17px;
transform: rotate(90deg);
}
}
</style>

View File

@@ -0,0 +1,20 @@
<template>
<div>
<ul v-for="(v, k) in errors" :key="k">
<li v-for="error in v" :key="error" class="text-sm">
{{ error }}
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
errors: {
type: Object,
required: true,
},
},
}
</script>

View File

@@ -0,0 +1,21 @@
<template>
<v-btn icon @click="handleFullScreen()">
<v-icon small>icon-fullscreen</v-icon>
</v-btn>
</template>
<script>
import Util from '@/util';
export default {
methods: {
handleFullScreen () {
Util.toggleFullScreen();
}
}
};
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,38 @@
<template>
<div class="text-center">
<!-- <v-btn color="error" @click="dialog = !dialog">Show Dialog</v-btn> -->
<v-overlay :value="dialog">
<v-card>
<v-card-title class="headline">{{headline}}</v-card-title>
<v-card-text>{{text}}</v-card-text>
<v-card-actions>
<v-btn color="info" @click="dialog=false" tile depressed>back</v-btn>
<v-spacer/>
<v-btn color="error" @click="proceed()" tile depressed>confirm</v-btn>
</v-card-actions>
</v-card>
</v-overlay>
</div>
</template>
<script>
export default {
props: {
headline: {
type: String
},
text: {
type: String
}
},
data() {
return {
dialog: false
}
},
methods: {
confirm() {}
}
}
</script>

View File

@@ -0,0 +1,60 @@
<template>
<v-snackbar
v-model="display"
:timeout="3000"
:color="color"
:multi-line="hasErrors"
right
top
>
<v-icon class="mr-2">{{ icon }}</v-icon>
{{ content }}
<ul v-if="hasErrors">
<li v-for="(allErrors, field) in errors" :key="field">
{{ field }}
<ul>
<li v-for="(error, i) in allErrors" :key="i">
{{ error }}
</li>
</ul>
</li>
</ul>
<!-- <v-btn text @click="display = false">Close</v-btn> -->
</v-snackbar>
</template>
<script>
// https://dev.to/stephannv/how-to-create-a-global-snackbar-using-nuxt-vuetify-and-vuex-1bda
export default {
data() {
return {
display: false,
color: 'success',
content: '',
icon: '',
errors: {},
}
},
mounted() {
this.$store.subscribe((mutation, state) => {
if (mutation.type === 'snackbar/showMessage') {
this.content = state.snackbar.content
this.color = state.snackbar.color
this.icon = state.snackbar.icon
this.errors = state.snackbar.errors
this.display = true
}
})
},
computed: {
hasErrors() {
return Object.keys(this.errors).length > 0
},
},
}
</script>

View File

@@ -0,0 +1,75 @@
<template>
<v-overlay :value="loading" color="#013447">
<div class="loading">
<div class="spinner">
<img class="logo one" :src="mySVG" />
</div>
<div class="spinner">
<img class="logo two" :src="mySVG" />
</div>
<span>{{ $t('general.loading') }}</span>
</div>
</v-overlay>
</template>
<script>
export default {
data() {
return{
loading: false,
mySVG: require('assets/img/ggz/loading-logo.svg')
}
},
methods: {
start() {
this.loading = true
},
finish() {
this.loading = false
},
},
}
</script>
<style lang="scss" scoped>
.loading {
width: 140px;
height: 180px;
margin: auto;
margin-bottom: 25px;
display: flex;
justify-content: center;
.spinner {
width: 100%;
position: absolute;
.one {
transform: rotate(25deg);
}
-webkit-animation: spin 5s linear infinite;
animation: spin 5s linear infinite;
}
span{
display:flex;
align-items: flex-end;
font-family:"Source Sans Pro", sans-serif;
}
}
@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@@ -0,0 +1,5 @@
<template>
<h2 class="pa-4 txt--text">
<slot></slot>
</h2>
</template>

View File

@@ -0,0 +1,207 @@
<template>
<v-autocomplete
v-if="isNotMember()"
:items="isMemberPage ? members : learningProducts"
:item-text="isMemberPage ? 'informal_name' : 'title'"
filled
dense
id="searchbar"
rounded
:search-input.sync="search"
v-model="selected"
append-icon="icon-search"
hide-details
:placeholder="$t('general.search')"
single-line
full-width
flat
return-object
class="mx-4"
:filter="customFilter"
@focus="toDoOnFocus"
@blur="switchOverlay(false)"
@keydown="switchOverlay(true)"
@click="switchOverlay(true)"
>
<template v-slot:prepend-item v-if="prependText">
<v-list-item>
<v-list-item-title class="header-search-title pb-4">
<span class="pt-4">
{{ prependText }}
</span>
</v-list-item-title>
</v-list-item>
</template>
</v-autocomplete>
</template>
<script>
import Util from '@/util'
var Diacritics = require('diacritic')
export default {
data() {
return {
search: '',
loading: false,
selected: null,
}
},
computed: {
learningProducts() {
return this.$store.getters.learningProducts.filter((p) => {
if (p.parent_id) return false
if (p.deleted_at) return false
if (!p.published) return false
return true
})
},
isMemberPage() {
return this.$route.name.includes('manager-members')
},
members() {
return this.$store.getters['members/membersFiltered']
},
hasMembers() {
return this.members?.length > 0
},
prependText() {
if (this.loading) return this.$t('general.loading')
if (this.isMemberPage) {
if (!this.hasMembers) return `No results found`
if (this.search) return `Zoeksuggesties voor '${this.search}'`
}
if (!this.$store.getters.hasLearningProducts) {
return `No results found`
}
if (this.$store.getters.hasLearningProducts && this.search) {
return `Zoeksuggesties voor '${this.search}'`
}
return null
},
},
watch: {
async selected(item) {
if (!item || !item.slug) return
const route = this.isMemberPage
? `/manager/members/${item.slug}`
: `/manager/learning/${item.slug}`
await this.$router.push(this.localePath(route))
this.$store.commit('navigation/TOGGLE_SEARCH_OVERLAY', false)
this.search = null
this.selected = null
this.loading = false
},
},
methods: {
customFilter(item, queryText, itemText) {
const textOne = Diacritics.clean(
item[!this.isMemberPage ? 'title' : 'informal_name'].toLowerCase()
)
let textTwo = ''
if (!this.isMemberPage) {
if (item.synonyms.length > 0) {
item.synonyms.forEach((s) => (textTwo += `${s.title.toLowerCase()} `))
}
}
const searchText = queryText.toLowerCase()
return (
textOne.indexOf(Diacritics.clean(searchText)) > -1 ||
textTwo.indexOf(Diacritics.clean(searchText)) > -1
)
},
async toDoOnFocus() {
if (this.isMemberPage && !this.hasMembers) {
this.$nextTick(() => this.$nuxt.$loading.start())
this.loading = true
await this.$store.dispatch('members/pullData')
await this.$nuxt.$loading.finish()
this.loading = false
}
if (!this.isMemberPage && !this.$store.getters.hasLearningProducts) {
this.$nextTick(() => this.$nuxt.$loading.start())
this.loading = true
await this.$store.dispatch('learning/pullProducts')
await this.$nuxt.$loading.finish()
this.loading = false
}
this.$store.commit('navigation/TOGGLE_SEARCH_OVERLAY', true)
},
switchOverlay(value) {
if (value == this.$store.getters.searchOverlay) return
this.$store.commit('navigation/TOGGLE_SEARCH_OVERLAY', value)
},
isNotMember() {
const roles = this.$auth.user.roles.map(({ name }) => name)
return !Util.findCommonValuesInArray(roles, ['member'])
},
},
}
</script>
<style scoped>
#searchbar {
width: 400px !important;
color: var(--v-txt-base) !important;
}
.v-input >>> textarea {
color: var(--v-txt-base) !important;
}
#searchbar >>> .icon-search {
color: var(--v-txt-base) !important;
font-weight: 600;
}
#searchbar >>> .v-text-field--outlined fieldset {
color: rgba(255, 255, 255, 0);
}
#searchbar
>>> .v-select.v-select--is-menu-active
.v-input__icon--append
.v-icon {
transform: unset !important;
}
.v-list {
padding: 30px 90px 30px 100px !important;
border-radius: 0px;
}
@media only screen and (min-width: 1536px) {
.v-list {
padding: 30px 80px 30px 53px !important;
}
}
.v-list:focus {
outline: none !important;
}
.v-list >>> .v-list-item {
color: var(--v-tertiary-base) !important;
background: var(--v-primary-base) !important;
}
.v-list >>> .v-list-item__title {
font-size: 16px;
color: var(--v-tab-base);
}
.header-search-title {
pointer-events: none;
cursor: initial;
font-size: 24px !important;
height: 22px !important;
color: var(--v-txt-base) !important;
font-weight: 700;
border-bottom: 2px solid var(--v-search-base);
padding-bottom: 15px;
font-weight: 700;
}
</style>

View File

@@ -0,0 +1,303 @@
<template>
<div>
<v-select
v-model="vModelSelected"
:items="filterItems"
item-text="title"
item-value="id"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
append-icon="icon-dropdown"
v-if="isSelect"
>
<template slot="item" slot-scope="data">
<v-icon
x-small
class="mr-2"
v-if="data.item.color"
:color="data.item.color"
>mdi-circle</v-icon
>
<span>
{{ data.item.title }}
</span>
</template>
</v-select>
<v-list
v-if="isCheckbox"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
>
<v-list-item-group v-model="vModelSelected" multiple>
<template v-for="(item, i) in filterItems">
<v-list-item
:key="`item-${i}`"
:value="item.id"
active-class="secondary"
two-line
>
<template v-slot:default="{ active, toggle }">
<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>
<v-checkbox :input-value="active" :true-value="item.id" />
</v-list-item-action>
</template>
</v-list-item>
</template>
</v-list-item-group>
</v-list>
<v-select
chips
multiple
v-model="vModelSelected"
:items="filterItems"
item-text="title"
item-value="id"
:outlined="editMode"
:solo="!editMode"
:disabled="!editMode"
:flat="!editMode"
append-icon="icon-dropdown"
v-if="isMixed"
/>
<v-list v-if="isMenu">
<v-list-item-group v-model="filtersSelected" multiple id="searchbar">
<v-text-field
filled
dense
rounded
v-model="search"
append-icon="icon-search"
hide-details
:placeholder="$t('general.search')"
single-line
full-width
flat
outlined
v-if="hasManyItems"
class="mb-4"
/>
<template v-for="(item, i) in filterItemsFiltered">
<v-divider v-if="!item" :key="`divider-${i}`"></v-divider>
<v-list-item v-else :key="`item-${i}`" :value="item.id">
<template v-slot:default="{ active, toggle }">
<v-list-item-content>
<v-list-item-title v-text="item.title"></v-list-item-title>
</v-list-item-content>
<v-list-item-action>
<v-checkbox :input-value="active" :true-value="item.id" />
</v-list-item-action>
</template>
</v-list-item>
</template>
</v-list-item-group>
</v-list>
</div>
</template>
<script>
import searchBar from '~/components/UI/SearchBar/SearchBar'
export default {
components: {
searchBar,
},
props: {
filterTitle: {
type: String,
required: true,
},
filterType: {
type: String,
default: 'select',
},
editMode: {
type: Boolean,
default: false,
},
target: {
type: String,
default: 'filters',
validator: function (value) {
// The value must match one of these strings
return ['filters', 'versions', 'accreditations'].indexOf(value) !== -1
},
},
},
data() {
return {
search: '',
}
},
computed: {
isSelect() {
return this.filterType === 'select'
},
isCheckbox() {
return this.filterType === 'checkbox'
},
isMixed() {
return this.filterType === 'mixed'
},
isMenu() {
return this.filterType === 'menu'
},
hasItems() {
return this.filterItems.length > 0
},
hasManyItems() {
return this.filterItems.length >= 5
},
// Both Checkbox and Mixed have multiple filters
multiple() {
return this.isCheckbox || this.isMixed
},
filterId() {
return this.$store.getters.getFilterByTitle(this.filterTitle).id
},
filterItems() {
if (this.filterTitle) {
return this.$store.getters.getFilterByTitle(this.filterTitle).items
}
return []
},
filterItemsFiltered() {
if (this.search) {
return this.$store.getters
.getFilterByTitle(this.filterTitle)
.items.filter((filter) => {
return filter.title
.toLowerCase()
.includes(this.search.toLowerCase())
})
}
return this.filterItems
},
filtersSelected: {
get() {
return this.$store.state.learning.filtersSelected
},
set(value) {
this.$store.commit('learning/SELECT_FILTERS', value)
},
},
vModelSelected: {
// Gets Object / Array of Objects from filters
get() {
if (!this.$store.getters.localProduct.filtersGrouped) {
return []
}
if (
this.target === 'filters' &&
typeof this.$store.getters.localProduct.filtersGrouped[
this.filterId
] === 'undefined'
) {
return []
}
let filterItemsIdsSelectedRaw
if (this.target === 'filters') {
// Gets ids from the local learning product array associated to the filterId
filterItemsIdsSelectedRaw =
this.$store.getters.localProduct.filtersGrouped[this.filterId] || []
}
if (this.target === 'versions') {
filterItemsIdsSelectedRaw =
this.$store.state.learning.versionsFiltersSelected[this.filterId] ||
[]
}
if (this.target === 'accreditations') {
filterItemsIdsSelectedRaw =
this.$store.state.learning.accreditationsFiltersSelected[
this.filterId
] || []
}
// if multiple filter, checkbox type, return array of filter items ids only
if (this.multiple) {
this.selected = filterItemsIdsSelectedRaw
return filterItemsIdsSelectedRaw
}
// Single Select Input
return this.filterItems.find(
(filterItem) => filterItem.id == filterItemsIdsSelectedRaw[0]
)
},
set(value) {
this.setFilterItems(value)
},
},
},
methods: {
setFilterItems(value) {
this.$store.commit('learning/UPDATE_FILTERS', {
filterId: this.filterId,
target: this.target || 'filters',
value,
})
},
},
}
</script>
<style scoped>
.v-input >>> .v-icon {
color: var(--v-txt-base) !important;
}
.v-input >>> .mdi-checkbox-blank-outline::before {
font-family: 'mijnggz';
content: '\e921' !important;
}
.v-input >>> .mdi-checkbox-marked::before {
font-family: 'mijnggz';
content: '\e923' !important;
}
#searchbar.mx-4 {
margin: 30px -24px !important;
margin-top: 10px !important;
}
.v-input >>> .v-expansion-panel-header {
color: var(--v-txt-base) !important;
}
/* .v-input >>> .v-input__slot label {
font-weight: 600;
color: var(--v-txt-base) !important;
} */
/* .v-input >>> .v-messages__message {
padding-left: 33px;
font-size: 16px;
color: var(--v-tertiary-base) !important;
}
.v-input >>> .v-input__control:hover label,
.v-input >>> .v-input__control:hover .v-messages__message {
color: var(--v-secAccent-base) !important;
} */
</style>

68
components/Usp/Usp.vue Normal file
View File

@@ -0,0 +1,68 @@
<template>
<section id="usp">
<v-container fluid class="px-4 mx-4">
<div class="row">
<div class="col-12 col-md-4 item">
<div class="d-flex">
<v-icon size="60" color="#5885C0">mdi-script-outline</v-icon>
<div class="d-flex flex-column pa-4 mx-4">
<span class="title">Behangrol advies</span>
<span>Hulp nodig bij het uitrekenen van het aantal rollen behang?</span>
</div>
</div>
</div>
<div class="col-12 col-md-4 item">
<div class="d-flex">
<v-icon size="60" color="#5885C0">mdi-script-outline</v-icon>
<div class="d-flex flex-column pa-4 mx-4">
<span class="title">Interieuradvies</span>
<span>Hulp nodig bij het uitrekenen van het aantal rollen behang?</span>
</div>
</div>
</div>
<div class="col-12 col-md-4 item">
<div class="d-flex">
<v-icon size="60" color="#5885C0">mdi-script-outline</v-icon>
<div class="d-flex flex-column pa-4 mx-4">
<span class="title">Bezoek ons</span>
<span>Wij ontvangen je graag op afspraak voor een persoonlijk gesprek!</span>
</div>
</div>
</div>
</div>
</v-container>
</section>
</template>
<script>
export default {
data: () => ({
items: [
{icon: 'mdi-script-outline', title: '', text: ''},
{icon: '', title: '', text: ''},
{icon: '', title: '', text: ''},
]
})
}
</script>
<style lang="scss" scoped>
#usp {
margin: 80px 0;
.item {
#icon {
font-size: 60px;
color: yellow;
width: 90px;
float: left;
}
.title {
font-weight: 700;
display: block;
}
}
}
</style>

View File

@@ -0,0 +1,176 @@
<template>
<v-footer light padless>
<v-card class="flex my-0">
<v-card-title>
<v-row>
<v-col cols="12" sm="12" md="3">
<v-list dense>
<v-subheader>Productcatalogus</v-subheader>
<v-list-item-group v-model="item" color="primary">
<v-list-item v-for="(item, i) in items" :key="i">
<v-list-item-content>
<v-list-item-title v-text="item.text"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-list dense>
<v-subheader>Diensten</v-subheader>
<v-list-item-group v-model="item" color="primary">
<v-list-item v-for="(item, i) in items" :key="i">
<v-list-item-content>
<v-list-item-title v-text="item.text"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-list dense>
<v-subheader>CATEGORY</v-subheader>
<v-list-item-group v-model="item" color="primary">
<v-list-item v-for="(item, i) in items" :key="i">
<v-list-item-content>
<v-list-item-title v-text="item.text"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-list dense>
<v-subheader>CATEGORY</v-subheader>
<v-list-item-group v-model="item" color="primary">
<v-list-item v-for="(item, i) in items" :key="i">
<v-list-item-content>
<v-list-item-title v-text="item.text"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-col>
</v-row>
</v-card-title>
<v-card-text>
<v-row no-gutters>
<v-col cols="12" sm="3">
<v-list dense>
<v-subheader>CATEGORY</v-subheader>
<v-list-item-group v-model="item" color="primary">
<v-list-item v-for="(item, i) in items" :key="i">
<v-list-item-content>
<v-list-item-title v-text="item.text"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-col>
<v-col cols="12" sm="3">
<v-list dense>
<v-subheader>CATEGORY</v-subheader>
<v-list-item-group v-model="item" color="primary">
<v-list-item v-for="(item, i) in items" :key="i">
<v-list-item-content>
<v-list-item-title v-text="item.text"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-col>
<v-col cols="12" sm="3">
<v-list dense>
<v-subheader>CATEGORY</v-subheader>
<v-list-item-group v-model="item" color="primary">
<v-list-item v-for="(item, i) in items" :key="i">
<v-list-item-content>
<v-list-item-title v-text="item.text"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-col>
<v-col cols="12" sm="3" class="d-flex flex-column">
<v-list dense>
<v-subheader>Betalingsmethode</v-subheader>
<v-row>
<v-col
v-for="payment in payments"
:key="payment.name"
class="d-flex child-flex"
cols="2"
>
<v-img
:src="require(`@/assets/img/${payment.filename}`)"
:lazy-src="require(`@/assets/img/${payment.filename}`)"
contain
>
<template v-slot:placeholder>
<v-row class="fill-height ma-0" align="center" justify="center">
<v-progress-circular indeterminate color="grey lighten-5"></v-progress-circular>
</v-row>
</template>
</v-img>
</v-col>
</v-row>
</v-list>
<v-list dense>
<v-subheader>Volg ons</v-subheader>
<v-btn v-for="icon in icons" :key="icon" class="mx-2" light icon>
<v-icon small>{{ 'mdi-' + icon }}</v-icon>
</v-btn>
</v-list>
</v-col>
</v-row>
</v-card-text>
<v-card-actions class="py-2 grey lighten-4">
<span>© Buningh {{ new Date().getFullYear() }}</span>
<v-spacer></v-spacer>
<span>Alle vermelde prijzen zijn in euro's en inclusief BTW</span>
</v-card-actions>
</v-card>
</v-footer>
</template>
<script>
import Newsletter from '~/components/Newsletter/Newsletter'
import Disclaimer from '~/components/Info/Disclaimer'
import Privacy from '~/components/Info/Privacy'
import Cookies from '~/components/Info/Cookies'
export default {
components: {
Newsletter,
Disclaimer,
Privacy,
Cookies
},
data: () => ({
item: 99,
payments: [
{ name: 'Bancontact', filename: 'BC_logo_ORGNL_RGB.png' },
{ name: 'PayPal', filename: 'PayPal.png' },
{ name: 'Maestro', filename: 'MAES.png' },
{ name: 'IDeal', filename: 'iDeal.png' },
{ name: 'MasterCard', filename: 'MC.png' },
{ name: 'Visa', filename: 'Visa.png' }
],
items: [
{ text: 'Real-Time' },
{ text: 'Audience' },
{ text: 'Conversions' },
{ text: 'Whatever' }
],
icons: ['twitter', 'facebook', 'linkedin', 'instagram'],
disclaimer: false,
privacy: false,
cookies: false
})
}
</script>

View File

@@ -0,0 +1,80 @@
<template>
<div>
<topbar />
<v-row align="center" justify="space-between">
<div class="d-flex mx-4">
<v-card class="ma-3 pa-6" outlined tile>LOGO Buningh</v-card>
<v-row align="center" class="mx-4">
<v-col>
<v-btn
text
small
:to="item.to"
v-for="(item,i) in menu"
:key="i"
class="mx-4"
>{{item.title}}</v-btn>
</v-col>
</v-row>
</div>
<div class="d-flex">
<v-text-field
v-model="search"
append-icon="icon-search"
label="Zoeken naar merk of product.."
single-line
hide-details
outlined
></v-text-field>
<v-row align="center" class="mx-4">
<v-col>
<v-btn icon to="/login">
<v-icon>icon-password</v-icon>
</v-btn>
</v-col>
<v-col>
<v-btn icon>
<v-icon>mdi-heart</v-icon>
</v-btn>
</v-col>
<v-col>
<v-btn icon>
<v-icon>mdi-shopping-outline</v-icon>
</v-btn>
</v-col>
</v-row>
</div>
</v-row>
</div>
</template>
<script>
import Topbar from '~/components/layout/Header/Topbar'
export default {
components: {
Topbar
},
data() {
return {
search: '',
menu: [
{ title: 'Verlichting', to: '/' },
{ title: 'Meubels', to: '/' },
{ title: 'Behang', to: '/' },
{ title: 'Stoffen', to: '/' },
{ title: 'Accessoires', to: '/' }
]
}
},
computed: {
title() {
return `${process.env.NAME}`
}
}
}
</script>

View File

@@ -0,0 +1,53 @@
<template>
<v-toolbar dense flat>
<span v-for="(item,i) in menuItems.left" :key="`x${i}`" class="mx-4 caption">
<v-avatar color="#ffcc00" size="6" class="mx-2"></v-avatar>
<span>{{item.label}}</span>
</span>
<v-spacer></v-spacer>
<span v-for="(item,i) in menuItems.right" :key="i" class="mx-4 caption">
{{item.label}}
</span>
</v-toolbar>
</template>
<script>
export default {
data() {
return {
menuItems: {
left: [
{ label: 'Gratis verzending in NL & BE' },
{ label: 'Meer dan 500 merken' },
{ label: '135 jaar de nieuwste trends' }
],
right: [
{ label: 'Interieuradvies', to: '/interieuradvies' },
{ label: 'Over Buningh', to: '/algemeen' },
{ label: 'Klantenservice', to: '/klantenservice' },
{ label: '+31 10 41 40 560' }
]
},
menu: [
{ title: 'Verlichting', route: '/' },
{ title: 'Meubels', route: '/' },
{ title: 'Behang', route: '/' },
{ title: 'Stoffen', route: '/' },
{ title: 'Accessoires', route: '/' },
{ title: 'Sale', route: '/' },
{ title: 'Inspiratie', route: '/' },
{ title: 'Merken', route: '/' }
]
}
},
computed: {
title() {
return `${process.env.NAME}`
}
}
}
</script>

View File

@@ -0,0 +1,31 @@
<template>
<v-navigation-drawer v-model="drawer" app>
<v-list dense>
<v-list-item link>
<v-list-item-action>
<v-icon>mdi-home</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>Home</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item link>
<v-list-item-action>
<v-icon>mdi-contact-mail</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>Contact</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
</template>
<script>
export default {
data: () => ({
drawer: false
})
}
</script>

View File

@@ -0,0 +1,10 @@
<template>
<v-system-bar window light>
<v-icon>mdi-message</v-icon>
<span>10 unread messages</span>
<v-spacer></v-spacer>
<v-icon>mdi-minus</v-icon>
<v-icon>icon-checkmarkbox-blank-outline</v-icon>
<v-icon x-small>icon-close</v-icon>
</v-system-bar>
</template>