|
|
|
<script lang="ts" setup>
|
|
|
|
import {
|
|
|
|
type BoolType,
|
|
|
|
type ColumnType,
|
|
|
|
type LookupType,
|
|
|
|
type RollupType,
|
|
|
|
dateFormats,
|
|
|
|
isCreatedOrLastModifiedByCol,
|
|
|
|
isCreatedOrLastModifiedTimeCol,
|
|
|
|
timeFormats,
|
|
|
|
} from 'nocodb-sdk'
|
|
|
|
import dayjs from 'dayjs'
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
column: ColumnType
|
|
|
|
modelValue: any
|
|
|
|
bold?: BoolType
|
|
|
|
italic?: BoolType
|
|
|
|
underline?: BoolType
|
|
|
|
}
|
|
|
|
|
|
|
|
const props = defineProps<Props>()
|
|
|
|
|
|
|
|
const meta = inject(MetaInj)
|
|
|
|
|
|
|
|
const { t } = useI18n()
|
|
|
|
|
|
|
|
const { metas } = useMetas()
|
|
|
|
|
|
|
|
const column = toRef(props, 'column')
|
|
|
|
|
|
|
|
const { sqlUis } = storeToRefs(useBase())
|
|
|
|
|
|
|
|
const basesStore = useBases()
|
|
|
|
|
|
|
|
const { basesUser } = storeToRefs(basesStore)
|
|
|
|
|
|
|
|
const { isXcdbBase, isMssql, isMysql } = useBase()
|
|
|
|
|
|
|
|
const sqlUi = ref(column.value?.source_id ? sqlUis.value[column.value?.source_id] : Object.values(sqlUis.value)[0])
|
|
|
|
|
|
|
|
const abstractType = computed(() => column.value && sqlUi.value.getAbstractType(column.value))
|
|
|
|
|
|
|
|
const getCheckBoxValue = (modelValue: boolean | string | number | '0' | '1') => {
|
|
|
|
return !!modelValue && modelValue !== '0' && modelValue !== 0 && modelValue !== 'false'
|
|
|
|
}
|
|
|
|
|
|
|
|
const getMultiSelectValue = (modelValue: any, col: ColumnType): string => {
|
|
|
|
if (!modelValue) {
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
return modelValue
|
|
|
|
? Array.isArray(modelValue)
|
|
|
|
? modelValue.join(', ')
|
|
|
|
: modelValue.toString()
|
|
|
|
: isMysql(col.source_id)
|
|
|
|
? modelValue.toString().split(',').join(', ')
|
|
|
|
: modelValue.split(', ')
|
|
|
|
}
|
|
|
|
|
|
|
|
const getDateValue = (modelValue: string | null | number, col: ColumnType, isSystemCol?: boolean) => {
|
|
|
|
const dateFormat = !isSystemCol ? parseProp(col.meta)?.date_format ?? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'
|
|
|
|
if (!modelValue || !dayjs(modelValue).isValid()) {
|
|
|
|
return ''
|
|
|
|
} else {
|
|
|
|
return dayjs(/^\d+$/.test(String(modelValue)) ? +modelValue : modelValue).format(dateFormat)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const getYearValue = (modelValue: string | null) => {
|
|
|
|
if (!modelValue) {
|
|
|
|
return ''
|
|
|
|
} else if (!dayjs(modelValue).isValid()) {
|
|
|
|
return ''
|
|
|
|
} else {
|
|
|
|
return dayjs(modelValue.toString(), 'YYYY').format('YYYY')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const getDateTimeValue = (modelValue: string | null, col: ColumnType) => {
|
|
|
|
if (!modelValue || !dayjs(modelValue).isValid()) {
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
|
|
|
|
const dateFormat = parseProp(col?.meta)?.date_format ?? dateFormats[0]
|
|
|
|
const timeFormat = parseProp(col?.meta)?.time_format ?? timeFormats[0]
|
|
|
|
const dateTimeFormat = `${dateFormat} ${timeFormat}`
|
|
|
|
|
|
|
|
const isXcDB = isXcdbBase(col.source_id)
|
|
|
|
|
|
|
|
if (!isXcDB) {
|
|
|
|
return dayjs(/^\d+$/.test(modelValue) ? +modelValue : modelValue, dateTimeFormat).format(dateTimeFormat)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isMssql(col.source_id)) {
|
|
|
|
// e.g. 2023-04-29T11:41:53.000Z
|
|
|
|
return dayjs(modelValue, dateTimeFormat).format(dateTimeFormat)
|
|
|
|
} else {
|
|
|
|
return dayjs(modelValue).utc().local().format(dateTimeFormat)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const getTimeValue = (modelValue: string | null) => {
|
|
|
|
if (!modelValue) {
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
let dateTime = dayjs(modelValue)
|
|
|
|
|
|
|
|
if (!dateTime.isValid()) {
|
|
|
|
dateTime = dayjs(modelValue, 'HH:mm:ss')
|
|
|
|
}
|
|
|
|
if (!dateTime.isValid()) {
|
|
|
|
dateTime = dayjs(`1999-01-01 ${modelValue}`)
|
|
|
|
}
|
|
|
|
if (!dateTime.isValid()) {
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
|
|
|
|
return dateTime.format('HH:mm')
|
|
|
|
}
|
|
|
|
|
|
|
|
const getDurationValue = (modelValue: string | null, col: ColumnType) => {
|
|
|
|
const durationType = parseProp(col.meta)?.duration || 0
|
|
|
|
return convertMS2Duration(modelValue, durationType)
|
|
|
|
}
|
|
|
|
|
|
|
|
const getPercentValue = (modelValue: string | null) => {
|
|
|
|
return modelValue ? `${modelValue}%` : ''
|
|
|
|
}
|
|
|
|
|
|
|
|
const getCurrencyValue = (modelValue: string | number | null | undefined, col: ColumnType): string => {
|
|
|
|
const currencyMeta = {
|
|
|
|
currency_locale: 'en-US',
|
|
|
|
currency_code: 'USD',
|
|
|
|
...parseProp(col.meta),
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
if (modelValue === null || modelValue === undefined || isNaN(modelValue)) {
|
|
|
|
return modelValue === null || modelValue === undefined ? '' : (modelValue as string)
|
|
|
|
}
|
|
|
|
return new Intl.NumberFormat(currencyMeta.currency_locale || 'en-US', {
|
|
|
|
style: 'currency',
|
|
|
|
currency: currencyMeta.currency_code || 'USD',
|
|
|
|
}).format(+modelValue)
|
|
|
|
} catch (e) {
|
|
|
|
return modelValue as string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const getUserValue = (modelValue: string | string[] | null | Array<any>) => {
|
|
|
|
if (!modelValue) {
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
const baseUsers = meta?.value.base_id ? basesUser.value.get(meta?.value.base_id) || [] : []
|
|
|
|
|
|
|
|
if (typeof modelValue === 'string') {
|
|
|
|
const idsOrMails = modelValue.split(',')
|
|
|
|
|
|
|
|
return idsOrMails
|
|
|
|
.map((idOrMail) => {
|
|
|
|
const user = baseUsers.find((u) => u.id === idOrMail || u.email === idOrMail)
|
|
|
|
return user ? user.display_name || user.email : idOrMail.id
|
|
|
|
})
|
|
|
|
.join(', ')
|
|
|
|
} else {
|
|
|
|
if (Array.isArray(modelValue)) {
|
|
|
|
return modelValue
|
|
|
|
.map((idOrMail) => {
|
|
|
|
const user = baseUsers.find((u) => u.id === idOrMail.id || u.email === idOrMail.email)
|
|
|
|
return user ? user.display_name || user.email : idOrMail.id
|
|
|
|
})
|
|
|
|
.join(', ')
|
|
|
|
} else {
|
|
|
|
return modelValue ? modelValue.display_name || modelValue.email : ''
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const getDecimalValue = (modelValue: string | null | number, col: ColumnType) => {
|
|
|
|
if (!modelValue || isNaN(Number(modelValue))) {
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
const columnMeta = parseProp(col.meta)
|
|
|
|
|
|
|
|
return Number(modelValue).toFixed(columnMeta?.precision ?? 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
const getIntValue = (modelValue: string | null | number) => {
|
|
|
|
if (!modelValue || isNaN(Number(modelValue))) {
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
return Number(modelValue) as unknown as string
|
|
|
|
}
|
|
|
|
|
|
|
|
const getTextAreaValue = (modelValue: string | null, col: ColumnType) => {
|
|
|
|
const isRichMode = typeof col.meta === 'string' ? JSON.parse(col.meta).richMode : col.meta?.richMode
|
|
|
|
if (isRichMode) {
|
|
|
|
return modelValue?.replace(/[*_~\[\]]|<\/?[^>]+(>|$)/g, '') || ''
|
|
|
|
}
|
|
|
|
return modelValue || ''
|
|
|
|
}
|
|
|
|
|
|
|
|
const getRollupValue = (modelValue: string | null | number, col: ColumnType) => {
|
|
|
|
const colOptions = col.colOptions as RollupType
|
|
|
|
|
|
|
|
const fns = ['count', 'avg', 'sum', 'countDistinct', 'sumDistinct', 'avgDistinct']
|
|
|
|
if (fns.includes(colOptions.rollup_function!)) {
|
|
|
|
return modelValue as string
|
|
|
|
} else {
|
|
|
|
const relationColumnOptions = colOptions.fk_relation_column_id
|
|
|
|
? meta?.value.columns?.find((c) => c.id === colOptions.fk_relation_column_id)?.colOptions
|
|
|
|
: null
|
|
|
|
const relatedTableMeta =
|
|
|
|
relationColumnOptions?.fk_related_model_id && metas.value?.[relationColumnOptions.fk_related_model_id as string]
|
|
|
|
|
|
|
|
const childColumn = relatedTableMeta?.columns.find((c: ColumnType) => c.id === colOptions.fk_rollup_column_id)
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
|
|
return parseValue(modelValue, childColumn) as string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const getLookupValue = (modelValue: string | null | number | Array<any>, col: ColumnType) => {
|
|
|
|
const colOptions = col.colOptions as LookupType
|
|
|
|
const relationColumnOptions = colOptions.fk_relation_column_id
|
|
|
|
? meta?.value.columns?.find((c) => c.id === colOptions.fk_relation_column_id)?.colOptions
|
|
|
|
: null
|
|
|
|
const relatedTableMeta =
|
|
|
|
relationColumnOptions?.fk_related_model_id && metas.value?.[relationColumnOptions.fk_related_model_id as string]
|
|
|
|
|
|
|
|
const childColumn = relatedTableMeta?.columns.find((c: ColumnType) => c.id === colOptions.fk_lookup_column_id) as
|
|
|
|
| ColumnType
|
|
|
|
| undefined
|
|
|
|
|
|
|
|
if (Array.isArray(modelValue)) {
|
|
|
|
return modelValue
|
|
|
|
.map((v) => {
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
|
|
return parseValue(v, childColumn!)
|
|
|
|
})
|
|
|
|
.join(', ')
|
|
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
|
|
return parseValue(modelValue, childColumn!)
|
|
|
|
}
|
|
|
|
|
|
|
|
const getAttachmentValue = (modelValue: string | null | number | Array<any>) => {
|
|
|
|
if (Array.isArray(modelValue)) {
|
|
|
|
return modelValue.map((v) => `${v.title}`).join(', ')
|
|
|
|
}
|
|
|
|
return modelValue as string
|
|
|
|
}
|
|
|
|
|
|
|
|
const getLinksValue = (modelValue: string, col: ColumnType) => {
|
|
|
|
if (typeof col.meta === 'string') {
|
|
|
|
col.meta = JSON.parse(col.meta)
|
|
|
|
}
|
|
|
|
|
|
|
|
const parsedValue = +modelValue || 0
|
|
|
|
if (!parsedValue) {
|
|
|
|
return ''
|
|
|
|
} else if (parsedValue === 1) {
|
|
|
|
return `1 ${col?.meta?.singular || t('general.link')}`
|
|
|
|
} else {
|
|
|
|
return `${parsedValue} ${col?.meta?.plural || t('general.links')}`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const parseValue = (value: any, col: ColumnType): string => {
|
|
|
|
if (!col) {
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
if (isGeoData(col)) {
|
|
|
|
const [latitude, longitude] = ((value as string) || '').split(';')
|
|
|
|
return latitude && longitude ? `${latitude}; ${longitude}` : value
|
|
|
|
}
|
|
|
|
if (isTextArea(col)) {
|
|
|
|
return getTextAreaValue(value, col)
|
|
|
|
}
|
|
|
|
if (isBoolean(col, abstractType)) {
|
|
|
|
return getCheckBoxValue(value) ? 'Checked' : 'Unchecked'
|
|
|
|
}
|
|
|
|
if (isMultiSelect(col)) {
|
|
|
|
return getMultiSelectValue(value, col)
|
|
|
|
}
|
|
|
|
if (isDate(col, abstractType)) {
|
|
|
|
return getDateValue(value, col)
|
|
|
|
}
|
|
|
|
if (isYear(col, abstractType)) {
|
|
|
|
return getYearValue(value)
|
|
|
|
}
|
|
|
|
if (isDateTime(col, abstractType)) {
|
|
|
|
return getDateTimeValue(value, col)
|
|
|
|
}
|
|
|
|
if (isTime(col, abstractType)) {
|
|
|
|
return getTimeValue(value)
|
|
|
|
}
|
|
|
|
if (isDuration(col)) {
|
|
|
|
return getDurationValue(value, col)
|
|
|
|
}
|
|
|
|
if (isPercent(col)) {
|
|
|
|
return getPercentValue(value)
|
|
|
|
}
|
|
|
|
if (isCurrency(col)) {
|
|
|
|
return getCurrencyValue(value, col)
|
|
|
|
}
|
|
|
|
if (isUser(col)) {
|
|
|
|
return getUserValue(value)
|
|
|
|
}
|
|
|
|
if (isDecimal(col)) {
|
|
|
|
return getDecimalValue(value, col)
|
|
|
|
}
|
|
|
|
if (isInt(col, abstractType)) {
|
|
|
|
return getIntValue(value)
|
|
|
|
}
|
|
|
|
if (isJSON(col)) {
|
|
|
|
return JSON.stringify(value, null, 2)
|
|
|
|
}
|
|
|
|
if (isRollup(col)) {
|
|
|
|
return getRollupValue(value, col)
|
|
|
|
}
|
|
|
|
if (isLookup(col)) {
|
|
|
|
return getLookupValue(value, col)
|
|
|
|
}
|
|
|
|
if (isCreatedOrLastModifiedTimeCol(col)) {
|
|
|
|
return getDateValue(value, col, true)
|
|
|
|
}
|
|
|
|
if (isCreatedOrLastModifiedByCol(col)) {
|
|
|
|
return getUserValue(value)
|
|
|
|
}
|
|
|
|
if (isAttachment(col)) {
|
|
|
|
return getAttachmentValue(value)
|
|
|
|
}
|
|
|
|
if (isLink(col)) {
|
|
|
|
return getLinksValue(value, col)
|
|
|
|
}
|
|
|
|
|
|
|
|
return value as unknown as string
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<span
|
|
|
|
class="plain-cell before:px-1"
|
|
|
|
:class="{
|
|
|
|
'!font-bold': bold,
|
|
|
|
'italic': italic,
|
|
|
|
'underline': underline,
|
|
|
|
}"
|
|
|
|
data-testid="nc-plain-cell"
|
|
|
|
>
|
|
|
|
{{ parseValue(modelValue, column) }}
|
|
|
|
</span>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
.plain-cell {
|
|
|
|
&::before {
|
|
|
|
content: '•';
|
|
|
|
padding: 0 4px;
|
|
|
|
}
|
|
|
|
&:first-child::before {
|
|
|
|
content: '';
|
|
|
|
padding: 0;
|
|
|
|
}
|
|
|
|
&:first-child {
|
|
|
|
padding-left: 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|