Древовидные справочники: parent_id, drag&drop, древовидный выбор в полях table

This commit is contained in:
2026-07-12 20:55:30 +03:00
parent 833fa97f56
commit ec08f3222d
10 changed files with 1284 additions and 68 deletions

View File

@@ -184,13 +184,102 @@ export class DataTablesCoreStorage extends BotsStorage {
));
if (!table) return;
await db
.update(dataTableRows)
.set({ isDeleted: true, updatedAt: new Date() })
.where(and(
eq(dataTableRows.id, id),
eq(dataTableRows.tableId, tableId)
));
await db.transaction(async (tx) => {
// Move direct children to root before soft-deleting the row
await tx
.update(dataTableRows)
.set({ parentId: null, updatedAt: new Date() })
.where(and(
eq(dataTableRows.parentId, id),
eq(dataTableRows.tableId, tableId)
));
await tx
.update(dataTableRows)
.set({ isDeleted: true, updatedAt: new Date() })
.where(and(
eq(dataTableRows.id, id),
eq(dataTableRows.tableId, tableId)
));
});
}
private async getRowsMap(tableId: number, organizationId: number): Promise<Map<number, DataTableRow>> {
const rows = await this.getDataTableRows(tableId, organizationId);
const map = new Map<number, DataTableRow>();
for (const row of rows) {
map.set(row.id, row);
}
return map;
}
private getDescendantIds(rowsById: Map<number, DataTableRow>, rootId: number): Set<number> {
const ids = new Set<number>();
const walk = (parentId: number) => {
for (const row of rowsById.values()) {
if (row.parentId === parentId) {
ids.add(row.id);
walk(row.id);
}
}
};
walk(rootId);
return ids;
}
async moveDataTableRow(
id: number,
tableId: number,
organizationId: number,
parentId: number | null,
position: number
): Promise<DataTableRow> {
const table = await this.getDataTable(tableId, organizationId);
if (!table) throw new Error('Table not found');
const row = await this.getDataTableRow(id, tableId, organizationId);
if (!row) throw new Error('Row not found');
const targetParentId = parentId ?? null;
if (targetParentId !== null) {
if (targetParentId === id) {
throw new Error('Cannot move row into itself');
}
const rowsById = await this.getRowsMap(tableId, organizationId);
if (this.getDescendantIds(rowsById, id).has(targetParentId)) {
throw new Error('Cannot move row into its descendant');
}
}
const rows = await this.getDataTableRows(tableId, organizationId);
const siblings = rows
.filter((r) => r.id !== id && (r.parentId ?? null) === targetParentId)
.sort((a, b) => a.position - b.position);
const clampedPosition = Math.max(0, Math.min(position, siblings.length));
const reordered = [
...siblings.slice(0, clampedPosition),
row,
...siblings.slice(clampedPosition),
];
await db.transaction(async (tx) => {
for (let i = 0; i < reordered.length; i++) {
const updates: Partial<DataTableRow> = { position: i };
if (reordered[i].id === id) {
updates.parentId = targetParentId;
}
await tx
.update(dataTableRows)
.set({ ...updates, updatedAt: new Date() })
.where(eq(dataTableRows.id, reordered[i].id));
}
});
const movedRow = await this.getDataTableRow(id, tableId, organizationId);
if (!movedRow) throw new Error('Row not found after move');
return movedRow;
}
async bulkCreateDataTableRows(tableId: number, rows: { values: string[] }[]): Promise<DataTableRow[]> {