feat: port stroy-bot-objects as objects page

Project object tree with add/edit/delete, drag & drop re-parenting,
context menu and Excel export, 1:1 with the legacy web part. The tree
widgets live in pages/_tree so the works page can reuse them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Тимур Городецкий
2026-10-16 09:48:04 +05:00
parent 99a7631b65
commit e12fce6032
30 changed files with 2399 additions and 2 deletions
@@ -0,0 +1,64 @@
import * as React from 'react';
import { Button } from '@tnsu/ui-kit';
import { EmptyState } from '../../shared/components/EmptyState';
import { IMenuAnchor } from '../_tree/ContextMenu';
import { countNodes } from '../_tree/treeRules';
import { ObjectNode, ObjectTree } from './objectsTypes';
import { IObjectTreeView } from './useObjectTreeView';
import { IObjectDrag } from './useObjectDrag';
import { ObjectTreeNode } from './ObjectTreeNode';
import { OBJECTS_TEXTS } from './texts';
import styles from './ObjectsPage.module.scss';
interface Props {
tree: ObjectTree;
canEdit: boolean;
view: IObjectTreeView;
drag: IObjectDrag;
onOpenMenu: (node: ObjectNode, anchor: IMenuAnchor) => void;
onAddRoot: () => void;
}
/** Панель «N объектов / Развернуть / Свернуть» и дерево объектов (или «Объекты не найдены»). */
export const ObjectsTree: React.FC<Props> = ({ tree, canEdit, view, drag, onOpenMenu, onAddRoot }) => (
<>
<div className={styles.toolbar}>
<span className={styles.totalCount}>{OBJECTS_TEXTS.toolbar.total(countNodes(tree.roots))}</span>
<div className={styles.toolbarActions}>
<Button variant="secondary" size="sm" onClick={view.expandAll}>
{OBJECTS_TEXTS.toolbar.expandAll}
</Button>
<Button variant="secondary" size="sm" onClick={view.collapseAll}>
{OBJECTS_TEXTS.toolbar.collapseAll}
</Button>
</div>
</div>
{tree.roots.length === 0 && (
<EmptyState
icon="folder"
title={OBJECTS_TEXTS.empty}
action={
canEdit && (
<Button variant="primary" onClick={onAddRoot}>
{OBJECTS_TEXTS.emptyAdd}
</Button>
)
}
/>
)}
<div className={styles.tree} onDrop={drag.onDrop}>
{tree.roots.map((node) => (
<ObjectTreeNode
key={node.id}
node={node}
canEdit={canEdit}
view={view}
drag={drag}
onOpenMenu={onOpenMenu}
/>
))}
</div>
</>
);