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
|
borderless
|
||||||
class="mb-0"
|
class="mb-0"
|
||||||
>
|
>
|
||||||
<template
|
<filter-toolbox
|
||||||
v-for="(filterGroup, groupIndex) in componentFilter"
|
v-model="componentFilter"
|
||||||
>
|
:module="module"
|
||||||
<template v-if="filterGroup.filter.length">
|
:namespace="namespace"
|
||||||
<b-tr
|
:mock.sync="mock"
|
||||||
v-for="(filter, index) in filterGroup.filter"
|
@prevent-close="onValueChange"
|
||||||
: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>
|
|
||||||
</b-table-simple>
|
</b-table-simple>
|
||||||
</b-card-body>
|
</b-card-body>
|
||||||
|
|
||||||
@ -223,8 +88,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import FieldEditor from '../ModuleFields/Editor'
|
|
||||||
import { compose, validator } from '@cortezaproject/corteza-js'
|
import { compose, validator } from '@cortezaproject/corteza-js'
|
||||||
|
import FilterToolbox from 'corteza-webapp-compose/src/components/Common/FilterToolbox.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
i18nOptions: {
|
i18nOptions: {
|
||||||
@ -232,7 +97,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
FieldEditor,
|
FilterToolbox,
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
@ -291,11 +156,6 @@ export default {
|
|||||||
return {
|
return {
|
||||||
componentFilter: [],
|
componentFilter: [],
|
||||||
|
|
||||||
conditions: [
|
|
||||||
{ value: 'AND', text: this.$t('recordList.filter.conditions.and') },
|
|
||||||
{ value: 'OR', text: this.$t('recordList.filter.conditions.or') },
|
|
||||||
],
|
|
||||||
|
|
||||||
mock: {},
|
mock: {},
|
||||||
|
|
||||||
// Used to prevent unwanted closure of popover
|
// Used to prevent unwanted closure of popover
|
||||||
@ -304,22 +164,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
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 () {
|
inFilter () {
|
||||||
return this.recordListFilter.some(({ filter }) => {
|
return this.recordListFilter.some(({ filter }) => {
|
||||||
return filter.some(({ name }) => name === this.selectedField.name)
|
return filter.some(({ name }) => name === this.selectedField.name)
|
||||||
@ -383,96 +227,6 @@ export default {
|
|||||||
}, 100)
|
}, 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 = '') {
|
getField (name = '') {
|
||||||
const field = name ? this.mock.module.fields.find(f => f.name === name) : undefined
|
const field = name ? this.mock.module.fields.find(f => f.name === name) : undefined
|
||||||
|
|
||||||
@ -519,34 +273,6 @@ export default {
|
|||||||
this.$emit('reset')
|
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 () {
|
onOpen () {
|
||||||
// Create record and fill its values property if value exists
|
// Create record and fill its values property if value exists
|
||||||
this.componentFilter = this.recordListFilter
|
this.componentFilter = this.recordListFilter
|
||||||
@ -623,29 +349,12 @@ export default {
|
|||||||
this.$emit(type, this.processFilter())
|
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) {
|
isBetweenOperator (op) {
|
||||||
return ['BETWEEN', 'NOT BETWEEN'].includes(op)
|
return ['BETWEEN', 'NOT BETWEEN'].includes(op)
|
||||||
},
|
},
|
||||||
|
|
||||||
getOptionKey ({ name }) {
|
|
||||||
return name
|
|
||||||
},
|
|
||||||
|
|
||||||
setDefaultValues () {
|
setDefaultValues () {
|
||||||
this.componentFilter = []
|
this.componentFilter = []
|
||||||
this.conditions = []
|
|
||||||
this.mock = {}
|
this.mock = {}
|
||||||
this.preventPopoverClose = false
|
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>
|
</template>
|
||||||
</wrap>
|
</wrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { mapGetters, mapActions } from 'vuex'
|
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 FieldViewer from 'corteza-webapp-compose/src/components/ModuleFields/Viewer'
|
||||||
import FieldEditor from 'corteza-webapp-compose/src/components/ModuleFields/Editor'
|
import FieldEditor from 'corteza-webapp-compose/src/components/ModuleFields/Editor'
|
||||||
import ExporterModal from 'corteza-webapp-compose/src/components/Public/Record/Exporter'
|
import ExporterModal from 'corteza-webapp-compose/src/components/Public/Record/Exporter'
|
||||||
import ImporterModal from 'corteza-webapp-compose/src/components/Public/Record/Importer'
|
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 { compose, validator, NoID } from '@cortezaproject/corteza-js'
|
||||||
import users from 'corteza-webapp-compose/src/mixins/users'
|
import users from 'corteza-webapp-compose/src/mixins/users'
|
||||||
import records from 'corteza-webapp-compose/src/mixins/records'
|
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 FieldPicker from 'corteza-webapp-compose/src/components/Common/FieldPicker'
|
||||||
import RecordListFilter from 'corteza-webapp-compose/src/components/Common/RecordListFilter'
|
import RecordListFilter from 'corteza-webapp-compose/src/components/Common/RecordListFilter'
|
||||||
import { components } from '@cortezaproject/corteza-vue'
|
import { components } from '@cortezaproject/corteza-vue'
|
||||||
|
import Prefilter from './RecordList/Prefilter.vue'
|
||||||
import ColumnPicker from 'corteza-webapp-compose/src/components/Admin/Module/Records/ColumnPicker'
|
import ColumnPicker from 'corteza-webapp-compose/src/components/Admin/Module/Records/ColumnPicker'
|
||||||
const { CInputPresort, CInputRole } = components
|
const { CInputPresort, CInputRole } = components
|
||||||
|
|
||||||
@ -843,6 +844,7 @@ export default {
|
|||||||
Draggable,
|
Draggable,
|
||||||
CInputRole,
|
CInputRole,
|
||||||
ColumnPicker,
|
ColumnPicker,
|
||||||
|
Prefilter,
|
||||||
},
|
},
|
||||||
|
|
||||||
extends: base,
|
extends: base,
|
||||||
|
|||||||
@ -199,7 +199,7 @@ export function evaluatePrefilter (prefilter, { record, user, recordID, ownerID,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Removes char from end of string
|
// 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) {
|
if (text.substring(text.length - char.length, text.length) === char) {
|
||||||
text = text.substring(0, text.length - char.length)
|
text = text.substring(0, text.length - char.length)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -302,6 +302,8 @@ recordList:
|
|||||||
addRecord: Add
|
addRecord: Add
|
||||||
cancelSelection: Cancel
|
cancelSelection: Cancel
|
||||||
customFilter: Custom filter
|
customFilter: Custom filter
|
||||||
|
prefilter:
|
||||||
|
toggleInputType: Toggle input type
|
||||||
filterPresets:
|
filterPresets:
|
||||||
filterName: Filter name
|
filterName: Filter name
|
||||||
saveFilterAsPreset: Save filter as preset
|
saveFilterAsPreset: Save filter as preset
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user