Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nc-gui): calendar field customization #7673

Merged
merged 8 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/nc-gui/assets/nc-icons/bold.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions packages/nc-gui/assets/nc-icons/italic.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions packages/nc-gui/assets/nc-icons/underline.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion packages/nc-gui/components/nc/Select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const onChange = (value: string) => {
height: fit-content;
.ant-select-selector {
box-shadow: 0px 5px 3px -2px rgba(0, 0, 0, 0.02), 0px 3px 1px -2px rgba(0, 0, 0, 0.06);
@apply border-1 border-gray-200 !rounded-lg;
@apply border-1 border-gray-200 rounded-lg;
}

.ant-select-selection-item {
Expand Down
371 changes: 371 additions & 0 deletions packages/nc-gui/components/smartsheet/calendar/Cell.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,371 @@
<script lang="ts" setup>
import {
type BoolType,
type ColumnType,
type LookupType,
type RollupType,
dateFormats,
isCreatedOrLastModifiedByCol,
isCreatedOrLastModifiedTimeCol,
timeFormats,
} from 'nocodb-sdk'
import dayjs from 'dayjs'
import {
computed,
isBoolean,
isDate,
isDateTime,
isInt,
parseProp,
ref,
storeToRefs,
useAttachment,
useBase,
useMetas,
} from '#imports'

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 { getPossibleAttachmentSrc } = useAttachment()

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>) => {
console.log(modelValue)
if (Array.isArray(modelValue)) {
return modelValue.map((v) => `${v.title} (${getPossibleAttachmentSrc(v).join(', ')})`).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="{
'font-bold': bold,
'italic': italic,
'underline': underline,
}"
data-testid="nc-calendar-cell"
>
{{ parseValue(modelValue, column) }}
</span>
</template>

<style lang="scss" scoped></style>