Browse Source

Merge branch 'develop' of https://github.com/nocodb/nocodb into fix/dropdown-ui

pull/7181/head
musharaf-nocodb 9 months ago
parent
commit
ca0fdbead8
  1. 21
      packages/nc-gui/components/cell/DatePicker.vue
  2. 4
      packages/nc-gui/components/cell/DateTimePicker.vue
  3. 9
      packages/nc-gui/components/cell/RichText.vue
  4. 21
      packages/nc-gui/components/cell/TextArea.vue
  5. 3
      packages/nc-gui/components/dashboard/settings/AuditTab.vue
  6. 3
      packages/nc-gui/components/dashboard/settings/BaseAudit.vue
  7. 2
      packages/nc-gui/components/notification/Item/Wrapper.vue
  8. 11
      packages/nc-gui/components/project/AccessSettings.vue
  9. 2
      packages/nc-gui/components/project/View.vue
  10. 9
      packages/nc-gui/components/smartsheet/column/DateOptions.vue
  11. 3
      packages/nc-gui/components/smartsheet/column/DateTimeOptions.vue
  12. 92
      packages/nc-gui/components/smartsheet/column/FormulaOptions.vue
  13. 4
      packages/nc-gui/components/smartsheet/column/RichLongTextDefaultValue.vue
  14. 67
      packages/nc-gui/components/smartsheet/column/RollupOptions.vue
  15. 3
      packages/nc-gui/components/smartsheet/expanded-form/Comments.vue
  16. 33
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
  17. 5
      packages/nc-gui/components/template/Editor.vue
  18. 3
      packages/nc-gui/components/virtual-cell/Formula.vue
  19. 41
      packages/nc-gui/components/virtual-cell/Rollup.vue
  20. 3
      packages/nc-gui/components/webhook/CallLog.vue
  21. 4
      packages/nc-gui/components/workspace/CollaboratorsList.vue
  22. 3
      packages/nc-gui/components/workspace/ProjectList.vue
  23. 27
      packages/nc-gui/composables/useMultiSelect/index.ts
  24. 3
      packages/nc-gui/helpers/parsers/CSVTemplateAdapter.ts
  25. 3
      packages/nc-gui/helpers/parsers/ExcelTemplateAdapter.ts
  26. 4
      packages/nc-gui/lang/ar.json
  27. 4
      packages/nc-gui/lang/bn_IN.json
  28. 4
      packages/nc-gui/lang/cs.json
  29. 4
      packages/nc-gui/lang/da.json
  30. 4
      packages/nc-gui/lang/de.json
  31. 4
      packages/nc-gui/lang/es.json
  32. 4
      packages/nc-gui/lang/eu.json
  33. 4
      packages/nc-gui/lang/fa.json
  34. 4
      packages/nc-gui/lang/fi.json
  35. 4
      packages/nc-gui/lang/fr.json
  36. 4
      packages/nc-gui/lang/he.json
  37. 4
      packages/nc-gui/lang/hi.json
  38. 4
      packages/nc-gui/lang/hr.json
  39. 4
      packages/nc-gui/lang/id.json
  40. 4
      packages/nc-gui/lang/it.json
  41. 4
      packages/nc-gui/lang/ko.json
  42. 4
      packages/nc-gui/lang/lv.json
  43. 4
      packages/nc-gui/lang/nl.json
  44. 4
      packages/nc-gui/lang/no.json
  45. 4
      packages/nc-gui/lang/pl.json
  46. 4
      packages/nc-gui/lang/pt.json
  47. 4
      packages/nc-gui/lang/pt_BR.json
  48. 4
      packages/nc-gui/lang/ru.json
  49. 4
      packages/nc-gui/lang/sk.json
  50. 4
      packages/nc-gui/lang/sl.json
  51. 4
      packages/nc-gui/lang/sv.json
  52. 4
      packages/nc-gui/lang/th.json
  53. 4
      packages/nc-gui/lang/tr.json
  54. 4
      packages/nc-gui/lang/uk.json
  55. 4
      packages/nc-gui/lang/vi.json
  56. 4
      packages/nc-gui/lang/zh-Hans.json
  57. 4
      packages/nc-gui/lang/zh-Hant.json
  58. 89
      packages/nc-gui/utils/dateTimeUtils.ts
  59. 413
      packages/nc-gui/utils/filterUtils.ts
  60. 1
      packages/nc-gui/utils/index.ts
  61. 3
      packages/nocodb-sdk/package.json
  62. 121
      packages/nocodb-sdk/src/lib/dateTimeHelper.ts
  63. 1
      packages/nocodb-sdk/src/lib/index.ts
  64. 13
      packages/nocodb/src/db/BaseModelSqlv2.ts
  65. 15
      packages/nocodb/src/db/conditionV2.ts
  66. 11
      packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
  67. 2
      packages/nocodb/src/db/functionMappings/sqlite.ts
  68. 30
      packages/nocodb/src/helpers/formulaFnHelper.ts
  69. 8
      packages/nocodb/src/utils/acl.ts
  70. 75
      packages/nocodb/src/utils/dateTimeUtils.ts
  71. 3
      pnpm-lock.yaml
  72. 10
      tests/playwright/pages/Dashboard/Grid/Column/index.ts
  73. 7
      tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts
  74. 75
      tests/playwright/tests/db/columns/columnDateTime.spec.ts
  75. 2
      tests/playwright/tests/db/features/keyboardShortcuts.spec.ts

21
packages/nc-gui/components/cell/DatePicker.vue

