feat: группа без детей с пустым отображением становится конечной строкой в дереве
This commit is contained in:
@@ -23,6 +23,7 @@ interface TreeGroup {
|
||||
values: Record<number, string>;
|
||||
rows: DataTableRow[];
|
||||
children: TreeGroup[];
|
||||
isLeaf?: boolean;
|
||||
}
|
||||
|
||||
function getRowLabel(row: DataTableRow, displayColumns: number[]): string {
|
||||
@@ -43,6 +44,7 @@ function getRowLabel(row: DataTableRow, displayColumns: number[]): string {
|
||||
function buildTree(
|
||||
rows: DataTableRow[],
|
||||
treeColumns: number[],
|
||||
displayColumns: number[],
|
||||
level = 0,
|
||||
values: Record<number, string> = {}
|
||||
): TreeGroup[] {
|
||||
@@ -68,19 +70,38 @@ function buildTree(
|
||||
const result: TreeGroup[] = [];
|
||||
for (const [label, groupRows] of groups) {
|
||||
const groupValues = { ...values, [columnIndex]: label };
|
||||
result.push({
|
||||
id: `${level}:${label}`,
|
||||
label,
|
||||
level,
|
||||
values: groupValues,
|
||||
rows: groupRows,
|
||||
children: buildTree(groupRows, treeColumns, level + 1, groupValues),
|
||||
// Если у группы все строки имеют пустое значение в displayColumns,
|
||||
// группа становится конечной строкой (можно выбрать в задачах)
|
||||
const allEmpty = groupRows.every((row) => {
|
||||
const vals = Array.isArray(row.values) ? row.values : [];
|
||||
return displayColumns.every((ci) => !String(vals[ci] ?? '').trim());
|
||||
});
|
||||
|
||||
if (allEmpty && groupRows.length === 1) {
|
||||
result.push({
|
||||
id: `${level}:${label}`,
|
||||
label,
|
||||
level,
|
||||
values: groupValues,
|
||||
rows: groupRows,
|
||||
children: [],
|
||||
isLeaf: true,
|
||||
});
|
||||
} else {
|
||||
result.push({
|
||||
id: `${level}:${label}`,
|
||||
label,
|
||||
level,
|
||||
values: groupValues,
|
||||
rows: groupRows,
|
||||
children: buildTree(groupRows, treeColumns, displayColumns, level + 1, groupValues),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Строки без значения в этой колонке относятся к корню текущего уровня
|
||||
if (rootRows.length > 0) {
|
||||
const rootGroup = buildTree(rootRows, treeColumns, level + 1, values);
|
||||
const rootGroup = buildTree(rootRows, treeColumns, displayColumns, level + 1, values);
|
||||
result.push(...rootGroup);
|
||||
}
|
||||
|
||||
@@ -149,7 +170,54 @@ function TreeNode({
|
||||
onAddChild,
|
||||
}: TreeNodeProps) {
|
||||
const isExpanded = expanded.has(group.id);
|
||||
const isLeaf = group.children.length === 0;
|
||||
const isLeaf = group.isLeaf || group.children.length === 0;
|
||||
|
||||
if (group.isLeaf && group.rows.length === 1) {
|
||||
const row = group.rows[0];
|
||||
const isSelected = selectedRowId === row.id;
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'group flex items-center gap-1 rounded-md px-2 py-1.5 text-sm cursor-pointer select-none transition-colors',
|
||||
isSelected ? 'bg-accent text-accent-foreground' : 'hover:bg-muted'
|
||||
)}
|
||||
style={{ paddingLeft: `${group.level * 16 + 28}px` }}
|
||||
onClick={() => onSelect(row)}
|
||||
>
|
||||
<FileText className="w-4 h-4 shrink-0 text-muted-foreground" />
|
||||
<span className="flex-1 break-words whitespace-normal leading-tight">{group.label}</span>
|
||||
<div
|
||||
className="shrink-0 opacity-0 group-hover:opacity-100 transition-opacity flex items-center"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6"
|
||||
title="Редактировать"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEdit(row);
|
||||
}}
|
||||
>
|
||||
<Pencil className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 text-destructive"
|
||||
title="Удалить"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete(row);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -262,7 +330,7 @@ export function DirectoryColumnTree({
|
||||
searchQuery,
|
||||
}: DirectoryColumnTreeProps) {
|
||||
const groups = useMemo(
|
||||
() => buildTree(rows, treeColumns),
|
||||
() => buildTree(rows, treeColumns, treeDisplayColumns),
|
||||
[rows, treeColumns]
|
||||
);
|
||||
|
||||
|
||||
@@ -585,13 +585,30 @@ export function registerDataTableRoutes(app: import("express").Express): void {
|
||||
|
||||
const nodes: TreeNode[] = [];
|
||||
for (const [label, groupRows] of groups) {
|
||||
nodes.push({
|
||||
type: 'group',
|
||||
id: `group:${level}:${label}`,
|
||||
label,
|
||||
level,
|
||||
children: buildMultiColumnTree(groupRows, columns, level + 1),
|
||||
// Если у группы все строки имеют пустое значение в treeDisplayColumns,
|
||||
// группа становится конечной строкой (можно выбрать в задачах)
|
||||
const allEmpty = groupRows.every((row) => {
|
||||
const vals = Array.isArray(row.values) ? row.values : [];
|
||||
return treeDisplayColumns.every((ci) => !String(vals[ci] ?? '').trim());
|
||||
});
|
||||
|
||||
if (allEmpty && groupRows.length === 1) {
|
||||
nodes.push({
|
||||
type: 'row',
|
||||
id: groupRows[0].id,
|
||||
label,
|
||||
level,
|
||||
row: groupRows[0],
|
||||
});
|
||||
} else {
|
||||
nodes.push({
|
||||
type: 'group',
|
||||
id: `group:${level}:${label}`,
|
||||
label,
|
||||
level,
|
||||
children: buildMultiColumnTree(groupRows, columns, level + 1),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (rootRows.length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user