forked from vikunja/frontend
fix(filter): validate title before creating or editing a filter
Resolves #3152
This commit is contained in:
parent
4033c28a67
commit
1f40b68108
|
@ -405,7 +405,8 @@
|
||||||
"create": {
|
"create": {
|
||||||
"title": "New Saved Filter",
|
"title": "New Saved Filter",
|
||||||
"description": "A saved filter is a virtual list which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
|
"description": "A saved filter is a virtual list which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
|
||||||
"action": "Create new saved filter"
|
"action": "Create new saved filter",
|
||||||
|
"titleRequired": "Please provide a title for the filter."
|
||||||
},
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"header": "Delete this saved filter",
|
"header": "Delete this saved filter",
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {computed, ref, shallowReactive, unref, watch} from 'vue'
|
||||||
import {useRouter} from 'vue-router'
|
import {useRouter} from 'vue-router'
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
import type {MaybeRef} from '@vueuse/core'
|
import type {MaybeRef} from '@vueuse/core'
|
||||||
|
import {useDebounceFn} from '@vueuse/core'
|
||||||
|
|
||||||
import type {IList} from '@/modelTypes/IList'
|
import type {IList} from '@/modelTypes/IList'
|
||||||
import type {ISavedFilter} from '@/modelTypes/ISavedFilter'
|
import type {ISavedFilter} from '@/modelTypes/ISavedFilter'
|
||||||
|
@ -133,14 +134,38 @@ export function useSavedFilter(listId?: MaybeRef<IList['id']>) {
|
||||||
router.push({name: 'namespaces.index'})
|
router.push({name: 'namespaces.index'})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const titleValid = ref(true)
|
||||||
|
const validateTitleField = useDebounceFn(() => {
|
||||||
|
titleValid.value = filter.value.title !== ''
|
||||||
|
}, 100)
|
||||||
|
|
||||||
|
async function createFilterWithValidation() {
|
||||||
|
if (!titleValid.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return createFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveFilterWithValidation() {
|
||||||
|
if (!titleValid.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return saveFilter()
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createFilter,
|
createFilter,
|
||||||
|
createFilterWithValidation,
|
||||||
saveFilter,
|
saveFilter,
|
||||||
|
saveFilterWithValidation,
|
||||||
deleteFilter,
|
deleteFilter,
|
||||||
|
|
||||||
filter,
|
filter,
|
||||||
filters,
|
filters,
|
||||||
|
|
||||||
filterService,
|
filterService,
|
||||||
|
|
||||||
|
titleValid,
|
||||||
|
validateTitleField,
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,25 +3,27 @@
|
||||||
:title="$t('filters.edit.title')"
|
:title="$t('filters.edit.title')"
|
||||||
primary-icon=""
|
primary-icon=""
|
||||||
:primary-label="$t('misc.save')"
|
:primary-label="$t('misc.save')"
|
||||||
@primary="saveFilter"
|
@primary="saveFilterWithValidation"
|
||||||
:tertiary="$t('misc.delete')"
|
:tertiary="$t('misc.delete')"
|
||||||
@tertiary="$router.push({ name: 'filter.settings.delete', params: { id: listId } })"
|
@tertiary="$router.push({ name: 'filter.settings.delete', params: { id: listId } })"
|
||||||
>
|
>
|
||||||
<form @submit.prevent="saveFilter()">
|
<form @submit.prevent="saveFilterWithValidation()">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="title">{{ $t('filters.attributes.title') }}</label>
|
<label class="label" for="title">{{ $t('filters.attributes.title') }}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input
|
<input
|
||||||
:class="{ 'disabled': filterService.loading}"
|
v-model="filter.title"
|
||||||
|
:class="{ 'disabled': filterService.loading, 'is-danger': !titleValid }"
|
||||||
:disabled="filterService.loading || undefined"
|
:disabled="filterService.loading || undefined"
|
||||||
@keyup.enter="saveFilter"
|
|
||||||
class="input"
|
class="input"
|
||||||
id="title"
|
id="Title"
|
||||||
:placeholder="$t('filters.attributes.titlePlaceholder')"
|
:placeholder="$t('filters.attributes.titlePlaceholder')"
|
||||||
type="text"
|
type="text"
|
||||||
v-focus
|
v-focus
|
||||||
v-model="filter.title"/>
|
@focusout="validateTitleField"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="help is-danger" v-if="!titleValid">{{ $t('filters.create.titleRequired') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="description">{{ $t('filters.attributes.description') }}</label>
|
<label class="label" for="description">{{ $t('filters.attributes.description') }}</label>
|
||||||
|
@ -65,9 +67,11 @@ import type {IList} from '@/modelTypes/IList'
|
||||||
const props = defineProps<{ listId: IList['id'] }>()
|
const props = defineProps<{ listId: IList['id'] }>()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
saveFilter,
|
saveFilterWithValidation,
|
||||||
filter,
|
filter,
|
||||||
filters,
|
filters,
|
||||||
filterService,
|
filterService,
|
||||||
|
titleValid,
|
||||||
|
validateTitleField,
|
||||||
} = useSavedFilter(toRef(props, 'listId'))
|
} = useSavedFilter(toRef(props, 'listId'))
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -12,15 +12,17 @@
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input
|
<input
|
||||||
v-model="filter.title"
|
v-model="filter.title"
|
||||||
:class="{ 'disabled': filterService.loading}"
|
:class="{ 'disabled': filterService.loading, 'is-danger': !titleValid }"
|
||||||
:disabled="filterService.loading || undefined"
|
:disabled="filterService.loading || undefined"
|
||||||
class="input"
|
class="input"
|
||||||
id="Title"
|
id="Title"
|
||||||
:placeholder="$t('filters.attributes.titlePlaceholder')"
|
:placeholder="$t('filters.attributes.titlePlaceholder')"
|
||||||
type="text"
|
type="text"
|
||||||
v-focus
|
v-focus
|
||||||
|
@focusout="validateTitleField"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="help is-danger" v-if="!titleValid">{{ $t('filters.create.titleRequired') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="description">{{ $t('filters.attributes.description') }}</label>
|
<label class="label" for="description">{{ $t('filters.attributes.description') }}</label>
|
||||||
|
@ -51,8 +53,8 @@
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<x-button
|
<x-button
|
||||||
:loading="filterService.loading"
|
:loading="filterService.loading"
|
||||||
:disabled="filterService.loading"
|
:disabled="filterService.loading || !titleValid"
|
||||||
@click="createFilter()"
|
@click="createFilterWithValidation()"
|
||||||
class="is-fullwidth"
|
class="is-fullwidth"
|
||||||
>
|
>
|
||||||
{{ $t('filters.create.action') }}
|
{{ $t('filters.create.action') }}
|
||||||
|
@ -71,7 +73,9 @@ import {useSavedFilter} from '@/services/savedFilter'
|
||||||
const {
|
const {
|
||||||
filter,
|
filter,
|
||||||
filters,
|
filters,
|
||||||
createFilter,
|
createFilterWithValidation,
|
||||||
filterService,
|
filterService,
|
||||||
|
titleValid,
|
||||||
|
validateTitleField,
|
||||||
} = useSavedFilter()
|
} = useSavedFilter()
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue