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:
+54
-1
@@ -190,6 +190,14 @@
|
|||||||
color: #fff;
|
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 {
|
.column-header-inner {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -11172,6 +11180,25 @@
|
|||||||
const [activeGridView, setActiveGridView] = useState('view-main');
|
const [activeGridView, setActiveGridView] = useState('view-main');
|
||||||
const [gridUiAction, setGridUiAction] = useState(null);
|
const [gridUiAction, setGridUiAction] = useState(null);
|
||||||
const [sidebarContextMenu, setSidebarContextMenu] = 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(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem(GRID_VIEW_STORAGE_KEY, JSON.stringify(gridViews));
|
localStorage.setItem(GRID_VIEW_STORAGE_KEY, JSON.stringify(gridViews));
|
||||||
@@ -11308,12 +11335,38 @@
|
|||||||
{gridViews.map((v) => (
|
{gridViews.map((v) => (
|
||||||
<button
|
<button
|
||||||
key={v.id}
|
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={() => {
|
onClick={() => {
|
||||||
setPage('fundraising-grid');
|
setPage('fundraising-grid');
|
||||||
setActiveGridView(v.id);
|
setActiveGridView(v.id);
|
||||||
}}
|
}}
|
||||||
onContextMenu={(e) => openSidebarContextMenu(e, { kind: 'view', viewId: 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}
|
• {v.name}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user