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

43
store/columnBrowser.js Normal file
View File

@@ -0,0 +1,43 @@
export const state = () => ({
page: 1,
resultsPerPage: 3,
});
export const getters = {
dataset: (state, rootGetters, rootState) => rootState.learning.columnsSorted.filter(el => el.display && !el.fixed),
firstEntry: (state) => (state.page - 1) * state.resultsPerPage,
lastEntry: (state, getters) => getters.firstEntry + state.resultsPerPage,
subset: (state, getters) => getters.dataset.slice(getters.firstEntry, getters.lastEntry),
isFirstPage: (state) => state.page === 1,
isLastPage: (state, getters) => state.page === getters.totalPages,
totalPages: (state, getters) => Math.ceil(getters.dataset.length / state.resultsPerPage) || 1,
}
export const actions = {
async changePage({ commit, state, getters }, direction) {
if (!['previous', 'next'].includes(direction)) return
const currentPage = state.page
let newPage;
if ((direction === 'next') && !getters.isLastPage) newPage = currentPage + 1
if ((direction === 'previous') && !getters.isFirstPage) newPage = currentPage - 1
await commit('SET_PAGE', newPage);
},
}
export const mutations = {
SET_PAGE: (state, page) => state.page = +page,
}

176
store/index.js Normal file
View File

@@ -0,0 +1,176 @@
import Util from '@/util'
export const getters = {
loggedInUser: (state) => state.auth.user,
learningProducts: (state) => state.learning.products,
learningProductsActive: (state, getters) => {
getters.learningProducts.filter((p) => !p.deleted_at)
},
hasLearningProducts: (state, getters) => getters.learningProducts.length > 0,
searchOverlay: (state) => state.navigation.searchOverlay,
snackbar: (state) => state.snackbar,
rightDrawer: (state) => state.navigation.rightDrawer,
isSuperAdmin: (state) =>
state.auth.user.roles.some((r) => r.name === 'super_admin'),
isAdmin: (state) => state.auth.user.roles.some((r) => r.name === 'admin'),
isSuperAdminOrAdmin: (state, getters) =>
getters.isSuperAdmin || getters.isAdmin,
isOperator: (state) =>
state.auth.user.roles.some((r) => r.name === 'operator'),
isUser: (state) => state.auth.user.roles.some((r) => r.name === 'user'),
isMember: (state) => state.auth.user.isMemberEditor || false,
hasCharges: (state, getters) =>
getters.isSuperAdmin || getters.isAdmin || getters.isOperator,
isOnlyMemberEditor: (state, getters) =>
!getters.hasCharges && getters.isMember,
notifications: (state) => state.auth.user.notifications,
unreadNotifications: (state) =>
state.auth.user.notifications.filter((n) => !n.read_at),
readNotifications: (state) =>
state.auth.user.notifications.filter((n) => n.read_at),
hasNotifications: (state) => state.auth.user.notifications.length > 0,
hasUnreadNotifications: (state, getters) =>
getters.unreadNotifications.length > 0,
isReadNotification: (state, getters) => (notificationId) => {
if (!notificationId) return false
return getters.notifications.find((n) => n.id === notificationId)
},
columnsSortedSubset: (state, rootGetters) => {
const actionsColumn = [
{
text: '',
value: 'actions',
sortable: false,
fixed: true,
},
]
const columns = [
...state.learning.columnsSorted.filter((el) => el.fixed),
...rootGetters['columnBrowser/subset'],
...actionsColumn,
]
return columns
},
filters: (state) => state.learning.filters,
filtersSelected: (state) => state.learning.filtersSelected,
filtersSearchable: (state) => {
return state.learning.filters.filter(
(f) => !state.learning.filtersDisabledForSearch.includes(f.title)
)
},
// Map with filterId as key and full Filter object as value
filtersMap: (state, getters) => {
const map = new Map()
getters.filters.forEach((filter) => map.set(filter.id, filter))
return map
},
filtersItemsMap: (state, getters) => {
const map = new Map()
// Cycle all filters
getters.filters.forEach((filter) => {
// Cycle all filter items
filter.items.forEach((item) => {
map.set(item.id, item)
})
})
return map
},
getFilterByTitle: (state, getters) => (title) => {
if (!getters.filters.length > 0) return []
return getters.filters.find((filter) => filter.title === title)
},
getFilterById: (state, getters) => (filterId) => {
const emptyFilter = {
id: '*',
title: '*',
items: [],
}
if (!filterId) {
return emptyFilter
}
if (!getters.filtersMap.has(filterId)) {
return emptyFilter
}
return getters.filtersMap.get(filterId)
},
getFilterItemById: (state, getters) => (filterItemId) => {
const emptyFilterItem = {
id: '*',
filter_id: '*',
title: '*',
}
if (!filterItemId) {
return emptyFilterItem
}
if (!getters.filtersItemsMap.has(filterItemId)) {
return emptyFilterItem
}
return getters.filtersItemsMap.get(filterItemId)
},
filterItemsResolved:
(state, getters) =>
({ filterTitle = '', filters = [] }) => {
const filter = getters.getFilterByTitle(filterTitle)
return filters.filter((f) => f.filter_item.filter_id === filter.id)
},
synonyms: (state) => state.learning.synonyms || [],
attachedSynonymsIds: (state, getters) => {
return getters.remoteProduct.synonyms
? getters.remoteProduct.synonyms.map(({ id }) => id)
: []
},
synonymsHaveChanges: (state, getters) =>
!Util.arraysMatch(
getters.attachedSynonymsIds,
state.learning.synonymsSelected
),
remoteProduct: (state) => state.learning.remote,
localProduct: (state) => state.learning.local,
productHasChanges: (state, getters) => {
if (!Util.areEqualInputs(getters.remoteProduct, getters.localProduct))
return true
if (getters.synonymsHaveChanges) return true
if (state.learning.cover.url) return true
if (state.learning.tile.url) return true
return false
},
isFilterTypeEmpty: (state, getters) => {
const idType = getters.getFilterByTitle('type').id
if (!getters.localProduct.filtersGrouped) return true
if (!getters.localProduct.filtersGrouped[idType]) return true
if (getters.localProduct.filtersGrouped[idType].length > 0) return false
return true
},
isLearningProductValidated: (state, getters) => {
return (
!!getters.localProduct.title &&
!!getters.localProduct.code &&
!!getters.localProduct.seo_title
)
},
}

