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