Skip to content

Commit 762f8c6

Browse files
committed
app/vmui: allow table column reordering
- add drag-and-drop ordering in table settings and store order in URL - render table columns in chosen order and restyle drag handle - add TableLogs order unit test Testing: npm run lint:local; npm test Fixes: #714
1 parent f001283 commit 762f8c6

File tree

4 files changed

+178
-2
lines changed

4 files changed

+178
-2
lines changed

app/vmui/packages/vmui/src/components/Table/TableSettings/TableSettings.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ const TableSettings: FC<TableSettingsProps> = ({
4141

4242
const [searchColumn, setSearchColumn] = useState("");
4343
const [indexFocusItem, setIndexFocusItem] = useState(-1);
44+
const [draggingColumn, setDraggingColumn] = useState<string | null>(null);
45+
const [dragOverColumn, setDragOverColumn] = useState<string | null>(null);
4446

4547
const customColumns = useMemo(() => {
4648
return selectedColumns.filter(col => !columns.includes(col));
@@ -122,6 +124,43 @@ const TableSettings: FC<TableSettingsProps> = ({
122124
onChangeColumns(columnsArray);
123125
}, []);
124126

127+
const handleDragStart = (column: string) => (e: DragEvent) => {
128+
setDraggingColumn(column);
129+
e.dataTransfer?.setData("text/plain", column);
130+
};
131+
132+
const handleDragOver = (column: string) => (e: DragEvent) => {
133+
e.preventDefault();
134+
if (dragOverColumn === column) return;
135+
setDragOverColumn(column);
136+
};
137+
138+
const handleDragLeave = () => {
139+
setDragOverColumn(null);
140+
};
141+
142+
const handleDrop = (targetColumn: string) => (e: DragEvent) => {
143+
e.preventDefault();
144+
if (!draggingColumn || draggingColumn === targetColumn) return;
145+
146+
const fromIndex = selectedColumns.indexOf(draggingColumn);
147+
const toIndex = selectedColumns.indexOf(targetColumn);
148+
if (fromIndex === -1 || toIndex === -1) return;
149+
150+
const updatedColumns = [...selectedColumns];
151+
updatedColumns.splice(fromIndex, 1);
152+
updatedColumns.splice(toIndex, 0, draggingColumn);
153+
154+
handleChangeDisplayColumns(updatedColumns);
155+
setDragOverColumn(null);
156+
setDraggingColumn(null);
157+
};
158+
159+
const handleDragEnd = () => {
160+
setDragOverColumn(null);
161+
setDraggingColumn(null);
162+
};
163+
125164
return (
126165
<div className="vm-table-settings">
127166
<Tooltip title={title}>
@@ -193,6 +232,39 @@ const TableSettings: FC<TableSettingsProps> = ({
193232
))}
194233
</div>
195234
</div>
235+
{!!selectedColumns.length && (
236+
<div className="vm-table-settings-modal-columns-order">
237+
<div className="vm-table-settings-modal-columns-order__title">
238+
Drag to reorder selected columns
239+
</div>
240+
<div className="vm-table-settings-modal-columns-order__list">
241+
{selectedColumns.map((col) => (
242+
<div
243+
className={classNames({
244+
"vm-table-settings-modal-columns-order__item": true,
245+
"vm-table-settings-modal-columns-order__item_dragging": draggingColumn === col,
246+
"vm-table-settings-modal-columns-order__item_over": dragOverColumn === col,
247+
})}
248+
key={col}
249+
draggable
250+
onDragStart={handleDragStart(col)}
251+
onDragOver={handleDragOver(col)}
252+
onDragLeave={handleDragLeave}
253+
onDrop={handleDrop(col)}
254+
onDragEnd={handleDragEnd}
255+
>
256+
<span
257+
className="vm-table-settings-modal-columns-order__drag"
258+
aria-hidden="true"
259+
/>
260+
<span className="vm-table-settings-modal-columns-order__label">
261+
{col}
262+
</span>
263+
</div>
264+
))}
265+
</div>
266+
</div>
267+
)}
196268
</div>
197269
{toggleTableCompact && tableCompact !== undefined && (
198270
<div className="vm-table-settings-modal-section">

app/vmui/packages/vmui/src/components/Table/TableSettings/style.scss

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,5 +88,74 @@
8888
}
8989
}
9090
}
91+
92+
&-columns-order {
93+
padding: 0 $padding-global $padding-global;
94+
display: flex;
95+
flex-direction: column;
96+
gap: $padding-small;
97+
98+
&__title {
99+
font-size: $font-size;
100+
font-weight: bold;
101+
}
102+
103+
&__list {
104+
display: flex;
105+
flex-direction: column;
106+
gap: $padding-small;
107+
}
108+
109+
&__item {
110+
display: grid;
111+
grid-template-columns: 16px 1fr;
112+
align-items: center;
113+
gap: $padding-small;
114+
padding: $padding-small $padding-global;
115+
border: $border-divider;
116+
border-radius: $border-radius-small;
117+
background-color: $color-background-block;
118+
cursor: grab;
119+
120+
&_dragging {
121+
cursor: grabbing;
122+
opacity: 0.7;
123+
}
124+
125+
&_over {
126+
background-color: $color-hover-black;
127+
border-color: $color-secondary;
128+
}
129+
}
130+
131+
&__drag {
132+
width: 16px;
133+
height: 16px;
134+
display: inline-flex;
135+
align-items: center;
136+
justify-content: center;
137+
color: $color-text-secondary;
138+
139+
&:before {
140+
content: "";
141+
display: block;
142+
width: 10px;
143+
height: 2px;
144+
background-color: currentColor;
145+
border-radius: 2px;
146+
box-shadow:
147+
0 -4px 0 currentColor,
148+
0 4px 0 currentColor;
149+
}
150+
}
151+
152+
&__label {
153+
overflow: hidden;
154+
text-overflow: ellipsis;
155+
white-space: nowrap;
156+
text-decoration: none;
157+
font-family: $font-family-monospace;
158+
}
159+
}
91160
}
92161
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { render, screen } from "@testing-library/preact";
2+
import { describe, expect, it } from "vitest";
3+
import TableLogs from "./TableLogs";
4+
5+
const logs = [
6+
{ _time: "2025-01-01T00:00:00Z", file: "a.go", _msg: "first message" },
7+
{ _time: "2025-01-01T00:01:00Z", file: "b.go", _msg: "second message" },
8+
];
9+
10+
describe("TableLogs", () => {
11+
it("renders columns in the provided display order", () => {
12+
render(
13+
<TableLogs
14+
logs={logs}
15+
displayColumns={["_msg", "file", "_time"]}
16+
tableCompact={false}
17+
columns={["_time", "file", "_msg"]}
18+
rowsPerPage={10}
19+
/>
20+
);
21+
22+
const headers = screen
23+
.getAllByRole("columnheader")
24+
.map((header) => header.textContent?.trim() || "");
25+
26+
expect(headers.slice(0, 3)).toEqual(["_msg", "file", "_time"]);
27+
});
28+
});

app/vmui/packages/vmui/src/components/Views/TableView/TableLogs.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,19 @@ const TableLogs: FC<TableLogsProps> = ({ logs, displayColumns, tableCompact, col
4545
}));
4646
}, [columns]);
4747

48+
const tableColumnsMap = useMemo(() => {
49+
return new Map(tableColumns.map((col) => [String(col.key), col]));
50+
}, [tableColumns]);
4851

4952
const filteredColumns = useMemo(() => {
5053
if (tableCompact) return compactColumns;
5154
if (!displayColumns?.length) return [];
52-
return tableColumns.filter(c => displayColumns.includes(c.key as string));
53-
}, [tableColumns, displayColumns, tableCompact]);
55+
return displayColumns.reduce<typeof tableColumns>((acc, key) => {
56+
const column = tableColumnsMap.get(key);
57+
if (column) acc.push(column);
58+
return acc;
59+
}, []);
60+
}, [tableColumnsMap, displayColumns, tableCompact]);
5461

5562
const paginationOffset = useMemo(() => {
5663
const startIndex = (page - 1) * rowsPerPage;

0 commit comments

Comments
 (0)