From 09861fc966c05ca19894048d5d368f39f25d057f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=D1=81=20=D0=A1=D1=83=D0=BB=D1=82?=
=?UTF-8?q?=D0=B0=D0=BD=D0=BE=D0=B2?=
Date: Sun, 12 Jul 2026 23:07:34 +0300
Subject: [PATCH] =?UTF-8?q?feat:=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=B0?=
=?UTF-8?q?=D0=B8=D0=B2=D0=B0=D0=B5=D0=BC=D0=B0=D1=8F=20=D0=BA=D0=BE=D0=BB?=
=?UTF-8?q?=D0=BE=D0=BD=D0=BA=D0=B0=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0?=
=?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D0=B4=D0=B5=D1=80?=
=?UTF-8?q?=D0=B5=D0=B2=D0=B5=20=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BE=D1=87?=
=?UTF-8?q?=D0=BD=D0=B8=D0=BA=D0=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Добавлено поле data_tables.tree_display_column_index.
- Backend /api/directories/:id/rows/tree возвращает displayColumnIndex и
формирует label для row-узлов на основе выбранной колонки.
- В редакторе справочника добавлен селектор «Колонка для отображения в дереве».
- DirectoryRowTree, DirectoryColumnTree, DirectoryTreeSelect,
DirectoryTreeMultiSelect используют настроенную колонку.
- TableFieldSelect / TableFieldMultiSelect используют
treeDisplayColumnIndex справочника как fallback, если в поле формы
не задан displayColumn.
- Миграция 0057 + запись в _journal.json.
---
.../src/components/TableFieldMultiSelect.tsx | 4 +--
client/src/components/TableFieldSelect.tsx | 4 +--
.../directories/DirectoryRowTree.tsx | 7 ++--
.../directories/DirectoryTreeMultiSelect.tsx | 24 ++++----------
.../directories/DirectoryTreeSelect.tsx | 24 ++++----------
client/src/pages/TableEditor.tsx | 32 ++++++++++++++++---
.../0057_add_tree_display_column_index.sql | 1 +
migrations/meta/_journal.json | 7 ++++
server/routes/data-tables.routes.ts | 29 +++++++++++------
shared/schema.ts | 1 +
10 files changed, 76 insertions(+), 57 deletions(-)
create mode 100644 migrations/0057_add_tree_display_column_index.sql
diff --git a/client/src/components/TableFieldMultiSelect.tsx b/client/src/components/TableFieldMultiSelect.tsx
index cad0151..4469e57 100644
--- a/client/src/components/TableFieldMultiSelect.tsx
+++ b/client/src/components/TableFieldMultiSelect.tsx
@@ -35,6 +35,7 @@ export default function TableFieldMultiSelect({
rows: DataTableRow[];
columns: Array<{ name: string }>;
treeColumnIndex: number | null;
+ treeDisplayColumnIndex: number | null;
};
}>({
queryKey: ['/api/directories', tableId],
@@ -69,7 +70,7 @@ export default function TableFieldMultiSelect({
let displayColumnIndex = table?.columns?.findIndex((c) => c.name === displayColumn);
if (displayColumnIndex === undefined || displayColumnIndex < 0) {
- displayColumnIndex = 0;
+ displayColumnIndex = table?.treeDisplayColumnIndex ?? 0;
}
const selectedValues = useMemo(() => {
@@ -166,7 +167,6 @@ export default function TableFieldMultiSelect({
{hasTree ? (
;
treeColumnIndex: number | null;
+ treeDisplayColumnIndex: number | null;
};
}>({
queryKey: ['/api/directories', tableId],
@@ -67,7 +68,7 @@ export default function TableFieldSelect({
let displayColumnIndex = table?.columns?.findIndex((c) => c.name === displayColumn);
if (displayColumnIndex === undefined || displayColumnIndex < 0) {
- displayColumnIndex = 0;
+ displayColumnIndex = table?.treeDisplayColumnIndex ?? 0;
}
const sortedRows = useMemo(() => {
@@ -151,7 +152,6 @@ export default function TableFieldSelect({
{hasTree ? (
): D
function filterTree(
tree: DirectoryRowTreeItem[],
- query: string
+ query: string,
+ labelColumnIndex = 0
): { tree: DirectoryRowTreeItem[]; expanded: Set } {
if (!query.trim()) return { tree, expanded: new Set() };
const q = query.toLowerCase();
@@ -115,7 +116,7 @@ function filterTree(
function walk(nodes: DirectoryRowTreeItem[]): DirectoryRowTreeItem[] {
const visible: DirectoryRowTreeItem[] = [];
for (const node of nodes) {
- const label = String(node.values[0] ?? '').toLowerCase();
+ const label = getRowLabel(node, labelColumnIndex).toLowerCase();
const children = walk(node.children);
const childMatch = children.length > 0;
if (label.includes(q) || childMatch) {
@@ -298,7 +299,7 @@ export function DirectoryRowTree({
searchQuery,
}: DirectoryRowTreeProps & { searchQuery?: string }) {
const tree = useMemo(() => buildTree(rows), [rows]);
- const filtered = useMemo(() => filterTree(tree, searchQuery || ''), [tree, searchQuery]);
+ const filtered = useMemo(() => filterTree(tree, searchQuery || '', labelColumnIndex), [tree, searchQuery, labelColumnIndex]);
const effectiveExpanded = useMemo(
() => (searchQuery?.trim() ? filtered.expanded : expanded),
[searchQuery, filtered.expanded, expanded]
diff --git a/client/src/components/directories/DirectoryTreeMultiSelect.tsx b/client/src/components/directories/DirectoryTreeMultiSelect.tsx
index 65c56f3..27f3fec 100644
--- a/client/src/components/directories/DirectoryTreeMultiSelect.tsx
+++ b/client/src/components/directories/DirectoryTreeMultiSelect.tsx
@@ -6,7 +6,7 @@ import type { DataTableRow } from '@shared/schema';
export type DirectoryTreeNode =
| { type: 'group'; id: string; label: string; level: number; children: DirectoryTreeNode[] }
- | { type: 'row'; id: number; level: number; row: DataTableRow };
+ | { type: 'row'; id: number; label: string; level: number; row: DataTableRow };
function assignLevels(nodes: DirectoryTreeNode[], level = 0): DirectoryTreeNode[] {
return nodes.map((node) => {
@@ -19,7 +19,6 @@ function assignLevels(nodes: DirectoryTreeNode[], level = 0): DirectoryTreeNode[
interface DirectoryTreeMultiSelectProps {
nodes: DirectoryTreeNode[];
- displayColumnIndex: number;
selected: Set;
onToggle: (rowId: string) => void;
searchTerm?: string;
@@ -27,8 +26,7 @@ interface DirectoryTreeMultiSelectProps {
function filterNodes(
nodes: DirectoryTreeNode[],
- query: string,
- displayColumnIndex: number
+ query: string
): { nodes: DirectoryTreeNode[]; expanded: Set } {
if (!query.trim()) return { nodes, expanded: new Set() };
const q = query.toLowerCase();
@@ -38,7 +36,7 @@ function filterNodes(
const visible: DirectoryTreeNode[] = [];
for (const node of items) {
if (node.type === 'row') {
- const label = String(node.row.values[displayColumnIndex] ?? node.row.values[0] ?? '').toLowerCase();
+ const label = node.label.toLowerCase();
if (label.includes(q)) visible.push(node);
} else {
const children = walk(node.children);
@@ -56,21 +54,14 @@ function filterNodes(
return { nodes: walk(nodes), expanded };
}
-function getRowLabel(row: DataTableRow, displayColumnIndex: number): string {
- const values = Array.isArray(row.values) ? row.values : [];
- return String(values[displayColumnIndex] ?? values[0] ?? '') || '(без названия)';
-}
-
function TreeNode({
node,
- displayColumnIndex,
selected,
onToggle,
expanded,
onToggleExpand,
}: {
node: DirectoryTreeNode;
- displayColumnIndex: number;
selected: Set;
onToggle: (rowId: string) => void;
expanded: Set;
@@ -87,7 +78,7 @@ function TreeNode({
- {getRowLabel(node.row, displayColumnIndex)}
+ {node.label}
);
@@ -117,7 +108,6 @@ function TreeNode({
assignLevels(nodes), [nodes]);
const filtered = useMemo(
- () => filterNodes(nodesWithLevels, searchTerm, displayColumnIndex),
- [nodesWithLevels, searchTerm, displayColumnIndex]
+ () => filterNodes(nodesWithLevels, searchTerm),
+ [nodesWithLevels, searchTerm]
);
const [manualExpanded, setManualExpanded] = useState>(new Set());
@@ -168,7 +157,6 @@ export function DirectoryTreeMultiSelect({
{
@@ -18,7 +18,6 @@ function assignLevels(nodes: DirectoryTreeNode[], level = 0): DirectoryTreeNode[
interface DirectoryTreeSelectProps {
nodes: DirectoryTreeNode[];
- displayColumnIndex: number;
value?: string | number | null;
onChange: (rowId: string) => void;
searchTerm?: string;
@@ -26,8 +25,7 @@ interface DirectoryTreeSelectProps {
function filterNodes(
nodes: DirectoryTreeNode[],
- query: string,
- displayColumnIndex: number
+ query: string
): { nodes: DirectoryTreeNode[]; expanded: Set } {
if (!query.trim()) return { nodes, expanded: new Set() };
const q = query.toLowerCase();
@@ -37,7 +35,7 @@ function filterNodes(
const visible: DirectoryTreeNode[] = [];
for (const node of items) {
if (node.type === 'row') {
- const label = String(node.row.values[displayColumnIndex] ?? node.row.values[0] ?? '').toLowerCase();
+ const label = node.label.toLowerCase();
if (label.includes(q)) visible.push(node);
} else {
const children = walk(node.children);
@@ -55,21 +53,14 @@ function filterNodes(
return { nodes: walk(nodes), expanded };
}
-function getRowLabel(row: DataTableRow, displayColumnIndex: number): string {
- const values = Array.isArray(row.values) ? row.values : [];
- return String(values[displayColumnIndex] ?? values[0] ?? '') || '(без названия)';
-}
-
function TreeNode({
node,
- displayColumnIndex,
value,
onChange,
expanded,
onToggle,
}: {
node: DirectoryTreeNode;
- displayColumnIndex: number;
value?: string | number | null;
onChange: (rowId: string) => void;
expanded: Set;
@@ -89,7 +80,7 @@ function TreeNode({
onClick={() => onChange(rowId)}
>
- {getRowLabel(node.row, displayColumnIndex)}
+ {node.label}
);
}
@@ -116,7 +107,6 @@ function TreeNode({
assignLevels(nodes), [nodes]);
const filtered = useMemo(
- () => filterNodes(nodesWithLevels, searchTerm, displayColumnIndex),
- [nodesWithLevels, searchTerm, displayColumnIndex]
+ () => filterNodes(nodesWithLevels, searchTerm),
+ [nodesWithLevels, searchTerm]
);
const [manualExpanded, setManualExpanded] = useState>(new Set());
@@ -176,7 +165,6 @@ export function DirectoryTreeSelect({
([]);
const [treeColumnIndex, setTreeColumnIndex] = useState(null);
+ const [treeDisplayColumnIndex, setTreeDisplayColumnIndex] = useState(null);
const [importOpen, setImportOpen] = useState(false);
const [importPreview, setImportPreview] = useState<{
added: { values: string[] }[];
@@ -175,6 +176,7 @@ export default function TableEditor() {
setTableDescription(data.table.description || '');
setTableColumns(data.table.columns as TableColumn[]);
setTreeColumnIndex(data.table.treeColumnIndex ?? null);
+ setTreeDisplayColumnIndex(data.table.treeDisplayColumnIndex ?? null);
setNewRowValues(new Array(data.table.columns.length).fill(''));
}
}, [data]);
@@ -186,7 +188,7 @@ export default function TableEditor() {
}, [rowEditorOpen, rowEditorInitialValues]);
const updateTableMutation = useMutation({
- mutationFn: async (updates: { name?: string; description?: string; columns?: TableColumn[]; treeColumnIndex?: number | null }) => {
+ mutationFn: async (updates: { name?: string; description?: string; columns?: TableColumn[]; treeColumnIndex?: number | null; treeDisplayColumnIndex?: number | null }) => {
return apiRequest('PATCH', `/api/directories/${tableId}`, updates);
},
onSuccess: () => {
@@ -723,7 +725,7 @@ export default function TableEditor() {
- {activeRow.values[0] || '—'}
+ {activeRow.values[treeDisplayColumnIndex ?? 0] || activeRow.values[0] || '—'}
) : null}
@@ -1195,13 +1198,34 @@ export default function TableEditor() {
Строки будут сгруппированы по уникальным значениям выбранной колонки.
+
+
+
+
+ Значение этой колонки будет показано как название узла в дереве.
+
+