feat(views): allow reordering views
Some checks failed
continuous-integration/drone/push Build is failing

Resolves https://community.vikunja.io/t/reordering-views/2394
This commit is contained in:
kolaente 2024-06-18 16:39:52 +02:00
parent 9f604eca79
commit d12deee977
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
5 changed files with 101 additions and 47 deletions

View File

@ -401,8 +401,9 @@
"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",
"onlyAdminsCanEdit": "Only project admins can edit views."
"deleteSuccess": "The view was deleted successfully.",
"onlyAdminsCanEdit": "Only project admins can edit views.",
"updateSuccess": "The view was updated successfully."
}
},
"filters": {

View File

@ -224,11 +224,13 @@ export const useProjectStore = defineStore('project', () => {
const viewPos = projects.value[view.projectId].views.findIndex(v => v.id === view.id)
if (viewPos !== -1) {
projects.value[view.projectId].views[viewPos] = view
projects.value[view.projectId].views.sort((a, b) => a.position < b.position ? -1 : 1)
setProject(projects.value[view.projectId])
return
}
projects.value[view.projectId].views.push(view)
projects.value[view.projectId].views.sort((a, b) => a.position < b.position ? -1 : 1)
setProject(projects.value[view.projectId])
}

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import CreateEdit from '@/components/misc/CreateEdit.vue'
import {watch, ref, computed} from 'vue'
import {watch, ref} from 'vue'
import {useProjectStore} from '@/stores/projects'
import ProjectViewModel from '@/models/projectView'
import type {IProjectView} from '@/modelTypes/IProjectView'
@ -13,6 +13,8 @@ import ProjectService from '@/services/project'
import {RIGHTS} from '@/constants/rights'
import ProjectModel from '@/models/project'
import Message from '@/components/misc/Message.vue'
import draggable from 'zhyswan-vuedraggable'
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
const {
projectId,
@ -23,7 +25,19 @@ const {
const projectStore = useProjectStore()
const {t} = useI18n()
const views = computed(() => projectStore.projects[projectId]?.views)
const views = ref<IProjectView[]>([])
watch(
projectStore.projects[projectId]?.views,
allViews => {
if (!allViews) {
views.value = []
return
}
views.value = [...allViews]
},
{immediate: true},
)
const showCreateForm = ref(false)
const projectViewService = ref(new ProjectViewService())
@ -91,6 +105,21 @@ async function saveView() {
const result = await projectViewService.value.update(viewToEdit.value)
projectStore.setProjectView(result)
viewToEdit.value = null
success({message: t('project.views.updateSuccess')})
}
async function saveViewPosition(e) {
const view = views.value[e.newIndex]
const viewBefore = views.value[e.newIndex - 1] ?? null
const viewAfter = views.value[e.newIndex + 1] ?? null
const position = calculateItemPosition(viewBefore !== null ? viewBefore.position : null, viewAfter !== null ? viewAfter.position : null)
const result = await projectViewService.value.update({
...view,
position,
})
projectStore.setProjectView(result)
success({message: t('project.views.updateSuccess')})
}
</script>
@ -117,7 +146,7 @@ async function saveView() {
{{ $t('project.views.create') }}
</XButton>
</div>
<Message v-if="!isAdmin">
{{ $t('project.views.onlyAdminsCanEdit') }}
</Message>
@ -135,45 +164,54 @@ async function saveView() {
</th>
</tr>
</thead>
<tbody>
<tr
v-for="v in views"
:key="v.id"
>
<template v-if="viewToEdit !== null && viewToEdit.id === v.id">
<td colspan="3">
<ViewEditForm
v-model="viewToEdit"
class="mb-4"
:loading="projectViewService.loading"
:show-save-buttons="true"
@cancel="viewToEdit = null"
@update:modelValue="saveView"
/>
</td>
</template>
<template v-else>
<td>{{ v.title }}</td>
<td>{{ v.viewKind }}</td>
<td class="has-text-right">
<XButton
v-if="isAdmin"
class="is-danger mr-2"
icon="trash-alt"
@click="() => {
viewIdToDelete = v.id
showDeleteModal = true
}"
/>
<XButton
v-if="isAdmin"
icon="pen"
@click="viewToEdit = {...v}"
/>
</td>
</template>
</tr>
</tbody>
<draggable
v-model="views"
tag="tbody"
item-key="id"
handle=".handle"
:animation="100"
@end="saveViewPosition"
>
<template #item="{element: v}">
<tr>
<template v-if="viewToEdit !== null && viewToEdit.id === v.id">
<td colspan="3">
<ViewEditForm
v-model="viewToEdit"
class="mb-4"
:loading="projectViewService.loading"
:show-save-buttons="true"
@cancel="viewToEdit = null"
@update:modelValue="saveView"
/>
</td>
</template>
<template v-else>
<td>{{ v.title }}</td>
<td>{{ v.viewKind }}</td>
<td class="has-text-right actions">
<XButton
v-if="isAdmin"
class="is-danger mr-2"
icon="trash-alt"
@click="() => {
viewIdToDelete = v.id
showDeleteModal = true
}"
/>
<XButton
v-if="isAdmin"
icon="pen"
@click="viewToEdit = {...v}"
/>
<span class="icon handle">
<icon icon="grip-lines" />
</span>
</td>
</template>
</tr>
</template>
</draggable>
</table>
</CreateEdit>
@ -191,3 +229,16 @@ async function saveView() {
</template>
</modal>
</template>
<style scoped>
.handle {
cursor: grab;
margin-left: .25rem;
}
.actions {
display: flex;
align-items: center;
justify-content: flex-end;
}
</style>

View File

@ -302,9 +302,7 @@ func (p *Project) ReadOne(s *xorm.Session, a web.Auth) (err error) {
return nil
}
err = s.
Where("project_id = ?", p.ID).
Find(&p.Views)
p.Views, err = getViewsForProject(s, p.ID)
return
}
@ -640,6 +638,7 @@ func addProjectDetails(s *xorm.Session, projects []*Project, a web.Auth) (err er
views := []*ProjectView{}
err = s.
In("project_id", projectIDs).
OrderBy("position asc").
Find(&views)
if err != nil {
return

View File

@ -160,6 +160,7 @@ func getViewsForProject(s *xorm.Session, projectID int64) (views []*ProjectView,
views = []*ProjectView{}
err = s.
Where("project_id = ?", projectID).
OrderBy("position asc").
Find(&views)
return
}