448
store/learning.js Normal file
View File

@@ -0,0 +1,448 @@
import Vue from 'vue'
export const state = () => ({
products: [],
remote: {},
local: { filtersGrouped: {} },
images: [],
cover: {
name: '',
url: '',
file: null,
},
tile: {
name: '',
url: '',
file: null,
},
columns: [
{
text: 'learning.product',
sortable: false,
value: 'cover',
fixed: true,
},
{
text: '',
sortable: false,
value: 'title',
fixed: true,
divider: true,
},
{
text: 'learning.code',
sortable: true,
value: 'code',
display: true,
},
{
text: 'learning.partner',
sortable: true,
value: 'partner',
display: true,
},
{
text: 'learning.owner',
sortable: true,
value: 'owner',
display: true,
},
{
text: 'learning.status.title',
sortable: false,
value: 'status',
display: true,
},
{
text: 'learning.product_overview.duration',
sortable: true,
value: 'lead_time',
display: true,
},
{
text: 'learning.filters.product_type',
sortable: false,
value: 'product_type',
display: true,
},
{
text: 'learning.filters.theme',
sortable: true,
value: 'theme',
display: true,
},
{
text: 'learning.filters.course',
sortable: false,
value: 'course',
display: true,
},
],
filters: [],
columnsSorted: [],
columnsSortedSubset: [],
filtersSelected: [],
filtersDisabledForSearch: ['format_version'],
versionsFiltersSelected: {},
accreditationsFiltersSelected: {},
checklists: [],
checklistsSelected: [],
synonyms: [],
synonymsSelected: [],
coursesTableOptions: {},
})
export const actions = {
async pullProducts({ commit, state }) {
try {
const response = await this.$axios.get('/learning-products')
const learningProducts = response.data
await commit('STORE_PRODUCTS', learningProducts)
} catch (error) {
console.log('pullProducts -> error', error)
}
},
async setColumns({ commit, state }, arrayColumns = state.columns) {
await commit('SORT_COLUMNS', arrayColumns)
},
async setFilters({ commit, state }, arrayFilters = state.filters) {
await commit('SORT_FILTERS', arrayFilters)
},
async resetProduct({ commit }) {
await commit('RESET_PRODUCT')
await commit('RESET_COVER')
await commit('RESET_TILE')
await commit('RESET_SELECTED_SYNONYMS')
},
async storeProduct({ commit, state, rootGetters }, published = false) {
// Set product to save as published
if (published)
await commit('UPDATE_FIELD', { field: 'published', value: 1 })
if (state.local.in_the_picture)
await commit('UPDATE_FIELD', { field: 'in_the_picture', value: 1 })
if (state.local.for_members)
await commit('UPDATE_FIELD', { field: 'for_members', value: 1 })
// Check if remotely the product was already set as published
const wasPublished = state.remote.published
// If it has a parent_id, it's a draft of a product published
const hasParentId = state.remote.parent_id
// Preparing data
//-----------------------------------------
const formData = new FormData()
for (let key in state.local) {
if (state.local[key] && state.local[key] != 'null') {
// If value is array/object, stringify that
formData.append(
key,
typeof state.local[key] === 'object'
? JSON.stringify(state.local[key])
: state.local[key]
)
} else {
formData.append(key, '')
}
}
if (state.tile.file) formData.append('tile', state.tile.file)
else formData.delete('tile')
if (state.cover.file) formData.append('cover', state.cover.file)
else formData.delete('cover')
if (state.local.parent_id == null) formData.delete('parent_id')
// If wasn't published and we want to save as draft
if (wasPublished && !published && !hasParentId) {
formData.append('parent_id', state.remote.id)
formData.delete('published')
formData.delete('id')
}
if (rootGetters.synonymsHaveChanges) {
formData.append(
'synonymsSelected',
JSON.stringify(state.synonymsSelected)
)
}
if (!formData.get('published')) {
formData.delete('published')
}
let slug = ''
// console.log([...formData])
await this.$axios
.post('/learning-products', formData)
.then((response) => {
// 'Cause if it's published we close the page
Promise.resolve(response)
if (!published) commit('SET_PRODUCT', response.data)
slug = response.data.slug
})
.catch((error) => Promise.reject(error))
return slug
},
async deleteProduct({ dispatch, state }, productId) {
if (!productId) return
try {
// $nuxt.$loading.start();
await this.$axios.delete(`/learning-products/${productId}`)
await dispatch('pullProducts')
$nuxt.$loading.finish()
$nuxt.$notifier.showMessage({
content: `Product deleted`,
color: 'success',
icon: 'mdi-delete',
})
} catch (error) {
console.log('deleteProduct -> error', error)
$nuxt.$loading.finish()
$nuxt.$notifier.showMessage({
content: error,
color: 'error',
icon: 'mdi-close',
})
}
},
async removeSynonym({ commit, state }, synonymId) {
if (!synonymId) return
const backup = [...state.synonymsSelected]
const synonymsFiltered = backup.filter((s) => s !== synonymId)
commit('SELECT_SYNONYM', synonymsFiltered)
},
}
export const mutations = {
STORE_PRODUCTS: (state, products) => {
state.products = []
state.products = [...products]
},
SORT_COLUMNS: (state, columns) => {
state.columnsSorted = columns
},
SET_COLUMNS_SUBSET: (state, columns) => {
state.columnsSortedSubset = null
state.columnsSortedSubset = columns
},
SORT_FILTERS: (state, filters) => {
// Workaround because Vuex doesn't recognize changes otherwise
state.filters = []
state.filters = [...filters]
},
SET_PRODUCT: (state, product) => {
state.remote = {}
state.local = {}
state.remote = Object.assign({}, product)
state.local = Object.assign({}, product)
// console.log(JSON.stringify(state.remote))
// console.log(JSON.stringify(state.local))
},
RESET_PRODUCT: (state) => {
state.remote = {}
state.local = {}
state.versionsFiltersSelected = {}
},
RESET_VERSIONS_FILTERS: (state) => {
state.versionsFiltersSelected = {}
},
RESET_ACCREDITATIONS_FILTERS: (state) => {
state.accreditationsFiltersSelected = {}
},
UPDATE_FIELD: (state, payload) => {
state.local[payload.field] = payload.value
},
UPDATE_FILTERS: async (state, payload) => {
if (payload.target === 'versions') {
return Vue.set(
state.versionsFiltersSelected,
payload.filterId,
Array.isArray(payload.value) ? payload.value : [payload.value]
)
// Workaround because Vuex doesn't recognize changes otherwise
// let tmp = Object.assign({}, state.versionsFiltersSelected)
// state.versionsFiltersSelected = {}
// state.versionsFiltersSelected = Object.assign({}, tmp)
// return state.versionsFiltersSelected[payload.filterId] = Array.isArray(payload.value)
// ? payload.value
// : [payload.value]
}
if (payload.target === 'accreditations') {
return Vue.set(
state.accreditationsFiltersSelected,
payload.filterId,
Array.isArray(payload.value) ? payload.value : [payload.value]
)
// Workaround because Vuex doesn't recognize changes otherwise
// let tmp = Object.assign({}, state.accreditationsFiltersSelected)
// state.accreditationsFiltersSelected = {}
// state.accreditationsFiltersSelected = Object.assign({}, tmp)
// return state.accreditationsFiltersSelected[payload.filterId] = Array.isArray(payload.value)
// ? payload.value
// : [payload.value]
}
if (!state.local['filtersGrouped']) state.local['filtersGrouped'] = {}
state.local['filtersGrouped'][payload.filterId] = Array.isArray(
payload.value
)
? payload.value
: [payload.value]
state.local['filterItemsSelected'] = Object.values(
state.local.filtersGrouped
).flat()
},
ADD_VERSION_LOCAL: (state, payload) => {
// Workaround because Vuex doesn't recognize changes otherwise
const tmp = [...state.local.versions]
tmp.push(payload)
state.local.versions = []
state.remote.versions = []
state.local.versions = [...tmp]
state.remote.versions = [...tmp]
},
EDIT_VERSION_LOCAL: (state, payload) => {
// Workaround because Vuex doesn't recognize changes otherwise
let tmp = [...state.local.versions]
tmp[payload.index] = Object.assign({}, payload.version)
state.local.versions = []
state.remote.versions = []
state.local.versions = [...tmp]
state.remote.versions = [...tmp]
},
DELETE_VERSION_LOCAL: (state, payload) => {
const indexLocal = state.local.versions.indexOf(payload)
const indexRemote = state.remote.versions.indexOf(payload)
state.local.versions.splice(indexLocal, 1)
state.remote.versions.splice(indexRemote, 1)
},
ADD_ACCREDITATION_LOCAL: (state, payload) => {
// Workaround because Vuex doesn't recognize changes otherwise
const tmp = [...state.local.accreditations]
tmp.push(payload)
state.local.accreditations = []
state.local.accreditations = [...tmp]
},
EDIT_ACCREDITATION_LOCAL: (state, payload) => {
// Workaround because Vuex doesn't recognize changes otherwise
let tmp = [...state.local.accreditations]
tmp[payload.index] = Object.assign({}, payload.accreditation)
state.local.accreditations = []
state.local.accreditations = [...tmp]
},
DELETE_ACCREDITATION_LOCAL: (state, payload) => {
const index = state.local.accreditations.indexOf(payload)
state.local.accreditations.splice(index, 1)
},
ADD_COURSE_NOTIFICATION_LOCAL: (state, payload) => {
// Workaround because Vuex doesn't recognize changes otherwise
const tmp = [...state.local.notifications]
tmp.push(payload)
state.local.notifications = []
state.local.notifications = [...tmp]
},
EDIT_COURSE_NOTIFICATION_LOCAL: (state, payload) => {
// Workaround because Vuex doesn't recognize changes otherwise
let tmp = [...state.local.notifications]
tmp[payload.index] = Object.assign({}, payload.notification)
state.local.notifications = []
state.local.notifications = [...tmp]
},
DELETE_COURSE_NOTIFICATION_LOCAL: (state, payload) => {
const index = state.local.notifications.indexOf(payload)
state.local.notifications.splice(index, 1)
},
RESET_TILE: (state) => {
state.tile = {
name: '',
url: '',
file: null,
}
},
UPDATE_TILE: (state, payload) => {
state.tile = payload
},
RESET_COVER: (state) => {
state.cover = {
name: '',
url: '',
file: null,
}
},
UPDATE_COVER: (state, payload) => {
state.cover = payload
},
SELECT_FILTERS: (state, payload) => {
state.filtersSelected = payload
state.coursesTableOptions.page = 1
},
REMOVE_SELECTED_FILTER: (state, payload) => {
state.filtersSelected = state.filtersSelected.filter(
(item) => item !== payload
)
state.coursesTableOptions.page = 1
},
SET_CHECKLIST: (state, checklists) => {
// Workaround because Vuex doesn't recognize changes otherwise
let tmp = [...checklists]
state.checklists = []
state.checklists = [...tmp]
},
RESET_CHECKLIST_SELECTED: (state) => (state.checklistsSelected = []),
SET_CHECKED_CHECKLISTS: (state, payload) => {
state.checklistsSelected = [...payload]
},
SELECT_CHECKLIST: (state, payload) => {
state.checklistsSelected = payload
},
SET_SYNONYMS_LIST: (state, payload) => {
state.synonyms = [...payload]
},
SELECT_SYNONYM: (state, payload) => {
state.synonymsSelected = payload
},
RESET_SELECTED_SYNONYMS: (state) => {
state.synonymsSelected = []
},
SET_COURSES_TABLE_OPTIONS: (state, payload) => {
state.coursesTableOptions = {}
state.coursesTableOptions = payload
},
}

