WIP - configure list views

This commit is contained in:
Francisco Gaona
2026-01-31 02:46:33 +01:00
parent c7282ee2a0
commit 97fc235636
12 changed files with 1409 additions and 59 deletions

View File

@@ -0,0 +1,25 @@
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function(knex) {
return knex.schema.alterTable('page_layouts', (table) => {
// Add layout_type column to distinguish between detail/edit layouts and list view layouts
// Default to 'detail' for existing layouts
table.enum('layout_type', ['detail', 'list']).notNullable().defaultTo('detail').after('name');
// Update the unique index to include layout_type so we can have both a default detail and default list layout
table.dropIndex(['object_id', 'is_default']);
});
};
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function(knex) {
return knex.schema.alterTable('page_layouts', (table) => {
table.dropColumn('layout_type');
table.index(['object_id', 'is_default']);
});
};

View File

@@ -1,4 +1,6 @@
import { IsString, IsUUID, IsBoolean, IsOptional, IsObject } from 'class-validator';
import { IsString, IsUUID, IsBoolean, IsOptional, IsObject, IsIn } from 'class-validator';
export type PageLayoutType = 'detail' | 'list';
export class CreatePageLayoutDto {
@IsString()
@@ -7,18 +9,25 @@ export class CreatePageLayoutDto {
@IsUUID()
objectId: string;
@IsIn(['detail', 'list'])
@IsOptional()
layoutType?: PageLayoutType = 'detail';
@IsBoolean()
@IsOptional()
isDefault?: boolean;
@IsObject()
layoutConfig: {
// For detail layouts: grid-based field positions
fields: Array<{
fieldId: string;
x: number;
y: number;
w: number;
h: number;
x?: number;
y?: number;
w?: number;
h?: number;
// For list layouts: field order (optional, defaults to array index)
order?: number;
}>;
relatedLists?: string[];
};
@@ -42,10 +51,11 @@ export class UpdatePageLayoutDto {
layoutConfig?: {
fields: Array<{
fieldId: string;
x: number;
y: number;
w: number;
h: number;
x?: number;
y?: number;
w?: number;
h?: number;
order?: number;
}>;
relatedLists?: string[];
};

View File

@@ -10,7 +10,7 @@ import {
Query,
} from '@nestjs/common';
import { PageLayoutService } from './page-layout.service';
import { CreatePageLayoutDto, UpdatePageLayoutDto } from './dto/page-layout.dto';
import { CreatePageLayoutDto, UpdatePageLayoutDto, PageLayoutType } from './dto/page-layout.dto';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { TenantId } from '../tenant/tenant.decorator';
@@ -25,13 +25,21 @@ export class PageLayoutController {
}
@Get()
findAll(@TenantId() tenantId: string, @Query('objectId') objectId?: string) {
return this.pageLayoutService.findAll(tenantId, objectId);
findAll(
@TenantId() tenantId: string,
@Query('objectId') objectId?: string,
@Query('layoutType') layoutType?: PageLayoutType,
) {
return this.pageLayoutService.findAll(tenantId, objectId, layoutType);
}
@Get('default/:objectId')
findDefaultByObject(@TenantId() tenantId: string, @Param('objectId') objectId: string) {
return this.pageLayoutService.findDefaultByObject(tenantId, objectId);
findDefaultByObject(
@TenantId() tenantId: string,
@Param('objectId') objectId: string,
@Query('layoutType') layoutType?: PageLayoutType,
) {
return this.pageLayoutService.findDefaultByObject(tenantId, objectId, layoutType || 'detail');
}
@Get(':id')

View File

@@ -1,6 +1,6 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { TenantDatabaseService } from '../tenant/tenant-database.service';
import { CreatePageLayoutDto, UpdatePageLayoutDto } from './dto/page-layout.dto';
import { CreatePageLayoutDto, UpdatePageLayoutDto, PageLayoutType } from './dto/page-layout.dto';
@Injectable()
export class PageLayoutService {
@@ -8,17 +8,19 @@ export class PageLayoutService {
async create(tenantId: string, createDto: CreatePageLayoutDto) {
const knex = await this.tenantDbService.getTenantKnex(tenantId);
const layoutType = createDto.layoutType || 'detail';
// If this layout is set as default, unset other defaults for the same object
// If this layout is set as default, unset other defaults for the same object and layout type
if (createDto.isDefault) {
await knex('page_layouts')
.where({ object_id: createDto.objectId })
.where({ object_id: createDto.objectId, layout_type: layoutType })
.update({ is_default: false });
}
const [id] = await knex('page_layouts').insert({
name: createDto.name,
object_id: createDto.objectId,
layout_type: layoutType,
is_default: createDto.isDefault || false,
layout_config: JSON.stringify(createDto.layoutConfig),
description: createDto.description || null,
@@ -29,7 +31,7 @@ export class PageLayoutService {
return result;
}
async findAll(tenantId: string, objectId?: string) {
async findAll(tenantId: string, objectId?: string, layoutType?: PageLayoutType) {
const knex = await this.tenantDbService.getTenantKnex(tenantId);
let query = knex('page_layouts');
@@ -38,6 +40,10 @@ export class PageLayoutService {
query = query.where({ object_id: objectId });
}
if (layoutType) {
query = query.where({ layout_type: layoutType });
}
const layouts = await query.orderByRaw('is_default DESC, name ASC');
return layouts;
}
@@ -54,11 +60,11 @@ export class PageLayoutService {
return layout;
}
async findDefaultByObject(tenantId: string, objectId: string) {
async findDefaultByObject(tenantId: string, objectId: string, layoutType: PageLayoutType = 'detail') {
const knex = await this.tenantDbService.getTenantKnex(tenantId);
const layout = await knex('page_layouts')
.where({ object_id: objectId, is_default: true })
.where({ object_id: objectId, is_default: true, layout_type: layoutType })
.first();
return layout || null;
@@ -68,13 +74,12 @@ export class PageLayoutService {
const knex = await this.tenantDbService.getTenantKnex(tenantId);
// Check if layout exists
await this.findOne(tenantId, id);
const layout = await this.findOne(tenantId, id);
// If setting as default, unset other defaults for the same object
// If setting as default, unset other defaults for the same object and layout type
if (updateDto.isDefault) {
const layout = await this.findOne(tenantId, id);
await knex('page_layouts')
.where({ object_id: layout.object_id })
.where({ object_id: layout.object_id, layout_type: layout.layout_type })
.whereNot({ id })
.update({ is_default: false });
}