@ -1,5 +1,6 @@
<script setup lang="ts">
import dayjs from 'dayjs'
import { isDateMonthFormat } from 'nocodb-sdk'
import {
ActiveCellInj,
CellClickHookInj,
@ -10,8 +11,13 @@ import {
computed,
inject,
isDrawerOrModalExist,
onClickOutside,
onMounted,
onUnmounted,
parseProp,
ref,
useGlobal,
useI18n,
useSelectedCellKeyupListener,
watch,
} from '#imports'
@ -43,6 +49,8 @@ const isDateInvalid = ref(false)
const dateFormat = computed(() => parseProp(columnMeta?.value?.meta)?.date_format ?? 'YYYY-MM-DD')
const picker = computed(() => (isDateMonthFormat(dateFormat.value) ? 'month' : ''))
const localState = computed({
get() {
if (!modelValue) {
@ -54,7 +62,9 @@ const localState = computed({
return undefined
}
return /^\d+$/.test(modelValue) ? dayjs(+modelValue) : dayjs(modelValue)
const format = picker.value === 'month' ? dateFormat : 'YYYY-MM-DD'
return dayjs(/^\d+$/.test(modelValue) ? +modelValue : modelValue, format)
},
set(val?: dayjs.Dayjs) {
if (!val) {
@ -62,6 +72,11 @@ const localState = computed({
return
}
if (picker.value === 'month') {
// reset day to 1st
val = dayjs(val).date(1)
}
if (val.isValid()) {
emit('update:modelValue', val?.format('YYYY-MM-DD'))
}
@ -198,12 +213,15 @@ const updateOpen = (next: boolean) => {
}
const cellClickHook = inject(CellClickHookInj, null)
const cellClickHandler = () => {
open.value = (active.value || editable.value) && !open.value
}
onMounted(() => {
cellClickHook?.on(cellClickHandler)
})
onUnmounted(() => {
cellClickHook?.on(cellClickHandler)
})
@ -219,6 +237,7 @@ const clickHandler = () => {
<template>
<a-date-picker
v-model:value="localState"
:picker="picker"
:bordered="false"
class="!w-full !px-1 !border-none"
:class="{ 'nc-null': modelValue === null && showNull }"

4
packages/nc-gui/components/cell/DateTimePicker.vue

@ -1,18 +1,16 @@
<script setup lang="ts">
import dayjs from 'dayjs'
import { isSystemColumn } from 'nocodb-sdk'
import { dateFormats, isSystemColumn, timeFormats } from 'nocodb-sdk'
import {
ActiveCellInj,
CellClickHookInj,
ColumnInj,
EditColumnInj,
ReadonlyInj,
dateFormats,
inject,
isDrawerOrModalExist,
parseProp,
ref,
timeFormats,
useBase,
useSelectedCellKeyupListener,
watch,

9
packages/nc-gui/components/cell/RichText.vue

@ -148,7 +148,7 @@ watch(editorDom, () => {
class="h-full"
:class="{
'flex flex-col flex-grow nc-rich-text-full': props.fullMode,
'nc-rich-text-embed': !props.fullMode,
'nc-rich-text-embed flex flex-col pl-1 w-full': !props.fullMode,
}"
>
<div v-if="props.showMenu" class="absolute top-0 right-0.5">
@ -159,9 +159,10 @@ watch(editorDom, () => {
<EditorContent
ref="editorDom"
:editor="editor"
class="flex flex-col nc-textarea-rich-editor w-full flex-grow"
class="flex flex-col nc-textarea-rich-editor w-full"
:class="{
'ml-1 mt-2.5': props.fullMode,
'ml-1 mt-2.5 flex-grow': props.fullMode,
'nc-scrollbar-md': !props.fullMode && !props.readonly,
}"
/>
</div>
@ -181,7 +182,7 @@ watch(editorDom, () => {
.nc-rich-text-embed {
.ProseMirror {
@apply !border-transparent;
@apply !border-transparent max-h-full;
}
}

21
packages/nc-gui/components/cell/TextArea.vue

@ -120,8 +120,8 @@ const onMouseMove = (e: MouseEvent) => {
e.stopPropagation()
position.value = {
top: e.clientY - 22,
left: e.clientX - 46,
top: e.clientY - 30,
left: e.clientX - 120,
}
}
@ -149,7 +149,7 @@ watch(position, () => {
dom.style.left = `${position.value.left}px`
dom.style.top = `${position.value.top}px`
}, 100)
}, 1)
})
const dragStart = () => {
@ -191,7 +191,7 @@ watch(editEnabled, () => {
}"
@dblclick="onExpand"
>
<LazyCellRichText v-model:value="vModel" sync-value-change readonly class="!pointer-events-none" />
<LazyCellRichText v-model:value="vModel" sync-value-change readonly />
</div>
<textarea
v-else-if="editEnabled && !isVisible"
@ -296,13 +296,10 @@ watch(editEnabled, () => {
textarea:focus {
box-shadow: none;
}
:deep(.nc-text-area-expand-btn) {
@apply !hidden;
}
.rich-wrapper:hover,
.rich-wrapper:active {
:deep(.nc-text-area-expand-btn) {
@apply !block cursor-pointer;
}
</style>
<style lang="scss">
.cell:hover .nc-text-area-expand-btn {
@apply !block;
}
</style>

3
packages/nc-gui/components/dashboard/settings/AuditTab.vue

@ -1,7 +1,8 @@
<script setup lang="ts">
import { Tooltip as ATooltip, Empty } from 'ant-design-vue'
import type { AuditType } from 'nocodb-sdk'
import { ProjectIdInj, h, iconMap, onMounted, storeToRefs, timeAgo, useBase, useGlobal, useI18n, useNuxtApp } from '#imports'
import { timeAgo } from 'nocodb-sdk'
import { ProjectIdInj, h, iconMap, onMounted, storeToRefs, useBase, useGlobal, useI18n, useNuxtApp } from '#imports'
const { $api } = useNuxtApp()

3
packages/nc-gui/components/dashboard/settings/BaseAudit.vue

@ -1,7 +1,8 @@
<script setup lang="ts">
import { Tooltip as ATooltip, Empty } from 'ant-design-vue'
import type { AuditType } from 'nocodb-sdk'
import { h, iconMap, onMounted, storeToRefs, timeAgo, useBase, useGlobal, useI18n, useNuxtApp } from '#imports'
import { timeAgo } from 'nocodb-sdk'
import { h, iconMap, onMounted, storeToRefs, useBase, useGlobal, useI18n, useNuxtApp } from '#imports'
interface Props {
sourceId: string

2
packages/nc-gui/components/notification/Item/Wrapper.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import { timeAgo } from '#imports'
import { timeAgo } from 'nocodb-sdk'
const props = defineProps<{
item: {

11
packages/nc-gui/components/project/AccessSettings.vue

@ -1,8 +1,15 @@
<script lang="ts" setup>
import {
OrderedProjectRoles,
OrgUserRoles,
ProjectRoles,
WorkspaceRolesToProjectRoles,
extractRolesObj,
timeAgo,
} from 'nocodb-sdk'
import type { WorkspaceUserRoles } from 'nocodb-sdk'
import { OrderedProjectRoles, OrgUserRoles, ProjectRoles, WorkspaceRolesToProjectRoles, extractRolesObj } from 'nocodb-sdk'
import InfiniteLoading from 'v3-infinite-loading'
import { isEeUI, storeToRefs, timeAgo } from '#imports'
import { isEeUI, storeToRefs } from '#imports'
const basesStore = useBases()
const { getProjectUsers, createProjectUser, updateProjectUser, removeProjectUser } = basesStore

2
packages/nc-gui/components/project/View.vue

@ -35,6 +35,8 @@ const baseSettingsState = ref('')
const userCount = isEeUI ? workspaceUserCount : baseUserCount
const updateBaseUserCount = async () => {
if (!baseUserCount) return
try {
const { totalRows } = await getProjectUsers({
baseId: activeProjectId.value!,

9
packages/nc-gui/components/smartsheet/column/DateOptions.vue

@ -1,5 +1,6 @@
<script setup lang="ts">
import { dateFormats, useVModel } from '#imports'
import { dateFormats, dateMonthFormats } from 'nocodb-sdk'
import { useVModel } from '#imports'
const props = defineProps<{
value: any
@ -17,9 +18,9 @@ if (!vModel.value.meta?.date_format) {
<template>
<a-form-item :label="$t('labels.dateFormat')">
<a-select v-model:value="vModel.meta.date_format" dropdown-class-name="nc-dropdown-date-format">
<a-select-option v-for="(format, i) of dateFormats" :key="i" :value="format">
<div class="flex gap-2 w-full justify-between items-center">
<a-select v-model:value="vModel.meta.date_format" class="nc-date-select" dropdown-class-name="nc-dropdown-date-format">
<a-select-option v-for="(format, i) of [...dateFormats, ...dateMonthFormats]" :key="i" :value="format">
<div class="flex gap-2 justify-between items-center">
{{ format }}
<component
:is="iconMap.check"

3
packages/nc-gui/components/smartsheet/column/DateTimeOptions.vue

@ -1,5 +1,6 @@
<script setup lang="ts">
import { dateFormats, timeFormats, useVModel } from '#imports'
import { dateFormats, timeFormats } from 'nocodb-sdk'
import { useVModel } from '#imports'
const props = defineProps<{
value: any

92
packages/nc-gui/components/smartsheet/column/FormulaOptions.vue

@ -2,23 +2,40 @@
import type { Ref } from 'vue'
import type { ListItem as AntListItem } from 'ant-design-vue'
import jsep from 'jsep'
import type { ColumnType, FormulaType } from 'nocodb-sdk'
import { UITypes, jsepCurlyHook, substituteColumnIdWithAliasInFormula } from 'nocodb-sdk'
import type { ColumnType, FormulaType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import {
UITypes,
isLinksOrLTAR,
isNumericCol,
isSystemColumn,
jsepCurlyHook,
substituteColumnIdWithAliasInFormula,
validateDateWithUnknownFormat,
} from 'nocodb-sdk'
import {
MetaInj,
NcAutocompleteTree,
computed,
formulaList,
formulaTypes,
formulas,
getUIDTIcon,
getWordUntilCaret,
iconMap,
inject,
insertAtCursor,
isDate,
nextTick,
onMounted,
ref,
storeToRefs,
useBase,
useColumnCreateStoreOrThrow,
useDebounceFn,
useI18n,
useMetas,
useNocoEe,
useVModel,
validateDateWithUnknownFormat,
} from '#imports'
const props = defineProps<{
@ -35,6 +52,10 @@ const { setAdditionalValidations, validateInfos, sqlUi, column } = useColumnCrea
const { t } = useI18n()
const baseStore = useBase()
const { tables } = storeToRefs(baseStore)
const { predictFunction: _predictFunction } = useNocoEe()
enum JSEPNode {
@ -54,6 +75,23 @@ const meta = inject(MetaInj, ref())
const supportedColumns = computed(
() => meta?.value?.columns?.filter((col) => !uiTypesNotSupportedInFormulas.includes(col.uidt as UITypes)) || [],
)
const { metas } = useMetas()
const refTables = computed(() => {
if (!tables.value || !tables.value.length || !meta.value || !meta.value.columns) {
return []
}
const _refTables = meta.value.columns
.filter((column) => isLinksOrLTAR(column) && !column.system && column.source_id === meta.value?.source_id)
.map((column) => ({
col: column.colOptions,
column,
...tables.value.find((table) => table.id === (column.colOptions as LinkToAnotherRecordType).fk_related_model_id),
}))
.filter((table) => (table.col as LinkToAnotherRecordType)?.fk_related_model_id === table.id && !table.mm)
return _refTables as Required<TableType & { column: ColumnType; col: Required<LinkToAnotherRecordType> }>[]
})
const validators = {
formula_raw: [
@ -501,6 +539,53 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
}
break
case UITypes.Rollup: {
const rollupFunction = col.colOptions.rollup_function
if (['count', 'avg', 'sum', 'countDistinct', 'sumDistinct', 'avgDistinct'].includes(rollupFunction)) {
// these functions produce a numeric value, which can be used in numeric functions
if (expectedType !== formulaTypes.NUMERIC) {
typeErrors.add(
t('msg.formula.columnWithTypeFoundButExpected', {
columnName: parsedTree.name,
columnType: formulaTypes.NUMERIC,
expectedType,
}),
)
}
} else {
// the value is based on the foreign rollup column type
const selectedTable = refTables.value.find((t) => t.column.id === col.colOptions.fk_relation_column_id)
const refTableColumns = metas.value[selectedTable.id].columns.filter(
(c: ColumnType) =>
vModel.value.fk_lookup_column_id === c.id ||
(!isSystemColumn(c) && c.id !== vModel.value.id && c.uidt !== UITypes.Links),
)
const childFieldColumn = refTableColumns.find(
(column: ColumnType) => column.id === col.colOptions.fk_rollup_column_id,
)
const abstractType = sqlUi.value.getAbstractType(childFieldColumn)
if (expectedType === formulaTypes.DATE && !isDate(childFieldColumn, sqlUi.value.getAbstractType(childFieldColumn))) {
typeErrors.add(
t('msg.formula.columnWithTypeFoundButExpected', {
columnName: parsedTree.name,
columnType: abstractType,
expectedType,
}),
)
} else if (expectedType === formulaTypes.NUMERIC && !isNumericCol(childFieldColumn)) {
typeErrors.add(
t('msg.formula.columnWithTypeFoundButExpected', {
columnName: parsedTree.name,
columnType: abstractType,
expectedType,
}),
)
}
}
break
}
// not supported
case UITypes.ForeignKey:
case UITypes.Attachment:
@ -508,7 +593,6 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
case UITypes.Time:
case UITypes.Percent:
case UITypes.Duration:
case UITypes.Rollup:
case UITypes.Lookup:
case UITypes.Barcode:
case UITypes.Button:

4
packages/nc-gui/components/smartsheet/column/RichLongTextDefaultValue.vue

@ -18,8 +18,8 @@ const cdfValue = computed({
<div>
<div class="!my-3 text-xs">{{ $t('placeholder.defaultValue') }}</div>
<div class="flex flex-row gap-2">
<div class="border-1 relative pt-11 flex items-center w-full px-0 my-[-4px] border-gray-300 rounded-md !max-h-70 !pb-2.5">
<LazyCellRichText v-model:value="cdfValue" class="border-t-1 border-gray-100" show-menu full-mode />
<div class="border-1 relative pt-11 flex items-center w-full px-0 border-gray-300 rounded-md max-h-70 pb-1">
<LazyCellRichText v-model:value="cdfValue" class="border-t-1 border-gray-100 !max-h-80 !min-h-30" show-menu />
</div>
</div>
</div>

67
packages/nc-gui/components/smartsheet/column/RollupOptions.vue

@ -1,8 +1,23 @@
<script setup lang="ts">
import { onMounted } from '@vue/runtime-core'
import type { ColumnType, LinkToAnotherRecordType, TableType, UITypes } from 'nocodb-sdk'
import { isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import { MetaInj, inject, ref, storeToRefs, useBase, useColumnCreateStoreOrThrow, useMetas, useVModel } from '#imports'
import { isLinksOrLTAR, isNumericCol, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import type { Ref } from '#imports'
import {
MetaInj,
computed,
h,
inject,
ref,
resolveComponent,
storeToRefs,
useBase,
useColumnCreateStoreOrThrow,
useI18n,
useMetas,
useVModel,
watch,
} from '#imports'
const props = defineProps<{
value: any
@ -17,6 +32,7 @@ const meta = inject(MetaInj, ref())
const { setAdditionalValidations, validateInfos, onDataTypeChange, isEdit } = useColumnCreateStoreOrThrow()
const baseStore = useBase()
const { tables } = storeToRefs(baseStore)
const { metas } = useMetas()
@ -29,17 +45,6 @@ setAdditionalValidations({
rollup_function: [{ required: true, message: t('general.required') }],
})
const aggrFunctionsList = [
{ text: t('datatype.Count'), value: 'count' },
{ text: t('general.min'), value: 'min' },
{ text: t('general.max'), value: 'max' },
{ text: t('general.avg'), value: 'avg' },
{ text: t('general.sum'), value: 'sum' },
{ text: t('general.countDistinct'), value: 'countDistinct' },
{ text: t('general.sumDistinct'), value: 'sumDistinct' },
{ text: t('general.avgDistinct'), value: 'avgDistinct' },
]
if (!vModel.value.fk_relation_column_id) vModel.value.fk_relation_column_id = null
if (!vModel.value.fk_rollup_column_id) vModel.value.fk_rollup_column_id = null
if (!vModel.value.rollup_function) vModel.value.rollup_function = null
@ -94,6 +99,40 @@ const cellIcon = (column: ColumnType) =>
h(isVirtualCol(column) ? resolveComponent('SmartsheetHeaderVirtualCellIcon') : resolveComponent('SmartsheetHeaderCellIcon'), {
columnMeta: column,
})
const aggFunctionsList: Ref<Record<string, string>[]> = ref([])
watch(
() => vModel.value.fk_rollup_column_id,
() => {
const childFieldColumn = columns.value?.find((column: ColumnType) => column.id === vModel.value.fk_rollup_column_id)
const showNumericFunctions = isNumericCol(childFieldColumn)
const nonNumericFunctions = [
// functions for non-numeric types,
// e.g. count / min / max / countDistinct date field
{ text: t('datatype.Count'), value: 'count' },
{ text: t('general.min'), value: 'min' },
{ text: t('general.max'), value: 'max' },
{ text: t('general.countDistinct'), value: 'countDistinct' },
]
const numericFunctions = showNumericFunctions
? [
{ text: t('general.avg'), value: 'avg' },
{ text: t('general.sum'), value: 'sum' },
{ text: t('general.sumDistinct'), value: 'sumDistinct' },
{ text: t('general.avgDistinct'), value: 'avgDistinct' },
]
: []
aggFunctionsList.value = [...nonNumericFunctions, ...numericFunctions]
if (!showNumericFunctions && ['avg', 'sum', 'sumDistinct', 'avgDistinct'].includes(vModel.value.rollup_function)) {
// when the previous roll up function was numeric type and the current child field is non-numeric
// reset rollup function with a non-numeric type
vModel.value.rollup_function = aggFunctionsList.value[0].value
}
},
)
</script>
<template>
@ -161,7 +200,7 @@ const cellIcon = (column: ColumnType) =>
class="!mt-0.5"
@change="onDataTypeChange"
>
<a-select-option v-for="(func, index) of aggrFunctionsList" :key="index" :value="func.value">
<a-select-option v-for="(func, index) of aggFunctionsList" :key="index" :value="func.value">
<div class="flex gap-2 justify-between items-center">
{{ func.text }}
<component

3
packages/nc-gui/components/smartsheet/expanded-form/Comments.vue

@ -1,8 +1,9 @@
<script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core'
import type { AuditType } from 'nocodb-sdk'
import { timeAgo } from 'nocodb-sdk'
import { Icon } from '@iconify/vue'
import { ref, timeAgo, useExpandedFormStoreOrThrow, useGlobal, useRoles, watch } from '#imports'
import { ref, useExpandedFormStoreOrThrow, useGlobal, useRoles, watch } from '#imports'
const props = defineProps<{
loading: boolean

33
packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue

@ -107,12 +107,16 @@ const isFilterDraft = (filter: Filter, col: ColumnType) => {
if (
filter.comparison_op &&
comparisonSubOpList(filter.comparison_op).find((compOp) => compOp.value === filter.comparison_sub_op)?.ignoreVal
comparisonSubOpList(filter.comparison_op, col?.meta?.date_format).find((compOp) => compOp.value === filter.comparison_sub_op)
?.ignoreVal
) {
return false
}
if (comparisonOpList(col.uidt as UITypes).find((compOp) => compOp.value === filter.comparison_op)?.ignoreVal) {
if (
comparisonOpList(col.uidt as UITypes, col?.meta?.date_format).find((compOp) => compOp.value === filter.comparison_op)
?.ignoreVal
) {
return false
}
@ -145,7 +149,7 @@ const filterUpdateCondition = (filter: FilterType, i: number) => {
// hence remove the previous value
filter.value = null
if (
!comparisonSubOpList(filter.comparison_op!)
!comparisonSubOpList(filter.comparison_op!, col?.meta?.date_format)
.map((op) => op.value)
.includes(filter.comparison_sub_op!)
) {
@ -224,8 +228,9 @@ const selectFilterField = (filter: Filter, index: number) => {
// since the existing one may not be supported for the new field
// e.g. `eq` operator is not supported in checkbox field
// hence, get the first option of the supported operators of the new field
filter.comparison_op = comparisonOpList(col.uidt as UITypes).find((compOp) => isComparisonOpAllowed(filter, compOp))
?.value as FilterType['comparison_op']
filter.comparison_op = comparisonOpList(col.uidt as UITypes, col?.meta?.date_format).find((compOp) =>
isComparisonOpAllowed(filter, compOp),
)?.value as FilterType['comparison_op']
if ([UITypes.Date, UITypes.DateTime].includes(col.uidt as UITypes) && !['blank', 'notblank'].includes(filter.comparison_op!)) {
if (filter.comparison_op === 'isWithin') {
@ -297,12 +302,16 @@ const addFilterGroup = async () => {
}
const showFilterInput = (filter: Filter) => {
const col = getColumn(filter)
if (!filter.comparison_op) return false
if (filter.comparison_sub_op) {
return !comparisonSubOpList(filter.comparison_op).find((op) => op.value === filter.comparison_sub_op)?.ignoreVal
return !comparisonSubOpList(filter.comparison_op, getColumn(filter)?.meta?.date_format).find(
(op) => op.value === filter.comparison_sub_op,
)?.ignoreVal
} else {
return !comparisonOpList(getColumn(filter)?.uidt as UITypes).find((op) => op.value === filter.comparison_op)?.ignoreVal
return !comparisonOpList(col?.uidt as UITypes, col?.meta?.date_format).find((op) => op.value === filter.comparison_op)
?.ignoreVal
}
}
@ -439,7 +448,10 @@ onBeforeUnmount(() => {
dropdown-class-name="nc-dropdown-filter-comp-op !max-w-80"
@change="filterUpdateCondition(filter, i)"
>
<template v-for="compOp of comparisonOpList(getColumn(filter)?.uidt)" :key="compOp.value">
<template
v-for="compOp of comparisonOpList(getColumn(filter)?.uidt, getColumn(filter)?.meta?.date_format)"
:key="compOp.value"
>
<a-select-option v-if="isComparisonOpAllowed(filter, compOp)" :value="compOp.value">
<div class="flex items-center w-full justify-between w-full gap-2">
<div class="truncate flex-1">{{ compOp.text }}</div>
@ -470,7 +482,10 @@ onBeforeUnmount(() => {
dropdown-class-name="nc-dropdown-filter-comp-sub-op"
@change="filterUpdateCondition(filter, i)"
>
<template v-for="compSubOp of comparisonSubOpList(filter.comparison_op)" :key="compSubOp.value">
<template
v-for="compSubOp of comparisonSubOpList(filter.comparison_op, getColumn(filter)?.meta?.date_format)"
:key="compSubOp.value"
>
<a-select-option v-if="isComparisonSubOpAllowed(filter, compSubOp)" :value="compSubOp.value">
<div class="flex items-center w-full justify-between w-full gap-2 max-w-40">
<NcTooltip show-on-truncate-only class="truncate flex-1">

5
packages/nc-gui/components/template/Editor.vue

@ -2,7 +2,7 @@
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import type { ColumnType, TableType } from 'nocodb-sdk'
import { UITypes, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import { UITypes, getDateFormat, getDateTimeFormat, isSystemColumn, isVirtualCol, parseStringDate } from 'nocodb-sdk'
import type { CheckboxChangeEvent } from 'ant-design-vue/es/checkbox/interface'
import { srcDestMappingColumns, tableColumns } from './utils'
import {
@ -18,15 +18,12 @@ import {
extractSdkResponseErrorMsg,
fieldLengthValidator,
fieldRequiredValidator,
getDateFormat,
getDateTimeFormat,
getUIDTIcon,
iconMap,
inject,
message,
nextTick,
onMounted,
parseStringDate,
reactive,
ref,
storeToRefs,

3
packages/nc-gui/components/virtual-cell/Formula.vue

@ -1,7 +1,8 @@
<script lang="ts" setup>
import { handleTZ } from 'nocodb-sdk'
import type { ColumnType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { CellValueInj, ColumnInj, computed, handleTZ, inject, renderValue, replaceUrlsWithLink, useBase } from '#imports'
import { CellValueInj, ColumnInj, computed, inject, renderValue, replaceUrlsWithLink, useBase } from '#imports'
// todo: column type doesn't have required property `error` - throws in typecheck
const column = inject(ColumnInj) as Ref<ColumnType & { colOptions: { error: any } }>

41
packages/nc-gui/components/virtual-cell/Rollup.vue

@ -1,18 +1,51 @@
<script setup lang="ts">
import { CellValueInj, inject, useShowNotEditableWarning } from '#imports'
import type { ColumnType, LinkToAnotherRecordType, RollupType } from 'nocodb-sdk'
import { CellValueInj, ColumnInj, MetaInj, computed, inject, isRollup, ref, useMetas, useShowNotEditableWarning } from '#imports'
const { metas } = useMetas()
const value = inject(CellValueInj)
const column = inject(ColumnInj)!
const meta = inject(MetaInj, ref())
const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning, activateShowEditNonEditableFieldWarning } =
useShowNotEditableWarning()
const relationColumnOptions = computed<LinkToAnotherRecordType | null>(() => {
if ((column?.value?.colOptions as RollupType)?.fk_relation_column_id) {
return meta?.value?.columns?.find((c) => c.id === (column?.value?.colOptions as RollupType)?.fk_relation_column_id)
?.colOptions as LinkToAnotherRecordType
}
return null
})
const relatedTableMeta = computed(
() =>
relationColumnOptions.value?.fk_related_model_id && metas.value?.[relationColumnOptions.value?.fk_related_model_id as string],
)
const colOptions = computed(() => column.value?.colOptions)
const childColumn = computed(() => {
if (relatedTableMeta.value?.columns) {
if (isRollup(column.value)) {
return relatedTableMeta.value?.columns.find(
(c: ColumnType) => c.id === (colOptions.value as RollupType).fk_rollup_column_id,
)
}
}
return ''
})
</script>
<template>
<div @dblclick="activateShowEditNonEditableFieldWarning">
<span class="text-center pl-3">
<div v-if="['count', 'avg', 'sum', 'countDistinct', 'sumDistinct', 'avgDistinct'].includes(colOptions.rollup_function)">
{{ value }}
</span>
</div>
<LazySmartsheetCell v-else v-model="value" :column="childColumn" :edit-enabled="false" :read-only="true" />
<div>
<div v-if="showEditNonEditableFieldWarning" class="text-left text-wrap mt-2 text-[#e65100] text-xs">
{{ $t('msg.info.computedFieldEditWarning') }}

3
packages/nc-gui/components/webhook/CallLog.vue

@ -1,6 +1,7 @@
<script setup lang="ts">
import type { HookLogType, HookType } from 'nocodb-sdk'
import { AutomationLogLevel, extractSdkResponseErrorMsg, onBeforeMount, parseProp, timeAgo, useApi, useGlobal } from '#imports'
import { timeAgo } from 'nocodb-sdk'
import { AutomationLogLevel, extractSdkResponseErrorMsg, onBeforeMount, parseProp, useApi, useGlobal } from '#imports'
interface Props {
hook: HookType

4
packages/nc-gui/components/workspace/CollaboratorsList.vue

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { OrderedWorkspaceRoles, WorkspaceUserRoles } from 'nocodb-sdk'
import { storeToRefs, timeAgo, useWorkspace } from '#imports'
import { OrderedWorkspaceRoles, WorkspaceUserRoles, timeAgo } from 'nocodb-sdk'
import { storeToRefs, useWorkspace } from '#imports'
const { workspaceRoles, loadRoles } = useRoles()

3
packages/nc-gui/components/workspace/ProjectList.vue

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { Empty } from 'ant-design-vue'
import type { BaseType } from 'nocodb-sdk'
import { ProjectRoles, ProjectStatus, WorkspaceUserRoles } from 'nocodb-sdk'
import { ProjectRoles, ProjectStatus, WorkspaceUserRoles, timeAgo } from 'nocodb-sdk'
import { nextTick } from '@vue/runtime-core'
import {
NcProjectType,
@ -12,7 +12,6 @@ import {
navigateTo,
ref,
storeToRefs,
timeAgo,
useBases,
useGlobal,
useNuxtApp,

27
packages/nc-gui/composables/useMultiSelect/index.ts

@ -3,14 +3,13 @@ import dayjs from 'dayjs'
import type { Ref } from 'vue'
import type { MaybeRef } from '@vueuse/core'
import type { ColumnType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { RelationTypes, UITypes, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import { RelationTypes, UITypes, dateFormats, isDateMonthFormat, isSystemColumn, isVirtualCol, timeFormats } from 'nocodb-sdk'
import { parse } from 'papaparse'
import type { Cell } from './cellRange'
import { CellRange } from './cellRange'
import convertCellData from './convertCellData'
import type { Nullable, Row } from '#imports'
import {
dateFormats,
extractPkFromRow,
extractSdkResponseErrorMsg,
isDrawerOrModalExist,
@ -20,7 +19,6 @@ import {
message,
reactive,
ref,
timeFormats,
unref,
useBase,
useCopy,
@ -155,6 +153,20 @@ export function useMultiSelect(
}
}
if (columnObj.uidt === UITypes.Date) {
const dateFormat = columnObj.meta?.date_format
if (dateFormat && isDateMonthFormat(dateFormat)) {
// any date month format (e.g. YYYY-MM) couldn't be stored in database
// with date type since it is not a valid date
// therefore, we reformat the value here to display with the formatted one
// e.g. 2023-06-03 -> 2023-06
textToCopy = dayjs(textToCopy, dateFormat).format(dateFormat)
} else {
// e.g. 2023-06-03 (in DB) -> 03/06/2023 (in UI)
textToCopy = dayjs(textToCopy, 'YYYY-MM-DD').format(dateFormat)
}
}
if (columnObj.uidt === UITypes.Time) {
// remove `"`
// e.g. "2023-05-12T08:03:53.000Z" -> 2023-05-12T08:03:53.000Z
@ -752,11 +764,13 @@ export function useMultiSelect(
const clipboardMatrix = parsedClipboard.data as string[][]
const pasteMatrixRows = clipboardMatrix.length
const selectionRowCount = Math.max(clipboardMatrix.length, selectedRange.end.row - selectedRange.start.row + 1)
const pasteMatrixRows = selectionRowCount
const pasteMatrixCols = clipboardMatrix[0].length
const colsToPaste = unref(fields).slice(activeCell.col, activeCell.col + pasteMatrixCols)
const rowsToPaste = unref(data).slice(activeCell.row, activeCell.row + pasteMatrixRows)
const rowsToPaste = unref(data).slice(activeCell.row, activeCell.row + selectionRowCount)
const propsToPaste: string[] = []
let pastedRows = 0
@ -780,7 +794,8 @@ export function useMultiSelect(
const pasteValue = convertCellData(
{
value: clipboardMatrix[i][j],
// Repeat the clipboard data array if the matrix is smaller than the selection
value: clipboardMatrix[i % clipboardMatrix.length][j],
to: pasteCol.uidt as UITypes,
column: pasteCol,
appInfo: unref(appInfo),

3
packages/nc-gui/helpers/parsers/CSVTemplateAdapter.ts

@ -1,7 +1,6 @@
import { parse } from 'papaparse'
import type { UploadFile } from 'ant-design-vue'
import { UITypes } from 'nocodb-sdk'
import { getDateFormat, validateDateWithUnknownFormat } from '../../utils/dateTimeUtils'
import { UITypes, getDateFormat, validateDateWithUnknownFormat } from 'nocodb-sdk'
import {
extractMultiOrSingleSelectProps,
getCheckboxValue,

3
packages/nc-gui/helpers/parsers/ExcelTemplateAdapter.ts

@ -1,5 +1,4 @@
import { UITypes } from 'nocodb-sdk'
import { getDateFormat } from '../../utils/dateTimeUtils'
import { UITypes, getDateFormat } from 'nocodb-sdk'
import TemplateGenerator from './TemplateGenerator'
import {
extractMultiOrSingleSelectProps,

4
packages/nc-gui/lang/ar.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "تعطيل قاعدة مشتركة",
"enable": "أي شخص لديه الرابط",
"link": "رابط قاعدة مشتركة"

4
packages/nc-gui/lang/bn_IN.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "ভগ করস অকষম করন",
"enable": "লিক সহ যউ",
"link": "ভগ করস লিক"

4
packages/nc-gui/lang/cs.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Povolit veřejný přístup",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Správa přístupu k projektu",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Seskupit dle",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Sdílet projekt",
"label": "Share Base",
"disable": "Zakázat sdílenou základnu",
"enable": "Kdokoli s odkazem",
"link": "Sdílený základní odkaz"

4
packages/nc-gui/lang/da.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Deaktiver delt base",
"enable": "Nogen med linket",
"link": "Shared Base Link."

4
packages/nc-gui/lang/de.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Freigegebene Datenbank deaktivieren",
"enable": "Jeder mit dem Link",
"link": "Freigegebene Datenbank-Link"

4
packages/nc-gui/lang/es.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Deshabilitar la base compartida",
"enable": "Cualquiera con el link",
"link": "Enlace de base compartida"

4
packages/nc-gui/lang/eu.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Disable shared base",
"enable": "Anyone with the link",
"link": "Shared base link"

4
packages/nc-gui/lang/fa.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "غیرفعال کردن پایگاه مشترک",
"enable": "هر کسی لینک را داشته باشد",
"link": "لینک پایگاه مشترک"

4
packages/nc-gui/lang/fi.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Poista jaettu pohja",
"enable": "Jokainen, jolla on linkki",
"link": "Jaettu peruslinkki"

4
packages/nc-gui/lang/fr.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Désactiver la base partagée",
"enable": "N'importe qui disposant du lien",
"link": "Partager le lien de la base"

4
packages/nc-gui/lang/he.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "השבת בסיס משותף",
"enable": "כל אחד עם הקישור",
"link": "קישור בסיס משותף"

4
packages/nc-gui/lang/hi.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "स आधर अकषम कर",
"enable": "कई भयकििसकस लिक ह",
"link": "स आधर लिक"

4
packages/nc-gui/lang/hr.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Onemogući zajedničku bazu",
"enable": "Bilo tko s linkom",
"link": "Zajednička baza veza"

4
packages/nc-gui/lang/id.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Nonaktifkan basis bersama",
"enable": "Siapa pun dengan tautannya",
"link": "Tautan dasar bersama"

4
packages/nc-gui/lang/it.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Disabilitare la base condivisa",
"enable": "Chiunque con il link",
"link": "Collegamento base condiviso"

4
packages/nc-gui/lang/ko.json

@ -666,7 +666,7 @@
"enablePublicAccess": "전체 공개",
"doYouWantToSaveTheChanges": "바뀐 내용을 저장할까요?",
"editingAccess": "수정 권한",
"enabledPublicViewing": "공개 보기 사용 설정",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "비밀번호로 액세스 제한",
"manageProjectAccess": "프로젝트 접근 권한",
"allowDownload": "다운로드 허용",
@ -719,7 +719,7 @@
"groupBy": "분류 기준",
"addSubGroup": "하위 그룹 추가",
"shareBase": {
"label": "프로젝트 공유",
"label": "Share Base",
"disable": "공유 베이스 비활성화",
"enable": "링크가 있는 모든 사용자",
"link": "공유 링크"

4
packages/nc-gui/lang/lv.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Atspējot koplietošanu",
"enable": "Jebkurš ar saiti",
"link": "Koplietota pamatsaite"

4
packages/nc-gui/lang/nl.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Schakel de gedeelde database uit",
"enable": "Iedereen met de link",
"link": "Gedeelde databaselink"

4
packages/nc-gui/lang/no.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Deaktiver delt base",
"enable": "Alle med linken",
"link": "Delt grunnlinje"

4
packages/nc-gui/lang/pl.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Wyłącz wspólną bazę",
"enable": "Każdy z linkiem",
"link": "Wspólny link bazowy."

4
packages/nc-gui/lang/pt.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Desativar a base compartilhada",
"enable": "Alguém com o link",
"link": "Link base compartilhada"

4
packages/nc-gui/lang/pt_BR.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Desativar a base compartilhada",
"enable": "Qualquer um com o link",
"link": "Link da base compartilhada"

4
packages/nc-gui/lang/ru.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Отключить общую базу",
"enable": "Включить общую базу",
"link": "Ссылка на общую базу"

4
packages/nc-gui/lang/sk.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Zakázanie zdieľanej základne",
"enable": "Ktokoľvek s odkazom",
"link": "Spoločné základné prepojenie"

4
packages/nc-gui/lang/sl.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Onemogoči skupno bazo",
"enable": "Kdorkoli s povezavo",
"link": "Skupna bazna povezava"

4
packages/nc-gui/lang/sv.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Inaktivera delad bas",
"enable": "Någon med länken",
"link": "Delad baslänk"

4
packages/nc-gui/lang/th.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "ปดใชงานฐานทใชวมกน",
"enable": "ทกคนทงค",
"link": "ลงคฐานทใชวมกน"

4
packages/nc-gui/lang/tr.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Paylaşılan bağlantıyı devre dışı bırak",
"enable": "Bağlantıya sahip olan herkes",
"link": "Paylaşılan bağlantı"

4
packages/nc-gui/lang/uk.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Вимкнути спільну базу",
"enable": "Будь-хто, хто має посилання",
"link": "Посилання на спільну базу"

4
packages/nc-gui/lang/vi.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "Vô hiệu hóa cơ sở chia sẻ",
"enable": "Bất cứ ai có liên kết",
"link": "Liên kết cơ sở chia sẻ"

4
packages/nc-gui/lang/zh-Hans.json

@ -666,7 +666,7 @@
"enablePublicAccess": "启用公共访问",
"doYouWantToSaveTheChanges": "您要保存这些更改吗?",
"editingAccess": "编辑访问权限",
"enabledPublicViewing": "启用公开查看",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "使用密码限制访问",
"manageProjectAccess": "管理项目访问",
"allowDownload": "允许下载",
@ -719,7 +719,7 @@
"groupBy": "分组",
"addSubGroup": "添加子分组",
"shareBase": {
"label": "分享项目",
"label": "Share Base",
"disable": "停止分享项目",
"enable": "任何有链接的人",
"link": "分享项目链接"

4
packages/nc-gui/lang/zh-Hant.json

@ -666,7 +666,7 @@
"enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?",
"editingAccess": "Editing access",
"enabledPublicViewing": "Enable public viewing",
"enabledPublicViewing": "Enable Public Viewing",
"restrictAccessWithPassword": "Restrict access with password",
"manageProjectAccess": "Manage Base Access",
"allowDownload": "Allow Download",
@ -719,7 +719,7 @@
"groupBy": "Group By",
"addSubGroup": "Add subgroup",
"shareBase": {
"label": "Share base",
"label": "Share Base",
"disable": "停用共享資料庫",
"enable": "任何有連結的人",
"link": "共享資料庫連結"

89
packages/nc-gui/utils/dateTimeUtils.ts

@ -1,89 +0,0 @@
import dayjs from 'dayjs'
export const timeAgo = (date: any) => {
if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(date)) {
// if there is no timezone info, consider as UTC
// e.g. 2023-01-01 08:00:00 (MySQL)
date += '+00:00'
}
// show in local time
return dayjs(date).fromNow()
}
export const dateFormats = [
'YYYY-MM-DD',
'YYYY/MM/DD',
'DD-MM-YYYY',
'MM-DD-YYYY',
'DD/MM/YYYY',
'MM/DD/YYYY',
'DD MM YYYY',
'MM DD YYYY',
'YYYY MM DD',
]
export const timeFormats = ['HH:mm', 'HH:mm:ss']
export const handleTZ = (val: any) => {
if (val === undefined || val === null) {
return
}
if (typeof val !== 'string') {
return val
}
return val.replace(
/((?:-?(?:[1-9][0-9]*)?[0-9]{4})-(?:1[0-2]|0[1-9])-(?:3[01]|0[1-9]|[12][0-9])T(?:2[0-3]|[01][0-9]):(?:[0-5][0-9]):(?:[0-5][0-9])(?:\.[0-9]+)?(?:Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9]))/g,
(i, v) => {
return dayjs(v).format('YYYY-MM-DD HH:mm')
},
)
}
export function validateDateFormat(v: string) {
return dateFormats.includes(v)
}
export function validateDateWithUnknownFormat(v: string) {
for (const format of dateFormats) {
if (dayjs(v, format, true).isValid() as any) {
return true
}
for (const timeFormat of ['HH:mm', 'HH:mm:ss', 'HH:mm:ss.SSS']) {
if (dayjs(v, `${format} ${timeFormat}`, true).isValid() as any) {
return true
}
}
}
return false
}
export function getDateFormat(v: string) {
for (const format of dateFormats) {
if (dayjs(v, format, true).isValid()) {
return format
}
}
return 'YYYY/MM/DD'
}
export function getDateTimeFormat(v: string) {
for (const format of dateFormats) {
for (const timeFormat of timeFormats) {
const dateTimeFormat = `${format} ${timeFormat}`
if (dayjs(v, dateTimeFormat, true).isValid() as any) {
return dateTimeFormat
}
}
}
return 'YYYY/MM/DD'
}
export function parseStringDate(v: string, dateFormat: string) {
const dayjsObj = dayjs(v)
if (dayjsObj.isValid()) {
v = dayjsObj.format('YYYY-MM-DD')
} else {
v = dayjs(v, dateFormat).format('YYYY-MM-DD')
}
return v
}

413
packages/nc-gui/utils/filterUtils.ts

@ -1,4 +1,4 @@
import { UITypes, isNumericCol, numericUITypes } from 'nocodb-sdk'
import { UITypes, isDateMonthFormat, isNumericCol, numericUITypes } from 'nocodb-sdk'
const getEqText = (fieldUiType: UITypes) => {
if (isNumericCol(fieldUiType) || fieldUiType === UITypes.Time) {
@ -70,210 +70,215 @@ const getLteText = (fieldUiType: UITypes) => {
export const comparisonOpList = (
fieldUiType: UITypes,
dateFormat?: string,
): {
text: string
value: string
ignoreVal: boolean
includedTypes?: UITypes[]
excludedTypes?: UITypes[]
}[] => [
{
text: 'is checked',
value: 'checked',
ignoreVal: true,
includedTypes: [UITypes.Checkbox],
},
{
text: 'is not checked',
value: 'notchecked',
ignoreVal: true,
includedTypes: [UITypes.Checkbox],
},
{
text: getEqText(fieldUiType),
value: 'eq',
ignoreVal: false,
excludedTypes: [UITypes.Checkbox, UITypes.MultiSelect, UITypes.Attachment],
},
{
text: getNeqText(fieldUiType),
value: 'neq',
ignoreVal: false,
excludedTypes: [UITypes.Checkbox, UITypes.MultiSelect, UITypes.Attachment],
},
{
text: getLikeText(fieldUiType),
value: 'like',
ignoreVal: false,
excludedTypes: [
UITypes.Checkbox,
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.Collaborator,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
...numericUITypes,
],
},
{
text: getNotLikeText(fieldUiType),
value: 'nlike',
ignoreVal: false,
excludedTypes: [
UITypes.Checkbox,
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.Collaborator,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
...numericUITypes,
],
},
{
text: 'is empty',
value: 'empty',
ignoreVal: true,
excludedTypes: [
UITypes.Checkbox,
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.Collaborator,
UITypes.Attachment,
UITypes.LinkToAnotherRecord,
UITypes.Lookup,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
...numericUITypes,
],
},
{
text: 'is not empty',
value: 'notempty',
ignoreVal: true,
excludedTypes: [
UITypes.Checkbox,
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.Collaborator,
UITypes.Attachment,
UITypes.LinkToAnotherRecord,
UITypes.Lookup,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
...numericUITypes,
],
},
{
text: 'is null',
value: 'null',
ignoreVal: true,
excludedTypes: [
...numericUITypes,
UITypes.Checkbox,
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.Collaborator,
UITypes.Attachment,
UITypes.LinkToAnotherRecord,
UITypes.Lookup,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
],
},
{
text: 'is not null',
value: 'notnull',
ignoreVal: true,
excludedTypes: [
...numericUITypes,
UITypes.Checkbox,
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.Collaborator,
UITypes.Attachment,
UITypes.LinkToAnotherRecord,
UITypes.Lookup,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
],
},
{
text: 'contains all of',
value: 'allof',
ignoreVal: false,
includedTypes: [UITypes.MultiSelect],
},
{
text: 'contains any of',
value: 'anyof',
ignoreVal: false,
includedTypes: [UITypes.MultiSelect, UITypes.SingleSelect],
},
{
text: 'does not contain all of',
value: 'nallof',
ignoreVal: false,
includedTypes: [UITypes.MultiSelect],
},
{
text: 'does not contain any of',
value: 'nanyof',
ignoreVal: false,
includedTypes: [UITypes.MultiSelect, UITypes.SingleSelect],
},
{
text: getGtText(fieldUiType),
value: 'gt',
ignoreVal: false,
includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime, UITypes.Time],
},
{
text: getLtText(fieldUiType),
value: 'lt',
ignoreVal: false,
includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime, UITypes.Time],
},
{
text: getGteText(fieldUiType),
value: 'gte',
ignoreVal: false,
includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime, UITypes.Time],
},
{
text: getLteText(fieldUiType),
value: 'lte',
ignoreVal: false,
includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime, UITypes.Time],
},
{
text: 'is within',
value: 'isWithin',
ignoreVal: true,
includedTypes: [UITypes.Date, UITypes.DateTime],
},
{
text: 'is blank',
value: 'blank',
ignoreVal: true,
excludedTypes: [UITypes.Checkbox, UITypes.Links, UITypes.Rollup],
},
{
text: 'is not blank',
value: 'notblank',
ignoreVal: true,
excludedTypes: [UITypes.Checkbox, UITypes.Links, UITypes.Rollup],
},
]
}[] => {
const isDateMonth = dateFormat && isDateMonthFormat(dateFormat)
return [
{
text: 'is checked',
value: 'checked',
ignoreVal: true,
includedTypes: [UITypes.Checkbox],
},
{
text: 'is not checked',
value: 'notchecked',
ignoreVal: true,
includedTypes: [UITypes.Checkbox],
},
{
text: getEqText(fieldUiType),
value: 'eq',
ignoreVal: false,
excludedTypes: [UITypes.Checkbox, UITypes.MultiSelect, UITypes.Attachment],
},
{
text: getNeqText(fieldUiType),
value: 'neq',
ignoreVal: false,
excludedTypes: [UITypes.Checkbox, UITypes.MultiSelect, UITypes.Attachment],
},
{
text: getLikeText(fieldUiType),
value: 'like',
ignoreVal: false,
excludedTypes: [
UITypes.Checkbox,
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.Collaborator,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
...numericUITypes,
],
},
{
text: getNotLikeText(fieldUiType),
value: 'nlike',
ignoreVal: false,
excludedTypes: [
UITypes.Checkbox,
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.Collaborator,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
...numericUITypes,
],
},
{
text: 'is empty',
value: 'empty',
ignoreVal: true,
excludedTypes: [
UITypes.Checkbox,
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.Collaborator,
UITypes.Attachment,
UITypes.LinkToAnotherRecord,
UITypes.Lookup,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
...numericUITypes,
],
},
{
text: 'is not empty',
value: 'notempty',
ignoreVal: true,
excludedTypes: [
UITypes.Checkbox,
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.Collaborator,
UITypes.Attachment,
UITypes.LinkToAnotherRecord,
UITypes.Lookup,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
...numericUITypes,
],
},
{
text: 'is null',
value: 'null',
ignoreVal: true,
excludedTypes: [
...numericUITypes,
UITypes.Checkbox,
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.Collaborator,
UITypes.Attachment,
UITypes.LinkToAnotherRecord,
UITypes.Lookup,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
],
},
{
text: 'is not null',
value: 'notnull',
ignoreVal: true,
excludedTypes: [
...numericUITypes,
UITypes.Checkbox,
UITypes.SingleSelect,
UITypes.MultiSelect,
UITypes.Collaborator,
UITypes.Attachment,
UITypes.LinkToAnotherRecord,
UITypes.Lookup,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
],
},
{
text: 'contains all of',
value: 'allof',
ignoreVal: false,
includedTypes: [UITypes.MultiSelect],
},
{
text: 'contains any of',
value: 'anyof',
ignoreVal: false,
includedTypes: [UITypes.MultiSelect, UITypes.SingleSelect],
},
{
text: 'does not contain all of',
value: 'nallof',
ignoreVal: false,
includedTypes: [UITypes.MultiSelect],
},
{
text: 'does not contain any of',
value: 'nanyof',
ignoreVal: false,
includedTypes: [UITypes.MultiSelect, UITypes.SingleSelect],
},
{
text: getGtText(fieldUiType),
value: 'gt',
ignoreVal: false,
includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime, UITypes.Time],
},
{
text: getLtText(fieldUiType),
value: 'lt',
ignoreVal: false,
includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime, UITypes.Time],
},
{
text: getGteText(fieldUiType),
value: 'gte',
ignoreVal: false,
includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime, UITypes.Time],
},
{
text: getLteText(fieldUiType),
value: 'lte',
ignoreVal: false,
includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime, UITypes.Time],
},
{
text: 'is within',
value: 'isWithin',
ignoreVal: true,
includedTypes: [...(isDateMonth ? [] : [UITypes.Date, UITypes.DateTime])],
},
{
text: 'is blank',
value: 'blank',
ignoreVal: true,
excludedTypes: [UITypes.Checkbox, UITypes.Links, UITypes.Rollup],
},
{
text: 'is not blank',
value: 'notblank',
ignoreVal: true,
excludedTypes: [UITypes.Checkbox, UITypes.Links, UITypes.Rollup],
},
]
}
export const comparisonSubOpList = (
// TODO: type
comparison_op: string,
dateFormat?: string,
): {
text: string
value: string
@ -281,6 +286,8 @@ export const comparisonSubOpList = (
includedTypes?: UITypes[]
excludedTypes?: UITypes[]
}[] => {
const isDateMonth = dateFormat && isDateMonthFormat(dateFormat)
if (comparison_op === 'isWithin') {
return [
{
@ -338,31 +345,31 @@ export const comparisonSubOpList = (
text: 'today',
value: 'today',
ignoreVal: true,
includedTypes: [UITypes.Date, UITypes.DateTime],
includedTypes: [...(isDateMonth ? [] : [UITypes.Date, UITypes.DateTime])],
},
{
text: 'tomorrow',
value: 'tomorrow',
ignoreVal: true,
includedTypes: [UITypes.Date, UITypes.DateTime],
includedTypes: [...(isDateMonth ? [] : [UITypes.Date, UITypes.DateTime])],
},
{
text: 'yesterday',
value: 'yesterday',
ignoreVal: true,
includedTypes: [UITypes.Date, UITypes.DateTime],
includedTypes: [...(isDateMonth ? [] : [UITypes.Date, UITypes.DateTime])],
},
{
text: 'one week ago',
value: 'oneWeekAgo',
ignoreVal: true,
includedTypes: [UITypes.Date, UITypes.DateTime],
includedTypes: [...(isDateMonth ? [] : [UITypes.Date, UITypes.DateTime])],
},
{
text: 'one week from now',
value: 'oneWeekFromNow',
ignoreVal: true,
includedTypes: [UITypes.Date, UITypes.DateTime],
includedTypes: [...(isDateMonth ? [] : [UITypes.Date, UITypes.DateTime])],
},
{
text: 'one month ago',
@ -380,16 +387,16 @@ export const comparisonSubOpList = (
text: 'number of days ago',
value: 'daysAgo',
ignoreVal: false,
includedTypes: [UITypes.Date, UITypes.DateTime],
includedTypes: [...(isDateMonth ? [] : [UITypes.Date, UITypes.DateTime])],
},
{
text: 'number of days from now',
value: 'daysFromNow',
ignoreVal: false,
includedTypes: [UITypes.Date, UITypes.DateTime],
includedTypes: [...(isDateMonth ? [] : [UITypes.Date, UITypes.DateTime])],
},
{
text: 'exact date',
text: isDateMonth ? 'exact month' : 'exact date',
value: 'exactDate',
ignoreVal: false,
includedTypes: [UITypes.Date, UITypes.DateTime],

1
packages/nc-gui/utils/index.ts

@ -1,6 +1,5 @@
export * from './NcAutocompleteTree'
export * from './colorsUtils'
export * from './dateTimeUtils'
export * from './deepCompare'
export * from './formulaUtils'
export * from './durationUtils'

3
packages/nocodb-sdk/package.json

@ -39,7 +39,8 @@
},
"dependencies": {
"axios": "^1.6.2",
"jsep": "^1.3.8"
"jsep": "^1.3.8",
"dayjs": "^1.11.9"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.13.1",

121
packages/nocodb-sdk/src/lib/dateTimeHelper.ts

@ -0,0 +1,121 @@
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime.js';
import customParseFormat from 'dayjs/plugin/customParseFormat.js';
import duration from 'dayjs/plugin/duration.js';
import utc from 'dayjs/plugin/utc.js';
import weekday from 'dayjs/plugin/weekday.js';
import timezone from 'dayjs/plugin/timezone.js';
dayjs.extend(utc);
dayjs.extend(relativeTime);
dayjs.extend(customParseFormat);
dayjs.extend(duration);
dayjs.extend(weekday);
dayjs.extend(timezone);
export const dateMonthFormats = ['YYYY-MM', 'YYYY MM'];
export const timeFormats = ['HH:mm', 'HH:mm:ss', 'HH:mm:ss.SSS'];
export const dateFormats = [
'YYYY-MM-DD',
'YYYY/MM/DD',
'DD-MM-YYYY',
'MM-DD-YYYY',
'DD/MM/YYYY',
'MM/DD/YYYY',
'DD MM YYYY',
'MM DD YYYY',
'YYYY MM DD',
];
export const isDateMonthFormat = (format: string) =>
dateMonthFormats.includes(format);
export function validateDateWithUnknownFormat(v: string) {
for (const format of dateFormats) {
if (dayjs(v, format, true).isValid() as any) {
return true;
}
for (const timeFormat of timeFormats) {
if (dayjs(v, `${format} ${timeFormat}`, true).isValid() as any) {
return true;
}
}
}
return false;
}
export function getDateFormat(v: string) {
for (const format of dateFormats) {
if (dayjs(v, format, true).isValid()) {
return format;
}
}
return 'YYYY/MM/DD';
}
export function getDateTimeFormat(v: string) {
for (const format of dateFormats) {
for (const timeFormat of timeFormats) {
const dateTimeFormat = `${format} ${timeFormat}`;
if (dayjs(v, dateTimeFormat, true).isValid() as any) {
return dateTimeFormat;
}
}
}
return 'YYYY/MM/DD';
}
export function parseStringDate(v: string, dateFormat: string) {
const dayjsObj = dayjs(v);
if (dayjsObj.isValid()) {
v = dayjsObj.format('YYYY-MM-DD');
} else {
v = dayjs(v, dateFormat).format('YYYY-MM-DD');
}
return v;
}
export function convertToTargetFormat(
v: string,
oldDataFormat,
newDateFormat: string
) {
if (
!dateFormats.includes(oldDataFormat) ||
!dateFormats.includes(newDateFormat)
)
return v;
return dayjs(v, oldDataFormat).format(newDateFormat);
}
export const handleTZ = (val: any) => {
if (val === undefined || val === null) {
return;
}
if (typeof val !== 'string') {
return val;
}
return val.replace(
// match and extract dates and times in the ISO 8601 format
/((?:-?(?:[1-9][0-9]*)?[0-9]{4})-(?:1[0-2]|0[1-9])-(?:3[01]|0[1-9]|[12][0-9])T(?:2[0-3]|[01][0-9]):(?:[0-5][0-9]):(?:[0-5][0-9])(?:\.[0-9]+)?(?:Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9]))/g,
(_, v) => {
return dayjs(v).format('YYYY-MM-DD HH:mm');
}
);
};
export function validateDateFormat(v: string) {
return dateFormats.includes(v);
}
export const timeAgo = (date: any) => {
if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(date)) {
// if there is no timezone info, consider as UTC
// e.g. 2023-01-01 08:00:00 (MySQL)
date += '+00:00';
}
// show in local time
return dayjs(date).fromNow();
};

1
packages/nocodb-sdk/src/lib/index.ts

@ -18,3 +18,4 @@ export { default as CustomAPI, FileType } from '~/lib/CustomAPI';
export { default as TemplateGenerator } from '~/lib/TemplateGenerator';
export * from '~/lib/passwordHelpers';
export * from '~/lib/mergeSwaggerSchema';
export * from '~/lib/dateTimeHelper';

13
packages/nocodb/src/db/BaseModelSqlv2.ts

@ -4711,6 +4711,14 @@ class BaseModelSqlv2 {
continue;
}
if (col.uidt === UITypes.Date) {
const dateFormat = col.meta?.date_format;
if (dateFormat) {
d[col.title] = dayjs(d[col.title], dateFormat).format(dateFormat);
}
continue;
}
let keepLocalTime = true;
if (this.isSqlite) {
@ -4765,7 +4773,10 @@ class BaseModelSqlv2 {
const dateTimeColumns = (
childTable ? childTable.columns : this.model.columns
).filter(
(c) => c.uidt === UITypes.DateTime || c.uidt === UITypes.Formula,
(c) =>
c.uidt === UITypes.DateTime ||
c.uidt === UITypes.Date ||
c.uidt === UITypes.Formula,
);
if (dateTimeColumns.length) {
if (Array.isArray(data)) {

15
packages/nocodb/src/db/conditionV2.ts

@ -1,4 +1,9 @@
import { isNumericCol, RelationTypes, UITypes } from 'nocodb-sdk';
import {
isDateMonthFormat,
isNumericCol,
RelationTypes,
UITypes,
} from 'nocodb-sdk';
import dayjs from 'dayjs';
// import customParseFormat from 'dayjs/plugin/customParseFormat.js';
import type { BaseModelSqlv2 } from '~/db/BaseModelSqlv2';
@ -466,7 +471,13 @@ const parseConditionV2 = async (
: 'YYYY-MM-DD HH:mm:ssZ';
if ([UITypes.Date, UITypes.DateTime].includes(column.uidt)) {
const now = dayjs(new Date());
let now = dayjs(new Date());
const dateFormatFromMeta = column?.meta?.date_format;
if (dateFormatFromMeta && isDateMonthFormat(dateFormatFromMeta)) {
// reset to 1st
now = dayjs(now).date(1);
if (val) val = dayjs(val).date(1);
}
// handle sub operation
switch (filter.comparison_sub_op) {
case 'today':

11
packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts

@ -1,5 +1,9 @@
import jsep from 'jsep';
import { jsepCurlyHook, UITypes } from 'nocodb-sdk';
import {
jsepCurlyHook,
UITypes,
validateDateWithUnknownFormat,
} from 'nocodb-sdk';
import mapFunctionName from '../mapFunctionName';
import genRollupSelectv2 from '../genRollupSelectv2';
import type Column from '~/models/Column';
@ -10,10 +14,7 @@ import type LookupColumn from '~/models/LookupColumn';
import type { BaseModelSqlv2 } from '~/db/BaseModelSqlv2';
import NocoCache from '~/cache/NocoCache';
import { CacheGetType, CacheScope } from '~/utils/globals';
import {
convertDateFormatForConcat,
validateDateWithUnknownFormat,
} from '~/helpers/formulaFnHelper';
import { convertDateFormatForConcat } from '~/helpers/formulaFnHelper';
import FormulaColumn from '~/models/FormulaColumn';
// todo: switch function based on database

2
packages/nocodb/src/db/functionMappings/sqlite.ts

@ -1,9 +1,9 @@
import dayjs from 'dayjs';
import { convertToTargetFormat, getDateFormat } from 'nocodb-sdk';
import commonFns from './commonFns';
import type { MapFnArgs } from '../mapFunctionName';
import { convertUnits } from '~/helpers/convertUnits';
import { getWeekdayByText } from '~/helpers/formulaFnHelper';
import { convertToTargetFormat, getDateFormat } from '~/utils/dateTimeUtils';
const sqlite3 = {
...commonFns,

30
packages/nocodb/src/helpers/formulaFnHelper.ts

@ -1,12 +1,7 @@
import dayjs from 'dayjs';
import { UITypes } from 'nocodb-sdk';
import { convertDateFormat } from './convertDateFormat';
import Column from '~/models/Column';
// todo: tobe fixed
// import customParseFormat from 'dayjs/plugin/customParseFormat';
// extend(customParseFormat);
export function getWeekdayByText(v: string) {
return {
monday: 0,
@ -31,31 +26,6 @@ export function getWeekdayByIndex(idx: number): string {
}[idx || 0];
}
export function validateDateWithUnknownFormat(v: string) {
const dateFormats = [
'DD-MM-YYYY',
'MM-DD-YYYY',
'YYYY-MM-DD',
'DD/MM/YYYY',
'MM/DD/YYYY',
'YYYY/MM/DD',
'DD MM YYYY',
'MM DD YYYY',
'YYYY MM DD',
];
for (const format of dateFormats) {
if (dayjs(v, format, true).isValid() as any) {
return true;
}
for (const timeFormat of ['HH:mm', 'HH:mm:ss', 'HH:mm:ss.SSS']) {
if (dayjs(v, `${format} ${timeFormat}`, true).isValid() as any) {
return true;
}
}
}
return false;
}
export async function convertDateFormatForConcat(
o,
columnIdToUidt,

8
packages/nocodb/src/utils/acl.ts

@ -117,6 +117,9 @@ const permissionScopes = {
'relationDataRemove',
'relationDataAdd',
'duplicateColumn',
'nestedDataList',
'nestedDataLink',
'nestedDataUnlink',
// Base API Tokens
'baseApiTokenList',
@ -179,6 +182,8 @@ const rolePermissions:
list: true,
dataCount: true,
swaggerJson: true,
nestedDataList: true,
},
},
[ProjectRoles.COMMENTER]: {
@ -217,6 +222,9 @@ const rolePermissions:
bulkDataDeleteAll: true,
relationDataRemove: true,
relationDataAdd: true,
nestedDataLink: true,
nestedDataUnlink: true,
// TODO add ACL with base scope
// upload: true,
// uploadViaURL: true,

75
packages/nocodb/src/utils/dateTimeUtils.ts

@ -1,75 +0,0 @@
import dayjs from 'dayjs';
export const dateFormats = [
'DD-MM-YYYY',
'MM-DD-YYYY',
'YYYY-MM-DD',
'DD/MM/YYYY',
'MM/DD/YYYY',
'YYYY/MM/DD',
'DD MM YYYY',
'MM DD YYYY',
'YYYY MM DD',
];
export function validateDateFormat(v: string) {
return dateFormats.includes(v);
}
export function validateDateWithUnknownFormat(v: string) {
for (const format of dateFormats) {
if (dayjs(v, format, true).isValid() as any) {
return true;
}
for (const timeFormat of ['HH:mm', 'HH:mm:ss', 'HH:mm:ss.SSS']) {
if (dayjs(v, `${format} ${timeFormat}`, true).isValid() as any) {
return true;
}
}
}
return false;
}
export function getDateFormat(v: string) {
for (const format of dateFormats) {
if (dayjs(v, format, true).isValid()) {
return format;
}
}
return 'YYYY/MM/DD';
}
export function getDateTimeFormat(v: string) {
for (const format of dateFormats) {
for (const timeFormat of ['HH:mm', 'HH:mm:ss', 'HH:mm:ss.SSS']) {
const dateTimeFormat = `${format} ${timeFormat}`;
if (dayjs(v, dateTimeFormat, true).isValid() as any) {
return dateTimeFormat;
}
}
}
return 'YYYY/MM/DD';
}
export function parseStringDate(v: string, dateFormat: string) {
const dayjsObj = dayjs(v);
if (dayjsObj.isValid()) {
v = dayjsObj.format('YYYY-MM-DD');
} else {
v = dayjs(v, dateFormat).format('YYYY-MM-DD');
}
return v;
}
export function convertToTargetFormat(
v: string,
oldDataFormat,
newDateFormat: string,
) {
if (
!dateFormats.includes(oldDataFormat) ||
!dateFormats.includes(newDateFormat)
)
return v;
return dayjs(v, oldDataFormat).format(newDateFormat);
}

3
pnpm-lock.yaml

@ -908,6 +908,9 @@ importers:
axios:
specifier: ^1.6.2
version: 1.6.2
dayjs:
specifier: ^1.11.9
version: 1.11.9
jsep:
specifier: ^1.3.8
version: 1.3.8

10
tests/playwright/pages/Dashboard/Grid/Column/index.ts

@ -104,6 +104,11 @@ export class ColumnPageObject extends BasePage {
.click();
}
break;
case 'Date':
// Date Format
await this.get().locator('.nc-date-select').click();
await this.rootPage.locator('.ant-select-item').locator(`text="${dateFormat}"`).click();
break;
case 'DateTime':
// Date Format
await this.get().locator('.nc-date-select').click();
@ -310,6 +315,11 @@ export class ColumnPageObject extends BasePage {
await this.get().locator('.nc-time-select').click();
await this.rootPage.locator('.ant-select-item').locator(`text="${timeFormat}"`).click();
break;
case 'Date':
// Date Format
await this.get().locator('.nc-date-select').click();
await this.rootPage.locator('.ant-select-item').locator(`text="${dateFormat}"`).click();
break;
default:
break;
}

7
tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts

@ -29,8 +29,10 @@ export class DateTimeCellPageObject extends BasePage {
async selectDate({
// date formats in `YYYY-MM-DD`
date,
skipDate = false,
}: {
date: string;
skipDate?: boolean;
}) {
// title date format needs to be YYYY-MM-DD
const [year, month, day] = date.split('-');
@ -40,6 +42,11 @@ export class DateTimeCellPageObject extends BasePage {
await this.rootPage.locator('.ant-picker-year-btn:visible').click();
await this.rootPage.locator(`td[title="${year}"]`).click();
if (skipDate) {
await this.rootPage.locator(`td[title="${year}-${month}"]`).click();
return;
}
// configure month
await this.rootPage.locator('.ant-picker-month-btn:visible').click();
await this.rootPage.locator(`td[title="${year}-${month}"]`).click();

75
tests/playwright/tests/db/columns/columnDateTime.spec.ts

@ -57,6 +57,29 @@ const dateTimeData = [
},
];
const dateData = [
{
dateFormat: 'YYYY-MM-DD',
date: '2022-12-12',
output: '2022-12-12',
},
{
dateFormat: 'YYYY/MM/DD',
date: '2022-12-13',
output: '2022/12/13',
},
{
dateFormat: 'DD-MM-YYYY',
date: '2022-12-10',
output: '10-12-2022',
},
{
dateFormat: 'YYYY-MM',
date: '2022-12-26',
output: '2022-12',
},
];
test.describe('DateTime Column', () => {
if (enableQuickRun()) test.skip();
let dashboard: DashboardPage;
@ -117,3 +140,55 @@ test.describe('DateTime Column', () => {
}
});
});
test.describe('Date Column', () => {
// if (enableQuickRun()) test.skip();
let dashboard: DashboardPage;
let context: NcContext;
test.beforeEach(async ({ page }) => {
context = await setup({ page, isEmptyProject: true });
dashboard = new DashboardPage(page, context.base);
});
test.afterEach(async () => {
await unsetup(context);
});
test('Create Date Column', async () => {
await dashboard.treeView.createTable({ title: 'test_date', baseTitle: context.base.title });
// Create DateTime column
await dashboard.grid.column.create({
title: 'NC_DATE_0',
type: 'Date',
dateFormat: dateData[0].dateFormat,
});
for (let i = 0; i < dateData.length; i++) {
// Edit DateTime column
await dashboard.grid.column.openEdit({
title: 'NC_DATE_0',
type: 'Date',
dateFormat: dateData[i].dateFormat,
});
await dashboard.grid.column.save({ isUpdated: true });
await dashboard.grid.cell.dateTime.open({
index: 0,
columnHeader: 'NC_DATE_0',
});
await dashboard.grid.cell.dateTime.selectDate({
date: dateData[i].date,
skipDate: dateData[i].dateFormat === 'YYYY-MM',
});
await dashboard.grid.cell.verifyDateCell({
index: 0,
columnHeader: 'NC_DATE_0',
value: dateData[i].output,
});
}
});
});

2
tests/playwright/tests/db/features/keyboardShortcuts.spec.ts

@ -173,7 +173,7 @@ test.describe('Clipboard support', () => {
{ column_name: 'MultiSelect', uidt: UITypes.MultiSelect, dtxp: "'Option1','Option2'" },
{ column_name: 'Rating', uidt: UITypes.Rating },
{ column_name: 'Checkbox', uidt: UITypes.Checkbox },
{ column_name: 'Date', uidt: UITypes.Date },
{ column_name: 'Date', uidt: UITypes.Date, meta: { date_format: 'YYYY-MM-DD' } },
{ column_name: 'Attachment', uidt: UITypes.Attachment },
];

Loading…
Cancel
Save