mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
309 lines
9.3 KiB
309 lines
9.3 KiB
2 years ago
|
<script setup lang="ts">
|
||
2 years ago
|
import type { FilterType } from 'nocodb-sdk'
|
||
2 years ago
|
import { UITypes } from 'nocodb-sdk'
|
||
|
import FieldListAutoCompleteDropdown from './FieldListAutoCompleteDropdown.vue'
|
||
2 years ago
|
import {
|
||
|
ActiveViewInj,
|
||
|
MetaInj,
|
||
|
ReloadViewDataHookInj,
|
||
|
comparisonOpList,
|
||
|
computed,
|
||
|
inject,
|
||
|
ref,
|
||
|
useNuxtApp,
|
||
|
useViewFilters,
|
||
|
watch,
|
||
|
} from '#imports'
|
||
|
import type { Filter } from '~/lib'
|
||
|
|
||
|
interface Props {
|
||
|
nested?: boolean
|
||
|
parentId?: string
|
||
|
autoSave: boolean
|
||
|
hookId?: string
|
||
2 years ago
|
showLoading?: boolean
|
||
2 years ago
|
modelValue?: Filter[]
|
||
2 years ago
|
webHook?: boolean
|
||
2 years ago
|
}
|
||
|
|
||
2 years ago
|
const { nested = false, parentId, autoSave = true, hookId = null, modelValue, showLoading = true, webHook } = defineProps<Props>()
|
||
2 years ago
|
|
||
2 years ago
|
const emit = defineEmits(['update:filtersLength'])
|
||
2 years ago
|
|
||
2 years ago
|
const logicalOps = [
|
||
|
{ value: 'and', text: 'AND' },
|
||
|
{ value: 'or', text: 'OR' },
|
||
|
]
|
||
2 years ago
|
|
||
2 years ago
|
const meta = inject(MetaInj, ref())
|
||
2 years ago
|
|
||
2 years ago
|
const activeView = inject(ActiveViewInj, ref())
|
||
2 years ago
|
|
||
2 years ago
|
const reloadDataHook = inject(ReloadViewDataHookInj)!
|
||
2 years ago
|
|
||
2 years ago
|
const { $e } = useNuxtApp()
|
||
|
|
||
2 years ago
|
const { nestedFilters } = useSmartsheetStoreOrThrow()
|
||
2 years ago
|
const { filters, deleteFilter, saveOrUpdate, loadFilters, addFilter, addFilterGroup, sync } = useViewFilters(
|
||
2 years ago
|
activeView,
|
||
|
parentId,
|
||
2 years ago
|
computed(() => autoSave),
|
||
2 years ago
|
() => reloadDataHook.trigger(showLoading),
|
||
2 years ago
|
modelValue || nestedFilters.value,
|
||
|
!modelValue,
|
||
2 years ago
|
)
|
||
2 years ago
|
|
||
2 years ago
|
const localNestedFilters = ref()
|
||
2 years ago
|
|
||
2 years ago
|
const filterUpdateCondition = (filter: FilterType, i: number) => {
|
||
2 years ago
|
saveOrUpdate(filter, i)
|
||
|
$e('a:filter:update', {
|
||
|
logical: filter.logical_op,
|
||
|
comparison: filter.comparison_op,
|
||
|
})
|
||
|
}
|
||
2 years ago
|
|
||
2 years ago
|
// todo : filter based on type
|
||
2 years ago
|
// const filterComparisonOp = (f) =>
|
||
|
// comparisonOp.filter((op) => {
|
||
|
// // if (
|
||
|
// // f &&
|
||
|
// // f.fk_column_id &&
|
||
|
// // this.columnsById[f.fk_column_id] &&
|
||
|
// // this.columnsById[f.fk_column_id].uidt === UITypes.LinkToAnotherRecord &&
|
||
|
// // this.columnsById[f.fk_column_id].uidt === UITypes.Lookup
|
||
|
// // ) {
|
||
|
// // return !['notempty', 'empty', 'notnull', 'null'].includes(op.value)
|
||
|
// // }
|
||
|
// return true
|
||
|
// })
|
||
2 years ago
|
|
||
2 years ago
|
const columns = computed(() => meta.value?.columns)
|
||
|
|
||
2 years ago
|
const types = computed(() => {
|
||
2 years ago
|
if (!meta.value?.columns?.length) {
|
||
2 years ago
|
return {}
|
||
|
}
|
||
2 years ago
|
|
||
|
return meta.value?.columns?.reduce((obj: any, col: any) => {
|
||
2 years ago
|
switch (col.uidt) {
|
||
|
case UITypes.Number:
|
||
|
case UITypes.Decimal:
|
||
|
obj[col.title] = obj[col.column_name] = 'number'
|
||
|
break
|
||
|
case UITypes.Checkbox:
|
||
|
obj[col.title] = obj[col.column_name] = 'boolean'
|
||
|
break
|
||
|
}
|
||
|
return obj
|
||
|
}, {})
|
||
|
})
|
||
|
|
||
|
watch(
|
||
2 years ago
|
() => activeView.value?.id,
|
||
2 years ago
|
(n, o) => {
|
||
2 years ago
|
if (n !== o && (hookId || !webHook)) loadFilters(hookId as string)
|
||
2 years ago
|
},
|
||
|
{ immediate: true },
|
||
|
)
|
||
2 years ago
|
|
||
2 years ago
|
watch(
|
||
2 years ago
|
() => filters.value.length,
|
||
2 years ago
|
(length) => {
|
||
2 years ago
|
emit('update:filtersLength', length ?? 0)
|
||
2 years ago
|
},
|
||
|
)
|
||
2 years ago
|
|
||
2 years ago
|
const applyChanges = async (hookId?: string) => {
|
||
|
await sync(hookId)
|
||
2 years ago
|
|
||
2 years ago
|
if (!localNestedFilters.value?.length) return
|
||
2 years ago
|
|
||
2 years ago
|
for (const nestedFilter of localNestedFilters.value) {
|
||
2 years ago
|
if (nestedFilter.parentId) {
|
||
2 years ago
|
await nestedFilter.applyChanges(hookId, true)
|
||
2 years ago
|
}
|
||
2 years ago
|
}
|
||
2 years ago
|
}
|
||
|
|
||
|
defineExpose({
|
||
2 years ago
|
applyChanges,
|
||
|
parentId,
|
||
2 years ago
|
})
|
||
2 years ago
|
</script>
|
||
|
|
||
|
<template>
|
||
2 years ago
|
<div
|
||
2 years ago
|
class="p-4 menu-filter-dropdown bg-gray-50 !border mt-4"
|
||
|
:class="{ 'shadow min-w-[430px] max-w-[630px] max-h-[max(80vh,500px)] overflow-auto': !nested, 'border-1 w-full': nested }"
|
||
2 years ago
|
>
|
||
|
<div v-if="filters && filters.length" class="nc-filter-grid mb-2" @click.stop>
|
||
2 years ago
|
<template v-for="(filter, i) in filters" :key="i">
|
||
2 years ago
|
<template v-if="filter.status !== 'delete'">
|
||
2 years ago
|
<template v-if="filter.is_group">
|
||
2 years ago
|
<MdiCloseBox
|
||
2 years ago
|
v-if="!filter.readOnly"
|
||
2 years ago
|
:key="i"
|
||
2 years ago
|
small
|
||
2 years ago
|
class="nc-filter-item-remove-btn cursor-pointer text-grey"
|
||
2 years ago
|
@click.stop="deleteFilter(filter, i)"
|
||
|
/>
|
||
2 years ago
|
<span v-else :key="`${i}dummy`" />
|
||
2 years ago
|
|
||
2 years ago
|
<div :key="`${i}nested`" class="flex">
|
||
2 years ago
|
<a-select
|
||
|
v-model:value="filter.logical_op"
|
||
2 years ago
|
:dropdown-match-select-width="false"
|
||
2 years ago
|
class="shrink grow-0"
|
||
2 years ago
|
placeholder="Group op"
|
||
2 years ago
|
dropdown-class-name="nc-dropdown-filter-logical-op-group"
|
||
2 years ago
|
@click.stop
|
||
|
@change="saveOrUpdate(filter, i)"
|
||
|
>
|
||
2 years ago
|
<a-select-option v-for="op in logicalOps" :key="op.value" :value="op.value" class="">
|
||
2 years ago
|
{{ op.text }}
|
||
|
</a-select-option>
|
||
2 years ago
|
</a-select>
|
||
2 years ago
|
</div>
|
||
2 years ago
|
<span class="col-span-3" />
|
||
|
<div class="col-span-5">
|
||
|
<SmartsheetToolbarColumnFilter
|
||
2 years ago
|
v-if="filter.id || filter.children"
|
||
2 years ago
|
ref="localNestedFilters"
|
||
2 years ago
|
v-model="filter.children"
|
||
|
:parent-id="filter.id"
|
||
|
nested
|
||
2 years ago
|
:auto-save="autoSave"
|
||
2 years ago
|
/>
|
||
|
</div>
|
||
|
</template>
|
||
2 years ago
|
<template v-else>
|
||
2 years ago
|
<!-- <v-icon
|
||
|
v-if="!filter.readOnly"
|
||
|
:key="`${i}_3`"
|
||
|
small
|
||
|
class="nc-filter-item-remove-btn"
|
||
|
@click.stop="deleteFilter(filter, i)"
|
||
|
>
|
||
|
mdi-close-box
|
||
|
</v-icon> -->
|
||
|
|
||
2 years ago
|
<MdiCloseBox
|
||
2 years ago
|
v-if="!filter.readOnly"
|
||
2 years ago
|
class="nc-filter-item-remove-btn text-grey self-center"
|
||
2 years ago
|
@click.stop="deleteFilter(filter, i)"
|
||
2 years ago
|
/>
|
||
2 years ago
|
<span v-else />
|
||
|
|
||
2 years ago
|
<span v-if="!i" class="flex items-center">{{ $t('labels.where') }}</span>
|
||
2 years ago
|
|
||
2 years ago
|
<a-select
|
||
2 years ago
|
v-else
|
||
2 years ago
|
v-model:value="filter.logical_op"
|
||
2 years ago
|
:dropdown-match-select-width="false"
|
||
2 years ago
|
class="h-full"
|
||
2 years ago
|
hide-details
|
||
|
:disabled="filter.readOnly"
|
||
2 years ago
|
dropdown-class-name="nc-dropdown-filter-logical-op"
|
||
2 years ago
|
@click.stop
|
||
|
@change="filterUpdateCondition(filter, i)"
|
||
2 years ago
|
>
|
||
2 years ago
|
<a-select-option v-for="op in logicalOps" :key="op.value" :value="op.value">
|
||
2 years ago
|
{{ op.text }}
|
||
|
</a-select-option>
|
||
|
</a-select>
|
||
2 years ago
|
|
||
|
<FieldListAutoCompleteDropdown
|
||
|
:key="`${i}_6`"
|
||
|
v-model="filter.fk_column_id"
|
||
2 years ago
|
class="nc-filter-field-select"
|
||
2 years ago
|
:columns="columns"
|
||
|
:disabled="filter.readOnly"
|
||
|
@click.stop
|
||
|
@change="saveOrUpdate(filter, i)"
|
||
|
/>
|
||
|
|
||
2 years ago
|
<a-select
|
||
|
v-model:value="filter.comparison_op"
|
||
2 years ago
|
:dropdown-match-select-width="false"
|
||
2 years ago
|
class="caption nc-filter-operation-select"
|
||
2 years ago
|
:placeholder="$t('labels.operation')"
|
||
2 years ago
|
density="compact"
|
||
|
variant="solo"
|
||
2 years ago
|
:disabled="filter.readOnly"
|
||
|
hide-details
|
||
2 years ago
|
dropdown-class-name="nc-dropdown-filter-comp-op"
|
||
2 years ago
|
@change="filterUpdateCondition(filter, i)"
|
||
2 years ago
|
>
|
||
2 years ago
|
<a-select-option v-for="compOp in comparisonOpList" :key="compOp.value" :value="compOp.value" class="">
|
||
2 years ago
|
{{ compOp.text }}
|
||
|
</a-select-option>
|
||
|
</a-select>
|
||
|
<!--
|
||
2 years ago
|
todo: filter based on column type
|
||
|
|
||
|
item-value="value"
|
||
|
item-text="text"
|
||
|
:items="filterComparisonOp(filter)" -->
|
||
|
|
||
|
<!-- <template #item="{ item }"> -->
|
||
|
<!-- <span class="caption font-weight-regular">{{ item.text }}</span> -->
|
||
|
<!-- </template> -->
|
||
|
<!-- </v-select> -->
|
||
2 years ago
|
<span v-if="['null', 'notnull', 'empty', 'notempty'].includes(filter.comparison_op)" :key="`span${i}`" />
|
||
2 years ago
|
<a-checkbox
|
||
2 years ago
|
v-else-if="types[filter.field] === 'boolean'"
|
||
2 years ago
|
v-model:checked="filter.value"
|
||
2 years ago
|
dense
|
||
|
:disabled="filter.readOnly"
|
||
|
@change="saveOrUpdate(filter, i)"
|
||
|
/>
|
||
2 years ago
|
<a-input
|
||
2 years ago
|
v-else
|
||
|
:key="`${i}_7`"
|
||
2 years ago
|
v-model:value="filter.value"
|
||
2 years ago
|
class="nc-filter-value-select"
|
||
2 years ago
|
:disabled="filter.readOnly"
|
||
|
@click.stop
|
||
|
@input="saveOrUpdate(filter, i)"
|
||
|
/>
|
||
|
</template>
|
||
|
</template>
|
||
|
</template>
|
||
|
</div>
|
||
2 years ago
|
|
||
2 years ago
|
<div class="flex gap-2 mb-2 mt-4">
|
||
2 years ago
|
<a-button class="elevation-0 text-capitalize" type="primary" ghost @click.stop="addFilter">
|
||
2 years ago
|
<div class="flex items-center gap-1">
|
||
2 years ago
|
<!-- <v-icon small color="grey"> mdi-plus </v-icon> -->
|
||
2 years ago
|
<MdiPlus />
|
||
2 years ago
|
<!-- Add Filter -->
|
||
|
{{ $t('activity.addFilter') }}
|
||
|
</div>
|
||
|
</a-button>
|
||
2 years ago
|
<a-button v-if="!webHook" class="text-capitalize !text-gray-500" @click.stop="addFilterGroup">
|
||
2 years ago
|
<div class="flex items-center gap-1">
|
||
2 years ago
|
<!-- <v-icon small color="grey"> mdi-plus </v-icon> -->
|
||
2 years ago
|
<!-- Add Filter Group -->
|
||
2 years ago
|
<MdiPlus />
|
||
2 years ago
|
{{ $t('activity.addFilterGroup') }}
|
||
2 years ago
|
</div>
|
||
|
</a-button>
|
||
|
</div>
|
||
2 years ago
|
<slot />
|
||
2 years ago
|
</div>
|
||
2 years ago
|
</template>
|
||
|
|
||
|
<style scoped>
|
||
2 years ago
|
.nc-filter-grid {
|
||
2 years ago
|
display: grid;
|
||
2 years ago
|
grid-template-columns: 18px 75px auto auto auto;
|
||
2 years ago
|
@apply gap-[12px]
|
||
2 years ago
|
align-items: center;
|
||
|
}
|
||
|
|
||
2 years ago
|
:deep(.ant-select-item-option) {
|
||
2 years ago
|
@apply "!min-w-full";
|
||
2 years ago
|
}
|
||
2 years ago
|
</style>
|