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,236 @@
<template>
<div>
<div class="pa-6">
<page-header class="d-flex justify-space-between">
<div class="subtitle-1">
<span
:class="{ 'display-1': selector === 'allMembers' }"
:style="{ cursor: 'pointer' }"
@click="selector = 'allMembers'"
class="mr-4"
>
Leden
<small class="font-weight-light">({{ allMembers.length }})</small>
</span>
<span
:class="{ 'display-1': selector === 'active' }"
:style="{ cursor: 'pointer' }"
@click="selector = 'active'"
class="mx-4"
v-if="$store.getters.isAdmin || $store.getters.isOperator"
>
Actieve leden
<small class="font-weight-light">({{ active.length }})</small>
</span>
<span
:class="{ 'display-1': selector === 'inactive' }"
:style="{ cursor: 'pointer' }"
@click="selector = 'inactive'"
class="mx-4"
v-if="$store.getters.isAdmin || $store.getters.isOperator"
>
Inactieve leden
<small class="font-weight-light">({{ inactive.length }})</small>
</span>
<span
:class="{ 'display-1': selector === 'deleted' }"
:style="{ cursor: 'pointer' }"
@click="selector = 'deleted'"
class="mx-4"
v-if="$store.getters.isAdmin || $store.getters.isOperator"
>
Verwijderd
<small class="font-weight-light">({{ deleted.length }})</small>
</span>
</div>
<div>
<!-- here checks -->
</div>
</page-header>
</div>
<members-table :members="membersComputed" />
<v-footer
fixed
style="z-index: 4"
height="90"
color="primary"
v-if="$store.getters.isSuperAdminOrAdmin"
>
<v-btn text nuxt :to="localePath('/manager')">
<v-icon>icon-arrow-left</v-icon>
</v-btn>
<div class="mx-10">
<v-btn
color="accent"
depressed
rounded
to="/manager/members/new"
class="ml-10"
>
<v-icon left size="8">icon-add</v-icon>
<small>Lid toevoegen</small>
</v-btn>
</div>
<v-spacer />
<v-btn class="ma-2" tile text small @click="exportCsv">
<v-icon class="mx-2" x-small>icon-export</v-icon>
{{ $t('general.export_csv') | capitalize }}
</v-btn>
</v-footer>
</div>
</template>
<script>
import csv from 'csv'
import {DateTime} from 'luxon'
import dayjs from 'dayjs'
import download from 'downloadjs'
import MembersTable from '~/components/Members/MembersTable'
import PageHeader from '~/components/UI/PageHeader/PageHeader'
const I18N_CSV_WRAPPER_KEY = 'csv.members'
const I18N_CSV_HEADER_KEYS = [
'id',
'type',
'informal_name',
'formal_name',
'start_membership',
'main_branch',
'sub_branches',
]
const CSV_VALUE_NONE = '-'
export default {
layout: `${process.env.CUSTOMER}Admin`,
// middleware: 'adminsOrOperatorsOrMemberEditors',
components: {
PageHeader,
MembersTable,
},
data() {
return {
selector: 'allMembers',
}
},
methods: {
exportCsv() {
let inputData = [
I18N_CSV_HEADER_KEYS.map((key) => {
return this.$t(`${I18N_CSV_WRAPPER_KEY}.${key}`)
}),
]
this.allMembers.forEach((member) => {
let subBranchTitles = member.sub_branches.map(branch => branch.title)
subBranchTitles.sort()
inputData.push([
member.id,
member.type ?? CSV_VALUE_NONE,
member.informal_name ?? CSV_VALUE_NONE,
member.formal_name ?? CSV_VALUE_NONE,
this.formatCsvDate(
member.start_membership,
'yyyy-LL-dd HH:mm:ss',
) ?? CSV_VALUE_NONE,
member.main_branch ?? CSV_VALUE_NONE,
subBranchTitles.length
? subBranchTitles.join(', ')
: CSV_VALUE_NONE,
])
})
inputData.sort((rowLeft, rowRight) => rowRight.id - rowLeft.id);
csv.stringify(inputData, { quoted: true }, (err, output) => {
if (err) {
// TODO: handle
} else {
download(
output,
this.$t(`${I18N_CSV_WRAPPER_KEY}.filename`),
'text/csv'
)
}
})
},
formatCsvDate: (input, inputFormat) => {
if (input) {
return DateTime.fromFormat(input, inputFormat).toFormat('yyyy-LL-dd')
} else {
return null;
}
},
},
computed: {
allMembers() {
return this.$store.getters['members/membersFiltered']
},
published() {
return this.allMembers.filter((member) => !member.deleted_at)
},
active() {
return this.published.filter(
(member) =>
!member.end_membership || dayjs().isBefore(member.end_membership)
)
},
inactive() {
return this.published.filter(
(member) =>
member.end_membership && !dayjs().isBefore(member.end_membership)
)
},
deleted() {
return this.allMembers.filter((member) => member.deleted_at)
},
membersComputed() {
if (
!['allMembers', 'active', 'inactive', 'deleted'].includes(this.selector)
) {
return []
}
return this[this.selector]
},
},
async asyncData({ $axios, store }) {
try {
await store.dispatch('members/pullBranches')
if (!store.getters['members/hasMembers']) {
await store.dispatch('members/pullData')
}
} catch (error) {
console.log('Data -> error', error)
}
},
// #warning Laravel Echo has been manually disabled until broadcasting issue is fixed.
// async mounted() {
// this.$echo.channel('updates').listen(`.members-updated`, async (e) => {
// await this.$store.dispatch('members/pullData')
//
// this.$notifier.showMessage({
// content: 'Members updated',
// color: 'success',
// icon: 'icon-message',
// })
// })
// },
async beforeRouteLeave(to, from, next) {
await this.$store.commit('members/RESET_MEMBERS')
next()
},
}
</script>