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 @@ + + + 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, + }); + } + }); +});