677
store/members.js Normal file
View File

@@ -0,0 +1,677 @@
import Vue from 'vue'
import Util from '@/util'
export const state = () => ({
list: [],
remote: {},
local: {},
logo: {
name: '',
url: '',
file: null,
},
branches: [],
});
export const getters = {
hasMembers: (state) => state.list.length > 0,
hasChanges: (state) => {
if (!Util.areEqualInputs(
state.remote,
state.local,
['summaries', 'addresses', 'contacts', 'contributions']
)) return true
if (state.logo.url) return true
return false
},
revision: (state) => {
if (!state.local.revision) return false
return JSON.parse(state.local.revision.data)
},
revisionHasChanges: (state, getters) => {
if (!getters.revision) return false
if (Util.areEqualInputs(
state.local,
getters.revision
)) return true
return false
},
membersFiltered: (state, getters, rootState, rootGetters) => {
if (rootGetters.isSuperAdmin || rootGetters.isAdmin || rootGetters.isOperator) {
return state.list
}
if (rootGetters.loggedInUser.isMemberEditor) return state.list.filter(
(member) => member.user_id === rootGetters.loggedInUser.id
)
return state.list
},
isSuperAdminAdminOrDelegated: (state, getters, rootState, rootGetters) => {
if (rootGetters.isSuperAdmin || rootGetters.isAdmin) return true
if (!state.local.user_id) return false
return state.local.user_id === rootGetters.loggedInUser.id
},
isMemberValidated: (state) => {
return !!state.local.informal_name && !!state.local.formal_name && !!state.local.kvk_number
},
getBranchById: (state) => (id) => state.branches.find(b => b.id === id),
}
/** @type {import('@typedefs/store').ActionTree} */
export const actions = {
async pullData({ commit, state }) {
try {
const response = await this.$axios.get('/members')
await commit('STORE_MEMBERS', response.data)
} catch (error) {
console.log("pullMembers -> error", error)
$nuxt.$notifier.showMessage({
content: error,
color: "error",
icon: "mdi-close",
});
}
},
async pullBranches({ commit, state }) {
try {
const response = await this.$axios.get('members/branches')
await commit("STORE_BRANCHES", response.data)
} catch (error) {
console.log("pullBranches -> error", error)
$nuxt.$notifier.showMessage({
content: error,
color: "error",
icon: "mdi-close",
});
}
},
async pullTypes({ commit }) {
try {
const response = await this.$axios.get('members/types');
await commit('STORE_TYPES', response.data);
} catch (error) {
console.log('pullTypes -> error', error)
$nuxt.$notifier.showMessage({
content: error,
color: 'error',
icon: 'mdi-close',
});
}
},
async getAndSetMember({ commit, dispatch, state }, url) {
try {
const response = await dispatch('get', url)
await commit('SET_MEMBER', response)
} catch (error) {
$nuxt.$notifier.showMessage({
content: error,
color: "error",
icon: "mdi-close",
});
}
},
async get({ state }, url) {
try {
const response = await this.$axios.get(`/members/${url}`)
return response.data
} catch (error) {
$nuxt.error({ statusCode: 401, message: 'Lid niet gevonden' })
$nuxt.$notifier.showMessage({
content: error,
color: "error",
icon: "mdi-close",
});
}
},
async store({ dispatch, commit, state, getters, rootGetters }, revision = false) {
const formData = new FormData()
let data;
if (revision) {
data = { ...getters.revision }
if (revision.show_on_website) data.show_on_website = 1
if (data.sub_branches.some(branch => branch.id)) {
const subBranchesIds = data.sub_branches.map(({ id }) => id)
data.sub_branches = [...subBranchesIds]
}
data.revisor_id = rootGetters.loggedInUser.id
} else {
if (state.local.show_on_website) await commit('UPDATE_FIELD', { field: 'show_on_website', value: 1 })
if (state.local.sub_branches.some(branch => branch.id)) {
const subBranchesIds = state.local.sub_branches.map(({ id }) => id)
await commit('UPDATE_FIELD', { field: 'sub_branches', value: subBranchesIds })
}
data = { ...state.local }
}
const fieldsAllowedNull = this.$constants.store.members.fieldsAllowedNull
for (let key in data) {
if ((data[key] && data[key] != "null")
|| (data[key] === null && fieldsAllowedNull.indexOf(key) !== -1)
) {
const value = data[key] ?? ''
// If value is array/object, stringify that
formData.append(
key,
typeof value === 'object'
? JSON.stringify(value)
: value
)
}
}
if (state.logo.file) formData.append('logo', state.logo.file)
else formData.delete('logo')
let slug = '';
// console.log([...formData])
await this.$axios.post('/members', formData)
.then(async response => {
Promise.resolve(response)
await dispatch('pullData');
slug = response.data.slug
})
.catch(error => Promise.reject(error))
return slug
},
async storeRevision({ state, rootGetters, dispatch }) {
const formData = new FormData()
const tmpObj = { ...state.local }
// Remove not useful data attached
delete tmpObj.revision;
delete tmpObj.data;
delete tmpObj.addresses;
delete tmpObj.summaries;
delete tmpObj.contacts;
const revision = JSON.stringify(tmpObj)
if (state.local.revision) formData.append('id', state.local.revision.id)
if (state.logo.file) formData.append('logo', state.logo.file)
formData.append('member_id', state.local.id)
formData.append('user_id', rootGetters.loggedInUser.id)
formData.append('data', revision)
// console.log([...formData])
await this.$axios.post('/members/revision', formData)
.then(async response => {
Promise.resolve(response)
await dispatch('pullData');
})
.catch(error => Promise.reject(error))
},
async deleteMember({ dispatch, state }, memberId = state.remote.id) {
if (!memberId) return
try {
await this.$axios.delete(`/members/${memberId}`)
await dispatch('pullData');
$nuxt.$notifier.showMessage({
content: `Member deleted`,
color: 'success',
icon: 'mdi-delete',
});
} catch (error) {
console.log("deleteMember -> error", error)
$nuxt.$notifier.showMessage({
content: error,
color: "error",
icon: "mdi-close",
});
}
},
async storeSummary({ dispatch, state }, data) {
if (!state.remote.id) return
data['member_id'] = state.remote.id
if (data['toAll'] === false) { delete data['toAll'] }
await this.$axios.post(`/members/summaries`, data)
.then(async response => {
Promise.resolve(response)
await dispatch('getAndSetMember', data['member_id'])
$nuxt.$notifier.showMessage({
content: 'Werknemers opgeslagen',
color: 'success',
icon: 'mdi-check',
});
})
.catch(error => {
Promise.reject(error)
$nuxt.$notifier.showMessage({
content: error,
color: "error",
icon: "mdi-close",
});
})
},
async storeContribution({ dispatch, state, rootGetters }, data) {
if (!state.remote.id) return
if (!rootGetters.isSuperAdminOrAdmin && data.toAll) return
data['member_id'] = state.remote.id
if (data['toAll'] === false) { delete data['toAll'] }
await this.$axios.post(`/members/contributions`, data)
.then(async response => {
Promise.resolve(response)
// if (!data.toAll) {
await dispatch('getAndSetMember', data['member_id'])
// }
$nuxt.$notifier.showMessage({
content: `Contribution added`,
color: 'success',
icon: 'mdi-check',
});
})
.catch(error => {
Promise.reject(error)
$nuxt.$notifier.showMessage({
content: error,
color: "error",
icon: "mdi-close",
});
})
},
async storeAddress({ dispatch, state }, data) {
if (!state.remote.id) return
data['member_id'] = state.remote.id
await this.$axios.post(`/members/addresses`, data)
.then(async response => {
Promise.resolve(response)
await dispatch('getAndSetMember', data['member_id'])
$nuxt.$notifier.showMessage({
content: `Address added`,
color: 'success',
icon: 'mdi-check',
});
})
.catch(error => {
Promise.reject(error)
$nuxt.$notifier.showMessage({
content: error,
color: "error",
icon: "mdi-close",
});
})
},
async storeContact({ dispatch, state }, data) {
if (!state.remote.id) return
data['member_id'] = state.remote.id
await this.$axios.post(`/members/contacts`, data)
.then(async response => {
Promise.resolve(response)
await dispatch('getAndSetMember', data['member_id'])
$nuxt.$notifier.showMessage({
content: `Contact added`,
color: 'success',
icon: 'mdi-check',
});
})
.catch(error => {
Promise.reject(error)
$nuxt.$notifier.showMessage({
content: `${error.response ? error.response.data.message : error}.`,
...(error.response && {
errors: error.response.data.errors,
}),
color: "error",
icon: "mdi-close",
});
})
},
async deleteSummary({ commit, state }, item) {
if (!item.id) return
try {
await this.$axios.delete(`/members/summaries/${item.id}`)
await commit('DELETE_SUMMARY_LOCAL', item)
$nuxt.$notifier.showMessage({
content: `Summary deleted`,
color: 'success',
icon: 'mdi-delete',
});
} catch (error) {
console.log("addSummary -> error", error)
$nuxt.$notifier.showMessage({
content: error,
color: "error",
icon: "mdi-close",
});
}
},
async deleteContribution({ commit, state }, item) {
if (!item.id) return
try {
await this.$axios.delete(`/members/contributions/${item.id}`)
await commit('DELETE_CONTRIBUTION_LOCAL', item)
$nuxt.$notifier.showMessage({
content: `Contribution deleted`,
color: 'success',
icon: 'mdi-delete',
});
} catch (error) {
console.log(error)
$nuxt.$notifier.showMessage({
content: error,
color: "error",
icon: "mdi-close",
});
}
},
async addManagementLink({ dispatch, commit, stage }, data) {
if (!data) return
let link = { member_id: data };
await this.$axios.post(`/members/managementlink/addlink`, link)
.then(async response => {
Promise.resolve(response)
await dispatch('pullData');
$nuxt.$notifier.showMessage({
content: `Link added`,
color: 'success',
icon: 'mdi-check',
});
})
.catch(error => {
Promise.reject(error)
$nuxt.$notifier.showMessage({
content: `${error.response ? error.response.data.message : error}.`,
...(error.response && {
errors: error.response.data.errors,
}),
color: "error",
icon: "mdi-close",
});
})
},
async deleteManagementLink({ dispatch, commit, stage }, data) {
if (!data) return
let link = { link_id: data };
await this.$axios.post(`/members/managementlink/deletelink`, link)
.then(async response => {
Promise.resolve(response)
await dispatch('pullData');
$nuxt.$notifier.showMessage({
content: `Link removed`,
color: 'success',
icon: 'mdi-check',
});
})
.catch(error => {
Promise.reject(error)
$nuxt.$notifier.showMessage({
content: `${error.response ? error.response.data.message : error}.`,
...(error.response && {
errors: error.response.data.errors,
}),
color: "error",
icon: "mdi-close",
});
})
},
async changeManagementLink({ dispatch, commit, stage }, data) {
if (!data) return
await this.$axios.post(`/members/managementlink/changelink`, data)
.then(async response => {
Promise.resolve(response)
await dispatch('pullData');
$nuxt.$notifier.showMessage({
content: `Link added`,
color: 'success',
icon: 'mdi-check',
});
})
.catch(error => {
Promise.reject(error)
$nuxt.$notifier.showMessage({
content: `${error.response ? error.response.data.message : error}.`,
...(error.response && {
errors: error.response.data.errors,
}),
color: "error",
icon: "mdi-close",
});
})
},
async deleteAddress({ commit, state }, item) {
if (!item.id) return
try {
await this.$axios.delete(`/members/addresses/${item.id}`)
await commit('DELETE_ADDRESS_LOCAL', item)
$nuxt.$notifier.showMessage({
content: `Address deleted`,
color: 'success',
icon: 'mdi-delete',
});
} catch (error) {
console.log("deleteAddress -> error", error)
$nuxt.$notifier.showMessage({
content: error,
color: "error",
icon: "mdi-close",
});
}
},
async deleteContact({ commit, state }, item) {
if (!item.id) return
try {
await this.$axios.delete(`/members/contacts/${item.id}`)
await commit('DELETE_CONTACT_LOCAL', item)
$nuxt.$notifier.showMessage({
content: `Contact deleted`,
color: 'success',
icon: 'mdi-delete',
});
} catch (error) {
console.log("deleteContact -> error", error)
$nuxt.$notifier.showMessage({
content: error,
color: "error",
icon: "mdi-close",
});
}
},
async updateManagementLinks({ commit, state }, item) {
console.log(item);
},
async resetMember({ commit }) {
await commit('RESET_MEMBER');
await commit('RESET_LOGO');
},
}
export const mutations = {
STORE_MEMBERS: (state, payload) => state.list = payload,
STORE_BRANCHES: (state, payload) => state.branches = payload,
STORE_TYPES: (state, payload) => state.types = payload,
SET_MEMBER: (state, payload) => {
state.remote = { ...payload };
state.local = { ...payload };
},
UPDATE_FIELD: (state, payload) => {
// Old way with reactivity problems
// state.local[payload.field] = payload.value
// New way that solves with reactivity
Vue.set(state.local, payload.field, payload.value)
},
RESET_MEMBER: (state) => {
state.remote = {};
state.local = {};
},
RESET_MEMBERS: (state) => state.list = [],
RESET_LOGO: (state) => {
state.logo = {
name: '',
url: '',
file: null,
};
},
UPDATE_LOGO: (state, payload) => {
state.logo = payload;
},
DELETE_ADDRESS_LOCAL: (state, payload) => {
// Workaround because Vuex doesn't recognize changes otherwise
const tmp = [...state.local.addresses];
const index = tmp.indexOf(payload)
tmp.splice(index, 1)
state.local.addresses = []
state.local.addresses = [...tmp]
},
DELETE_CONTACT_LOCAL: (state, payload) => {
// Workaround because Vuex doesn't recognize changes otherwise
const tmp = [...state.local.contacts];
const index = tmp.indexOf(payload)
tmp.splice(index, 1)
state.local.contacts = []
state.local.contacts = [...tmp]
},
DELETE_SUMMARY_LOCAL: (state, payload) => {
// Workaround because Vuex doesn't recognize changes otherwise
const tmp = [...state.local.summaries];
const index = tmp.indexOf(payload)
tmp.splice(index, 1)
state.local.summaries = []
state.local.summaries = [...tmp]
},
DELETE_CONTRIBUTION_LOCAL: (state, payload) => {
// Workaround because Vuex doesn't recognize changes otherwise
const tmp = [...state.local.contributions];
const index = tmp.indexOf(payload)
tmp.splice(index, 1)
state.local.contributions = []
state.local.contributions = [...tmp]
},
}

