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.
308 lines
9.2 KiB
308 lines
9.2 KiB
<script setup lang="ts"> |
|
import type { FilterType } from 'nocodb-sdk' |
|
import { UITypes } from 'nocodb-sdk' |
|
import FieldListAutoCompleteDropdown from './FieldListAutoCompleteDropdown.vue' |
|
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 |
|
modelValue?: Filter[] |
|
} |
|
|
|
const { nested = false, parentId, autoSave = true, hookId = null, modelValue } = defineProps<Props>() |
|
|
|
const emit = defineEmits(['update:filtersLength']) |
|
|
|
const logicalOps = [ |
|
{ value: 'and', text: 'AND' }, |
|
{ value: 'or', text: 'OR' }, |
|
] |
|
|
|
const meta = inject(MetaInj)! |
|
|
|
const activeView = inject(ActiveViewInj)! |
|
|
|
const reloadDataHook = inject(ReloadViewDataHookInj)! |
|
|
|
const { $e } = useNuxtApp() |
|
|
|
const { nestedFilters } = useSmartsheetStoreOrThrow() |
|
const { filters, deleteFilter, saveOrUpdate, loadFilters, addFilter, addFilterGroup, sync } = useViewFilters( |
|
activeView, |
|
parentId, |
|
computed(() => autoSave), |
|
() => { |
|
reloadDataHook.trigger() |
|
}, |
|
modelValue || nestedFilters.value, |
|
!modelValue, |
|
) |
|
|
|
const localNestedFilters = ref() |
|
|
|
const filterUpdateCondition = (filter: FilterType, i: number) => { |
|
saveOrUpdate(filter, i) |
|
$e('a:filter:update', { |
|
logical: filter.logical_op, |
|
comparison: filter.comparison_op, |
|
}) |
|
} |
|
|
|
// todo : filter based on type |
|
// 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 |
|
// }) |
|
|
|
const columns = computed(() => meta.value?.columns) |
|
|
|
const types = computed(() => { |
|
if (!meta.value?.columns?.length) { |
|
return {} |
|
} |
|
|
|
return meta.value?.columns?.reduce((obj: any, col: any) => { |
|
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( |
|
() => activeView.value?.id, |
|
(n, o) => { |
|
if (n !== o) loadFilters(hookId as string) |
|
}, |
|
{ immediate: true }, |
|
) |
|
|
|
watch( |
|
() => filters.value.length, |
|
(length) => { |
|
emit('update:filtersLength', length ?? 0) |
|
}, |
|
) |
|
|
|
const applyChanges = async (hookId?: string) => { |
|
await sync(hookId) |
|
|
|
if (!localNestedFilters.value?.length) return |
|
|
|
for (const nestedFilter of localNestedFilters.value) { |
|
if (nestedFilter.parentId) { |
|
await nestedFilter.applyChanges(hookId, true) |
|
} |
|
} |
|
} |
|
|
|
defineExpose({ |
|
applyChanges, |
|
parentId, |
|
}) |
|
</script> |
|
|
|
<template> |
|
<div |
|
class="p-6 menu-filter-dropdown bg-gray-50 !border" |
|
:class="{ 'shadow-xl min-w-[430px] max-w-[630px] max-h-[max(80vh,500px)] overflow-auto': !nested, 'border-1 w-full': nested }" |
|
> |
|
<div v-if="filters && filters.length" class="nc-filter-grid mb-2" @click.stop> |
|
<template v-for="(filter, i) in filters" :key="filter.id || i"> |
|
<template v-if="filter.status !== 'delete'"> |
|
<template v-if="filter.is_group"> |
|
<MdiCloseBox |
|
v-if="!filter.readOnly" |
|
:key="i" |
|
small |
|
class="nc-filter-item-remove-btn cursor-pointer text-grey" |
|
@click.stop="deleteFilter(filter, i)" |
|
/> |
|
<span v-else :key="`${i}dummy`" /> |
|
|
|
<div :key="`${i}nested`" class="flex"> |
|
<a-select |
|
v-model:value="filter.logical_op" |
|
:dropdown-match-select-width="false" |
|
class="shrink grow-0" |
|
placeholder="Group op" |
|
dropdown-class-name="nc-dropdown-filter-logical-op-group" |
|
@click.stop |
|
@change="saveOrUpdate(filter, i)" |
|
> |
|
<a-select-option v-for="op in logicalOps" :key="op.value" :value="op.value" class=""> |
|
{{ op.text }} |
|
</a-select-option> |
|
</a-select> |
|
</div> |
|
<span class="col-span-3" /> |
|
<div class="col-span-5"> |
|
<SmartsheetToolbarColumnFilter |
|
v-if="filter.id || filter.children" |
|
ref="localNestedFilters" |
|
v-model="filter.children" |
|
:parent-id="filter.id" |
|
nested |
|
:auto-save="autoSave" |
|
/> |
|
</div> |
|
</template> |
|
<template v-else> |
|
<!-- <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> --> |
|
|
|
<MdiCloseBox |
|
v-if="!filter.readOnly" |
|
class="nc-filter-item-remove-btn text-grey self-center" |
|
@click.stop="deleteFilter(filter, i)" |
|
/> |
|
<span v-else /> |
|
|
|
<span v-if="!i" class="flex items-center">{{ $t('labels.where') }}</span> |
|
|
|
<a-select |
|
v-else |
|
v-model:value="filter.logical_op" |
|
:dropdown-match-select-width="false" |
|
class="h-full" |
|
hide-details |
|
:disabled="filter.readOnly" |
|
dropdown-class-name="nc-dropdown-filter-logical-op" |
|
@click.stop |
|
@change="filterUpdateCondition(filter, i)" |
|
> |
|
<a-select-option v-for="op in logicalOps" :key="op.value" :value="op.value"> |
|
{{ op.text }} |
|
</a-select-option> |
|
</a-select> |
|
|
|
<FieldListAutoCompleteDropdown |
|
:key="`${i}_6`" |
|
v-model="filter.fk_column_id" |
|
class="nc-filter-field-select" |
|
:columns="columns" |
|
:disabled="filter.readOnly" |
|
@click.stop |
|
@change="saveOrUpdate(filter, i)" |
|
/> |
|
|
|
<a-select |
|
v-model:value="filter.comparison_op" |
|
:dropdown-match-select-width="false" |
|
class="caption nc-filter-operation-select" |
|
:placeholder="$t('labels.operation')" |
|
density="compact" |
|
variant="solo" |
|
:disabled="filter.readOnly" |
|
hide-details |
|
dropdown-class-name="nc-dropdown-filter-comp-op" |
|
@change="filterUpdateCondition(filter, i)" |
|
> |
|
<a-select-option v-for="compOp in comparisonOpList" :key="compOp.value" :value="compOp.value" class=""> |
|
{{ compOp.text }} |
|
</a-select-option> |
|
</a-select> |
|
<!-- |
|
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> --> |
|
<span v-if="['null', 'notnull', 'empty', 'notempty'].includes(filter.comparison_op)" :key="`span${i}`" /> |
|
<a-checkbox |
|
v-else-if="types[filter.field] === 'boolean'" |
|
v-model:checked="filter.value" |
|
dense |
|
:disabled="filter.readOnly" |
|
@change="saveOrUpdate(filter, i)" |
|
/> |
|
<a-input |
|
v-else |
|
:key="`${i}_7`" |
|
v-model:value="filter.value" |
|
class="nc-filter-value-select" |
|
:disabled="filter.readOnly" |
|
@click.stop |
|
@input="saveOrUpdate(filter, i)" |
|
/> |
|
</template> |
|
</template> |
|
</template> |
|
</div> |
|
|
|
<div class="flex gap-2 mb-2 mt-4"> |
|
<a-button class="elevation-0 text-capitalize" type="primary" ghost @click.stop="addFilter"> |
|
<div class="flex items-center gap-1"> |
|
<!-- <v-icon small color="grey"> mdi-plus </v-icon> --> |
|
<MdiPlus /> |
|
<!-- Add Filter --> |
|
{{ $t('activity.addFilter') }} |
|
</div> |
|
</a-button> |
|
<a-button class="text-capitalize !text-gray-500" @click.stop="addFilterGroup"> |
|
<div class="flex items-center gap-1"> |
|
<!-- <v-icon small color="grey"> mdi-plus </v-icon> --> |
|
<!-- Add Filter Group --> |
|
<MdiPlus /> |
|
{{ $t('activity.addFilterGroup') }} |
|
</div> |
|
</a-button> |
|
</div> |
|
<slot /> |
|
</div> |
|
</template> |
|
|
|
<style scoped> |
|
.nc-filter-grid { |
|
display: grid; |
|
grid-template-columns: 18px 75px auto auto auto; |
|
@apply gap-[12px] |
|
align-items: center; |
|
} |
|
|
|
:deep(.ant-select-item-option) { |
|
@apply "!min-w-full"; |
|
} |
|
</style>
|
|
|