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:
parent
244ca262df
commit
68d233684f
@ -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, '&')
|
.replace(/&/g, '&')
|
||||||
@ -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
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user