diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1756302117195-createPageWidgetLayoutEntity.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1756302117195-createPageWidgetLayoutEntity.ts new file mode 100644 index 0000000000..de424cafec --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1756302117195-createPageWidgetLayoutEntity.ts @@ -0,0 +1,39 @@ +import { type MigrationInterface, type QueryRunner } from 'typeorm'; + +export class CreatePageWidgetLayoutEntity1756302117195 + implements MigrationInterface +{ + name = 'CreatePageWidgetLayoutEntity1756302117195'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "core"."pageLayoutWidget_type_enum" AS ENUM('VIEW', 'IFRAME', 'FIELDS', 'GRAPH')`, + ); + await queryRunner.query( + `CREATE TABLE "core"."pageLayoutWidget" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "pageLayoutTabId" uuid NOT NULL, "title" character varying NOT NULL, "type" "core"."pageLayoutWidget_type_enum" NOT NULL DEFAULT 'VIEW', "objectMetadataId" uuid, "gridPosition" jsonb NOT NULL, "configuration" jsonb, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_2f997489b8b15cb26a0b9d4220b" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_PAGE_LAYOUT_WIDGET_PAGE_LAYOUT_TAB_ID" ON "core"."pageLayoutWidget" ("pageLayoutTabId") `, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" ADD CONSTRAINT "FK_0659a4d171c93f5c046f18d24cd" FOREIGN KEY ("pageLayoutTabId") REFERENCES "core"."pageLayoutTab"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" ADD CONSTRAINT "FK_c4dc95034f53a12601e623d9171" FOREIGN KEY ("objectMetadataId") REFERENCES "core"."objectMetadata"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" DROP CONSTRAINT "FK_c4dc95034f53a12601e623d9171"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" DROP CONSTRAINT "FK_0659a4d171c93f5c046f18d24cd"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_PAGE_LAYOUT_WIDGET_PAGE_LAYOUT_TAB_ID"`, + ); + await queryRunner.query(`DROP TABLE "core"."pageLayoutWidget"`); + await queryRunner.query(`DROP TYPE "core"."pageLayoutWidget_type_enum"`); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/entities/page-layout-tab.entity.ts b/packages/twenty-server/src/engine/core-modules/page-layout/entities/page-layout-tab.entity.ts index baafcad816..7e822ff013 100644 --- a/packages/twenty-server/src/engine/core-modules/page-layout/entities/page-layout-tab.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/page-layout/entities/page-layout-tab.entity.ts @@ -6,11 +6,13 @@ import { Index, JoinColumn, ManyToOne, + OneToMany, PrimaryGeneratedColumn, Relation, UpdateDateColumn, } from 'typeorm'; +import { PageLayoutWidgetEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-widget.entity'; import { PageLayoutEntity } from 'src/engine/core-modules/page-layout/entities/page-layout.entity'; @Entity({ name: 'pageLayoutTab', schema: 'core' }) @@ -34,6 +36,11 @@ export class PageLayoutTabEntity implements Required { @JoinColumn({ name: 'pageLayoutId' }) pageLayout: Relation; + @OneToMany(() => PageLayoutWidgetEntity, (widget) => widget.pageLayoutTab, { + cascade: true, + }) + widgets: Relation; + @CreateDateColumn({ type: 'timestamptz' }) createdAt: Date; diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/entities/page-layout-widget.entity.ts b/packages/twenty-server/src/engine/core-modules/page-layout/entities/page-layout-widget.entity.ts new file mode 100644 index 0000000000..8d157e0dd1 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/page-layout/entities/page-layout-widget.entity.ts @@ -0,0 +1,71 @@ +import { + Column, + CreateDateColumn, + DeleteDateColumn, + Entity, + Index, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + Relation, + UpdateDateColumn, +} from 'typeorm'; + +import { PageLayoutTabEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-tab.entity'; +import { WidgetType } from 'src/engine/core-modules/page-layout/enums/widget-type.enum'; +import { GridPosition } from 'src/engine/core-modules/page-layout/types/grid-position.type'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; + +@Entity({ name: 'pageLayoutWidget', schema: 'core' }) +@Index('IDX_PAGE_LAYOUT_WIDGET_PAGE_LAYOUT_TAB_ID', ['pageLayoutTabId']) +export class PageLayoutWidgetEntity + implements Required +{ + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ nullable: false, type: 'uuid' }) + pageLayoutTabId: string; + + @ManyToOne(() => PageLayoutTabEntity, { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'pageLayoutTabId' }) + pageLayoutTab: Relation; + + @Column({ nullable: false }) + title: string; + + @Column({ + type: 'enum', + enum: Object.values(WidgetType), + nullable: false, + default: WidgetType.VIEW, + }) + type: WidgetType; + + @Column({ nullable: true, type: 'uuid' }) + objectMetadataId: string | null; + + @ManyToOne(() => ObjectMetadataEntity, { + onDelete: 'CASCADE', + nullable: true, + }) + @JoinColumn({ name: 'objectMetadataId' }) + objectMetadata: Relation | null; + + @Column({ type: 'jsonb', nullable: false }) + gridPosition: GridPosition; + + @Column({ type: 'jsonb', nullable: true }) + configuration: Record | null; + + @CreateDateColumn({ type: 'timestamptz' }) + createdAt: Date; + + @UpdateDateColumn({ type: 'timestamptz' }) + updatedAt: Date; + + @DeleteDateColumn({ type: 'timestamptz' }) + deletedAt: Date | null; +} diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/enums/widget-type.enum.ts b/packages/twenty-server/src/engine/core-modules/page-layout/enums/widget-type.enum.ts new file mode 100644 index 0000000000..20493a53ac --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/page-layout/enums/widget-type.enum.ts @@ -0,0 +1,6 @@ +export enum WidgetType { + VIEW = 'VIEW', + IFRAME = 'IFRAME', + FIELDS = 'FIELDS', + GRAPH = 'GRAPH', +} diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/page-layout.module.ts b/packages/twenty-server/src/engine/core-modules/page-layout/page-layout.module.ts index 1c37f323ba..69a207cd06 100644 --- a/packages/twenty-server/src/engine/core-modules/page-layout/page-layout.module.ts +++ b/packages/twenty-server/src/engine/core-modules/page-layout/page-layout.module.ts @@ -2,11 +2,15 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { PageLayoutTabEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-tab.entity'; +import { PageLayoutWidgetEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-widget.entity'; import { PageLayoutEntity } from 'src/engine/core-modules/page-layout/entities/page-layout.entity'; @Module({ imports: [ - TypeOrmModule.forFeature([PageLayoutEntity, PageLayoutTabEntity], 'core'), + TypeOrmModule.forFeature( + [PageLayoutEntity, PageLayoutTabEntity, PageLayoutWidgetEntity], + 'core', + ), ], exports: [], }) diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/types/grid-position.type.ts b/packages/twenty-server/src/engine/core-modules/page-layout/types/grid-position.type.ts new file mode 100644 index 0000000000..8b789b55bd --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/page-layout/types/grid-position.type.ts @@ -0,0 +1,6 @@ +export type GridPosition = { + row: number; + column: number; + rowSpan: number; + columnSpan: number; +};