Replicate record list filter ui to record list configurator prefilter
This commit is contained in:
parent
47ed5659a3
commit
632ac3f32a
501
client/web/compose/src/components/Common/FilterToolbox.vue
Normal file
501
client/web/compose/src/components/Common/FilterToolbox.vue
Normal file
@ -0,0 +1,501 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-for="(filterGroup, groupIndex) in value">
|
||||
<template v-if="filterGroup.filter.length">
|
||||
<b-tr
|
||||
v-for="(filter, index) in filterGroup.filter"
|
||||
:key="`${groupIndex}-${index}`"
|
||||
class="pb-2"
|
||||
>
|
||||
<b-td
|
||||
class="align-middle"
|
||||
style="width: 1%;"
|
||||
>
|
||||
<h6
|
||||
v-if="index === 0"
|
||||
class="mb-0"
|
||||
>
|
||||
{{ $t("recordList.filter.where") }}
|
||||
</h6>
|
||||
|
||||
<b-form-select
|
||||
v-else
|
||||
v-model="filter.condition"
|
||||
:options="conditions"
|
||||
/>
|
||||
</b-td>
|
||||
|
||||
<b-td
|
||||
class="px-2"
|
||||
style="width: 250px;"
|
||||
>
|
||||
<c-input-select
|
||||
v-model="filter.name"
|
||||
:options="fieldOptions"
|
||||
:get-option-key="getOptionKey"
|
||||
:clearable="false"
|
||||
:placeholder="$t('recordList.filter.fieldPlaceholder')"
|
||||
:reduce="(f) => f.name"
|
||||
:class="{ 'filter-field-picker': !!filter.name }"
|
||||
@input="onChange($event, groupIndex, index)"
|
||||
/>
|
||||
</b-td>
|
||||
|
||||
<b-td
|
||||
v-if="getField(filter.name)"
|
||||
style="width: 250px;"
|
||||
:class="{ 'pr-2': getField(filter.name) }"
|
||||
>
|
||||
<b-form-select
|
||||
v-if="getField(filter.name)"
|
||||
v-model="filter.operator"
|
||||
:options="getOperators(filter.kind, getField(filter.name))"
|
||||
class="d-flex field-operator w-100"
|
||||
@change="updateFilterProperties(filter)"
|
||||
/>
|
||||
</b-td>
|
||||
<b-td v-if="getField(filter.name)">
|
||||
<template v-if="isBetweenOperator(filter.operator)">
|
||||
<template v-if="getField(`${filter.name}-start`)">
|
||||
<field-editor
|
||||
v-bind="mock"
|
||||
class="mb-0 field-editor"
|
||||
value-only
|
||||
:field="getField(`${filter.name}-start`)"
|
||||
:record="filter.record"
|
||||
@change="onValueChange"
|
||||
/>
|
||||
<span class="my-1 text-center w-100">
|
||||
{{ $t("general.label.and") }}
|
||||
</span>
|
||||
<field-editor
|
||||
v-bind="mock"
|
||||
class="mb-0 field-editor"
|
||||
value-only
|
||||
:field="getField(`${filter.name}-end`)"
|
||||
:record="filter.record"
|
||||
@change="onValueChange"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<field-editor
|
||||
v-bind="mock"
|
||||
class="mb-0 field-editor"
|
||||
value-only
|
||||
:field="getField(filter.name)"
|
||||
:record="filter.record"
|
||||
@change="onValueChange"
|
||||
/>
|
||||
</template>
|
||||
</b-td>
|
||||
<b-td
|
||||
v-if="getField(filter.name)"
|
||||
class="align-middle"
|
||||
style="width: 1%;"
|
||||
>
|
||||
<b-button
|
||||
:id="`${groupIndex}-${index}`"
|
||||
ref="delete"
|
||||
variant="link"
|
||||
class="d-flex align-items-center"
|
||||
@click="deleteFilter(groupIndex, index)"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['far', 'trash-alt']"
|
||||
size="sm"
|
||||
/>
|
||||
</b-button>
|
||||
</b-td>
|
||||
</b-tr>
|
||||
|
||||
<b-tr :key="`addFilter-${groupIndex}`">
|
||||
<b-td class="pb-0">
|
||||
<b-button
|
||||
variant="link text-decoration-none"
|
||||
style="min-height: 38px; min-width: 84px;"
|
||||
@click="addFilter(groupIndex)"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'plus']"
|
||||
size="sm"
|
||||
class="mr-1"
|
||||
/>
|
||||
{{ $t("general.label.add") }}
|
||||
</b-button>
|
||||
</b-td>
|
||||
</b-tr>
|
||||
|
||||
<b-tr :key="`groupCondtion-${groupIndex}`">
|
||||
<b-td
|
||||
colspan="100%"
|
||||
class="p-0 justify-content-center"
|
||||
:class="{ 'pb-3': filterGroup.groupCondition }"
|
||||
>
|
||||
<div class="group-separator">
|
||||
<b-form-select
|
||||
v-if="filterGroup.groupCondition"
|
||||
v-model="filterGroup.groupCondition"
|
||||
class="m-auto w-auto d-block"
|
||||
:options="conditions"
|
||||
/>
|
||||
|
||||
<b-button
|
||||
v-else
|
||||
variant="outline-primary"
|
||||
class="py-2 px-3 m-auto bg-white d-block btn-add-group"
|
||||
@click="addGroup()"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'plus']"
|
||||
class="mb-0 h6"
|
||||
/>
|
||||
</b-button>
|
||||
</div>
|
||||
</b-td>
|
||||
</b-tr>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { compose } from '@cortezaproject/corteza-js'
|
||||
import FieldEditor from 'corteza-webapp-compose/src/components/ModuleFields/Editor'
|
||||
|
||||
export default {
|
||||
i18nOptions: {
|
||||
namespaces: 'block',
|
||||
},
|
||||
|
||||
name: 'FilterToolbox',
|
||||
|
||||
components: {
|
||||
FieldEditor,
|
||||
},
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
|
||||
module: {
|
||||
type: compose.Module,
|
||||
required: true,
|
||||
},
|
||||
|
||||
namespace: {
|
||||
type: compose.Namespace,
|
||||
required: true,
|
||||
},
|
||||
|
||||
mock: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
||||
resetFilterOnCreated: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
conditions: [
|
||||
{ value: 'AND', text: this.$t('recordList.filter.conditions.and') },
|
||||
{ value: 'OR', text: this.$t('recordList.filter.conditions.or') },
|
||||
],
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
fieldOptions () {
|
||||
return this.fields.map(({ name, label }) => ({
|
||||
name,
|
||||
label: label || name,
|
||||
}))
|
||||
},
|
||||
|
||||
fields () {
|
||||
return [
|
||||
...[...this.module.fields].sort((a, b) =>
|
||||
(a.label || a.name).localeCompare(b.label || b.name)
|
||||
),
|
||||
...this.module.systemFields().map((sf) => {
|
||||
sf.label = this.$t(`field:system.${sf.name}`)
|
||||
return sf
|
||||
}),
|
||||
].filter(({ isFilterable }) => isFilterable)
|
||||
},
|
||||
},
|
||||
|
||||
created () {
|
||||
if (this.resetFilterOnCreated) {
|
||||
this.resetFilter()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getField (name = '') {
|
||||
const field = name
|
||||
? this.mock.module.fields.find((f) => f.name === name)
|
||||
: undefined
|
||||
|
||||
return field ? { ...field } : undefined
|
||||
},
|
||||
|
||||
onChange (fieldName, groupIndex, index) {
|
||||
const field = this.getField(fieldName)
|
||||
const filterExists = !!(
|
||||
this.value[groupIndex] || { filter: [] }
|
||||
).filter[index]
|
||||
if (field && filterExists) {
|
||||
const value = this.value
|
||||
const tempFilter = [...value]
|
||||
tempFilter[groupIndex].filter[index].kind = field.kind
|
||||
tempFilter[groupIndex].filter[index].name = field.name
|
||||
tempFilter[groupIndex].filter[index].value = undefined
|
||||
tempFilter[groupIndex].filter[index].operator = field.multi
|
||||
? 'IN'
|
||||
: '='
|
||||
|
||||
this.$emit('input', value)
|
||||
}
|
||||
this.$emit('prevent-close')
|
||||
},
|
||||
|
||||
onValueChange () {
|
||||
this.$emit('prevent-close')
|
||||
},
|
||||
|
||||
getOperators (kind, field) {
|
||||
const operators = [
|
||||
{
|
||||
value: '=',
|
||||
text: this.$t('recordList.filter.operators.equal'),
|
||||
},
|
||||
{
|
||||
value: '!=',
|
||||
text: this.$t('recordList.filter.operators.notEqual'),
|
||||
},
|
||||
]
|
||||
|
||||
const inOperators = [
|
||||
{
|
||||
value: 'IN',
|
||||
text: this.$t('recordList.filter.operators.contains'),
|
||||
},
|
||||
{
|
||||
value: 'NOT IN',
|
||||
text: this.$t('recordList.filter.operators.notContains'),
|
||||
},
|
||||
]
|
||||
|
||||
const lgOperators = [
|
||||
{
|
||||
value: '>',
|
||||
text: this.$t('recordList.filter.operators.greaterThan'),
|
||||
},
|
||||
{
|
||||
value: '<',
|
||||
text: this.$t('recordList.filter.operators.lessThan'),
|
||||
},
|
||||
]
|
||||
const matchOperators = [
|
||||
{
|
||||
value: 'LIKE',
|
||||
text: this.$t('recordList.filter.operators.like'),
|
||||
},
|
||||
{
|
||||
value: 'NOT LIKE',
|
||||
text: this.$t('recordList.filter.operators.notLike'),
|
||||
},
|
||||
]
|
||||
|
||||
const betweenOperators = [
|
||||
{
|
||||
value: 'BETWEEN',
|
||||
text: this.$t('recordList.filter.operators.between'),
|
||||
},
|
||||
{
|
||||
value: 'NOT BETWEEN',
|
||||
text: this.$t('recordList.filter.operators.notBetween'),
|
||||
},
|
||||
]
|
||||
|
||||
if (field.multi) {
|
||||
return inOperators
|
||||
}
|
||||
|
||||
switch (kind) {
|
||||
case 'Number':
|
||||
return [...operators, ...lgOperators, ...betweenOperators]
|
||||
|
||||
case 'DateTime':
|
||||
return [...operators, ...lgOperators, ...betweenOperators]
|
||||
|
||||
case 'String':
|
||||
case 'Url':
|
||||
case 'Email':
|
||||
return [...operators, ...matchOperators]
|
||||
|
||||
default:
|
||||
return operators
|
||||
}
|
||||
},
|
||||
|
||||
updateFilterProperties (filter) {
|
||||
if (this.isBetweenOperator(filter.operator)) {
|
||||
filter.record.values[`${filter.name}-start`] =
|
||||
filter.record.values[`${filter.name}-start`]
|
||||
filter.record.values[`${filter.name}-end`] =
|
||||
filter.record.values[`${filter.name}-end`]
|
||||
|
||||
const field = this.mock.module.fields.find(
|
||||
(f) => f.name === filter.name
|
||||
)
|
||||
|
||||
this.mock.module.fields.push({ ...field, name: `${filter.name}-end` })
|
||||
this.mock.module.fields.push({
|
||||
...field,
|
||||
name: `${filter.name}-start`,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
isBetweenOperator (op) {
|
||||
return ['BETWEEN', 'NOT BETWEEN'].includes(op)
|
||||
},
|
||||
|
||||
deleteFilter (groupIndex, index) {
|
||||
const value = this.value
|
||||
const filterExists = !!(
|
||||
value[groupIndex] || { filter: [] }
|
||||
).filter[index]
|
||||
|
||||
if (filterExists) {
|
||||
// Delete filter from filterGroup
|
||||
value[groupIndex].filter.splice(index, 1)
|
||||
|
||||
// If filter was last in filterGroup, delete filterGroup
|
||||
if (!value[groupIndex].filter.length) {
|
||||
value.splice(groupIndex, 1)
|
||||
|
||||
// If no more filterGroups, add default back
|
||||
if (!value.length) {
|
||||
this.resetFilter()
|
||||
} else if (groupIndex === value.length) {
|
||||
// Reset first filterGroup groupCondition if last filterGroup was deleted
|
||||
value[groupIndex - 1].groupCondition = undefined
|
||||
}
|
||||
}
|
||||
this.$emit('input', value)
|
||||
}
|
||||
|
||||
this.$emit('prevent-close')
|
||||
},
|
||||
|
||||
createDefaultFilter (condition, field = {}) {
|
||||
return {
|
||||
condition,
|
||||
name: field.name,
|
||||
operator: field.isMulti ? 'IN' : '=',
|
||||
value: undefined,
|
||||
kind: field.kind,
|
||||
record: new compose.Record(this.mock.module, {}),
|
||||
}
|
||||
},
|
||||
|
||||
resetFilter () {
|
||||
this.$emit('input', [this.createDefaultFilterGroup()])
|
||||
},
|
||||
|
||||
getOptionKey ({ name }) {
|
||||
return name
|
||||
},
|
||||
|
||||
addFilter (groupIndex) {
|
||||
const value = this.value
|
||||
|
||||
if ((value[groupIndex] || {}).filter) {
|
||||
value[groupIndex].filter.push(
|
||||
this.createDefaultFilter('AND', this.selectedField)
|
||||
)
|
||||
}
|
||||
|
||||
this.$emit('input', value)
|
||||
this.$emit('prevent-close')
|
||||
},
|
||||
|
||||
createDefaultFilterGroup (groupCondition = undefined, field) {
|
||||
return {
|
||||
groupCondition,
|
||||
filter: [this.createDefaultFilter('Where', field)],
|
||||
}
|
||||
},
|
||||
|
||||
addGroup () {
|
||||
const value = this.value
|
||||
value[value.length - 1].groupCondition =
|
||||
'AND'
|
||||
value.push(
|
||||
this.createDefaultFilterGroup(undefined, this.selectedField)
|
||||
)
|
||||
|
||||
this.$emit('input', value)
|
||||
this.$emit('prevent-close')
|
||||
},
|
||||
|
||||
processFilter (filterGroup = this.value) {
|
||||
return filterGroup.map(({ groupCondition, filter = [], name }) => {
|
||||
filter = filter.map(({ record, ...f }) => {
|
||||
if (record) {
|
||||
f.value = record[f.name] || record.values[f.name]
|
||||
}
|
||||
|
||||
if (this.isBetweenOperator(f.operator)) {
|
||||
f.value = {
|
||||
start: this.getField(f.name).isSystem
|
||||
? record[`${f.name}-start`]
|
||||
: record.values[`${f.name}-start`],
|
||||
end: this.getField(f.name).isSystem
|
||||
? record[`${f.name}-end`]
|
||||
: record.values[`${f.name}-end`],
|
||||
}
|
||||
}
|
||||
|
||||
return f
|
||||
})
|
||||
|
||||
return { groupCondition, filter, name }
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.group-separator {
|
||||
background-image: linear-gradient(to left, lightgray, lightgray);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 1px;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0;
|
||||
padding-bottom: 0.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.btn-add-group {
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: var(--primary) !important;
|
||||
color: var(--white) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -39,148 +39,13 @@
|
||||
borderless
|
||||
class="mb-0"
|
||||
>
|
||||
<template
|
||||
v-for="(filterGroup, groupIndex) in componentFilter"
|
||||
>
|
||||
<template v-if="filterGroup.filter.length">
|
||||
<b-tr
|
||||
v-for="(filter, index) in filterGroup.filter"
|
||||
:key="`${groupIndex}-${index}`"
|
||||
class="pb-2"
|
||||
>
|
||||
<b-td
|
||||
class="px-2"
|
||||
>
|
||||
<c-input-select
|
||||
v-model="filter.name"
|
||||
:options="fieldOptions"
|
||||
:get-option-key="getOptionKey"
|
||||
:clearable="false"
|
||||
:placeholder="$t('recordList.filter.fieldPlaceholder')"
|
||||
:reduce="f => f.name"
|
||||
:class="{ 'filter-field-picker': !!filter.name }"
|
||||
@input="onChange($event, groupIndex, index)"
|
||||
/>
|
||||
</b-td>
|
||||
|
||||
<b-td
|
||||
v-if="getField(filter.name)"
|
||||
:class="{ 'pr-2': getField(filter.name) }"
|
||||
>
|
||||
<b-form-select
|
||||
v-if="getField(filter.name)"
|
||||
v-model="filter.operator"
|
||||
:options="getOperators(filter.kind, getField(filter.name))"
|
||||
class="d-flex field-operator w-100"
|
||||
@change="updateFilterProperties(filter)"
|
||||
/>
|
||||
</b-td>
|
||||
<b-td
|
||||
v-if="getField(filter.name)"
|
||||
>
|
||||
<template v-if="isBetweenOperator(filter.operator)">
|
||||
<template
|
||||
v-if="getField(`${filter.name}-start`)"
|
||||
>
|
||||
<field-editor
|
||||
v-bind="mock"
|
||||
class="field-editor mb-0"
|
||||
value-only
|
||||
:field="getField(`${filter.name}-start`)"
|
||||
:record="filter.record"
|
||||
@change="onValueChange"
|
||||
/>
|
||||
<span class="text-center my-1 w-100">
|
||||
{{ $t('general.label.and') }}
|
||||
</span>
|
||||
<field-editor
|
||||
v-bind="mock"
|
||||
class="field-editor mb-0"
|
||||
value-only
|
||||
:field="getField(`${filter.name}-end`)"
|
||||
:record="filter.record"
|
||||
@change="onValueChange"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<field-editor
|
||||
v-bind="mock"
|
||||
class="field-editor mb-0"
|
||||
value-only
|
||||
:field="getField(filter.name)"
|
||||
:record="filter.record"
|
||||
@change="onValueChange"
|
||||
/>
|
||||
</template>
|
||||
</b-td>
|
||||
<b-td
|
||||
v-if="getField(filter.name)"
|
||||
class="align-middle"
|
||||
style="width: 1%;"
|
||||
>
|
||||
<b-button
|
||||
:id="`${groupIndex}-${index}`"
|
||||
ref="delete"
|
||||
variant="link"
|
||||
class="d-flex align-items-center"
|
||||
@click="deleteFilter(groupIndex, index)"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['far', 'trash-alt']"
|
||||
size="sm"
|
||||
/>
|
||||
</b-button>
|
||||
</b-td>
|
||||
</b-tr>
|
||||
|
||||
<b-tr :key="`addFilter-${groupIndex}`">
|
||||
<b-td class="pb-0">
|
||||
<b-button
|
||||
variant="link text-decoration-none d-block mr-auto"
|
||||
style="min-height: 38px; min-width: 84px;"
|
||||
@click="addFilter(groupIndex)"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'plus']"
|
||||
size="sm"
|
||||
class="mr-1"
|
||||
/>
|
||||
{{ $t('general.label.add') }}
|
||||
</b-button>
|
||||
</b-td>
|
||||
</b-tr>
|
||||
|
||||
<b-tr
|
||||
:key="`groupCondtion-${groupIndex}`"
|
||||
>
|
||||
<b-td
|
||||
colspan="100%"
|
||||
class="p-0 justify-content-center"
|
||||
:class="{ 'pb-3': filterGroup.groupCondition }"
|
||||
>
|
||||
<div
|
||||
class="group-separator"
|
||||
>
|
||||
<div style="height: 20px; width: 100%;" />
|
||||
|
||||
<b-button
|
||||
v-if="groupIndex === (componentFilter.length - 1)"
|
||||
variant="outline-primary"
|
||||
class="btn-add-group bg-white py-2 px-3"
|
||||
@click="addGroup()"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'plus']"
|
||||
class="h6 mb-0 "
|
||||
/>
|
||||
</b-button>
|
||||
</div>
|
||||
</b-td>
|
||||
</b-tr>
|
||||
</template>
|
||||
</template>
|
||||
<filter-toolbox
|
||||
v-model="componentFilter"
|
||||
:module="module"
|
||||
:namespace="namespace"
|
||||
:mock.sync="mock"
|
||||
@prevent-close="onValueChange"
|
||||
/>
|
||||
</b-table-simple>
|
||||
</b-card-body>
|
||||
|
||||
@ -223,8 +88,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import FieldEditor from '../ModuleFields/Editor'
|
||||
import { compose, validator } from '@cortezaproject/corteza-js'
|
||||
import FilterToolbox from 'corteza-webapp-compose/src/components/Common/FilterToolbox.vue'
|
||||
|
||||
export default {
|
||||
i18nOptions: {
|
||||
@ -232,7 +97,7 @@ export default {
|
||||
},
|
||||
|
||||
components: {
|
||||
FieldEditor,
|
||||
FilterToolbox,
|
||||
},
|
||||
|
||||
props: {
|
||||
@ -291,11 +156,6 @@ export default {
|
||||
return {
|
||||
componentFilter: [],
|
||||
|
||||
conditions: [
|
||||
{ value: 'AND', text: this.$t('recordList.filter.conditions.and') },
|
||||
{ value: 'OR', text: this.$t('recordList.filter.conditions.or') },
|
||||
],
|
||||
|
||||
mock: {},
|
||||
|
||||
// Used to prevent unwanted closure of popover
|
||||
@ -304,22 +164,6 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
fields () {
|
||||
return [
|
||||
...[...this.module.fields].sort((a, b) =>
|
||||
(a.label || a.name).localeCompare(b.label || b.name),
|
||||
),
|
||||
...this.module.systemFields().map(sf => {
|
||||
sf.label = this.$t(`field:system.${sf.name}`)
|
||||
return sf
|
||||
}),
|
||||
].filter(({ isFilterable }) => isFilterable)
|
||||
},
|
||||
|
||||
fieldOptions () {
|
||||
return this.fields.map(({ name, label }) => ({ name, label: label || name }))
|
||||
},
|
||||
|
||||
inFilter () {
|
||||
return this.recordListFilter.some(({ filter }) => {
|
||||
return filter.some(({ name }) => name === this.selectedField.name)
|
||||
@ -383,96 +227,6 @@ export default {
|
||||
}, 100)
|
||||
},
|
||||
|
||||
onChange (fieldName, groupIndex, index) {
|
||||
const field = this.getField(fieldName)
|
||||
const filterExists = !!(this.componentFilter[groupIndex] || { filter: [] }).filter[index]
|
||||
if (field && filterExists) {
|
||||
const tempFilter = [...this.componentFilter]
|
||||
tempFilter[groupIndex].filter[index].kind = field.kind
|
||||
tempFilter[groupIndex].filter[index].name = field.name
|
||||
tempFilter[groupIndex].filter[index].value = undefined
|
||||
tempFilter[groupIndex].filter[index].operator = field.multi ? 'IN' : '='
|
||||
this.componentFilter = tempFilter
|
||||
}
|
||||
this.onValueChange()
|
||||
},
|
||||
|
||||
getOperators (kind, field) {
|
||||
const operators = [
|
||||
{
|
||||
value: '=',
|
||||
text: this.$t('recordList.filter.operators.equal'),
|
||||
},
|
||||
{
|
||||
value: '!=',
|
||||
text: this.$t('recordList.filter.operators.notEqual'),
|
||||
},
|
||||
]
|
||||
|
||||
const inOperators = [
|
||||
{
|
||||
value: 'IN',
|
||||
text: this.$t('recordList.filter.operators.contains'),
|
||||
},
|
||||
{
|
||||
value: 'NOT IN',
|
||||
text: this.$t('recordList.filter.operators.notContains'),
|
||||
},
|
||||
]
|
||||
|
||||
const lgOperators = [
|
||||
{
|
||||
value: '>',
|
||||
text: this.$t('recordList.filter.operators.greaterThan'),
|
||||
},
|
||||
{
|
||||
value: '<',
|
||||
text: this.$t('recordList.filter.operators.lessThan'),
|
||||
},
|
||||
]
|
||||
const matchOperators = [
|
||||
{
|
||||
value: 'LIKE',
|
||||
text: this.$t('recordList.filter.operators.like'),
|
||||
},
|
||||
{
|
||||
value: 'NOT LIKE',
|
||||
text: this.$t('recordList.filter.operators.notLike'),
|
||||
},
|
||||
]
|
||||
|
||||
const betweenOperators = [
|
||||
{
|
||||
value: 'BETWEEN',
|
||||
text: this.$t('recordList.filter.operators.between'),
|
||||
},
|
||||
{
|
||||
value: 'NOT BETWEEN',
|
||||
text: this.$t('recordList.filter.operators.notBetween'),
|
||||
},
|
||||
]
|
||||
|
||||
if (field.multi) {
|
||||
return inOperators
|
||||
}
|
||||
|
||||
switch (kind) {
|
||||
case 'Number':
|
||||
return [...operators, ...lgOperators, ...betweenOperators]
|
||||
|
||||
case 'DateTime':
|
||||
return [...operators, ...lgOperators, ...betweenOperators]
|
||||
|
||||
case 'String':
|
||||
case 'Url':
|
||||
case 'Email':
|
||||
return [...operators, ...matchOperators]
|
||||
|
||||
default:
|
||||
return operators
|
||||
}
|
||||
},
|
||||
|
||||
getField (name = '') {
|
||||
const field = name ? this.mock.module.fields.find(f => f.name === name) : undefined
|
||||
|
||||
@ -519,34 +273,6 @@ export default {
|
||||
this.$emit('reset')
|
||||
},
|
||||
|
||||
deleteFilter (groupIndex, index) {
|
||||
const filterExists = !!(this.componentFilter[groupIndex] || { filter: [] }).filter[index]
|
||||
|
||||
if (filterExists) {
|
||||
// Set focus to previous element
|
||||
this.onSetFocus(groupIndex, index)
|
||||
// Delete filter from filterGroup
|
||||
this.componentFilter[groupIndex].filter.splice(index, 1)
|
||||
|
||||
// If filter was last in filterGroup, delete filterGroup
|
||||
if (!this.componentFilter[groupIndex].filter.length) {
|
||||
this.componentFilter.splice(groupIndex, 1)
|
||||
|
||||
// If no more filterGroups, add default back
|
||||
if (!this.componentFilter.length) {
|
||||
this.resetFilter()
|
||||
} else if (groupIndex === this.componentFilter.length) {
|
||||
// Reset first filterGroup groupCondition if last filterGroup was deleted
|
||||
this.componentFilter[groupIndex - 1].groupCondition = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onSetFocus () {
|
||||
this.$refs.focusMe.focus()
|
||||
},
|
||||
|
||||
onOpen () {
|
||||
// Create record and fill its values property if value exists
|
||||
this.componentFilter = this.recordListFilter
|
||||
@ -623,29 +349,12 @@ export default {
|
||||
this.$emit(type, this.processFilter())
|
||||
},
|
||||
|
||||
updateFilterProperties (filter) {
|
||||
if (this.isBetweenOperator(filter.operator)) {
|
||||
filter.record.values[`${filter.name}-start`] = filter.record.values[`${filter.name}-start`]
|
||||
filter.record.values[`${filter.name}-end`] = filter.record.values[`${filter.name}-end`]
|
||||
|
||||
const field = this.mock.module.fields.find(f => f.name === filter.name)
|
||||
|
||||
this.mock.module.fields.push({ ...field, name: `${filter.name}-end` })
|
||||
this.mock.module.fields.push({ ...field, name: `${filter.name}-start` })
|
||||
}
|
||||
},
|
||||
|
||||
isBetweenOperator (op) {
|
||||
return ['BETWEEN', 'NOT BETWEEN'].includes(op)
|
||||
},
|
||||
|
||||
getOptionKey ({ name }) {
|
||||
return name
|
||||
},
|
||||
|
||||
setDefaultValues () {
|
||||
this.componentFilter = []
|
||||
this.conditions = []
|
||||
this.mock = {}
|
||||
this.preventPopoverClose = false
|
||||
},
|
||||
|
||||
@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<c-form-table-wrapper hide-add-button>
|
||||
<b-form-group
|
||||
:label="$t('recordList.record.prefilterCommand')"
|
||||
label-class="text-primary"
|
||||
class="m-0"
|
||||
>
|
||||
<b-row v-if="textInput">
|
||||
<b-col>
|
||||
<b-form-group label-class="text-primary">
|
||||
<b-form-textarea
|
||||
v-model="options.prefilter"
|
||||
:placeholder="$t('recordList.record.prefilterPlaceholder')"
|
||||
/>
|
||||
|
||||
<i18next
|
||||
path="recordList.record.prefilterFootnote"
|
||||
tag="small"
|
||||
class="text-muted"
|
||||
>
|
||||
<code>${record.values.fieldName}</code>
|
||||
<code>${recordID}</code>
|
||||
<code>${ownerID}</code>
|
||||
<span><code>${userID}</code>, <code>${user.name}</code></span>
|
||||
</i18next>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<c-form-table-wrapper
|
||||
v-else
|
||||
hide-add-button
|
||||
>
|
||||
<filter-toolbox
|
||||
v-model="filterGroup"
|
||||
:module="module"
|
||||
:namespace="namespace"
|
||||
:mock.sync="mock"
|
||||
reset-filter-on-created
|
||||
/>
|
||||
</c-form-table-wrapper>
|
||||
|
||||
<div class="mt-1 d-flex align-items-center">
|
||||
<b-button
|
||||
variant="link"
|
||||
size="sm"
|
||||
class="ml-auto text-decoration-none"
|
||||
@click="toggleFilterView"
|
||||
>
|
||||
{{ $t('recordList.prefilter.toggleInputType') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</b-form-group>
|
||||
</c-form-table-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { compose, validator } from '@cortezaproject/corteza-js'
|
||||
import {
|
||||
getRecordListFilterSql,
|
||||
trimChar,
|
||||
} from 'corteza-webapp-compose/src/lib/record-filter.js'
|
||||
import FilterToolbox from 'corteza-webapp-compose/src/components/Common/FilterToolbox.vue'
|
||||
|
||||
export default {
|
||||
i18nOptions: {
|
||||
namespaces: 'block',
|
||||
},
|
||||
|
||||
name: 'RecordListConfiguratorPrefilter',
|
||||
|
||||
components: {
|
||||
FilterToolbox,
|
||||
},
|
||||
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
||||
namespace: {
|
||||
type: compose.Namespace,
|
||||
required: true,
|
||||
},
|
||||
|
||||
module: {
|
||||
type: compose.Module,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
textInput: true,
|
||||
filterGroup: [],
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
// Change all module fields to single value to keep multi value fields and single value
|
||||
const module = JSON.parse(JSON.stringify(this.module || {}))
|
||||
|
||||
module.fields = [
|
||||
...[...module.fields].map((f) => {
|
||||
f.multi = f.isMulti
|
||||
f.isMulti = false
|
||||
|
||||
// Disable edge case options
|
||||
if (f.kind === 'DateTime') {
|
||||
f.options.onlyFutureValues = false
|
||||
f.options.onlyPastValues = false
|
||||
}
|
||||
|
||||
return f
|
||||
}),
|
||||
...this.module.systemFields().map((sf) => {
|
||||
return { ...sf, label: this.$t(`field:system.${sf.name}`) }
|
||||
}),
|
||||
]
|
||||
|
||||
this.mock = {
|
||||
namespace: this.namespace,
|
||||
module,
|
||||
errors: new validator.Validated(),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleFilterView () {
|
||||
if (!this.textInput) {
|
||||
this.options.prefilter = this.parseFilter()
|
||||
}
|
||||
|
||||
this.textInput = !this.textInput
|
||||
},
|
||||
|
||||
getOptionKey ({ name }) {
|
||||
return name
|
||||
},
|
||||
|
||||
processFilter (filterGroup = this.value) {
|
||||
return filterGroup.map(({ groupCondition, filter = [], name }) => {
|
||||
filter = filter.map(({ record, ...f }) => {
|
||||
if (record) {
|
||||
f.value = record[f.name] || record.values[f.name]
|
||||
}
|
||||
|
||||
if (this.isBetweenOperator(f.operator)) {
|
||||
f.value = {
|
||||
start: this.getField(f.name).isSystem
|
||||
? record[`${f.name}-start`]
|
||||
: record.values[`${f.name}-start`],
|
||||
end: this.getField(f.name).isSystem
|
||||
? record[`${f.name}-end`]
|
||||
: record.values[`${f.name}-end`],
|
||||
}
|
||||
}
|
||||
|
||||
return f
|
||||
})
|
||||
|
||||
return { groupCondition, filter, name }
|
||||
})
|
||||
},
|
||||
|
||||
isBetweenOperator (op) {
|
||||
return ['BETWEEN', 'NOT BETWEEN'].includes(op)
|
||||
},
|
||||
|
||||
parseFilter (filterGroup = this.filterGroup) {
|
||||
const filter = this.processFilter(filterGroup)
|
||||
|
||||
const filterSqlArray = filter
|
||||
.map(({ groupCondition, filter = [] }) => {
|
||||
groupCondition = groupCondition ? ` ${groupCondition} ` : ''
|
||||
filter = getRecordListFilterSql(filter)
|
||||
|
||||
return filter ? `${filter}${groupCondition}` : ''
|
||||
})
|
||||
.filter((filter) => filter)
|
||||
|
||||
const filterSql = trimChar(
|
||||
trimChar(filterSqlArray.join(''), ' AND '),
|
||||
' OR '
|
||||
)
|
||||
|
||||
return filterSql
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -820,15 +820,16 @@
|
||||
</template>
|
||||
</wrap>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import base from './base'
|
||||
import base from 'corteza-webapp-compose/src/components/PageBlocks/base'
|
||||
import FieldViewer from 'corteza-webapp-compose/src/components/ModuleFields/Viewer'
|
||||
import FieldEditor from 'corteza-webapp-compose/src/components/ModuleFields/Editor'
|
||||
import ExporterModal from 'corteza-webapp-compose/src/components/Public/Record/Exporter'
|
||||
import ImporterModal from 'corteza-webapp-compose/src/components/Public/Record/Importer'
|
||||
import AutomationButtons from './Shared/AutomationButtons'
|
||||
import AutomationButtons from 'corteza-webapp-compose/src/components/PageBlocks/Shared/AutomationButtons.vue'
|
||||
import { compose, validator, NoID } from '@cortezaproject/corteza-js'
|
||||
import users from 'corteza-webapp-compose/src/mixins/users'
|
||||
import records from 'corteza-webapp-compose/src/mixins/records'
|
||||
|
||||
@ -825,6 +825,7 @@ import AutomationTab from './Shared/AutomationTab'
|
||||
import FieldPicker from 'corteza-webapp-compose/src/components/Common/FieldPicker'
|
||||
import RecordListFilter from 'corteza-webapp-compose/src/components/Common/RecordListFilter'
|
||||
import { components } from '@cortezaproject/corteza-vue'
|
||||
import Prefilter from './RecordList/Prefilter.vue'
|
||||
import ColumnPicker from 'corteza-webapp-compose/src/components/Admin/Module/Records/ColumnPicker'
|
||||
const { CInputPresort, CInputRole } = components
|
||||
|
||||
@ -843,6 +844,7 @@ export default {
|
||||
Draggable,
|
||||
CInputRole,
|
||||
ColumnPicker,
|
||||
Prefilter,
|
||||
},
|
||||
|
||||
extends: base,
|
||||
|
||||
@ -199,7 +199,7 @@ export function evaluatePrefilter (prefilter, { record, user, recordID, ownerID,
|
||||
}
|
||||
|
||||
// Removes char from end of string
|
||||
function trimChar (text = '', char = '') {
|
||||
export function trimChar (text = '', char = '') {
|
||||
if (text.substring(text.length - char.length, text.length) === char) {
|
||||
text = text.substring(0, text.length - char.length)
|
||||
}
|
||||
|
||||
@ -302,6 +302,8 @@ recordList:
|
||||
addRecord: Add
|
||||
cancelSelection: Cancel
|
||||
customFilter: Custom filter
|
||||
prefilter:
|
||||
toggleInputType: Toggle input type
|
||||
filterPresets:
|
||||
filterName: Filter name
|
||||
saveFilterAsPreset: Save filter as preset
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user