feat(display): add layout manager with element CRUD (Task 8)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Christian Mueller 2026-04-06 12:56:05 +02:00
parent 2990f9aa4d
commit aaac2bedff

View File

@ -0,0 +1,211 @@
import { getDb } from '../../db/index';
export interface Layout {
id: number;
name: string;
type: string;
width: number;
height: number;
grid_cols: number | null;
grid_rows: number | null;
created_at: string;
}
export interface LayoutWithElements extends Layout {
elements: LayoutElement[];
}
export interface LayoutElement {
id: number;
layout_id: number;
datapoint_id: number | null;
type: string;
x: number;
y: number;
width: number | null;
height: number | null;
font_size: number;
font_weight: string;
text_align: string;
icon: string | null;
style: string;
static_text: string | null;
grid_col: number | null;
grid_row: number | null;
sort_order: number;
}
export interface CreateLayoutData {
name: string;
type?: string;
width?: number;
height?: number;
grid_cols?: number;
grid_rows?: number;
}
export interface UpdateLayoutData {
name?: string;
type?: string;
width?: number;
height?: number;
grid_cols?: number | null;
grid_rows?: number | null;
}
export interface CreateElementData {
datapoint_id?: number;
type: string;
x?: number;
y?: number;
width?: number;
height?: number;
font_size?: number;
font_weight?: string;
text_align?: string;
icon?: string;
style?: string;
static_text?: string;
grid_col?: number;
grid_row?: number;
sort_order?: number;
}
export type UpdateElementData = Partial<CreateElementData>;
export function getAllLayouts(): Layout[] {
const db = getDb();
return db.prepare('SELECT * FROM layouts ORDER BY id ASC').all() as Layout[];
}
export function getLayoutById(id: number): LayoutWithElements | undefined {
const db = getDb();
const layout = db.prepare('SELECT * FROM layouts WHERE id = ?').get(id) as Layout | undefined;
if (!layout) return undefined;
const elements = db
.prepare('SELECT * FROM layout_elements WHERE layout_id = ? ORDER BY sort_order ASC, id ASC')
.all(id) as LayoutElement[];
return { ...layout, elements };
}
export function createLayout(data: CreateLayoutData): Layout {
const db = getDb();
const result = db.run(
`INSERT INTO layouts (name, type, width, height, grid_cols, grid_rows)
VALUES (?, ?, ?, ?, ?, ?)`,
[
data.name,
data.type ?? 'free',
data.width ?? 800,
data.height ?? 480,
data.grid_cols ?? null,
data.grid_rows ?? null,
]
);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM layouts WHERE id = ?').get(id) as Layout;
}
export function updateLayout(id: number, data: UpdateLayoutData): Layout | undefined {
const db = getDb();
const fields: string[] = [];
const values: unknown[] = [];
if (data.name !== undefined) { fields.push('name = ?'); values.push(data.name); }
if (data.type !== undefined) { fields.push('type = ?'); values.push(data.type); }
if (data.width !== undefined) { fields.push('width = ?'); values.push(data.width); }
if (data.height !== undefined) { fields.push('height = ?'); values.push(data.height); }
if (data.grid_cols !== undefined) { fields.push('grid_cols = ?'); values.push(data.grid_cols); }
if (data.grid_rows !== undefined) { fields.push('grid_rows = ?'); values.push(data.grid_rows); }
if (fields.length === 0) {
return db.prepare('SELECT * FROM layouts WHERE id = ?').get(id) as Layout | undefined;
}
values.push(id);
db.run(`UPDATE layouts SET ${fields.join(', ')} WHERE id = ?`, values);
return db.prepare('SELECT * FROM layouts WHERE id = ?').get(id) as Layout | undefined;
}
export function deleteLayout(id: number): boolean {
const db = getDb();
const result = db.run('DELETE FROM layouts WHERE id = ?', [id]);
return (result.changes as number) > 0;
}
export function addElement(layoutId: number, data: CreateElementData): LayoutElement {
const db = getDb();
const result = db.run(
`INSERT INTO layout_elements
(layout_id, datapoint_id, type, x, y, width, height, font_size, font_weight,
text_align, icon, style, static_text, grid_col, grid_row, sort_order)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
layoutId,
data.datapoint_id ?? null,
data.type,
data.x ?? 0,
data.y ?? 0,
data.width ?? null,
data.height ?? null,
data.font_size ?? 24,
data.font_weight ?? 'normal',
data.text_align ?? 'left',
data.icon ?? null,
data.style ?? 'plain',
data.static_text ?? null,
data.grid_col ?? null,
data.grid_row ?? null,
data.sort_order ?? 0,
]
);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM layout_elements WHERE id = ?').get(id) as LayoutElement;
}
export function updateElement(id: number, data: UpdateElementData): LayoutElement | undefined {
const db = getDb();
const fields: string[] = [];
const values: unknown[] = [];
const fieldMap: Record<string, string> = {
datapoint_id: 'datapoint_id',
type: 'type',
x: 'x',
y: 'y',
width: 'width',
height: 'height',
font_size: 'font_size',
font_weight: 'font_weight',
text_align: 'text_align',
icon: 'icon',
style: 'style',
static_text: 'static_text',
grid_col: 'grid_col',
grid_row: 'grid_row',
sort_order: 'sort_order',
};
for (const [key, col] of Object.entries(fieldMap)) {
if (Object.prototype.hasOwnProperty.call(data, key)) {
fields.push(`${col} = ?`);
values.push((data as Record<string, unknown>)[key]);
}
}
if (fields.length === 0) {
return db.prepare('SELECT * FROM layout_elements WHERE id = ?').get(id) as LayoutElement | undefined;
}
values.push(id);
db.run(`UPDATE layout_elements SET ${fields.join(', ')} WHERE id = ?`, values);
return db.prepare('SELECT * FROM layout_elements WHERE id = ?').get(id) as LayoutElement | undefined;
}
export function deleteElement(id: number): boolean {
const db = getDb();
const result = db.run('DELETE FROM layout_elements WHERE id = ?', [id]);
return (result.changes as number) > 0;
}