mirror of
https://github.com/nocodb/nocodb.git
synced 2026-05-01 13:26:53 +00:00
211 lines
6.0 KiB
Vue
211 lines
6.0 KiB
Vue
<script setup lang="ts">
|
|
import { CommonAggregations, UITypes, getAvailableAggregations } from 'nocodb-sdk'
|
|
import type { ColumnType } from 'nocodb-sdk'
|
|
|
|
interface Props {
|
|
baseId?: string
|
|
tableId?: string
|
|
columnId?: string
|
|
value?: string
|
|
forceLayout?: 'vertical' | 'horizontal'
|
|
filterAggregation?: (aggregation: string) => boolean
|
|
forceLoadColumnMeta?: boolean
|
|
disableLabel?: boolean
|
|
autoSelect?: boolean
|
|
disabled?: boolean
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
forceLoadColumnMeta: false,
|
|
disableLabel: false,
|
|
autoSelect: false,
|
|
disabled: false,
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
'update:value': [value: string | undefined]
|
|
}>()
|
|
|
|
const { t } = useI18n()
|
|
|
|
const { getMeta } = useMetas()
|
|
|
|
const modelValue = useVModel(props, 'value', emit)
|
|
|
|
const isOpenAggregationSelectDropdown = ref(false)
|
|
|
|
const handleValueUpdate = (value: any) => {
|
|
const stringValue = String(value)
|
|
modelValue.value = stringValue
|
|
}
|
|
|
|
const column = ref<ColumnType | null>(null)
|
|
const isLoading = ref(false)
|
|
const aggregationList = ref<Array<{ label: string; value: string; ncItemDisabled: boolean; ncItemTooltip: string }>>([])
|
|
|
|
const loadAggregationList = async () => {
|
|
if (!props.tableId || !props.columnId) {
|
|
isLoading.value = false
|
|
aggregationList.value = []
|
|
return
|
|
}
|
|
|
|
try {
|
|
isLoading.value = true
|
|
|
|
const tableMeta = await getMeta(props.baseId, props.tableId, undefined, false, true)
|
|
if (!tableMeta) {
|
|
aggregationList.value = []
|
|
return
|
|
}
|
|
|
|
const columnMeta = tableMeta.columns?.find((col) => col.id === props.columnId)
|
|
if (!columnMeta) {
|
|
aggregationList.value = []
|
|
return
|
|
}
|
|
|
|
column.value = columnMeta
|
|
|
|
let availableAggregations: string[]
|
|
if (columnMeta.uidt === UITypes.Formula && columnMeta.colOptions && 'parsed_tree' in columnMeta.colOptions) {
|
|
availableAggregations = getAvailableAggregations(columnMeta.uidt, (columnMeta.colOptions as any).parsed_tree)
|
|
} else {
|
|
availableAggregations = getAvailableAggregations(columnMeta.uidt!)
|
|
}
|
|
|
|
// Filter out None aggregation
|
|
availableAggregations = availableAggregations.filter((agg) => agg !== CommonAggregations.None)
|
|
|
|
// Apply custom filter if provided
|
|
if (props.filterAggregation) {
|
|
availableAggregations = availableAggregations.filter(props.filterAggregation)
|
|
}
|
|
|
|
aggregationList.value = availableAggregations.map((agg) => {
|
|
return {
|
|
label: t(`aggregation_type.${agg}`),
|
|
value: agg,
|
|
ncItemDisabled: false,
|
|
ncItemTooltip: '',
|
|
}
|
|
})
|
|
} catch (error) {
|
|
aggregationList.value = []
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
watch([() => props.tableId, () => props.columnId], () => {
|
|
loadAggregationList()
|
|
})
|
|
|
|
const aggregationListMap = computed(() => {
|
|
if (!aggregationList.value || aggregationList.value.length === 0) return new Map()
|
|
|
|
return new Map(aggregationList.value.map((agg) => [agg.value, agg]))
|
|
})
|
|
|
|
const selectedAggregation = computed(() => {
|
|
if (!aggregationListMap.value || aggregationListMap.value.size === 0) return undefined
|
|
|
|
return aggregationListMap.value.get(modelValue.value) || undefined
|
|
})
|
|
|
|
watch(
|
|
aggregationList,
|
|
(newAggregationList) => {
|
|
if (newAggregationList && newAggregationList.length > 0) {
|
|
const newAggregationListMap = new Map(newAggregationList.map((agg) => [agg.value, agg]))
|
|
|
|
// Check if current value exists in the new aggregation list
|
|
if (modelValue.value && !newAggregationListMap.has(modelValue.value)) {
|
|
// Current value is not in the list, set null to clear it
|
|
modelValue.value = undefined
|
|
return
|
|
}
|
|
|
|
// Auto-select logic (only if autoSelect is enabled and no current value)
|
|
if (!modelValue.value && props.autoSelect) {
|
|
const newAggregationValue = newAggregationList[0]?.value
|
|
|
|
modelValue.value = newAggregationValue
|
|
}
|
|
}
|
|
},
|
|
{ immediate: true },
|
|
)
|
|
|
|
onMounted(() => {
|
|
loadAggregationList()
|
|
})
|
|
|
|
defineExpose({
|
|
modelValue,
|
|
selectedAggregation,
|
|
isOpenAggregationSelectDropdown,
|
|
aggregationList,
|
|
aggregationListMap,
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<a-form-item
|
|
name="aggregationId"
|
|
class="!mb-0 nc-aggregation-selector"
|
|
:class="`nc-force-layout-${forceLayout}`"
|
|
:validate-status="selectedAggregation?.ncItemDisabled ? 'error' : ''"
|
|
:help="selectedAggregation?.ncItemDisabled ? [selectedAggregation.ncItemTooltip] : []"
|
|
@click.stop
|
|
@dblclick.stop
|
|
>
|
|
<template v-if="!disableLabel" #label>
|
|
<div>
|
|
<slot name="label">{{ t('general.aggregation') }}</slot>
|
|
</div>
|
|
</template>
|
|
<NcListDropdown
|
|
v-model:is-open="isOpenAggregationSelectDropdown"
|
|
:disabled="isLoading || disabled"
|
|
:has-error="!!selectedAggregation?.ncItemDisabled"
|
|
>
|
|
<div class="flex-1 flex items-center gap-2 min-w-0">
|
|
<NcTooltip hide-on-click class="flex-1 truncate" show-on-truncate-only>
|
|
<span
|
|
v-if="selectedAggregation"
|
|
:key="selectedAggregation?.value"
|
|
class="text-sm flex-1 truncate"
|
|
:class="{ 'text-nc-content-gray-muted': !selectedAggregation }"
|
|
>
|
|
{{ selectedAggregation?.label }}
|
|
</span>
|
|
<span v-else class="text-sm flex-1 truncate text-nc-content-gray-muted">-- Select aggregation --</span>
|
|
|
|
<template #title>
|
|
{{ selectedAggregation?.label || 'Select aggregation' }}
|
|
</template>
|
|
</NcTooltip>
|
|
<GeneralIcon
|
|
icon="ncChevronDown"
|
|
class="flex-none h-4 w-4 transition-transform opacity-70"
|
|
:class="{ 'transform rotate-180': isOpenAggregationSelectDropdown }"
|
|
/>
|
|
</div>
|
|
<template #overlay="{ onEsc }">
|
|
<NcList
|
|
v-model:open="isOpenAggregationSelectDropdown"
|
|
:value="modelValue || selectedAggregation?.value || ''"
|
|
:list="aggregationList"
|
|
variant="medium"
|
|
class="!w-auto"
|
|
wrapper-class-name="!h-auto"
|
|
@update:value="handleValueUpdate"
|
|
@escape="onEsc"
|
|
>
|
|
</NcList>
|
|
</template>
|
|
</NcListDropdown>
|
|
</a-form-item>
|
|
</template>
|