25
store/navigation.js Normal file
View File

@@ -0,0 +1,25 @@
export const state = () => ({
searchOverlay: false,
rightDrawer: {
display: false,
component: null,
subMenu: null,
},
});
export const mutations = {
TOGGLE_SEARCH_OVERLAY: (state, value = null) => {
state.searchOverlay = value
},
SWITCH_RIGHT_DRAWER: (state, value = true) => {
if (typeof value === 'boolean') { state.rightDrawer.display = value }
else {
state.rightDrawer.component = value.component
state.rightDrawer.subMenu = value.subMenu
state.rightDrawer.display = true
}
},
};

15
store/snackbar.js Normal file
View File

@@ -0,0 +1,15 @@
export const state = () => ({
color: '',
content: '',
icon: '',
errors: []
})
export const mutations = {
showMessage(state, payload) {
state.content = payload.content
state.color = payload.color
state.icon = payload.icon;
state.errors = payload.errors;
}
}

53
store/utils.js Normal file
View File

@@ -0,0 +1,53 @@
import isEqual from 'lodash.isequal';
// export const state = () => ({});
export const getters = {
// console.log(this.$store.getters['utils/areEquals'](1, 1)) // It works
areEquals: (state) => (input1, input2) => isEqual(input1, input2),
isNotEmptyObj: (state) => (obj) => Object.keys(obj).length > 0,
filterArrayObjsByArrayOfProperties: (state) => (arrayObjs, arrayProperties) => {
const filteredData = arrayObjs.map((row) => {
const obj = {}
arrayProperties.forEach(property => obj[property] = row[property]);
return obj;
})
return filteredData;
},
arrayOfObjectToCsv: (state) => (data) => {
const headers = Object.keys(data[0])
const csvRows = []
for (const row of data) {
const values = headers.map((header) => {
const tmpArray = []
if (row[header]) {
// Replacing commas with whitespaces
tmpArray.push(row[header].replace(/,/g, ""))
return tmpArray
}
return '-'
})
csvRows.push([values.join(',')])
}
const csvString = [
[...headers],
...csvRows,
]
.map(e => e.join(","))
.join("\n");
return csvString
},
}