Filter/Sort working with renumbering afterwards
This commit is contained in:
@@ -169,6 +169,67 @@ body {
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-grid .header-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-grid .header-sort-btn,
|
||||||
|
.tree-grid .header-filter-btn {
|
||||||
|
border: 1px solid #c4cddc;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.15rem 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-grid .header-sort-btn {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-grid .header-filter-btn.active {
|
||||||
|
border-color: #2563eb;
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-grid .header-filter-menu {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 7;
|
||||||
|
margin-top: 0.2rem;
|
||||||
|
border: 1px solid #cfd7e5;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
|
||||||
|
width: 220px;
|
||||||
|
max-height: 260px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-grid .header-filter-clear {
|
||||||
|
border: 1px solid #c4cddc;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
padding: 0.12rem 0.3rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-grid .header-filter-values {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-grid .header-filter-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-grid .num {
|
.tree-grid .num {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ type SaveDirection = "stay" | "next" | "prev";
|
|||||||
type StartEditMode = "selectExisting" | "replaceWithTypedChar";
|
type StartEditMode = "selectExisting" | "replaceWithTypedChar";
|
||||||
type RowType = "section" | "circuitCompact" | "circuitSummary" | "deviceRow" | "reserveCircuit" | "placeholder";
|
type RowType = "section" | "circuitCompact" | "circuitSummary" | "deviceRow" | "reserveCircuit" | "placeholder";
|
||||||
type CellKind = "circuitField" | "deviceField" | "computed" | "readonly";
|
type CellKind = "circuitField" | "deviceField" | "computed" | "readonly";
|
||||||
|
type SortDirection = "asc" | "desc";
|
||||||
|
|
||||||
interface SelectedCell {
|
interface SelectedCell {
|
||||||
rowKey: string;
|
rowKey: string;
|
||||||
@@ -131,6 +132,9 @@ const columns: Array<{ key: CellKey; label: string; numeric?: boolean }> = [
|
|||||||
{ key: "remark", label: "Remark" },
|
{ key: "remark", label: "Remark" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const deviceOnlyColumns = new Set<CellKey>(["quantity", "powerPerUnit", "simultaneityFactor", "rowTotalPower", "roomSummary"]);
|
||||||
|
const circuitOnlyColumns = new Set<CellKey>(["equipmentIdentifier", "protectionSummary", "cableSummary", "circuitTotalPower"]);
|
||||||
|
|
||||||
const deviceFieldKeys = new Set<CellKey>([
|
const deviceFieldKeys = new Set<CellKey>([
|
||||||
"displayName",
|
"displayName",
|
||||||
"roomSummary",
|
"roomSummary",
|
||||||
@@ -243,6 +247,36 @@ function getCircuitValue(circuit: CircuitTreeCircuitDto, key: CellKey): string |
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeFilterValue(value: string | number | boolean | undefined) {
|
||||||
|
return formatValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBlockSortValue(circuit: CircuitTreeCircuitDto, key: CellKey) {
|
||||||
|
const firstRow = circuit.deviceRows[0];
|
||||||
|
if (circuitOnlyColumns.has(key)) {
|
||||||
|
return getCircuitValue(circuit, key);
|
||||||
|
}
|
||||||
|
if (deviceOnlyColumns.has(key)) {
|
||||||
|
return firstRow ? getDeviceValue(firstRow, key) : undefined;
|
||||||
|
}
|
||||||
|
if (key === "displayName" || key === "remark") {
|
||||||
|
if (circuit.displayName || circuit.remark) {
|
||||||
|
return getCircuitValue(circuit, key);
|
||||||
|
}
|
||||||
|
return firstRow ? getDeviceValue(firstRow, key) : undefined;
|
||||||
|
}
|
||||||
|
return getCircuitValue(circuit, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareSortValues(a: string | number | boolean | undefined, b: string | number | boolean | undefined) {
|
||||||
|
const av = a === undefined || a === null ? "" : a;
|
||||||
|
const bv = b === undefined || b === null ? "" : b;
|
||||||
|
if (typeof av === "number" && typeof bv === "number") {
|
||||||
|
return av - bv;
|
||||||
|
}
|
||||||
|
return String(av).localeCompare(String(bv), undefined, { sensitivity: "base", numeric: true });
|
||||||
|
}
|
||||||
|
|
||||||
function getCellKind(rowType: RowType, key: CellKey): CellKind {
|
function getCellKind(rowType: RowType, key: CellKey): CellKind {
|
||||||
if (key === "rowTotalPower" || key === "circuitTotalPower") {
|
if (key === "rowTotalPower" || key === "circuitTotalPower") {
|
||||||
return "computed";
|
return "computed";
|
||||||
@@ -313,6 +347,9 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
const [undoStack, setUndoStack] = useState<HistoryCommand[]>([]);
|
const [undoStack, setUndoStack] = useState<HistoryCommand[]>([]);
|
||||||
const [redoStack, setRedoStack] = useState<HistoryCommand[]>([]);
|
const [redoStack, setRedoStack] = useState<HistoryCommand[]>([]);
|
||||||
const [historyBusy, setHistoryBusy] = useState(false);
|
const [historyBusy, setHistoryBusy] = useState(false);
|
||||||
|
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 [pendingFocus, setPendingFocus] = useState<SelectedCell | null>(null);
|
const [pendingFocus, setPendingFocus] = useState<SelectedCell | null>(null);
|
||||||
const pendingSelectionAfterReload = useRef<SelectionIntent | null>(null);
|
const pendingSelectionAfterReload = useRef<SelectionIntent | null>(null);
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
@@ -353,12 +390,104 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
void loadProjectDeviceList();
|
void loadProjectDeviceList();
|
||||||
}, [projectId]);
|
}, [projectId]);
|
||||||
|
|
||||||
const visibleRows = useMemo(() => {
|
const hasActiveFilters = useMemo(
|
||||||
|
() => Object.values(columnFilters).some((values) => (values?.length ?? 0) > 0),
|
||||||
|
[columnFilters]
|
||||||
|
);
|
||||||
|
|
||||||
|
const distinctValuesByColumn = useMemo(() => {
|
||||||
|
const result = {} as Record<CellKey, string[]>;
|
||||||
|
for (const column of columns) {
|
||||||
|
const set = new Set<string>();
|
||||||
|
for (const section of data?.sections ?? []) {
|
||||||
|
for (const circuit of section.circuits) {
|
||||||
|
set.add(normalizeFilterValue(getCircuitValue(circuit, column.key)));
|
||||||
|
for (const row of circuit.deviceRows) {
|
||||||
|
set.add(normalizeFilterValue(getDeviceValue(row, column.key)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result[column.key] = [...set].sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const filteredSortedSections = useMemo(() => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
return [] as CircuitTreeResponseDto["sections"];
|
||||||
|
}
|
||||||
|
const matchesColumnFilter = (
|
||||||
|
circuit: CircuitTreeCircuitDto,
|
||||||
|
row: CircuitTreeDeviceRowDto | null,
|
||||||
|
key: CellKey,
|
||||||
|
selected: string[]
|
||||||
|
) => {
|
||||||
|
if (!selected.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const values = new Set<string>();
|
||||||
|
values.add(normalizeFilterValue(getCircuitValue(circuit, key)));
|
||||||
|
if (row) {
|
||||||
|
values.add(normalizeFilterValue(getDeviceValue(row, key)));
|
||||||
|
} else {
|
||||||
|
for (const device of circuit.deviceRows) {
|
||||||
|
values.add(normalizeFilterValue(getDeviceValue(device, key)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selected.some((entry) => values.has(entry));
|
||||||
|
};
|
||||||
|
|
||||||
|
const sections = data.sections
|
||||||
|
.map((section) => {
|
||||||
|
let circuits = section.circuits
|
||||||
|
.map((circuit) => {
|
||||||
|
const selectedFilters = Object.entries(columnFilters).filter(([, values]) => (values?.length ?? 0) > 0);
|
||||||
|
const circuitMatchesAll = selectedFilters.every(([key, values]) =>
|
||||||
|
matchesColumnFilter(circuit, null, key as CellKey, values ?? [])
|
||||||
|
);
|
||||||
|
if (!circuitMatchesAll) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasActiveFilters || circuit.deviceRows.length <= 1) {
|
||||||
|
return circuit;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchingRows = circuit.deviceRows.filter((row) =>
|
||||||
|
selectedFilters.every(([key, values]) =>
|
||||||
|
matchesColumnFilter(circuit, row, key as CellKey, values ?? [])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (matchingRows.length > 0) {
|
||||||
|
return { ...circuit, deviceRows: matchingRows };
|
||||||
|
}
|
||||||
|
if (selectedFilters.some(([key]) => circuitOnlyColumns.has(key as CellKey))) {
|
||||||
|
return circuit;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(Boolean) as CircuitTreeCircuitDto[];
|
||||||
|
|
||||||
|
if (sortState) {
|
||||||
|
circuits = [...circuits].sort((a, b) => {
|
||||||
|
const cmp = compareSortValues(getBlockSortValue(a, sortState.key), getBlockSortValue(b, sortState.key));
|
||||||
|
return sortState.direction === "asc" ? cmp : -cmp;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { ...section, circuits };
|
||||||
|
})
|
||||||
|
.filter((section) => section.circuits.length > 0);
|
||||||
|
|
||||||
|
return sections;
|
||||||
|
}, [data, columnFilters, hasActiveFilters, sortState]);
|
||||||
|
|
||||||
|
const visibleRows = useMemo(() => {
|
||||||
|
if (!filteredSortedSections.length) {
|
||||||
return [] as VisibleGridRow[];
|
return [] as VisibleGridRow[];
|
||||||
}
|
}
|
||||||
const rows: VisibleGridRow[] = [];
|
const rows: VisibleGridRow[] = [];
|
||||||
for (const section of data.sections) {
|
for (const section of filteredSortedSections) {
|
||||||
rows.push({
|
rows.push({
|
||||||
rowKey: `section:${section.id}`,
|
rowKey: `section:${section.id}`,
|
||||||
rowType: "section",
|
rowType: "section",
|
||||||
@@ -382,7 +511,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
rows.push(makeRow("placeholder", section.id));
|
rows.push(makeRow("placeholder", section.id));
|
||||||
}
|
}
|
||||||
return rows;
|
return rows;
|
||||||
}, [data]);
|
}, [filteredSortedSections]);
|
||||||
|
|
||||||
const searchableProjectDevices = useMemo(() => {
|
const searchableProjectDevices = useMemo(() => {
|
||||||
const term = projectDeviceSearch.trim().toLowerCase();
|
const term = projectDeviceSearch.trim().toLowerCase();
|
||||||
@@ -605,6 +734,55 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
await applyHistory(command, "redo");
|
await applyHistory(command, "redo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleApplySortedOrder() {
|
||||||
|
if (!sortState || hasActiveFilters || !data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const beforeBySection = data.sections.map((section) => ({
|
||||||
|
sectionId: section.id,
|
||||||
|
order: section.circuits.map((circuit) => circuit.id),
|
||||||
|
}));
|
||||||
|
const afterBySection = filteredSortedSections.map((section) => ({
|
||||||
|
sectionId: section.id,
|
||||||
|
order: section.circuits.map((circuit) => circuit.id),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const changedSections = afterBySection.filter((after) => {
|
||||||
|
const before = beforeBySection.find((entry) => entry.sectionId === after.sectionId);
|
||||||
|
if (!before) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (before.order.length !== after.order.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return before.order.some((id, index) => id !== after.order[index]);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (changedSections.length === 0) {
|
||||||
|
setSortState(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await runCommand({
|
||||||
|
label: "Apply sorted order",
|
||||||
|
redo: async () => {
|
||||||
|
for (const section of changedSections) {
|
||||||
|
await reorderSectionCircuits(section.sectionId, section.order);
|
||||||
|
}
|
||||||
|
setSortState(null);
|
||||||
|
setOpenFilterColumn(null);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
undo: async () => {
|
||||||
|
for (const section of beforeBySection) {
|
||||||
|
await reorderSectionCircuits(section.sectionId, section.order);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const intent = pendingSelectionAfterReload.current;
|
const intent = pendingSelectionAfterReload.current;
|
||||||
if (!intent || !data) {
|
if (!intent || !data) {
|
||||||
@@ -1658,6 +1836,35 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
if (!data || data.sections.length === 0) {
|
if (!data || data.sections.length === 0) {
|
||||||
return <div className="notice muted">No sections/circuits available.</div>;
|
return <div className="notice muted">No sections/circuits available.</div>;
|
||||||
}
|
}
|
||||||
|
if (filteredSortedSections.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="tree-editor-shell">
|
||||||
|
<div className="editor-toolbar">
|
||||||
|
<button type="button" onClick={() => void handleUndo()} disabled={undoStack.length === 0 || historyBusy || isSaving}>
|
||||||
|
Undo
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => void handleRedo()} disabled={redoStack.length === 0 || historyBusy || isSaving}>
|
||||||
|
Redo
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setSortState(null);
|
||||||
|
setColumnFilters({});
|
||||||
|
setOpenFilterColumn(null);
|
||||||
|
}}
|
||||||
|
disabled={!Boolean(sortState) && !hasActiveFilters}
|
||||||
|
>
|
||||||
|
Clear sort/filter
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="notice muted">No matching circuits for active filters.</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSortedView = Boolean(sortState);
|
||||||
|
const hasActiveSortOrFilter = isSortedView || hasActiveFilters;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tree-editor-shell">
|
<div className="tree-editor-shell">
|
||||||
@@ -1668,7 +1875,34 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
<button type="button" onClick={() => void handleRedo()} disabled={redoStack.length === 0 || historyBusy || isSaving}>
|
<button type="button" onClick={() => void handleRedo()} disabled={redoStack.length === 0 || historyBusy || isSaving}>
|
||||||
Redo
|
Redo
|
||||||
</button>
|
</button>
|
||||||
|
{isSortedView ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => void handleApplySortedOrder()}
|
||||||
|
disabled={hasActiveFilters || isSaving || historyBusy}
|
||||||
|
title={hasActiveFilters ? "Clear filters first to apply sorted order." : undefined}
|
||||||
|
>
|
||||||
|
Apply sorted order
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setSortState(null);
|
||||||
|
setColumnFilters({});
|
||||||
|
setOpenFilterColumn(null);
|
||||||
|
}}
|
||||||
|
disabled={!isSortedView && !hasActiveFilters}
|
||||||
|
>
|
||||||
|
Clear sort/filter
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{hasActiveSortOrFilter ? (
|
||||||
|
<div className="notice muted">Sorting/filtering is view-only. Renumber is disabled while sort/filter is active.</div>
|
||||||
|
) : null}
|
||||||
|
{isSortedView && hasActiveFilters ? (
|
||||||
|
<div className="notice muted">Apply sorted order is disabled while filters are active.</div>
|
||||||
|
) : null}
|
||||||
{isSaving ? <div className="notice info">Saving...</div> : null}
|
{isSaving ? <div className="notice info">Saving...</div> : null}
|
||||||
<div className="tree-editor-layout">
|
<div className="tree-editor-layout">
|
||||||
<aside className="project-device-sidebar">
|
<aside className="project-device-sidebar">
|
||||||
@@ -1763,7 +1997,73 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
<tr>
|
<tr>
|
||||||
{columns.map((column) => (
|
{columns.map((column) => (
|
||||||
<th key={column.key} className={column.numeric ? "num" : ""}>
|
<th key={column.key} className={column.numeric ? "num" : ""}>
|
||||||
|
<div className="header-cell">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="header-sort-btn"
|
||||||
|
onClick={() => {
|
||||||
|
setSortState((current) => {
|
||||||
|
if (!current || current.key !== column.key) {
|
||||||
|
return { key: column.key, direction: "asc" };
|
||||||
|
}
|
||||||
|
if (current.direction === "asc") {
|
||||||
|
return { key: column.key, direction: "desc" };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
{column.label}
|
{column.label}
|
||||||
|
{sortState?.key === column.key ? (sortState.direction === "asc" ? " ↑" : " ↓") : ""}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`header-filter-btn ${(columnFilters[column.key]?.length ?? 0) > 0 ? "active" : ""}`}
|
||||||
|
onClick={() => setOpenFilterColumn((current) => (current === column.key ? null : column.key))}
|
||||||
|
>
|
||||||
|
Filter
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{openFilterColumn === column.key ? (
|
||||||
|
<div className="header-filter-menu">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="header-filter-clear"
|
||||||
|
onClick={() =>
|
||||||
|
setColumnFilters((current) => ({
|
||||||
|
...current,
|
||||||
|
[column.key]: [],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
<div className="header-filter-values">
|
||||||
|
{distinctValuesByColumn[column.key].map((value) => {
|
||||||
|
const selected = columnFilters[column.key] ?? [];
|
||||||
|
const checked = selected.includes(value);
|
||||||
|
return (
|
||||||
|
<label key={`${column.key}-${value}`} className="header-filter-item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={checked}
|
||||||
|
onChange={(event) => {
|
||||||
|
setColumnFilters((current) => {
|
||||||
|
const previous = current[column.key] ?? [];
|
||||||
|
const next = event.target.checked
|
||||||
|
? [...previous, value]
|
||||||
|
: previous.filter((entry) => entry !== value);
|
||||||
|
return { ...current, [column.key]: next };
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span>{value}</span>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
@@ -1826,7 +2126,13 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
<button type="button" tabIndex={-1} onClick={() => void handleAddReserveCircuit(section.id)}>
|
<button type="button" tabIndex={-1} onClick={() => void handleAddReserveCircuit(section.id)}>
|
||||||
Add circuit
|
Add circuit
|
||||||
</button>
|
</button>
|
||||||
<button type="button" tabIndex={-1} onClick={() => void handleRenumberSection(section.id)}>
|
<button
|
||||||
|
type="button"
|
||||||
|
tabIndex={-1}
|
||||||
|
disabled={hasActiveSortOrFilter}
|
||||||
|
onClick={() => void handleRenumberSection(section.id)}
|
||||||
|
title={hasActiveSortOrFilter ? "Clear sort/filter first to renumber safely." : undefined}
|
||||||
|
>
|
||||||
Renumber section
|
Renumber section
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user