{{ dayjs(date[0]).format('DD ddd') }}
@@ -740,10 +743,10 @@ const viewMore = (hour: dayjs.Dayjs) => {
v-for="(hour, hourIndex) in date"
:key="hourIndex"
:class="{
- 'border-1 !border-brand-500': hour.isSame(selectedTime, 'hour'),
+ 'border-1 !border-brand-500 bg-gray-50': hour.isSame(selectedTime, 'hour'),
'!border-l-0': date[0].day() === selectedDateRange.start?.day(),
}"
- class="text-center relative h-20 text-sm text-gray-500 w-full py-1 border-gray-200 first:border-l-none border-1 border-r-gray-50 border-t-gray-50 bg-gray-50"
+ class="text-center relative h-20 text-sm text-gray-500 w-full py-1 border-gray-200 first:border-l-none border-1 border-r-gray-50 border-t-gray-50"
@click="
() => {
selectedTime = hour
@@ -751,7 +754,7 @@ const viewMore = (hour: dayjs.Dayjs) => {
}
"
>
-
+
{{ hour.format('h A') }}
{
-
+
{{ headerText }}
diff --git a/packages/nc-gui/components/smartsheet/toolbar/CalendarRange.vue b/packages/nc-gui/components/smartsheet/toolbar/CalendarRange.vue
index 827cb8ccbe..5067f03a70 100644
--- a/packages/nc-gui/components/smartsheet/toolbar/CalendarRange.vue
+++ b/packages/nc-gui/components/smartsheet/toolbar/CalendarRange.vue
@@ -125,14 +125,20 @@ const removeRange = async (id: number) => {
-
-
+
+
{{ $t('labels.organiseBy') }}
{
{
range.fk_to_column_id = undefined
@@ -177,6 +184,7 @@ const removeRange = async (id: number) => {
:disabled="!range.fk_from_column_id"
:placeholder="$t('placeholder.notSelected')"
class="!rounded-r-none nc-to-select"
+ data-testid="nc-calendar-range-to-field-select"
@change="saveCalendarRanges"
>
{
-
+
Add another date field
diff --git a/packages/nc-gui/composables/useSharedView.ts b/packages/nc-gui/composables/useSharedView.ts
index a75b45c5da..a460760d1e 100644
--- a/packages/nc-gui/composables/useSharedView.ts
+++ b/packages/nc-gui/composables/useSharedView.ts
@@ -121,7 +121,7 @@ export function useSharedView() {
nested?: any
offset?: number
},
- headers: {
+ headers?: {
ignorePagination?: boolean
},
) => {
diff --git a/packages/nocodb/src/services/datas.service.ts b/packages/nocodb/src/services/datas.service.ts
index 41265c60ce..1008419031 100644
--- a/packages/nocodb/src/services/datas.service.ts
+++ b/packages/nocodb/src/services/datas.service.ts
@@ -33,7 +33,7 @@ export class DatasService {
view,
query: param.query,
throwErrorIfInvalidParams: true,
- ignoreViewFilterAndSort: param.ignorePagination,
+ ignorePagination: param.ignorePagination,
});
}
diff --git a/tests/playwright/pages/Dashboard/ViewSidebar/index.ts b/tests/playwright/pages/Dashboard/ViewSidebar/index.ts
index fcea5c7e05..c11c855c9d 100644
--- a/tests/playwright/pages/Dashboard/ViewSidebar/index.ts
+++ b/tests/playwright/pages/Dashboard/ViewSidebar/index.ts
@@ -12,6 +12,7 @@ export class ViewSidebarPage extends BasePage {
readonly createFormButton: Locator;
readonly createKanbanButton: Locator;
readonly createMapButton: Locator;
+ readonly createCalendarButton: Locator;
readonly erdButton: Locator;
readonly apiSnippet: Locator;
@@ -25,6 +26,7 @@ export class ViewSidebarPage extends BasePage {
this.createGridButton = this.get().locator('.nc-create-grid-view:visible');
this.createFormButton = this.get().locator('.nc-create-form-view:visible');
this.createKanbanButton = this.get().locator('.nc-create-kanban-view:visible');
+ this.createCalendarButton = this.get().locator('.nc-create-calendar-view:visible');
this.erdButton = this.get().locator('.nc-view-sidebar-erd');
this.apiSnippet = this.get().locator('.nc-view-sidebar-api-snippet');
@@ -54,12 +56,6 @@ export class ViewSidebarPage extends BasePage {
await this.rootPage.goto(this.rootPage.url());
}
- private async createView({ title, type }: { title: string; type: ViewTypes }) {
- await this.rootPage.waitForTimeout(1000);
-
- await this.dashboard.sidebar.createView({ title, type });
- }
-
async createGalleryView({ title }: { title: string }) {
await this.createView({ title, type: ViewTypes.GALLERY });
}
@@ -84,6 +80,11 @@ export class ViewSidebarPage extends BasePage {
await this.rootPage.waitForTimeout(1500);
}
+ async createCalendarView({ title }: { title: string }) {
+ await this.createView({ title, type: ViewTypes.CALENDAR });
+ await this.rootPage.waitForTimeout(1500);
+ }
+
async createMapView({ title }: { title: string }) {
await this.createView({ title, type: ViewTypes.MAP });
}
@@ -203,6 +204,12 @@ export class ViewSidebarPage extends BasePage {
// await expect(this.webhookButton).toHaveCount(count);
}
+ private async createView({ title, type }: { title: string; type: ViewTypes }) {
+ await this.rootPage.waitForTimeout(1000);
+
+ await this.dashboard.sidebar.createView({ title, type });
+ }
+
// async openDeveloperTab({ option }: { option?: string }) {
// await this.get().locator('.nc-tab').nth(1).click();
// if (option === 'ERD') {
diff --git a/tests/playwright/pages/Dashboard/common/Toolbar/CalendarRange.ts b/tests/playwright/pages/Dashboard/common/Toolbar/CalendarRange.ts
index 4abb3b16f5..5e24f80f3f 100644
--- a/tests/playwright/pages/Dashboard/common/Toolbar/CalendarRange.ts
+++ b/tests/playwright/pages/Dashboard/common/Toolbar/CalendarRange.ts
@@ -14,7 +14,14 @@ export class ToolbarCalendarRangePage extends BasePage {
}
async click({ title }: { title: string }) {
- await this.get().locator(`.nc-kanban-grouping-field-select`).click();
+ await (await this.get()).getByTestId('nc-calendar-range-from-field-select').click();
await this.rootPage.locator('.ant-select-dropdown:visible').locator(`div[title="${title}"]`).click();
}
+
+ async newCalendarRange({ fromTitle, toTitle }: { fromTitle: string; toTitle: string }) {
+ await this.get().locator(`.nc-calendar-range-from`).click();
+ await this.rootPage.locator('.ant-picker-cell-in-view').locator(`:text("${fromTitle}")`).click();
+ await this.get().locator(`.nc-calendar-range-to`).click();
+ await this.rootPage.locator('.ant-picker-cell-in-view').locator(`:text("${toTitle}")`).click();
+ }
}
diff --git a/tests/playwright/pages/Dashboard/index.ts b/tests/playwright/pages/Dashboard/index.ts
index 604590c7c1..5e98c142c3 100644
--- a/tests/playwright/pages/Dashboard/index.ts
+++ b/tests/playwright/pages/Dashboard/index.ts
@@ -28,6 +28,7 @@ import { WorkspaceSettingsObject } from './WorkspaceSettings';
import { CmdJ } from './Command/CmdJPage';
import { CmdK } from './Command/CmdKPage';
import { CmdL } from './Command/CmdLPage';
+import { CalendarPage } from './Calendar';
export class DashboardPage extends BasePage {
readonly base: any;
@@ -38,6 +39,7 @@ export class DashboardPage extends BasePage {
readonly treeView: TreeViewPage;
readonly grid: GridPage;
readonly gallery: GalleryPage;
+ readonly calendar: CalendarPage;
readonly form: FormPage;
readonly kanban: KanbanPage;
readonly map: MapPage;
@@ -75,6 +77,7 @@ export class DashboardPage extends BasePage {
this.treeView = new TreeViewPage(this, base);
this.grid = new GridPage(this);
this.gallery = new GalleryPage(this);
+ this.calendar = new CalendarPage(this);
this.form = new FormPage(this);
this.kanban = new KanbanPage(this);
this.map = new MapPage(this);
@@ -162,22 +165,6 @@ export class DashboardPage extends BasePage {
await expect(this.tabBar.locator(`.ant-tabs-tab:has-text("${title}")`)).not.toBeVisible();
}
- private async _waitForDocsTabRender({ title, mode }: { title: string; mode: string }) {
- await this.tabBar.locator(`.ant-tabs-tab-active:has-text("${title}")`).waitFor();
-
- // wait active tab animation to finish
- await expect
- .poll(async () => {
- return await this.tabBar.getByTestId(`nc-root-tabs-${title}`).evaluate(el => {
- return window.getComputedStyle(el).getPropertyValue('color');
- });
- })
- .toBe('rgb(67, 81, 232)');
-
- await this.rootPage.waitForTimeout(500);
- }
-
- // When a tab is opened, it is not always immediately visible.
// Hence will wait till contents are visible
async waitForTabRender({
title,
@@ -189,6 +176,8 @@ export class DashboardPage extends BasePage {
type?: ProjectTypes;
}) {}
+ // When a tab is opened, it is not always immediately visible.
+
async toggleMobileMode() {
await this.baseMenuLink.click();
const projMenu = this.rootPage.locator('.nc-dropdown-base-menu');
@@ -251,26 +240,26 @@ export class DashboardPage extends BasePage {
await this.rootPage.locator('[data-testid="nc-loading"]').waitFor({ state: 'hidden' });
}
- /* async closeAllTabs() {
- await this.tabBar.locator(`.ant-tabs-tab`).waitFor({ state: 'visible' });
- const tab = await this.tabBar.locator(`.ant-tabs-tab`);
+ async closeAllTabs() {
+ const tab = this.tabBar.locator(`.ant-tabs-tab`);
const tabCount = await tab.count();
for (let i = 0; i < tabCount; i++) {
await tab.nth(i).locator('button.ant-tabs-tab-remove').click();
- await tab.nth(i).waitFor({ state: 'detached' });
+ await this.rootPage.waitForTimeout(200);
}
- }*/
+ }
- async closeAllTabs() {
- const tab = this.tabBar.locator(`.ant-tabs-tab`);
+ /* async closeAllTabs() {
+ await this.tabBar.locator(`.ant-tabs-tab`).waitFor({ state: 'visible' });
+ const tab = await this.tabBar.locator(`.ant-tabs-tab`);
const tabCount = await tab.count();
for (let i = 0; i < tabCount; i++) {
await tab.nth(i).locator('button.ant-tabs-tab-remove').click();
- await this.rootPage.waitForTimeout(200);
+ await tab.nth(i).waitFor({ state: 'detached' });
}
- }
+ }*/
async validateWorkspaceMenu(param: { role: string; mode?: string }) {
await this.grid.workspaceMenu.toggle();
@@ -309,4 +298,19 @@ export class DashboardPage extends BasePage {
await this.grid.workspaceMenu.toggle();
}
+
+ private async _waitForDocsTabRender({ title, mode }: { title: string; mode: string }) {
+ await this.tabBar.locator(`.ant-tabs-tab-active:has-text("${title}")`).waitFor();
+
+ // wait active tab animation to finish
+ await expect
+ .poll(async () => {
+ return await this.tabBar.getByTestId(`nc-root-tabs-${title}`).evaluate(el => {
+ return window.getComputedStyle(el).getPropertyValue('color');
+ });
+ })
+ .toBe('rgb(67, 81, 232)');
+
+ await this.rootPage.waitForTimeout(500);
+ }
}
diff --git a/tests/playwright/tests/db/views/viewCalendar.spec.ts b/tests/playwright/tests/db/views/viewCalendar.spec.ts
new file mode 100644
index 0000000000..a75105c48b
--- /dev/null
+++ b/tests/playwright/tests/db/views/viewCalendar.spec.ts
@@ -0,0 +1,320 @@
+import { test } from '@playwright/test';
+import { DashboardPage } from '../../../pages/Dashboard';
+import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
+
+import setup, { unsetup } from '../../../setup';
+import { enableQuickRun, isPg, isSqlite } from '../../../setup/db';
+import { TopbarPage } from '../../../pages/Dashboard/common/Topbar';
+import { CalendarTopbarPage } from '../../../pages/Dashboard/Calendar/CalendarTopBar';
+
+const filmRatings = ['G', 'PG', 'PG-13', 'R', 'NC-17'];
+
+test.describe('View', () => {
+ let dashboard: DashboardPage, toolbar: ToolbarPage, topbar: TopbarPage, calendarTopbar: CalendarTopbarPage;
+ let context: any;
+
+ test.beforeEach(async ({ page }) => {
+ context = await setup({ page, isEmptyProject: false });
+ dashboard = new DashboardPage(page, context.base);
+ toolbar = toolbar = dashboard.calendar.toolbar;
+ topbar = dashboard.calendar.topbar;
+ calendarTopbar = dashboard.calendar.calendarTopbar;
+
+ // await dashboard.treeView.openTable({ title: 'Film' });
+ });
+
+ test.afterEach(async () => {
+ await unsetup(context);
+ });
+
+ test('Calendar', async () => {
+ /*await dashboard.viewSidebar.createKanbanView({
+ title: 'Film Kanban',
+ });
+ await dashboard.viewSidebar.verifyView({
+ title: 'Film Kanban',
+ index: 0,
+ });
+
+ // configure stack-by field
+ await toolbar.clickStackByField();
+ await toolbar.stackBy.click({ title: 'Rating' });
+
+ // menu overlaps the stack-by field; use escape key to close instead of clicking it explicitly
+ // click again to close menu
+ // await toolbar.clickStackByField();
+ await toolbar.rootPage.keyboard.press('Escape');
+
+ const kanban = dashboard.kanban;
+ await kanban.verifyStackCount({ count: 6 });
+ await kanban.verifyStackOrder({
+ order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17'],
+ });
+ await kanban.verifyStackFooter({
+ count: [0, 178, 194, 223, 195, 210],
+ });
+ await kanban.verifyCardCount({
+ count: [0, 25, 25, 25, 25, 25],
+ });
+
+ // hide fields
+ await toolbar.fields.toggleShowAllFields();
+ await toolbar.fields.toggleShowAllFields();
+ await toolbar.fields.toggle({ title: 'Title' });
+ await kanban.verifyCardCount({
+ count: [0, 25, 25, 25, 25, 25],
+ });
+
+ // verify card order
+ const order = [
+ ['ACE GOLDFINGER', 'AFFAIR PREJUDICE', 'AFRICAN EGG'],
+ ['ACADEMY DINOSAUR', 'AGENT TRUMAN', 'ALASKA PHANTOM'],
+ ['AIRPLANE SIERRA', 'ALABAMA DEVIL', 'ALTER VICTORY'],
+ ['AIRPORT POLLOCK', 'ALONE TRIP', 'AMELIE HELLFIGHTERS'],
+ ['ADAPTATION HOLES', 'ALADDIN CALENDAR', 'ALICE FANTASIA'],
+ ];
+ for (let i = 1; i <= order.length; i++)
+ await kanban.verifyCardOrder({
+ stackIndex: i,
+ order: order[i - 1],
+ });
+
+ // // verify inter stack drag-drop
+ // await kanban.dragDropCard({
+ // from: "ACE GOLDFINGER",
+ // to: "ACADEMY DINOSAUR",
+ // });
+
+ // verify drag drop stack
+ await kanban.dragDropStack({
+ from: 1, // G
+ to: 2, // PG
+ });
+ await kanban.verifyStackOrder({
+ order: ['Uncategorized', 'PG', 'G', 'PG-13', 'R', 'NC-17'],
+ });
+ // verify drag drop stack
+ await kanban.dragDropStack({
+ from: 2, // G
+ to: 1, // PG
+ });
+ await kanban.verifyStackOrder({
+ order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17'],
+ });
+
+ // verify sort
+ await toolbar.sort.add({
+ title: 'Title',
+ ascending: false,
+ locallySaved: false,
+ });
+ // verify card order
+ const order2 = [
+ ['YOUNG LANGUAGE', 'WEST LION'],
+ ['WORST BANGER', 'WORDS HUNTER'],
+ ];
+ for (let i = 1; i <= order2.length; i++)
+ await kanban.verifyCardOrder({
+ stackIndex: i,
+ order: order2[i - 1],
+ });
+ await toolbar.sort.reset();
+ // verify card order
+ const order3 = [
+ ['ACE GOLDFINGER', 'AFFAIR PREJUDICE', 'AFRICAN EGG'],
+ ['ACADEMY DINOSAUR', 'AGENT TRUMAN', 'ALASKA PHANTOM'],
+ ];
+ for (let i = 1; i <= order3.length; i++)
+ await kanban.verifyCardOrder({
+ stackIndex: i,
+ order: order3[i - 1],
+ });
+
+ // verify filter
+ await toolbar.clickFilter({
+ networkValidation: true,
+ });
+ await toolbar.filter.add({
+ title: 'Title',
+ operation: 'is like',
+ value: 'BA',
+ locallySaved: false,
+ });
+ await toolbar.clickFilter();
+
+ // verify card order
+ const order4 = [
+ ['BAKED CLEOPATRA', 'BALLROOM MOCKINGBIRD'],
+ ['ARIZONA BANG', 'EGYPT TENENBAUMS'],
+ ];
+ for (let i = 1; i <= order4.length; i++)
+ await kanban.verifyCardOrder({
+ stackIndex: i,
+ order: order4[i - 1],
+ });
+ await toolbar.filter.reset();
+ const order5 = [
+ ['ACE GOLDFINGER', 'AFFAIR PREJUDICE', 'AFRICAN EGG'],
+ ['ACADEMY DINOSAUR', 'AGENT TRUMAN', 'ALASKA PHANTOM'],
+ ];
+ for (let i = 1; i <= order5.length; i++)
+ await kanban.verifyCardOrder({
+ stackIndex: i,
+ order: order5[i - 1],
+ });
+
+ await dashboard.rootPage.waitForTimeout(1000);*/
+ });
+
+ test('Calendar view operations', async () => {
+ /*test.slow();
+
+ await dashboard.viewSidebar.createKanbanView({
+ title: 'Film Kanban',
+ });
+ await dashboard.viewSidebar.verifyView({
+ title: 'Film Kanban',
+ index: 0,
+ });
+
+ await toolbar.sort.add({
+ title: 'Title',
+ ascending: false,
+ locallySaved: false,
+ });
+
+ await toolbar.clickFilter();
+ await toolbar.filter.add({
+ title: 'Title',
+ operation: 'is like',
+ value: 'BA',
+ locallySaved: false,
+ });
+ await toolbar.clickFilter();
+
+ await toolbar.fields.toggleShowAllFields();
+ await toolbar.fields.toggleShowAllFields();
+ await toolbar.fields.toggle({ title: 'Title' });
+
+ await dashboard.viewSidebar.copyView({ title: 'Film Kanban' });
+ await dashboard.viewSidebar.verifyView({
+ title: 'Kanban',
+ index: 1,
+ });
+ const kanban = dashboard.kanban;
+ await kanban.verifyStackCount({ count: 6 });
+ await kanban.verifyStackOrder({
+ order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17'],
+ });
+ await kanban.verifyStackFooter({
+ count: [0, 4, 5, 8, 6, 6],
+ });
+ await kanban.verifyCardCount({
+ count: [0, 4, 5, 8, 6, 6],
+ });
+ // verify card order
+ const order2 = [
+ ['BAREFOOT MANCHURIAN', 'BARBARELLA STREETCAR'],
+ ['WORST BANGER', 'PRESIDENT BANG'],
+ ];
+ for (let i = 1; i <= order2.length; i++)
+ await kanban.verifyCardOrder({
+ stackIndex: i,
+ order: order2[i - 1],
+ });
+
+ await dashboard.viewSidebar.changeViewIcon({
+ title: 'Kanban',
+ icon: 'american-football',
+ iconDisplay: '🏈',
+ });
+
+ await dashboard.viewSidebar.deleteView({ title: 'Kanban' });
+ ///////////////////////////////////////////////
+
+ await dashboard.viewSidebar.openView({ title: 'Film Kanban' });
+
+ // add new stack
+ await kanban.addNewStack({ title: 'Test' });
+ await dashboard.rootPage.waitForTimeout(1000);
+ await kanban.verifyStackCount({ count: 7 });
+ await kanban.verifyStackOrder({
+ order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17', 'Test'],
+ });
+
+ // collapse stack
+ await kanban.verifyCollapseStackCount({ count: 0 });
+ await kanban.collapseStack({ index: 0 });
+ await kanban.verifyCollapseStackCount({ count: 1 });
+ await kanban.expandStack({ index: 0 });
+ await kanban.verifyCollapseStackCount({ count: 0 });
+
+ // add record to stack & verify
+ await toolbar.fields.toggleShowAllFields();
+ await toolbar.fields.toggleShowAllFields();
+ await toolbar.fields.toggleShowSystemFields();
+ await toolbar.fields.toggle({ title: 'LanguageId' });
+ await toolbar.fields.toggle({ title: 'Title' });
+ await toolbar.sort.reset();
+ await toolbar.filter.reset();
+
+ await kanban.addCard({ stackIndex: 6 });
+ await dashboard.expandedForm.fillField({
+ columnTitle: 'Title',
+ value: 'New record',
+ });
+ await dashboard.expandedForm.fillField({
+ columnTitle: 'LanguageId',
+ value: '1',
+ });
+ // todo: Check why kanban doesnt reload the rows data
+ await dashboard.expandedForm.save({ waitForRowsData: false });
+ // kludge: reload the page
+ await dashboard.rootPage.reload();
+
+ await kanban.verifyStackCount({ count: 7 });
+ await kanban.verifyStackOrder({
+ order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17', 'Test'],
+ });
+ await kanban.verifyCardCount({
+ count: [0, 25, 25, 25, 25, 25, 1],
+ });
+
+ // delete stack
+ await kanban.deleteStack({ index: 6 });
+ await dashboard.rootPage.waitForTimeout(1000);
+ await kanban.verifyStackCount({ count: 6 });
+ await kanban.verifyStackOrder({
+ order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17'],
+ });
+ await kanban.verifyCardCount({
+ count: [1, 25, 25, 25, 25, 25],
+ });*/
+ });
+
+ test.skip('Calendar shared view operations', async ({ page }) => {
+ test.slow();
+ /*
+ await dashboard.viewSidebar.createKanbanView({
+ title: 'Film Kanban',
+ });
+ await dashboard.viewSidebar.verifyView({
+ title: 'Film Kanban',
+ index: 0,
+ });
+
+ // Share view
+ await toolbar.fields.toggle({ title: 'Rating' });
+ const sharedLink = await topbar.getSharedViewUrl();
+ // await toolbar.shareView.close();
+
+ // sign-out
+ await dashboard.signOut();
+
+ // Open shared view & verify stack count
+ await page.goto(sharedLink);
+ await page.reload();
+ const kanban = dashboard.kanban;
+ await kanban.verifyStackCount({ count: 6 });*/
+ });
+});