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,483 @@
<template>
<v-row>
<v-col>
<div class="d-flex justify-space-between">
<h2 class="ma-4" v-if="isNew">Nieuw</h2>
<span v-else>
<h2 class="ma-4">{{ local.informal_name || '' }}</h2>
<h3 class="ma-4">{{ local.formal_name || '' }}</h3>
</span>
<span class="justify-center d-flex flex-column" v-if="local.revision">
<small>
<v-icon color="success" class="mr-2">icon-checkmark</v-icon>
bijgewerkt op
<strong>{{ formatDate(local.revision.updated_at) }}</strong> door
<strong>{{ local.revision.user.fullName }}</strong>
</small>
<small v-if="local.revision.accepted_at">
<v-icon color="success">icon-checkmark</v-icon>
<v-icon color="success" class="mr-2">icon-checkmark</v-icon>
gecontroleerd op
<strong>{{ formatDate(local.revision.accepted_at) }}</strong> door
<strong>{{ local.revision.revisor.fullName }}</strong>
</small>
</span>
</div>
<div class="d-flex justify-space-between">
<div class="d-flex">
<small
v-for="(tab, index) in tabs"
:key="index"
@click="selectedTab = tab"
class="ma-4 dot"
:class="{ 'accent--text activeTab': selectedTab === tab }"
>{{ $t(`members.tabs.${tab}`) }}</small
>
</div>
<!-- here checks -->
</div>
<basic-members
v-if="isTabSelected('basic')"
:editMode="canEditSuperAdminsAndAdmins"
:isCreateMode="isNew"
:users="users"
/>
<address-members
v-if="isTabSelected('address')"
:editMode="canEditSuperAdminsAndAdmins"
:isCreateMode="isNew"
/>
<contacts-members
v-if="isTabSelected('contacts')"
:editMode="canEditSuperAdminsAdminsAndDelegated"
:isCreateMode="isNew"
/>
<contribution-members
v-if="isTabSelected('contribution')"
:editMode="canEditSuperAdminsAndAdmins"
:isCreateMode="isNew"
/>
<employees-members
v-if="isTabSelected('employees')"
:editMode="canEditSuperAdminsAdminsAndDelegated"
:isCreateMode="isNew"
/>
<page-members
v-if="isTabSelected('member_page')"
:editMode="canEditSuperAdminsAdminsAndDelegated"
/>
</v-col>
<v-footer
fixed
style="z-index: 4"
height="90"
color="primary"
v-if="$store.getters['members/isSuperAdminAdminOrDelegated']"
>
<v-btn title text small :to="localePath('/manager/members')">
<v-icon>icon-arrow-left</v-icon>
</v-btn>
<div class="mx-10">
<v-btn
class="ma-2 white--text"
color="accent"
depressed
v-if="
$store.getters['members/isSuperAdminAdminOrDelegated'] &&
!$store.getters.isOnlyMemberEditor &&
!isNew &&
!isEditMode
"
rounded
@click="switchToEdit"
>
<!-- :to="localePath(`${$nuxt.$route.path}?edit`)"
nuxt -->
{{ $t('general.edit') }}</v-btn
>
<v-btn
class="ma-2 white--text"
:color="$vuetify.theme.dark ? 'secondary' : 'txt'"
depressed
@click="save()"
v-if="
canSave && ($store.getters.isSuperAdmin || $store.getters.isAdmin)
"
rounded
>Tussentijds opslaan</v-btn
>
<!-- Accept Revision - only super admins or admins -->
<template v-if="$store.getters.isSuperAdmin || $store.getters.isAdmin">
<v-btn
v-cloak
class="ma-2 white--text"
color="accent"
depressed
rounded
v-if="
!isNew &&
$store.getters['members/revision'] &&
local.revision.hasChanges &&
isEditMode
"
@click="save(true)"
>Opslaan en indienen</v-btn
>
</template>
<!-- Store revision - only delegated users -->
<template v-else>
<v-btn
v-cloak
class="ma-2 white--text"
color="accent"
depressed
v-if="canSave && !isNew"
rounded
@click="storeRevision()"
>Opslaan en indienen</v-btn
>
</template>
</div>
<v-btn
class="ma-2"
tile
text
v-if="canSave && !isNew"
color="accent"
depressed
>
<v-icon class="mx-2" size="26">mdi-alert-circle-outline</v-icon>
wijziging
</v-btn>
<v-spacer />
<!-- <v-btn class="ma-2" tile text small>
<v-icon class="mx-2" small>icon-sharepoint</v-icon>
Documenten
</v-btn> -->
<!-- <v-dialog v-model="dialog" persistent max-width="740">
<template v-slot:activator="{ on }">
<v-btn
class="ma-2"
tile
text
small
v-if="!isNew && canEdit"
v-on="on"
>
<v-icon class="mx-2">icon-remove</v-icon>
</v-btn>
</template>
<v-card class="primary pa-10" flat>
<v-card-title class="headline">
{{
$t('learning.product_overview.delete_confirmation', {
productName: local.title,
})
}}
</v-card-title>
<v-card-actions>
<div class="ma-4">
<v-btn
color="accent"
class="mx-2"
@click="deleteMember()"
rounded
depressed
>{{ $t('general.delete') }}</v-btn
>
<v-btn
class="mx-2"
color="info"
@click="close"
rounded
depressed
>{{ $t('general.cancel') }}</v-btn
>
</div>
</v-card-actions>
</v-card>
</v-dialog> -->
</v-footer>
</v-row>
</template>
<script>
import dayjs from 'dayjs'
import PageHeader from '~/components/UI/PageHeader/PageHeader'
import basicMembers from '@/components/Members/BasicMembers'
import addressMembers from '@/components/Members/AddressMembers'
import contactsMembers from '@/components/Members/ContactsMembers'
import employeesMembers from '@/components/Members/EmployeesMembers'
import pageMembers from '@/components/Members/PageMembers'
import moreMembers from '@/components/Members/MoreMembers'
import contributionMembers from '~/components/Members/ContributionMembers.vue'
export default {
layout: `${process.env.CUSTOMER}Admin`,
components: {
PageHeader,
basicMembers,
addressMembers,
contactsMembers,
employeesMembers,
pageMembers,
moreMembers,
contributionMembers,
},
data() {
return {
tabs: [
'all',
'basic',
'address',
'contacts',
'contribution',
'employees',
'member_page',
],
selectedTab: 'all',
dialog: false,
users: [],
}
},
async asyncData({ $axios, store }) {
try {
await store.dispatch('members/pullBranches')
await store.dispatch('members/pullTypes')
if (store.getters.isAdmin || store.getters.isOperator) {
const response = await $axios.get('/admin/users/getList')
return { users: response.data }
}
return { users: [] }
} catch (error) {
console.log('asyncData -> error', error)
}
},
async mounted() {
if (!this.isValidSlug) {
this.$nuxt.error({ statusCode: 404, message: 'URL niet geldig' })
// this.$router.push('/manager')
}
if (this.isSlugNumber) {
await this.$store.dispatch('members/getAndSetMember', this.slug)
}
if (this.isNew && !this.$store.getters.isSuperAdminOrAdmin) {
this.$nuxt.error({ statusCode: 401, message: 'Je mag dit niet doen' })
// this.$router.push('/manager')
}
// #warning Laravel Echo has been manually disabled until broadcasting issue is fixed.
// this.$echo.channel('updates').listen(`.members-updated`, async (e) => {
// if (this.isSlugNumber) {
// await this.$store.dispatch('members/getAndSetMember', this.slug)
// }
//
// this.$notifier.showMessage({
// content: 'Member updated',
// color: 'success',
// icon: 'icon-message',
// })
// })
},
computed: {
canEditSuperAdminsAdminsAndDelegated() {
return (
(this.isNew || this.isEditMode) &&
this.$store.getters['members/isSuperAdminAdminOrDelegated']
)
},
canEditSuperAdminsAndAdmins() {
return (
(this.isNew || this.isEditMode) &&
(this.$store.getters.isSuperAdmin || this.$store.getters.isAdmin)
)
},
canSave() {
if (this.isNew && this.isMemberValidated) return true
// if (this.isNew) return true
return (
this.isSlugNumber &&
this.isDownloaded &&
this.$store.getters['members/hasChanges'] &&
this.isMemberValidated
)
},
isMemberValidated() {
return this.$store.getters['members/isMemberValidated']
},
slug() {
return this.$route.params.member
},
isNew() {
return this.slug.toLowerCase() === 'new'
},
isEditMode() {
return this.$route.query.edit === null
},
remote() {
return this.$store.state.members.remote
},
local() {
return this.$store.state.members.local
},
title() {
if (this.isNew) return `<h2 class="ma-4">Nieuw</h2>`
return `${this.local.informal_name || ''} <h2 class="ma-4">${
this.local.formal_name || ''
}`
},
isSlugNumber() {
return !isNaN(this.slug.split(['-'], 1))
},
isValidSlug() {
return this.isNew || this.isSlugNumber
},
isDownloaded() {
return Object.keys(this.remote).length > 0
},
},
methods: {
formatDate(date) {
return dayjs(date).format('D MMM YYYY hh:mm').toLowerCase()
},
close() {
this.dialog = false
},
isTabSelected(tab) {
return this.selectedTab === tab || this.selectedTab === 'all'
},
async save(revision = false) {
this.$nextTick(() => this.$nuxt.$loading.start())
try {
const response = await this.$store.dispatch('members/store', revision)
this.$nuxt.$loading.finish()
this.$router.push(this.localePath('/manager/members'))
$nuxt.$notifier.showMessage({
content: `Member stored`,
color: 'success',
icon: 'mdi-check',
})
} catch (error) {
this.$nuxt.$loading.finish()
console.log('save -> error', error)
this.$notifier.showMessage({
content: `${
this.slug.toLowerCase() === 'new' ? 'Creating' : 'Editing'
} page: ${error.response ? error.response.data.message : error}.`,
...(error.response && {
errors: error.response.data.errors,
}),
color: 'error',
icon: 'mdi-alert',
})
}
},
switchToEdit() {
this.$router.push(this.localePath(`${$nuxt.$route.path}?edit`))
},
async storeRevision() {
try {
await this.$store.dispatch('members/storeRevision')
this.$nuxt.$loading.finish()
this.$router.push(this.localePath('/manager/members'))
$nuxt.$notifier.showMessage({
content: `Revision stored`,
color: 'success',
icon: 'mdi-check',
})
} catch (error) {
this.$nuxt.$loading.finish()
console.log('storeRevision -> error', error)
this.$notifier.showMessage({
content: `${error.response ? error.response.data.message : error}.`,
...(error.response && {
errors: error.response.data.errors,
}),
color: 'error',
icon: 'mdi-alert',
})
}
},
async deleteMember() {
this.dialog = false
await this.$store.dispatch('members/deleteMember')
this.$router.push(this.localePath('/manager/members'))
},
},
async beforeRouteLeave(to, from, next) {
await this.$store.dispatch('members/resetMember')
next()
},
}
</script>
<style scoped>
.v-card >>> .v-btn__content,
.v-select .v-select__selection--comma,
.v-chip__content,
.v-list-item:not(.v-list-item--active):not(.v-list-item--disabled),
.v-select.v-select--chips:not(.v-text-field--single-line).v-text-field--enclosed
.v-select__selections,
.v-list-item .v-list-item__title,
.v-list-item .v-list-item__subtitle,
.v-list-item span,
.v-input--is-disabled input,
.v-input--is-disable,
.dot,
h2,
p {
/* color: var(--v-txt-base); */
}
.v-card >>> .v-chip--disabled {
opacity: 1 !important;
}
.v-card >>> .v-chip {
padding-left: 0 !important;
}
.dot {
cursor: pointer;
}
.dot::before {
content: '\A';
width: 5px;
height: 5px;
border-radius: 50%;
background: #e54e0f;
display: block;
position: relative;
top: 30px;
left: 50%;
opacity: 0;
}
.dot:hover::before {
opacity: 0.5 !important;
}
.dot.activeTab::before {
opacity: 1 !important;
}
</style>