From ce54eb5066ebc33e2d6070cbabeb61325642ad53 Mon Sep 17 00:00:00 2001 From: Thijn Date: Wed, 22 Jan 2025 13:25:12 +0100 Subject: [PATCH 01/14] fixed delete page modal functionality --- src/modals/Modals.vue | 3 +++ src/modals/page/DeletePage.vue | 30 ++++++++++++++---------------- src/views/pages/PageDetail.vue | 2 +- src/views/pages/PageList.vue | 2 +- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/modals/Modals.vue b/src/modals/Modals.vue index f35c1844..a316e90c 100644 --- a/src/modals/Modals.vue +++ b/src/modals/Modals.vue @@ -26,6 +26,7 @@ import { navigationStore, publicationStore } from './../store/store.js' + @@ -56,6 +57,7 @@ import EditPublicationDataModal from './publicationData/EditPublicationDataModal import AddThemeModal from './theme/AddThemeModal.vue' import EditThemeModal from './theme/EditThemeModal.vue' import PageForm from './page/PageForm.vue' +import DeletePage from './page/DeletePage.vue' import AddPageContentsModal from './pageContents/AddPageContents.vue' import EditMenuModal from './menu/EditMenuModal.vue' import DeleteMenuModal from './menu/DeleteMenuModal.vue' @@ -87,6 +89,7 @@ export default { AddThemeModal, EditThemeModal, PageForm, + DeletePage, AddPageContentsModal, EditMenuModal, DeleteMenuModal, diff --git a/src/modals/page/DeletePage.vue b/src/modals/page/DeletePage.vue index 5707ff95..f65c6516 100644 --- a/src/modals/page/DeletePage.vue +++ b/src/modals/page/DeletePage.vue @@ -3,12 +3,11 @@ import { pageStore, navigationStore } from '../../store/store.js' Content toevoegen - + diff --git a/src/views/pages/PageList.vue b/src/views/pages/PageList.vue index f5c8f562..22bc6d9f 100644 --- a/src/views/pages/PageList.vue +++ b/src/views/pages/PageList.vue @@ -65,7 +65,7 @@ import { navigationStore, pageStore } from '../../store/store.js' Kopiëren - + From 82a8c3cb2e64d582d5b3c748d79d35c17b3b6cfd Mon Sep 17 00:00:00 2001 From: Thijn Date: Wed, 22 Jan 2025 16:45:11 +0100 Subject: [PATCH 02/14] added copy menu option --- src/dialogs/Dialogs.vue | 3 + src/dialogs/menu/CopyMenuDialog.vue | 119 ++++++++++++++++++++++++++++ src/views/menus/MenuDetail.vue | 2 +- src/views/menus/MenuList.vue | 7 ++ 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 src/dialogs/menu/CopyMenuDialog.vue diff --git a/src/dialogs/Dialogs.vue b/src/dialogs/Dialogs.vue index 9160f1f7..f1439539 100644 --- a/src/dialogs/Dialogs.vue +++ b/src/dialogs/Dialogs.vue @@ -30,6 +30,7 @@ import { navigationStore } from '../store/store.js' + @@ -58,6 +59,7 @@ import DeletePublicationThemeDialog from './publicationTheme/DeletePublicationTh import CopyThemeDialog from './theme/CopyThemeDialog.vue' import DeleteThemeDialog from './theme/DeleteThemeDialog.vue' import DownloadPublicationDialog from './publication/DownloadPublicationDialog.vue' +import CopyMenuDialog from './menu/CopyMenuDialog.vue' export default { name: 'Dialogs', @@ -86,6 +88,7 @@ export default { DeleteThemeDialog, CopyThemeDialog, DownloadPublicationDialog, + CopyMenuDialog, }, } diff --git a/src/dialogs/menu/CopyMenuDialog.vue b/src/dialogs/menu/CopyMenuDialog.vue new file mode 100644 index 00000000..43608b67 --- /dev/null +++ b/src/dialogs/menu/CopyMenuDialog.vue @@ -0,0 +1,119 @@ + + + + + + + diff --git a/src/views/menus/MenuDetail.vue b/src/views/menus/MenuDetail.vue index 07f5ac0e..15d31f3b 100644 --- a/src/views/menus/MenuDetail.vue +++ b/src/views/menus/MenuDetail.vue @@ -39,7 +39,7 @@ import { getTheme } from '../../services/getTheme.js' Bewerken - + diff --git a/src/views/menus/MenuList.vue b/src/views/menus/MenuList.vue index f1214045..e6509169 100644 --- a/src/views/menus/MenuList.vue +++ b/src/views/menus/MenuList.vue @@ -59,6 +59,12 @@ import { navigationStore, menuStore } from '../../store/store.js' Bewerken + + + Kopiëren + @@ -59,8 +62,11 @@ import EditThemeModal from './theme/EditThemeModal.vue' import PageForm from './page/PageForm.vue' import DeletePage from './page/DeletePage.vue' import AddPageContentsModal from './pageContents/AddPageContents.vue' +// menu import EditMenuModal from './menu/EditMenuModal.vue' import DeleteMenuModal from './menu/DeleteMenuModal.vue' +import EditMenuItemModal from './menuItem/EditMenuItemModal.vue' +import DeleteMenuItemModal from './menuItem/DeleteMenuItemModal.vue' /** * Component that contains all modals used in the application @@ -91,8 +97,11 @@ export default { PageForm, DeletePage, AddPageContentsModal, + // menu EditMenuModal, DeleteMenuModal, + EditMenuItemModal, + DeleteMenuItemModal, }, } diff --git a/src/modals/menu/EditMenuModal.vue b/src/modals/menu/EditMenuModal.vue index eda394f8..9561cfb2 100644 --- a/src/modals/menu/EditMenuModal.vue +++ b/src/modals/menu/EditMenuModal.vue @@ -43,13 +43,10 @@ import { getTheme } from '../../services/getTheme.js' :value.sync="menuItem.name" :error="!!inputValidation.fieldErrors?.['name']" :helper-text="inputValidation.fieldErrors?.['name']?.[0]" /> - +
option.position === menuStore.menuItem.position) } }, /** @@ -182,6 +190,7 @@ export default { const menuItem = new Menu({ ...this.menuItem, items: this.menuItem.items ? JSON.parse(this.menuItem.items) : [], + position: this.menuPositionOptions.value.position, }) menuStore.saveMenu(menuItem).then(({ response }) => { diff --git a/src/modals/menuItem/DeleteMenuItemModal.vue b/src/modals/menuItem/DeleteMenuItemModal.vue new file mode 100644 index 00000000..ac3b4115 --- /dev/null +++ b/src/modals/menuItem/DeleteMenuItemModal.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/src/modals/menuItem/EditMenuItemModal.vue b/src/modals/menuItem/EditMenuItemModal.vue new file mode 100644 index 00000000..e184d927 --- /dev/null +++ b/src/modals/menuItem/EditMenuItemModal.vue @@ -0,0 +1,278 @@ + + + + + diff --git a/src/services/formatZodErrors.js b/src/services/formatZodErrors.js new file mode 100644 index 00000000..9713a644 --- /dev/null +++ b/src/services/formatZodErrors.js @@ -0,0 +1,232 @@ +export function createZodErrorHandler(result) { + const issues = result?.error?.issues || [] + + const normalizePath = (path) => { + if (typeof path === 'string') { + return path.split('.') + } + return path + } + + /** + * Get a single error message for a specific path + * + * @example + * ```js + * const error = getError('items.0.name') + * console.log(error) // "naam is verplicht" + * ``` + * + * @param {string} path - The path to get the error message for + * @return {string | undefined} The error message or undefined if no error is found + */ + const getError = (path) => { + const normalizedPath = normalizePath(path) + const error = issues.find((issue) => issue.path.join('.') === normalizedPath.join('.')) + return error?.message + } + + /** + * Get all error messages for a specific path + * + * @example + * ```js + * const errors = getErrors('items.0') + * console.log(errors) // ["naam is verplicht", "summary is verplicht", "link is verplicht"] + * ``` + * + * @param {string} path - The path to get the error message for + * @return {string[]} The error messages or undefined if no error is found + */ + const getErrors = (path) => { + const normalizedPath = normalizePath(path) + const errors = issues.filter((issue) => issue.path.join('.') === normalizedPath.join('.')) + return errors.map((error) => error.message) + } + + return { + // methods + getError, + getErrors, + + // properties + success: result.success, + + /** + * A simple list of flattened errors. + * + * @example + * ```json + * [ + * "items.0.name: naam is verplicht", + * "items.0.summary: summary is verplicht", + * "items.0.link: link is verplicht" + * ] + * ``` + * + * @type {string[]} + */ + flatErrorMessages: result?.error ? getFlatErrorMessages(result.error.issues) : [], + + /** + * A grouped list of errors by path. + * + * @example + * ```json + * { + * "items.0.name": ["naam is verplicht"], + * "items.0.summary": ["summary is verplicht"], + * "items.0.link": ["link is verplicht"] + * } + * ``` + * + * @type {object} + */ + groupedErrorsByPath: result?.error ? getGroupedErrorsByPath(result.error.issues) : {}, + + /** + * A nested list of errors by path. + * + * @example + * ```json + * { + * items: { + * 0: { + * name: ["naam is verplicht"], + * summary: ["summary is verplicht"], + * link: ["link is verplicht"] + * } + * } + * } + * ``` + * + * @type {object} + */ + nestedFieldErrors: result?.error ? getNestedFieldErrors(result.error.issues) : {}, + + /** + * A list of errors with path, message, code and type. + * + * @example + * ```json + * [ + * { + * path: "items.0.name", + * message: "naam is verplicht", + * code: "too_small", + * type: "string" + * }, + * { + * path: "items.0.summary", + * message: "summary is verplicht", + * code: "too_small", + * type: "string" + * } + * ] + * ``` + * + * @type {object} + */ + fieldSpecificErrors: result?.error ? getFieldSpecificErrors(result.error.issues) : [], + + /** + * A summary of the errors. + * + * @example + * ```json + * { + * totalErrors: 3, + * errorsByField: { + * "items.0.name": 1, + * "items.0.summary": 1, + * "items.0.link": 1 + * }, + * errorsByType: { + * "too_small": 3 + * } + * } + * ``` + * + * @type {object} + */ + errorSummary: result?.error ? getErrorSummary(result.error.issues) : {}, + } +} + +// BASE FUNCTIONS +const joinPath = (path) => path.join('.') + +// ERROR MAPPERS + +// Function to convert the issues array into error messages +const getFlatErrorMessages = (issues) => { + return issues.map((issue) => `${joinPath(issue.path)}: ${issue.message}`) || [] +} + +// Function to get a grouped errors by path +const getGroupedErrorsByPath = (issues) => { + const groupedErrors = {} + + issues.forEach((issue) => { + const path = joinPath(issue.path) + groupedErrors[path] = groupedErrors[path] || [] + groupedErrors[path].push(issue.message) + }) + + return groupedErrors +} + +// Function to convert the issues array into field errors +const getNestedFieldErrors = (issues) => { + const fieldErrors = {} + + issues.forEach((issue) => { + let currentLevel = fieldErrors + const path = issue.path + + // Traverse the path to build the nested structure + for (let i = 0; i < path.length; i++) { + const key = path[i] + + if (i === path.length - 1) { + // If it's the last element in the path, set the error message + currentLevel[key] = currentLevel[key] || [] + currentLevel[key].push(issue.message) + } else { + // Otherwise, continue traversing the nested structure + currentLevel[key] = currentLevel[key] || {} + currentLevel = currentLevel[key] + } + } + }) + + return fieldErrors +} + +// Function to get a list of errors with path, message, code and type +const getFieldSpecificErrors = (issues) => { + return issues.map((issue) => ({ + path: joinPath(issue.path), + message: issue.message, + code: issue.code, + type: issue.type, + })) +} + +// Function to get a summary of the errors as a number +const getErrorSummary = (issues) => { + const errorsByField = {} + const errorsByType = {} + + issues.forEach((issue) => { + const path = joinPath(issue.path) + errorsByField[path] = (errorsByField[path] || 0) + 1 + errorsByType[issue.code] = (errorsByType[issue.code] || 0) + 1 + }) + + return { + totalErrors: issues.length, + errorsByField, + errorsByType, + } +} diff --git a/src/store/modules/menu.ts b/src/store/modules/menu.ts index 0dec6c39..bfbb3be3 100644 --- a/src/store/modules/menu.ts +++ b/src/store/modules/menu.ts @@ -14,6 +14,7 @@ interface Options { interface MenuStoreState { menuItem: Menu; menuList: Menu[]; + menuItemItemsIndex: number; } /** @@ -23,6 +24,7 @@ export const useMenuStore = defineStore('menu', { state: () => ({ menuItem: null, menuList: [], + menuItemItemsIndex: null, } as MenuStoreState), actions: { /** diff --git a/src/views/menus/MenuDetail.vue b/src/views/menus/MenuDetail.vue index 15d31f3b..3cec912b 100644 --- a/src/views/menus/MenuDetail.vue +++ b/src/views/menus/MenuDetail.vue @@ -1,14 +1,14 @@ - + diff --git a/src/views/menus/MenuList.vue b/src/views/menus/MenuList.vue index e6509169..2d89c664 100644 --- a/src/views/menus/MenuList.vue +++ b/src/views/menus/MenuList.vue @@ -59,6 +59,12 @@ import { navigationStore, menuStore } from '../../store/store.js' Bewerken + + + Menu item toevoegen +