diff --git a/packages/nc-gui/components.d.ts b/packages/nc-gui/components.d.ts
index d48defb626..bb95e0722e 100644
--- a/packages/nc-gui/components.d.ts
+++ b/packages/nc-gui/components.d.ts
@@ -170,8 +170,6 @@ declare module '@vue/runtime-core' {
MdiExport: typeof import('~icons/mdi/export')['default']
MdiEyeCircleOutline: typeof import('~icons/mdi/eye-circle-outline')['default']
MdiEyeOffOutline: typeof import('~icons/mdi/eye-off-outline')['default']
- MdiEyeSettings: typeof import('~icons/mdi/eye-settings')['default']
- MdiEyeSettingsOutline: typeof import('~icons/mdi/eye-settings-outline')['default']
MdiFileDocumentOutline: typeof import('~icons/mdi/file-document-outline')['default']
MdiFileExcel: typeof import('~icons/mdi/file-excel')['default']
MdiFileEyeOutline: typeof import('~icons/mdi/file-eye-outline')['default']
@@ -203,7 +201,6 @@ declare module '@vue/runtime-core' {
MdiMagnify: typeof import('~icons/mdi/magnify')['default']
MdiMenu: typeof import('~icons/mdi/menu')['default']
MdiMenuDown: typeof import('~icons/mdi/menu-down')['default']
- MdiMenuIcon: typeof import('~icons/mdi/menu-icon')['default']
MdiMicrosoftTeams: typeof import('~icons/mdi/microsoft-teams')['default']
MdiMinusCircleOutline: typeof import('~icons/mdi/minus-circle-outline')['default']
MdiMoonFull: typeof import('~icons/mdi/moon-full')['default']
diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue
index 8ad758b8b8..af47b62e02 100644
--- a/packages/nc-gui/components/cell/DateTimePicker.vue
+++ b/packages/nc-gui/components/cell/DateTimePicker.vue
@@ -2,10 +2,13 @@
import dayjs from 'dayjs'
import {
ActiveCellInj,
+ ColumnInj,
ReadonlyInj,
+ dateFormats,
inject,
isDrawerOrModalExist,
ref,
+ timeFormats,
useProject,
useSelectedCellKeyupListener,
watch,
@@ -32,7 +35,11 @@ const column = inject(ColumnInj)!
let isDateInvalid = $ref(false)
-const dateFormat = isMysql(column.value.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ'
+const dateTimeFormat = $computed(() => {
+ const dateFormat = column?.value?.meta?.date_format ?? dateFormats[0]
+ const timeFormat = column?.value?.meta?.time_format ?? timeFormats[0]
+ return `${dateFormat} ${timeFormat}`
+})
let localState = $computed({
get() {
@@ -54,7 +61,7 @@ let localState = $computed({
}
if (val.isValid()) {
- emit('update:modelValue', val?.format(dateFormat))
+ emit('update:modelValue', val?.format(isMysql(column.value.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ'))
}
},
})
@@ -165,7 +172,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
:show-time="true"
:bordered="false"
class="!w-full !px-0 !border-none"
- format="YYYY-MM-DD HH:mm"
+ :format="dateTimeFormat"
:placeholder="isDateInvalid ? 'Invalid date' : ''"
:allow-clear="!readOnly && !localState && !isPk"
:input-read-only="true"
diff --git a/packages/nc-gui/components/smartsheet/column/DateTimeOptions.vue b/packages/nc-gui/components/smartsheet/column/DateTimeOptions.vue
new file mode 100644
index 0000000000..152b504fd4
--- /dev/null
+++ b/packages/nc-gui/components/smartsheet/column/DateTimeOptions.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+ {{ format }}
+
+
+
+
+
+
+ {{ format }}
+
+
+
+
diff --git a/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue b/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
index f7a63fe1de..cfe70bae07 100644
--- a/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
+++ b/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
@@ -177,6 +177,7 @@ useEventListener('keydown', (e: KeyboardEvent) => {
+
{
}
export const dateFormats = [
+ 'YYYY-MM-DD',
+ 'YYYY/MM/DD',
'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 const timeFormats = ['HH:mm', 'HH:mm:ss']
+
export const handleTZ = (val: any) => {
if (!val) {
return
@@ -60,7 +62,7 @@ export function getDateFormat(v: string) {
export function getDateTimeFormat(v: string) {
for (const format of dateFormats) {
- for (const timeFormat of ['HH:mm', 'HH:mm:ss', 'HH:mm:ss.SSS']) {
+ for (const timeFormat of timeFormats) {
const dateTimeFormat = `${format} ${timeFormat}`
if (dayjs(v, dateTimeFormat, true).isValid() as any) {
return dateTimeFormat
diff --git a/tests/playwright/pages/Dashboard/Grid/Column/index.ts b/tests/playwright/pages/Dashboard/Grid/Column/index.ts
index 787b2253d1..15bd509e27 100644
--- a/tests/playwright/pages/Dashboard/Grid/Column/index.ts
+++ b/tests/playwright/pages/Dashboard/Grid/Column/index.ts
@@ -34,7 +34,9 @@ export class ColumnPageObject extends BasePage {
childColumn = '',
relationType = '',
rollupType = '',
- format,
+ format = '',
+ dateFormat = '',
+ timeFormat = '',
insertAfterColumnTitle,
insertBeforeColumnTitle,
}: {
@@ -47,6 +49,8 @@ export class ColumnPageObject extends BasePage {
relationType?: string;
rollupType?: string;
format?: string;
+ dateFormat?: string;
+ timeFormat?: string;
insertBeforeColumnTitle?: string;
insertAfterColumnTitle?: string;
}) {
@@ -90,6 +94,14 @@ export class ColumnPageObject extends BasePage {
.click();
}
break;
+ case 'DateTime':
+ // Date Format
+ await this.get().locator('.nc-date-select').click();
+ await this.rootPage.locator('.ant-select-item').locator(`text="${dateFormat}"`).click();
+ // Time Format
+ await this.get().locator('.nc-time-select').click();
+ await this.rootPage.locator('.ant-select-item').locator(`text="${timeFormat}"`).click();
+ break;
case 'Formula':
await this.get().locator('.nc-formula-input').fill(formula);
break;
@@ -222,11 +234,15 @@ export class ColumnPageObject extends BasePage {
type = 'SingleLineText',
formula = '',
format,
+ dateFormat = '',
+ timeFormat = '',
}: {
title: string;
type?: string;
formula?: string;
format?: string;
+ dateFormat?: string;
+ timeFormat?: string;
}) {
await this.getColumnHeader(title).locator('.nc-ui-dt-dropdown').click();
await this.rootPage.locator('li[role="menuitem"]:has-text("Edit")').click();
@@ -245,6 +261,14 @@ export class ColumnPageObject extends BasePage {
})
.click();
break;
+ case 'DateTime':
+ // Date Format
+ await this.get().locator('.nc-date-select').click();
+ await this.rootPage.locator('.ant-select-item').locator(`text="${dateFormat}"`).click();
+ // Time Format
+ await this.get().locator('.nc-time-select').click();
+ await this.rootPage.locator('.ant-select-item').locator(`text="${timeFormat}"`).click();
+ break;
default:
break;
}
diff --git a/tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts b/tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts
new file mode 100644
index 0000000000..50b762b618
--- /dev/null
+++ b/tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts
@@ -0,0 +1,67 @@
+import { CellPageObject } from '.';
+import BasePage from '../../../Base';
+
+export class DateTimeCellPageObject extends BasePage {
+ readonly cell: CellPageObject;
+
+ constructor(cell: CellPageObject) {
+ super(cell.rootPage);
+ this.cell = cell;
+ }
+
+ get({ index, columnHeader }: { index?: number; columnHeader: string }) {
+ return this.cell.get({ index, columnHeader });
+ }
+
+ async open({ index, columnHeader }: { index: number; columnHeader: string }) {
+ await this.rootPage.locator('.nc-grid-add-new-cell').click();
+
+ await this.cell.dblclick({
+ index,
+ columnHeader,
+ });
+ }
+
+ async save() {
+ await this.rootPage.locator('button:has-text("Ok")').click();
+ }
+
+ async selectDate({
+ // date formats in `YYYY-MM-DD`
+ date,
+ }: {
+ date: string;
+ }) {
+ // title date format needs to be YYYY-MM-DD
+ await this.rootPage.locator(`td[title="${date}"]`).click();
+ }
+
+ async selectTime({
+ // hour: 0 - 23
+ // minute: 0 - 59
+ // second: 0 - 59
+ hour,
+ minute,
+ second,
+ }: {
+ hour: number;
+ minute: number;
+ second?: number | null;
+ }) {
+ await this.rootPage
+ .locator(`.ant-picker-time-panel-column:nth-child(1) > .ant-picker-time-panel-cell:nth-child(${hour + 1})`)
+ .click();
+ await this.rootPage
+ .locator(`.ant-picker-time-panel-column:nth-child(2) > .ant-picker-time-panel-cell:nth-child(${minute + 1})`)
+ .click();
+ if (second != null) {
+ await this.rootPage
+ .locator(`.ant-picker-time-panel-column:nth-child(3) > .ant-picker-time-panel-cell:nth-child(${second + 1})`)
+ .click();
+ }
+ }
+
+ async close() {
+ await this.rootPage.keyboard.press('Escape');
+ }
+}
diff --git a/tests/playwright/pages/Dashboard/common/Cell/index.ts b/tests/playwright/pages/Dashboard/common/Cell/index.ts
index 0e1bc8e04b..a277d31ee6 100644
--- a/tests/playwright/pages/Dashboard/common/Cell/index.ts
+++ b/tests/playwright/pages/Dashboard/common/Cell/index.ts
@@ -7,6 +7,7 @@ import { SharedFormPage } from '../../../SharedForm';
import { CheckboxCellPageObject } from './CheckboxCell';
import { RatingCellPageObject } from './RatingCell';
import { DateCellPageObject } from './DateCell';
+import { DateTimeCellPageObject } from './DateTimeCell';
export interface CellProps {
index?: number;
@@ -20,6 +21,7 @@ export class CellPageObject extends BasePage {
readonly checkbox: CheckboxCellPageObject;
readonly rating: RatingCellPageObject;
readonly date: DateCellPageObject;
+ readonly dateTime: DateTimeCellPageObject;
constructor(parent: GridPage | SharedFormPage) {
super(parent.rootPage);
@@ -29,6 +31,7 @@ export class CellPageObject extends BasePage {
this.checkbox = new CheckboxCellPageObject(this);
this.rating = new RatingCellPageObject(this);
this.date = new DateCellPageObject(this);
+ this.dateTime = new DateTimeCellPageObject(this);
}
get({ index, columnHeader }: CellProps): Locator {
@@ -113,6 +116,22 @@ export class CellPageObject extends BasePage {
}
}
+ async verifyDateCell({ index, columnHeader, value }: { index: number; columnHeader: string; value: string }) {
+ const _verify = async expectedValue => {
+ await expect
+ .poll(async () => {
+ const cell = await this.get({
+ index,
+ columnHeader,
+ }).locator('input');
+ return await cell.getAttribute('title');
+ })
+ .toEqual(expectedValue);
+ };
+
+ await _verify(value);
+ }
+
async verifyQrCodeCell({
index,
columnHeader,
diff --git a/tests/playwright/tests/columnDateTime.spec.ts b/tests/playwright/tests/columnDateTime.spec.ts
new file mode 100644
index 0000000000..c5349a9a98
--- /dev/null
+++ b/tests/playwright/tests/columnDateTime.spec.ts
@@ -0,0 +1,113 @@
+import { test } from '@playwright/test';
+import { DashboardPage } from '../pages/Dashboard';
+import setup from '../setup';
+
+const dateTimeData = [
+ {
+ dateFormat: 'YYYY-MM-DD',
+ timeFormat: 'HH:mm',
+ date: '2022-12-12',
+ hour: 10,
+ minute: 20,
+ output: '2022-12-12 10:20',
+ },
+ {
+ dateFormat: 'YYYY-MM-DD',
+ timeFormat: 'HH:mm:ss',
+ date: '2022-12-11',
+ hour: 20,
+ minute: 30,
+ second: 40,
+ output: '2022-12-11 20:30:40',
+ },
+ {
+ dateFormat: 'YYYY/MM/DD',
+ timeFormat: 'HH:mm',
+ date: '2022-12-13',
+ hour: 10,
+ minute: 20,
+ output: '2022/12/13 10:20',
+ },
+ {
+ dateFormat: 'YYYY/MM/DD',
+ timeFormat: 'HH:mm:ss',
+ date: '2022-12-14',
+ hour: 5,
+ minute: 30,
+ second: 40,
+ output: '2022/12/14 05:30:40',
+ },
+ {
+ dateFormat: 'DD-MM-YYYY',
+ timeFormat: 'HH:mm',
+ date: '2022-12-10',
+ hour: 4,
+ minute: 30,
+ output: '10-12-2022 04:30',
+ },
+ {
+ dateFormat: 'DD-MM-YYYY',
+ timeFormat: 'HH:mm:ss',
+ date: '2022-12-26',
+ hour: 2,
+ minute: 30,
+ second: 40,
+ output: '26-12-2022 02:30:40',
+ },
+];
+
+test.describe('DateTime Column', () => {
+ let dashboard: DashboardPage;
+ let context: any;
+
+ test.beforeEach(async ({ page }) => {
+ context = await setup({ page });
+ dashboard = new DashboardPage(page, context.project);
+ });
+
+ test('Create DateTime Column', async () => {
+ await dashboard.treeView.createTable({ title: 'test_datetime' });
+ // Create DateTime column
+ await dashboard.grid.column.create({
+ title: 'NC_DATETIME_0',
+ type: 'DateTime',
+ dateFormat: dateTimeData[0].dateFormat,
+ timeFormat: dateTimeData[0].timeFormat,
+ });
+
+ for (let i = 0; i < dateTimeData.length; i++) {
+ // Edit DateTime column
+ await dashboard.grid.column.openEdit({
+ title: 'NC_DATETIME_0',
+ type: 'DateTime',
+ dateFormat: dateTimeData[i].dateFormat,
+ timeFormat: dateTimeData[i].timeFormat,
+ });
+
+ await dashboard.grid.column.save({ isUpdated: true });
+
+ await dashboard.grid.cell.dateTime.open({
+ index: 0,
+ columnHeader: 'NC_DATETIME_0',
+ });
+
+ await dashboard.grid.cell.dateTime.selectDate({
+ date: dateTimeData[i].date,
+ });
+
+ await dashboard.grid.cell.dateTime.selectTime({
+ hour: dateTimeData[i].hour,
+ minute: dateTimeData[i].minute,
+ second: dateTimeData[i].second,
+ });
+
+ await dashboard.grid.cell.dateTime.save();
+
+ await dashboard.grid.cell.verifyDateCell({
+ index: 0,
+ columnHeader: 'NC_DATETIME_0',
+ value: dateTimeData[i].output,
+ });
+ }
+ });
+});