1
0

fix(views): edit views with filters

This change fixes a bug where filter values of views would be transformed in the wrong order, not transformed at all or at the wrong time. Transforming the filters now happens transparently in the background without anything funky happening visible to the user.
This commit is contained in:
kolaente 2024-06-03 22:20:57 +02:00
parent 244ca262df
commit 68d233684f
No known key found for this signature in database
GPG Key ID: F40E70337AB24C9B
3 changed files with 83 additions and 64 deletions

View File

@ -35,6 +35,11 @@ const {
const emit = defineEmits(['update:modelValue', 'blur']) const emit = defineEmits(['update:modelValue', 'blur'])
const userService = new UserService()
const projectUserService = new ProjectUserService()
const labelStore = useLabelStore()
const projectStore = useProjectStore()
const filterQuery = ref<string>('') const filterQuery = ref<string>('')
const { const {
textarea: filterInput, textarea: filterInput,
@ -60,9 +65,6 @@ watch(
}, },
) )
const userService = new UserService()
const projectUserService = new ProjectUserService()
function escapeHtml(unsafe: string): string { function escapeHtml(unsafe: string): string {
return unsafe return unsafe
.replace(/&/g, '&amp;') .replace(/&/g, '&amp;')
@ -196,8 +198,6 @@ const autocompleteMatchText = ref('')
const autocompleteResultType = ref<'labels' | 'assignees' | 'projects' | null>(null) const autocompleteResultType = ref<'labels' | 'assignees' | 'projects' | null>(null)
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const autocompleteResults = ref<any[]>([]) const autocompleteResults = ref<any[]>([])
const labelStore = useLabelStore()
const projectStore = useProjectStore()
function handleFieldInput() { function handleFieldInput() {
const cursorPosition = filterInput.value.selectionStart const cursorPosition = filterInput.value.selectionStart

View File

@ -2,77 +2,86 @@
import type {IProjectView} from '@/modelTypes/IProjectView' import type {IProjectView} from '@/modelTypes/IProjectView'
import XButton from '@/components/input/button.vue' import XButton from '@/components/input/button.vue'
import FilterInput from '@/components/project/partials/FilterInput.vue' import FilterInput from '@/components/project/partials/FilterInput.vue'
import {ref, watch} from 'vue' import {ref, onBeforeMount} from 'vue'
import {transformFilterStringForApi, transformFilterStringFromApi} from '@/helpers/filters' import {transformFilterStringForApi, transformFilterStringFromApi} from '@/helpers/filters'
import {useLabelStore} from '@/stores/labels' import {useLabelStore} from '@/stores/labels'
import {useProjectStore} from '@/stores/projects' import {useProjectStore} from '@/stores/projects'
const { const {
modelValue, modelValue,
loading = false,
showSaveButtons = false,
} = defineProps<{ } = defineProps<{
modelValue: IProjectView, modelValue: IProjectView,
loading?: bool,
showSaveButtons?: bool,
}>() }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue', 'cancel'])
const view = ref<IProjectView>() const view = ref<IProjectView>()
const labelStore = useLabelStore() const labelStore = useLabelStore()
const projectStore = useProjectStore() const projectStore = useProjectStore()
watch( onBeforeMount(() => {
() => modelValue, const transform = filterString => transformFilterStringFromApi(
newValue => { filterString,
labelId => labelStore.getLabelById(labelId)?.title,
const transform = filterString => transformFilterStringFromApi( projectId => projectStore.projects[projectId]?.title || null,
filterString, )
labelId => labelStore.getLabelById(labelId)?.title,
projectId => projectStore.projects[projectId]?.title || null,
)
const transformed = {
...newValue,
filter: transform(newValue.filter),
bucketConfiguration: newValue.bucketConfiguration.map(bc => ({
title: bc.title,
filter: transform(bc.filter),
})),
}
if (JSON.stringify(view.value) !== JSON.stringify(transformed)) { const transformed = {
view.value = transformed ...modelValue,
} filter: transform(modelValue.filter),
}, bucketConfiguration: modelValue.bucketConfiguration.map(bc => ({
{immediate: true, deep: true}, title: bc.title,
) filter: transform(bc.filter),
})),
}
watch( if (JSON.stringify(view.value) !== JSON.stringify(transformed)) {
() => view.value, view.value = transformed
newView => { }
emit('update:modelValue', { })
...newView,
filter: transformFilterStringForApi( function save() {
newView.filter, const transformFilter = filterQuery => transformFilterStringForApi(
labelTitle => labelStore.filterLabelsByQuery([], labelTitle)[0]?.id || null, filterQuery,
projectTitle => { labelTitle => labelStore.filterLabelsByQuery([], labelTitle)[0]?.id || null,
const found = projectStore.findProjectByExactname(projectTitle) projectTitle => {
return found?.id || null const found = projectStore.findProjectByExactname(projectTitle)
}, return found?.id || null
), },
}) )
},
{deep: true}, emit('update:modelValue', {
) ...view.value,
filter: transformFilter(view.value?.filter),
bucketConfiguration: view.value?.bucketConfiguration.map(bc => ({
title: bc.title,
filter: transformFilter(bc.filter),
})),
})
}
const titleValid = ref(true) const titleValid = ref(true)
function validateTitle() { function validateTitle() {
titleValid.value = view.value?.title !== '' titleValid.value = view.value?.title !== ''
} }
function handleBubbleSave() {
if (showSaveButtons) {
return
}
save()
}
</script> </script>
<template> <template>
<form> <form @focusout="handleBubbleSave">
<div class="field"> <div class="field">
<label <label
class="label" class="label"
@ -130,6 +139,7 @@ function validateTitle() {
<FilterInput <FilterInput
v-model="view.filter" v-model="view.filter"
:project-id="view.projectId"
:input-label="$t('project.views.filter')" :input-label="$t('project.views.filter')"
/> />
@ -199,6 +209,7 @@ function validateTitle() {
<FilterInput <FilterInput
v-model="view.bucketConfiguration[index].filter" v-model="view.bucketConfiguration[index].filter"
:project-id="view.projectId"
:input-label="$t('project.views.filter')" :input-label="$t('project.views.filter')"
/> />
</div> </div>
@ -214,6 +225,24 @@ function validateTitle() {
</div> </div>
</div> </div>
</div> </div>
<div
v-if="showSaveButtons"
class="is-flex is-justify-content-end"
>
<XButton
variant="tertiary"
class="mr-2"
@click="emit('cancel')"
>
{{ $t('misc.cancel') }}
</XButton>
<XButton
:loading="loading"
@click="save"
>
{{ $t('misc.save') }}
</XButton>
</div>
</form> </form>
</template> </template>

View File

@ -111,6 +111,7 @@ async function saveView() {
> >
<XButton <XButton
:loading="projectViewService.loading" :loading="projectViewService.loading"
:disabled="showCreateForm && newView.title === ''"
@click="createView" @click="createView"
> >
{{ $t('project.views.create') }} {{ $t('project.views.create') }}
@ -144,22 +145,11 @@ async function saveView() {
<ViewEditForm <ViewEditForm
v-model="viewToEdit" v-model="viewToEdit"
class="mb-4" class="mb-4"
:loading="projectViewService.loading"
:show-save-buttons="true"
@cancel="viewToEdit = null"
@update:modelValue="saveView"
/> />
<div class="is-flex is-justify-content-end">
<XButton
variant="tertiary"
class="mr-2"
@click="viewToEdit = null"
>
{{ $t('misc.cancel') }}
</XButton>
<XButton
:loading="projectViewService.loading"
@click="saveView"
>
{{ $t('misc.save') }}
</XButton>
</div>
</td> </td>
</template> </template>
<template v-else> <template v-else>