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,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>