Added column sorting

This commit is contained in:
2026-05-05 20:48:54 +02:00
parent 1aace105c7
commit 47dec0df39
2 changed files with 522 additions and 42 deletions
+70
View File
@@ -49,6 +49,7 @@ body {
.editor-toolbar {
display: flex;
gap: 0.5rem;
position: relative;
}
.editor-toolbar button {
@@ -63,6 +64,75 @@ body {
opacity: 0.45;
}
.column-settings-menu {
position: absolute;
z-index: 9;
margin-top: 2rem;
border: 1px solid #cfd7e5;
background: #fff;
border-radius: 4px;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
padding: 0.45rem;
width: 300px;
}
.column-settings-actions {
display: flex;
justify-content: flex-end;
margin-bottom: 0.35rem;
}
.column-settings-list {
display: flex;
flex-direction: column;
gap: 0.25rem;
max-height: 340px;
overflow: auto;
}
.column-settings-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.4rem;
border: 1px solid transparent;
border-radius: 4px;
padding: 0.2rem 0.25rem;
}
.column-settings-item.dragging {
opacity: 0.55;
}
.column-settings-item.drop-target {
border-color: #2b6cb0;
background: #ebf4ff;
}
.column-settings-item.locked {
background: #f7fafc;
}
.column-settings-item label {
display: flex;
gap: 0.3rem;
align-items: center;
font-size: 0.8rem;
}
.column-settings-order {
display: flex;
gap: 0.2rem;
}
.column-settings-order button {
border: 1px solid #c4cddc;
background: #fff;
border-radius: 3px;
font-size: 0.72rem;
padding: 0.08rem 0.28rem;
}
.tree-grid-wrap {
overflow: auto;
border: 1px solid #d9dee8;
+440 -30
View File
@@ -34,7 +34,26 @@ type CellKey =
| "protectionSummary"
| "cableSummary"
| "roomSummary"
| "remark";
| "remark"
| "technicalName"
| "connectionKind"
| "phaseType"
| "costGroup"
| "category"
| "level"
| "roomNumberSnapshot"
| "roomNameSnapshot"
| "cosPhi"
| "protectionType"
| "protectionRatedCurrent"
| "protectionCharacteristic"
| "cableType"
| "cableCrossSection"
| "cableLength"
| "rcdAssignment"
| "terminalDesignation"
| "status"
| "isReserve";
type SaveDirection = "stay" | "next" | "prev";
type StartEditMode = "selectExisting" | "replaceWithTypedChar";
@@ -118,37 +137,117 @@ type CircuitReorderDropIntent =
| { kind: "after-circuit"; sectionId: string; targetCircuitId: string; valid: boolean }
| { kind: "section-end"; sectionId: string; valid: boolean };
const columns: Array<{ key: CellKey; label: string; numeric?: boolean }> = [
{ key: "equipmentIdentifier", label: "Equipment identifier" },
{ key: "displayName", label: "Display name" },
{ key: "quantity", label: "Quantity", numeric: true },
{ key: "powerPerUnit", label: "Power / unit", numeric: true },
{ key: "simultaneityFactor", label: "Simultaneity", numeric: true },
{ key: "rowTotalPower", label: "Row total", numeric: true },
{ key: "circuitTotalPower", label: "Circuit total", numeric: true },
{ key: "protectionSummary", label: "Protection summary" },
{ key: "cableSummary", label: "Cable summary" },
{ key: "roomSummary", label: "Room" },
{ key: "remark", label: "Remark" },
const COLUMN_LAYOUT_STORAGE_KEY = "circuitTreeEditor.columnLayout.v1";
interface ColumnDef {
key: CellKey;
label: string;
numeric?: boolean;
defaultVisible?: boolean;
locked?: boolean;
}
const allColumns: ColumnDef[] = [
{ key: "equipmentIdentifier", label: "Equipment identifier", defaultVisible: true, locked: true },
{ key: "displayName", label: "Display name", defaultVisible: true },
{ key: "quantity", label: "Quantity", numeric: true, defaultVisible: true },
{ key: "powerPerUnit", label: "Power / unit", numeric: true, defaultVisible: true },
{ key: "simultaneityFactor", label: "Simultaneity", numeric: true, defaultVisible: true },
{ key: "rowTotalPower", label: "Row total", numeric: true, defaultVisible: true },
{ key: "circuitTotalPower", label: "Circuit total", numeric: true, defaultVisible: true },
{ key: "protectionSummary", label: "Protection summary", defaultVisible: true },
{ key: "cableSummary", label: "Cable summary", defaultVisible: true },
{ key: "roomSummary", label: "Room", defaultVisible: true },
{ key: "remark", label: "Remark", defaultVisible: true },
{ key: "technicalName", label: "Technical name" },
{ key: "connectionKind", label: "Connection kind" },
{ key: "phaseType", label: "Phase type" },
{ key: "costGroup", label: "Cost group" },
{ key: "category", label: "Category" },
{ key: "level", label: "Level" },
{ key: "roomNumberSnapshot", label: "Room number" },
{ key: "roomNameSnapshot", label: "Room name" },
{ key: "cosPhi", label: "cosPhi", numeric: true },
{ key: "protectionType", label: "Protection type" },
{ key: "protectionRatedCurrent", label: "Protection rated current", numeric: true },
{ key: "protectionCharacteristic", label: "Protection characteristic" },
{ key: "cableType", label: "Cable type" },
{ key: "cableCrossSection", label: "Cable cross-section" },
{ key: "cableLength", label: "Cable length", numeric: true },
{ key: "rcdAssignment", label: "RCD assignment" },
{ key: "terminalDesignation", label: "Terminal designation" },
{ key: "status", label: "Status" },
{ key: "isReserve", label: "Reserve" },
];
const deviceOnlyColumns = new Set<CellKey>(["quantity", "powerPerUnit", "simultaneityFactor", "rowTotalPower", "roomSummary"]);
const circuitOnlyColumns = new Set<CellKey>(["equipmentIdentifier", "protectionSummary", "cableSummary", "circuitTotalPower"]);
const defaultVisibleColumnKeys = allColumns.filter((column) => column.defaultVisible).map((column) => column.key);
const deviceFieldKeys = new Set<CellKey>([
"displayName",
"roomSummary",
const deviceOnlyColumns = new Set<CellKey>([
"quantity",
"powerPerUnit",
"simultaneityFactor",
"rowTotalPower",
"roomSummary",
"technicalName",
"connectionKind",
"phaseType",
"costGroup",
"category",
"level",
"roomNumberSnapshot",
"roomNameSnapshot",
"cosPhi",
]);
const circuitOnlyColumns = new Set<CellKey>([
"equipmentIdentifier",
"protectionSummary",
"cableSummary",
"circuitTotalPower",
"protectionType",
"protectionRatedCurrent",
"protectionCharacteristic",
"cableType",
"cableCrossSection",
"cableLength",
"rcdAssignment",
"terminalDesignation",
"status",
"isReserve",
]);
const deviceFieldKeys = new Set<CellKey>([
"displayName",
"technicalName",
"connectionKind",
"phaseType",
"costGroup",
"category",
"level",
"roomSummary",
"roomNumberSnapshot",
"roomNameSnapshot",
"quantity",
"powerPerUnit",
"simultaneityFactor",
"cosPhi",
"remark",
]);
const circuitFieldKeys = new Set<CellKey>([
"equipmentIdentifier",
"displayName",
"protectionType",
"protectionRatedCurrent",
"protectionCharacteristic",
"protectionSummary",
"cableType",
"cableCrossSection",
"cableLength",
"cableSummary",
"rcdAssignment",
"terminalDesignation",
"status",
"isReserve",
"remark",
]);
@@ -198,16 +297,34 @@ function parseNumeric(cellKey: CellKey, draft: string): number | undefined {
function getDeviceValue(device: CircuitTreeDeviceRowDto, key: CellKey): string | number | boolean | undefined {
switch (key) {
case "technicalName":
return device.name;
case "displayName":
return device.displayName || device.name;
case "phaseType":
return device.phaseType;
case "connectionKind":
return device.connectionKind;
case "costGroup":
return device.costGroup;
case "category":
return device.category;
case "level":
return device.level;
case "roomSummary":
return [device.roomNumberSnapshot, device.roomNameSnapshot].filter(Boolean).join(" ").trim() || undefined;
case "roomNumberSnapshot":
return device.roomNumberSnapshot;
case "roomNameSnapshot":
return device.roomNameSnapshot;
case "quantity":
return device.quantity;
case "powerPerUnit":
return device.powerPerUnit;
case "simultaneityFactor":
return device.simultaneityFactor;
case "cosPhi":
return device.cosPhi;
case "rowTotalPower":
return device.rowTotalPower;
case "remark":
@@ -225,6 +342,12 @@ function getCircuitValue(circuit: CircuitTreeCircuitDto, key: CellKey): string |
return circuit.displayName;
case "circuitTotalPower":
return circuit.circuitTotalPower;
case "protectionType":
return circuit.protectionType;
case "protectionRatedCurrent":
return circuit.protectionRatedCurrent;
case "protectionCharacteristic":
return circuit.protectionCharacteristic;
case "protectionSummary": {
const current =
circuit.protectionRatedCurrent !== undefined && circuit.protectionRatedCurrent !== null
@@ -240,6 +363,20 @@ function getCircuitValue(circuit: CircuitTreeCircuitDto, key: CellKey): string |
circuit.cableLength !== undefined && circuit.cableLength !== null ? `${circuit.cableLength} m` : "";
return [circuit.cableType, circuit.cableCrossSection, length].filter(Boolean).join(", ").trim() || undefined;
}
case "cableType":
return circuit.cableType;
case "cableCrossSection":
return circuit.cableCrossSection;
case "cableLength":
return circuit.cableLength;
case "rcdAssignment":
return circuit.rcdAssignment;
case "terminalDesignation":
return circuit.terminalDesignation;
case "status":
return circuit.status;
case "isReserve":
return circuit.isReserve;
case "remark":
return circuit.remark;
default:
@@ -350,12 +487,26 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
const [sortState, setSortState] = useState<{ key: CellKey; direction: SortDirection } | null>(null);
const [columnFilters, setColumnFilters] = useState<Partial<Record<CellKey, string[]>>>({});
const [openFilterColumn, setOpenFilterColumn] = useState<CellKey | null>(null);
const [visibleColumnKeys, setVisibleColumnKeys] = useState<CellKey[]>(defaultVisibleColumnKeys);
const [columnOrder, setColumnOrder] = useState<CellKey[]>(allColumns.map((column) => column.key));
const [isColumnMenuOpen, setIsColumnMenuOpen] = useState(false);
const [draggingColumnKey, setDraggingColumnKey] = useState<CellKey | null>(null);
const [columnDropTargetKey, setColumnDropTargetKey] = useState<CellKey | null>(null);
const [pendingFocus, setPendingFocus] = useState<SelectedCell | null>(null);
const pendingSelectionAfterReload = useRef<SelectionIntent | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
const inputRef = useRef<HTMLInputElement | null>(null);
const focusTokenRef = useRef(1);
const orderedColumns = useMemo(
() => columnOrder.map((key) => allColumns.find((column) => column.key === key)).filter(Boolean) as ColumnDef[],
[columnOrder]
);
const visibleColumns = useMemo(
() => orderedColumns.filter((column) => visibleColumnKeys.includes(column.key)),
[orderedColumns, visibleColumnKeys]
);
async function loadTree(options?: { showLoading?: boolean }) {
const showLoading = options?.showLoading ?? false;
if (showLoading) {
@@ -374,6 +525,13 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
}
}
function normalizeColumnOrder(keys: CellKey[]) {
const unique = [...new Set(keys)];
const allKeys = allColumns.map((column) => column.key);
const merged = [...unique, ...allKeys.filter((key) => !unique.includes(key))];
return ["equipmentIdentifier" as CellKey, ...merged.filter((key) => key !== "equipmentIdentifier")];
}
useEffect(() => {
void loadTree({ showLoading: true });
}, [projectId, circuitListId]);
@@ -390,6 +548,54 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
void loadProjectDeviceList();
}, [projectId]);
useEffect(() => {
try {
const raw = localStorage.getItem(COLUMN_LAYOUT_STORAGE_KEY);
if (!raw) {
return;
}
const parsed = JSON.parse(raw) as { order?: string[]; visible?: string[] };
const validKeys = new Set(allColumns.map((column) => column.key));
const parsedOrder = (parsed.order ?? []).filter((key): key is CellKey => validKeys.has(key as CellKey));
const parsedVisible = (parsed.visible ?? []).filter((key): key is CellKey => validKeys.has(key as CellKey));
if (!parsedOrder.length || !parsedVisible.length || !parsedVisible.includes("equipmentIdentifier")) {
return;
}
setColumnOrder(normalizeColumnOrder(parsedOrder));
setVisibleColumnKeys(parsedVisible);
} catch {
// ignore invalid local storage and use defaults
}
}, []);
useEffect(() => {
const payload = {
order: columnOrder,
visible: visibleColumnKeys,
};
localStorage.setItem(COLUMN_LAYOUT_STORAGE_KEY, JSON.stringify(payload));
}, [columnOrder, visibleColumnKeys]);
useEffect(() => {
const visible = new Set(visibleColumnKeys);
setSortState((current) => {
if (!current) {
return current;
}
return visible.has(current.key) ? current : null;
});
setColumnFilters((current) => {
const next: Partial<Record<CellKey, string[]>> = {};
for (const [key, values] of Object.entries(current)) {
if (visible.has(key as CellKey) && values && values.length > 0) {
next[key as CellKey] = values;
}
}
return next;
});
setOpenFilterColumn((current) => (current && visible.has(current) ? current : null));
}, [visibleColumnKeys]);
const hasActiveFilters = useMemo(
() => Object.values(columnFilters).some((values) => (values?.length ?? 0) > 0),
[columnFilters]
@@ -397,7 +603,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
const distinctValuesByColumn = useMemo(() => {
const result = {} as Record<CellKey, string[]>;
for (const column of columns) {
for (const column of visibleColumns) {
const set = new Set<string>();
for (const section of data?.sections ?? []) {
for (const circuit of section.circuits) {
@@ -410,7 +616,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
result[column.key] = [...set].sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
}
return result;
}, [data]);
}, [data, visibleColumns]);
const filteredSortedSections = useMemo(() => {
if (!data) {
@@ -492,7 +698,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
rowKey: `section:${section.id}`,
rowType: "section",
sectionId: section.id,
cells: columns.map((col) => ({ cellKey: col.key, editable: false, kind: "readonly", value: undefined })),
cells: allColumns.map((col) => ({ cellKey: col.key, editable: false, kind: "readonly", value: undefined })),
});
for (const circuit of section.circuits) {
if (circuit.deviceRows.length === 0) {
@@ -549,7 +755,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
: rowType === "deviceRow" && device
? `device:${device.id}`
: `${rowType}:${circuit?.id ?? sectionId}`;
const cells = columns.map((col) => {
const cells = allColumns.map((col) => {
const kind = getCellKind(rowType, col.key);
const editable = kind === "circuitField" || kind === "deviceField";
let value: string | number | boolean | undefined;
@@ -580,15 +786,18 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
);
const rowCellMap = useMemo(() => {
const visibleKeySet = new Set(visibleColumnKeys);
const map = new Map<string, CellKey[]>();
for (const row of visibleRows) {
const keys = row.cells.filter((cell) => cell.editable).map((cell) => cell.cellKey as CellKey);
const keys = row.cells
.filter((cell) => cell.editable && visibleKeySet.has(cell.cellKey))
.map((cell) => cell.cellKey as CellKey);
if (keys.length) {
map.set(row.rowKey, keys);
}
}
return map;
}, [visibleRows]);
}, [visibleRows, visibleColumnKeys]);
const editableRowOrder = useMemo(
() => visibleRows.filter((row) => rowCellMap.has(row.rowKey)).map((row) => row.rowKey),
@@ -783,6 +992,103 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
});
}
function toggleColumnVisibility(key: CellKey) {
const column = allColumns.find((entry) => entry.key === key);
if (!column || column.locked) {
return;
}
setVisibleColumnKeys((current) => {
if (current.includes(key)) {
return current.filter((entry) => entry !== key);
}
return [...current, key];
});
}
function moveColumn(key: CellKey, direction: -1 | 1) {
if (key === "equipmentIdentifier") {
return;
}
setColumnOrder((current) => {
const index = current.indexOf(key);
if (index < 0) {
return current;
}
const nextIndex = index + direction;
if (nextIndex <= 0 || nextIndex >= current.length) {
return current;
}
const clone = [...current];
const [item] = clone.splice(index, 1);
clone.splice(nextIndex, 0, item);
return normalizeColumnOrder(clone);
});
}
function resetColumns() {
setVisibleColumnKeys(defaultVisibleColumnKeys);
setColumnOrder(normalizeColumnOrder(allColumns.map((column) => column.key)));
setOpenFilterColumn(null);
}
function moveColumnByDrag(dragKey: CellKey, targetKey: CellKey) {
if (dragKey === "equipmentIdentifier" || targetKey === "equipmentIdentifier") {
return;
}
setColumnOrder((current) => {
const from = current.indexOf(dragKey);
const to = current.indexOf(targetKey);
if (from < 1 || to < 1 || from === to) {
return current;
}
const clone = [...current];
const [item] = clone.splice(from, 1);
const targetIndex = clone.indexOf(targetKey);
if (targetIndex < 1) {
return current;
}
clone.splice(targetIndex, 0, item);
return normalizeColumnOrder(clone);
});
}
function handleColumnDragStart(event: DragEvent<HTMLDivElement>, key: CellKey) {
if (key === "equipmentIdentifier") {
event.preventDefault();
return;
}
setDraggingColumnKey(key);
setColumnDropTargetKey(null);
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("application/x-column-key", key);
}
function handleColumnDragOver(event: DragEvent<HTMLDivElement>, targetKey: CellKey) {
if (!draggingColumnKey || targetKey === "equipmentIdentifier" || draggingColumnKey === targetKey) {
return;
}
event.preventDefault();
event.dataTransfer.dropEffect = "move";
setColumnDropTargetKey(targetKey);
}
function handleColumnDrop(event: DragEvent<HTMLDivElement>, targetKey: CellKey) {
event.preventDefault();
const dragKey = draggingColumnKey;
if (!dragKey || dragKey === targetKey) {
setColumnDropTargetKey(null);
return;
}
moveColumnByDrag(dragKey, targetKey);
setDraggingColumnKey(null);
setColumnDropTargetKey(null);
}
function handleColumnDragEnd() {
setDraggingColumnKey(null);
setColumnDropTargetKey(null);
}
useEffect(() => {
const intent = pendingSelectionAfterReload.current;
if (!intent || !data) {
@@ -805,6 +1111,13 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
setPendingFocus(null);
}, [pendingFocus]);
useEffect(() => {
if (!isColumnMenuOpen) {
setDraggingColumnKey(null);
setColumnDropTargetKey(null);
}
}, [isColumnMenuOpen]);
useEffect(() => {
if (!editingCell) {
return;
@@ -877,11 +1190,11 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
if (!targetCells.length) {
return;
}
const preferred = columns.findIndex((col) => col.key === selectedCell.cellKey);
const preferred = visibleColumns.findIndex((col) => col.key === selectedCell.cellKey);
let best = targetCells[0];
let bestDistance = Number.POSITIVE_INFINITY;
for (const key of targetCells) {
const idx = columns.findIndex((col) => col.key === key);
const idx = visibleColumns.findIndex((col) => col.key === key);
const dist = Math.abs(idx - preferred);
if (dist < bestDistance) {
bestDistance = dist;
@@ -895,6 +1208,11 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
const payload: Record<string, unknown> = {};
if (key === "protectionSummary" || key === "cableSummary") {
payload.remark = draft.trim() === "" ? undefined : draft;
} else if (["protectionRatedCurrent", "cableLength"].includes(key)) {
payload[key] = parseNumeric(key, draft);
} else if (key === "isReserve") {
const normalized = draft.trim().toLowerCase();
payload.isReserve = ["1", "true", "yes", "ja"].includes(normalized);
} else {
payload[key] = draft.trim() === "" ? undefined : draft;
}
@@ -903,7 +1221,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
async function patchDeviceRow(rowId: string, key: CellKey, draft: string) {
const payload: Record<string, unknown> = {};
if (["quantity", "powerPerUnit", "simultaneityFactor"].includes(key)) {
if (["quantity", "powerPerUnit", "simultaneityFactor", "cosPhi"].includes(key)) {
payload[key] = parseNumeric(key, draft);
} else if (key === "roomSummary") {
const trimmed = draft.trim();
@@ -1857,6 +2175,52 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
>
Clear sort/filter
</button>
<button type="button" onClick={() => setIsColumnMenuOpen((current) => !current)}>
Columns
</button>
{isColumnMenuOpen ? (
<div className="column-settings-menu">
<div className="column-settings-actions">
<button type="button" onClick={() => resetColumns()}>
Reset columns
</button>
</div>
<div className="column-settings-list">
{orderedColumns.map((column) => {
const visible = visibleColumnKeys.includes(column.key);
return (
<div
key={`col-empty-${column.key}`}
className={`column-settings-item ${draggingColumnKey === column.key ? "dragging" : ""} ${columnDropTargetKey === column.key ? "drop-target" : ""} ${column.locked ? "locked" : ""}`}
draggable={!column.locked}
onDragStart={(event) => handleColumnDragStart(event, column.key)}
onDragOver={(event) => handleColumnDragOver(event, column.key)}
onDrop={(event) => handleColumnDrop(event, column.key)}
onDragEnd={() => handleColumnDragEnd()}
>
<label>
<input
type="checkbox"
checked={visible}
disabled={column.locked}
onChange={() => toggleColumnVisibility(column.key)}
/>
<span>{column.label}</span>
</label>
<div className="column-settings-order">
<button type="button" onClick={() => moveColumn(column.key, -1)}>
</button>
<button type="button" onClick={() => moveColumn(column.key, 1)}>
</button>
</div>
</div>
);
})}
</div>
</div>
) : null}
</div>
<div className="notice muted">No matching circuits for active filters.</div>
</div>
@@ -1885,6 +2249,9 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
Apply sorted order
</button>
) : null}
<button type="button" onClick={() => setIsColumnMenuOpen((current) => !current)}>
Columns
</button>
<button
type="button"
onClick={() => {
@@ -1896,6 +2263,49 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
>
Clear sort/filter
</button>
{isColumnMenuOpen ? (
<div className="column-settings-menu">
<div className="column-settings-actions">
<button type="button" onClick={() => resetColumns()}>
Reset columns
</button>
</div>
<div className="column-settings-list">
{orderedColumns.map((column) => {
const visible = visibleColumnKeys.includes(column.key);
return (
<div
key={`col-${column.key}`}
className={`column-settings-item ${draggingColumnKey === column.key ? "dragging" : ""} ${columnDropTargetKey === column.key ? "drop-target" : ""} ${column.locked ? "locked" : ""}`}
draggable={!column.locked}
onDragStart={(event) => handleColumnDragStart(event, column.key)}
onDragOver={(event) => handleColumnDragOver(event, column.key)}
onDrop={(event) => handleColumnDrop(event, column.key)}
onDragEnd={() => handleColumnDragEnd()}
>
<label>
<input
type="checkbox"
checked={visible}
disabled={column.locked}
onChange={() => toggleColumnVisibility(column.key)}
/>
<span>{column.label}</span>
</label>
<div className="column-settings-order">
<button type="button" onClick={() => moveColumn(column.key, -1)}>
</button>
<button type="button" onClick={() => moveColumn(column.key, 1)}>
</button>
</div>
</div>
);
})}
</div>
</div>
) : null}
</div>
{hasActiveSortOrFilter ? (
<div className="notice muted">Sorting/filtering is view-only. Renumber is disabled while sort/filter is active.</div>
@@ -1995,7 +2405,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
<table className="tree-grid">
<thead>
<tr>
{columns.map((column) => (
{visibleColumns.map((column) => (
<th key={column.key} className={column.numeric ? "num" : ""}>
<div className="header-cell">
<button
@@ -2119,7 +2529,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
}
}}
>
<td colSpan={columns.length + 1} className="section-drop-cell">
<td colSpan={visibleColumns.length + 1} className="section-drop-cell">
<div className="section-content">
<strong>{section.displayName}</strong>
<div className="section-actions">
@@ -2364,7 +2774,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
}
}}
>
{columns.map((column) => {
{visibleColumns.map((column) => {
const cell = row.cells.find((entry) => entry.cellKey === column.key)!;
const isSelected =
selectedCell?.rowKey === row.rowKey && selectedCell.cellKey === column.key;