Древовидные справочники: выбор колонки для дерева (treeColumnIndex) и API /rows/tree
This commit is contained in:
@@ -323,11 +323,12 @@ export function registerDataTableRoutes(app: import("express").Express): void {
|
||||
return res.status(403).json({ error: 'Нет прав для редактирования таблицы' });
|
||||
}
|
||||
|
||||
const { name, description, columns } = req.body;
|
||||
const { name, description, columns, treeColumnIndex } = req.body;
|
||||
const updates: Record<string, unknown> = {};
|
||||
if (name !== undefined) updates.name = name;
|
||||
if (description !== undefined) updates.description = description;
|
||||
if (columns !== undefined) updates.columns = columns;
|
||||
if (treeColumnIndex !== undefined) updates.treeColumnIndex = treeColumnIndex === null ? null : Number(treeColumnIndex);
|
||||
|
||||
const updatedTable = await storage.updateDataTable(tableId, req.organizationId!, updates as Parameters<typeof storage.updateDataTable>[2]);
|
||||
res.json({ table: updatedTable });
|
||||
@@ -482,6 +483,110 @@ export function registerDataTableRoutes(app: import("express").Express): void {
|
||||
}
|
||||
);
|
||||
|
||||
// Get table rows as a tree (grouped by treeColumnIndex or parentId hierarchy)
|
||||
app.get(['/api/tables/:tableId/rows/tree', '/api/directories/:tableId/rows/tree'],
|
||||
authenticateToken,
|
||||
tenantIsolation,
|
||||
async (req: AuthenticatedRequest, res) => {
|
||||
try {
|
||||
const tableId = parseInt(req.params.tableId);
|
||||
const table = await storage.getDataTable(tableId, req.organizationId!);
|
||||
if (!table) {
|
||||
return res.status(404).json({ error: 'Таблица не найдена' });
|
||||
}
|
||||
|
||||
const role = await storage.getUserTableRole(tableId, req.user!.id, table.createdBy, req.organizationId!);
|
||||
if (!role) {
|
||||
return res.status(403).json({ error: 'Нет доступа' });
|
||||
}
|
||||
|
||||
let rows = await storage.getDataTableRows(tableId, req.organizationId!);
|
||||
const treeColumnIndex = table.treeColumnIndex;
|
||||
const search = String(req.query.search || '').toLowerCase();
|
||||
|
||||
type TreeNode =
|
||||
| { type: 'group'; id: string; label: string; children: TreeNode[] }
|
||||
| { type: 'row'; id: number; row: typeof rows[number] };
|
||||
|
||||
const buildParentTree = (parentRows: typeof rows, parentId: number | null): TreeNode[] => {
|
||||
const items = parentRows
|
||||
.filter((r) => (r.parentId ?? null) === parentId)
|
||||
.sort((a, b) => a.position - b.position);
|
||||
return items.map((row) => {
|
||||
const children = buildParentTree(parentRows, row.id);
|
||||
if (children.length > 0) {
|
||||
return { type: 'group' as const, id: `row-group:${row.id}`, label: String(row.values[0] ?? ''), children: [...children, { type: 'row' as const, id: row.id, row }] };
|
||||
}
|
||||
return { type: 'row' as const, id: row.id, row };
|
||||
});
|
||||
};
|
||||
|
||||
const buildColumnTree = (allRows: typeof rows, columnIndex: number): TreeNode[] => {
|
||||
const groups = new Map<string, typeof rows[number][]>();
|
||||
const rootRows: typeof rows[number][] = [];
|
||||
for (const row of allRows) {
|
||||
const vals = Array.isArray(row.values) ? row.values : [];
|
||||
const key = String(vals[columnIndex] ?? '').trim();
|
||||
if (!key) {
|
||||
rootRows.push(row);
|
||||
continue;
|
||||
}
|
||||
if (!groups.has(key)) groups.set(key, []);
|
||||
groups.get(key)!.push(row);
|
||||
}
|
||||
const nodes: TreeNode[] = [];
|
||||
for (const [label, groupRows] of groups) {
|
||||
const sorted = [...groupRows].sort((a, b) => a.position - b.position);
|
||||
nodes.push({
|
||||
type: 'group',
|
||||
id: `group:${label}`,
|
||||
label,
|
||||
children: sorted.map((row) => ({ type: 'row', id: row.id, row })),
|
||||
});
|
||||
}
|
||||
if (rootRows.length > 0) {
|
||||
rootRows.sort((a, b) => a.position - b.position);
|
||||
nodes.push(...rootRows.map((row) => ({ type: 'row' as const, id: row.id, row })));
|
||||
}
|
||||
return nodes.sort((a, b) => {
|
||||
const al = a.type === 'group' ? a.label : String(a.row.values[columnIndex] ?? '');
|
||||
const bl = b.type === 'group' ? b.label : String(b.row.values[columnIndex] ?? '');
|
||||
return al.localeCompare(bl, 'ru');
|
||||
});
|
||||
};
|
||||
|
||||
const filterNodes = (nodes: TreeNode[], query: string): TreeNode[] => {
|
||||
if (!query) return nodes;
|
||||
const result: TreeNode[] = [];
|
||||
for (const node of nodes) {
|
||||
if (node.type === 'row') {
|
||||
const label = String(node.row.values[0] ?? '').toLowerCase();
|
||||
if (label.includes(query)) result.push(node);
|
||||
} else {
|
||||
const children = filterNodes(node.children, query);
|
||||
if (children.length > 0) {
|
||||
result.push({ ...node, children });
|
||||
} else if (node.label.toLowerCase().includes(query)) {
|
||||
result.push({ ...node, children: [] });
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const rawNodes = treeColumnIndex != null
|
||||
? buildColumnTree(rows, treeColumnIndex)
|
||||
: buildParentTree(rows, null);
|
||||
const nodes = search ? filterNodes(rawNodes, search) : rawNodes;
|
||||
|
||||
res.json({ columnIndex: treeColumnIndex, nodes });
|
||||
} catch (error) {
|
||||
console.error('Get table rows tree error:', error);
|
||||
res.status(500).json({ error: 'Ошибка получения дерева строк' });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Create table row
|
||||
app.post(['/api/tables/:tableId/rows', '/api/directories/:tableId/rows'],
|
||||
authenticateToken,
|
||||
|
||||
Reference in New Issue
Block a user