Add drag-to-reorder for fundraising grid views

Sidebar view list is now drag-reorderable (HTML5 DnD mirroring the
column-reorder idiom: moveViewBefore + draggingViewId/dragOverViewId).
Order persists via the grid page's existing autosave (views is already
in its snapshot + deps), the same path rename/delete use; no backend
change. Render-smoke green.
This commit is contained in:
Keysat
2026-06-19 08:57:15 -05:00
parent 99404db48b
commit c7f959d7d5
+54 -1
View File
@@ -190,6 +190,14 @@
color: #fff;
}
.sub-nav-item.view-dragging {
opacity: 0.55;
}
.sub-nav-item.view-drag-over {
box-shadow: inset 0 2px 0 #4a9adf;
}
.column-header-inner {
position: relative;
display: flex;
@@ -11172,6 +11180,25 @@
const [activeGridView, setActiveGridView] = useState('view-main');
const [gridUiAction, setGridUiAction] = useState(null);
const [sidebarContextMenu, setSidebarContextMenu] = useState(null);
const [draggingViewId, setDraggingViewId] = useState(null);
const [dragOverViewId, setDragOverViewId] = useState(null);
// Drag-reorder the saved grid views in the sidebar. Mirrors moveColumnBefore:
// mutate gridViews only — the grid page's autosave (which has `views` in its
// snapshot + deps) persists the new order to /api/fundraising/state, same path
// rename/delete already use.
const moveViewBefore = (fromId, targetId) => {
if (!fromId || !targetId || fromId === targetId) return;
setGridViews((prev) => {
const fromIndex = prev.findIndex((v) => v.id === fromId);
const targetIndex = prev.findIndex((v) => v.id === targetId);
if (fromIndex < 0 || targetIndex < 0 || fromIndex === targetIndex) return prev;
const next = [...prev];
const [moved] = next.splice(fromIndex, 1);
next.splice(targetIndex, 0, moved);
return next;
});
};
useEffect(() => {
localStorage.setItem(GRID_VIEW_STORAGE_KEY, JSON.stringify(gridViews));
@@ -11308,12 +11335,38 @@
{gridViews.map((v) => (
<button
key={v.id}
className={`sub-nav-item ${activeGridView === v.id ? 'active' : ''}`}
className={`sub-nav-item ${activeGridView === v.id ? 'active' : ''} ${draggingViewId === v.id ? 'view-dragging' : ''} ${dragOverViewId === v.id ? 'view-drag-over' : ''}`.trim()}
draggable={true}
onClick={() => {
setPage('fundraising-grid');
setActiveGridView(v.id);
}}
onContextMenu={(e) => openSidebarContextMenu(e, { kind: 'view', viewId: v.id })}
onDragStart={(e) => {
setDraggingViewId(v.id);
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', v.id);
}}
onDragOver={(e) => {
e.preventDefault();
if (!draggingViewId || draggingViewId === v.id) return;
setDragOverViewId(v.id);
e.dataTransfer.dropEffect = 'move';
}}
onDragLeave={() => {
setDragOverViewId((cur) => (cur === v.id ? null : cur));
}}
onDrop={(e) => {
e.preventDefault();
const droppedId = e.dataTransfer.getData('text/plain') || draggingViewId;
moveViewBefore(droppedId, v.id);
setDragOverViewId(null);
setDraggingViewId(null);
}}
onDragEnd={() => {
setDragOverViewId(null);
setDraggingViewId(null);
}}
>
• {v.name}
</button>