From 434b1ea0e87086f58aed7d30a25cdd63669fdb7e Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 18 Mar 2024 13:56:44 +0100 Subject: [PATCH] feat(views): crud in frontend --- .../project/project-settings-dropdown.vue | 6 + .../components/project/views/viewEditForm.vue | 177 ++++++++++++++++++ frontend/src/i18n/lang/en.json | 19 +- frontend/src/modelTypes/IProjectView.ts | 27 ++- frontend/src/models/project.ts | 4 + frontend/src/models/projectView.ts | 30 +++ frontend/src/router/index.ts | 10 + frontend/src/services/projectViews.ts | 20 ++ frontend/src/stores/projects.ts | 22 ++- frontend/src/views/project/settings/views.vue | 173 +++++++++++++++++ 10 files changed, 476 insertions(+), 12 deletions(-) create mode 100644 frontend/src/components/project/views/viewEditForm.vue create mode 100644 frontend/src/models/projectView.ts create mode 100644 frontend/src/services/projectViews.ts create mode 100644 frontend/src/views/project/settings/views.vue diff --git a/frontend/src/components/project/project-settings-dropdown.vue b/frontend/src/components/project/project-settings-dropdown.vue index 1ab530aca..4ed1d2c5c 100644 --- a/frontend/src/components/project/project-settings-dropdown.vue +++ b/frontend/src/components/project/project-settings-dropdown.vue @@ -47,6 +47,12 @@ > {{ $t('menu.edit') }} + + {{ $t('menu.views') }} + +import type {IProjectView} from '@/modelTypes/IProjectView' +import XButton from '@/components/input/button.vue' +import FilterInput from '@/components/project/partials/FilterInput.vue' +import {ref} from 'vue' + +const model = defineModel() +const titleValid = ref(true) +function validateTitle() { + titleValid.value = model.value.title !== '' +} + + + + + \ No newline at end of file diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json index b9dcefc4c..9d39969c0 100644 --- a/frontend/src/i18n/lang/en.json +++ b/frontend/src/i18n/lang/en.json @@ -381,6 +381,22 @@ "secret": "Secret", "secretHint": "If provided, all requests to the webhook target URL will be signed using HMAC.", "secretDocs": "Check out the docs for more details about how to use secrets." + }, + "views": { + "header": "Edit views", + "title": "Title", + "actions": "Actions", + "kind": "Kind", + "bucketConfigMode": "Bucket configuration mode", + "bucketConfig": "Bucket configuration", + "bucketConfigManual": "Manual", + "filter": "Filter", + "create": "Create view", + "createSuccess": "The view was created successfully.", + "titleRequired": "Please provide a title.", + "delete": "Delete this view", + "deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!", + "deleteSuccess": "The view was successfully deleted" } }, "filters": { @@ -1049,7 +1065,8 @@ "newProject": "New project", "createProject": "Create project", "cantArchiveIsDefault": "You cannot archive this because it is your default project.", - "cantDeleteIsDefault": "You cannot delete this because it is your default project." + "cantDeleteIsDefault": "You cannot delete this because it is your default project.", + "views": "Views" }, "apiConfig": { "url": "Vikunja URL", diff --git a/frontend/src/modelTypes/IProjectView.ts b/frontend/src/modelTypes/IProjectView.ts index 9d38ef6b1..6a003b8ff 100644 --- a/frontend/src/modelTypes/IProjectView.ts +++ b/frontend/src/modelTypes/IProjectView.ts @@ -1,24 +1,31 @@ import type {IAbstract} from './IAbstract' -import type {ITask} from './ITask' -import type {IUser} from './IUser' -import type {ISubscription} from './ISubscription' import type {IProject} from '@/modelTypes/IProject' +export const PROJECT_VIEW_KINDS = ['list', 'gantt', 'table', 'kanban'] +export type ProjectViewKind = typeof PROJECT_VIEW_KINDS[number] + +export const PROJECT_VIEW_BUCKET_CONFIGURATION_MODES = ['none', 'manual', 'filter'] +export type ProjectViewBucketConfigurationMode = typeof PROJECT_VIEW_BUCKET_CONFIGURATION_MODES[number] + +export interface IProjectViewBucketConfiguration { + title: string + filter: string +} export interface IProjectView extends IAbstract { id: number title: string projectId: IProject['id'] - viewKind: 'list' | 'gantt' | 'table' | 'kanban' - - fitler: string + viewKind: ProjectViewKind + + filter: string position: number - - bucketConfigurationMode: 'none' | 'manual' | 'filter' - bucketConfiguration: object + + bucketConfigurationMode: ProjectViewBucketConfigurationMode + bucketConfiguration: IProjectViewBucketConfiguration[] defaultBucketId: number doneBucketId: number - + created: Date updated: Date } \ No newline at end of file diff --git a/frontend/src/models/project.ts b/frontend/src/models/project.ts index 145262dc3..06c9e8ee7 100644 --- a/frontend/src/models/project.ts +++ b/frontend/src/models/project.ts @@ -7,6 +7,7 @@ import type {IProject} from '@/modelTypes/IProject' import type {IUser} from '@/modelTypes/IUser' import type {ITask} from '@/modelTypes/ITask' import type {ISubscription} from '@/modelTypes/ISubscription' +import ProjectViewModel from '@/models/projectView' export default class ProjectModel extends AbstractModel implements IProject { id = 0 @@ -25,6 +26,7 @@ export default class ProjectModel extends AbstractModel implements IPr parentProjectId = 0 doneBucketId = 0 defaultBucketId = 0 + views = [] created: Date = null updated: Date = null @@ -48,6 +50,8 @@ export default class ProjectModel extends AbstractModel implements IPr this.subscription = new SubscriptionModel(this.subscription) } + this.views = this.views.map(v => new ProjectViewModel(v)) + this.created = new Date(this.created) this.updated = new Date(this.updated) } diff --git a/frontend/src/models/projectView.ts b/frontend/src/models/projectView.ts new file mode 100644 index 000000000..736810c90 --- /dev/null +++ b/frontend/src/models/projectView.ts @@ -0,0 +1,30 @@ +import type {IProjectView, ProjectViewBucketConfigurationMode, ProjectViewKind} from '@/modelTypes/IProjectView' +import AbstractModel from '@/models/abstractModel' + +export default class ProjectViewModel extends AbstractModel implements IProjectView { + id = 0 + title = '' + projectId = 0 + viewKind: ProjectViewKind = 'list' + + filter = '' + position = 0 + + bucketConfiguration = [] + bucketConfigurationMode: ProjectViewBucketConfigurationMode = 'manual' + defaultBucketId = 0 + doneBucketId = 0 + + created: Date = new Date() + updated: Date = new Date() + + constructor(data: Partial) { + super() + this.assignData(data) + + + if (!this.bucketConfiguration) { + this.bucketConfiguration = [] + } + } +} \ No newline at end of file diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index a3a4a89f9..d85d2dec8 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -44,6 +44,7 @@ const ProjectSettingShare = () => import('@/views/project/settings/share.vue') const ProjectSettingWebhooks = () => import('@/views/project/settings/webhooks.vue') const ProjectSettingDelete = () => import('@/views/project/settings/delete.vue') const ProjectSettingArchive = () => import('@/views/project/settings/archive.vue') +const ProjectSettingViews = () => import('@/views/project/settings/views.vue') // Saved Filters const FilterNew = () => import('@/views/filters/FilterNew.vue') @@ -306,6 +307,15 @@ const router = createRouter({ showAsModal: true, }, }, + { + path: '/projects/:projectId/settings/views', + name: 'project.settings.views', + component: ProjectSettingViews, + meta: { + showAsModal: true, + }, + props: route => ({ projectId: Number(route.params.projectId as string) }), + }, { path: '/projects/:projectId/settings/edit', name: 'filter.settings.edit', diff --git a/frontend/src/services/projectViews.ts b/frontend/src/services/projectViews.ts new file mode 100644 index 000000000..a35d0f325 --- /dev/null +++ b/frontend/src/services/projectViews.ts @@ -0,0 +1,20 @@ +import AbstractService from '@/services/abstractService' +import type {IAbstract} from '@/modelTypes/IAbstract' +import ProjectViewModel from '@/models/projectView' +import type {IProjectView} from '@/modelTypes/IProjectView' + +export default class ProjectViewService extends AbstractService { + constructor() { + super({ + get: '/projects/{projectId}/views/{id}', + getAll: '/projects/{projectId}/views', + create: '/projects/{projectId}/views', + update: '/projects/{projectId}/views/{id}', + delete: '/projects/{projectId}/views/{id}', + }) + } + + modelFactory(data: Partial): ProjectViewModel { + return new ProjectViewModel(data) + } +} diff --git a/frontend/src/stores/projects.ts b/frontend/src/stores/projects.ts index 7a1992407..4448a657e 100644 --- a/frontend/src/stores/projects.ts +++ b/frontend/src/stores/projects.ts @@ -18,6 +18,7 @@ import ProjectModel from '@/models/project' import {success} from '@/message' import {useBaseStore} from '@/stores/base' import {getSavedFilterIdFromProjectId} from '@/services/savedFilter' +import type {IProjectView} from '@/modelTypes/IProjectView' const {add, remove, search, update} = createNewIndexer('projects', ['title', 'description']) @@ -210,7 +211,24 @@ export const useProjectStore = defineStore('project', () => { project, ] } - + + function setProjectView(view: IProjectView) { + const viewPos = projects.value[view.projectId].views.findIndex(v => v.id === view.id) + if (viewPos !== -1) { + projects.value[view.projectId].views[viewPos] = view + return + } + + projects.value[view.projectId].views.push(view) + } + + function removeProjectView(projectId: IProject['id'], viewId: IProjectView['id']) { + const viewPos = projects.value[projectId].views.findIndex(v => v.id === viewId) + if (viewPos !== -1) { + projects.value[projectId].views.splice(viewPos, 1) + } + } + return { isLoading: readonly(isLoading), projects: readonly(projects), @@ -235,6 +253,8 @@ export const useProjectStore = defineStore('project', () => { updateProject, deleteProject, getAncestors, + setProjectView, + removeProjectView, } }) diff --git a/frontend/src/views/project/settings/views.vue b/frontend/src/views/project/settings/views.vue new file mode 100644 index 000000000..64babf196 --- /dev/null +++ b/frontend/src/views/project/settings/views.vue @@ -0,0 +1,173 @@ + + + + + \ No newline at end of file