From 1078062b53cecd0a414f81aeb2787391f885cb17 Mon Sep 17 00:00:00 2001 From: david raistrick <1108844+keen99@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:24:12 -0400 Subject: [PATCH] Campaign drag reorder via order field Campaign cards draggable (ChevronsUpDown handle in header). Drop reorders, batch updates order field on affected campaign docs. Sort by order (fallback createdAt). New campaign gets order = max+1. --- src/App.js | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/App.js b/src/App.js index f9f3497..8bd76d4 100644 --- a/src/App.js +++ b/src/App.js @@ -2330,6 +2330,7 @@ function AdminView({ userId }) { const { data: initialActiveInfo } = useFirestoreDocument(getPath.activeDisplay()); const [campaignsWithDetails, setCampaignsWithDetails] = useState([]); + const [draggedCampaignId, setDraggedCampaignId] = useState(null); const [selectedCampaignId, setSelectedCampaignId] = useState(null); const encounterStartedRef = useRef(false); const encounterActiveRef = useRef(false); @@ -2390,6 +2391,10 @@ function AdminView({ userId }) { fetchDetails(); } else if (campaignsData) { const sorted = [...campaignsData].sort((a, b) => { + const ao = a.order ?? null, bo = b.order ?? null; + if (ao !== null && bo !== null) return ao - bo; + if (ao !== null) return -1; + if (bo !== null) return 1; const at = a.createdAt || 0; const bt = b.createdAt || 0; if (at === bt) return (a.name || '').localeCompare(b.name || ''); @@ -2433,6 +2438,7 @@ function AdminView({ userId }) { createdAt: new Date().toISOString(), players: [], ruleset: ruleset || '5e', + order: (campaignsWithDetails || []).reduce((max, c) => Math.max(max, c.order ?? -1), -1) + 1, }); setShowCreateModal(false); @@ -2579,11 +2585,28 @@ function AdminView({ userId }) { ? { backgroundImage: `url(${campaign.playerDisplayBackgroundUrl})` } : {}; - const cardClasses = `h-40 flex flex-col justify-between rounded-lg shadow-md cursor-pointer transition-all relative overflow-hidden bg-cover bg-center ${selectedCampaignId === campaign.id ? 'ring-4 ring-amber-500' : ''} ${!campaign.playerDisplayBackgroundUrl ? 'bg-stone-800 hover:bg-stone-700' : 'hover:shadow-xl'}`; + const cardClasses = `h-40 flex flex-col justify-between rounded-lg shadow-md cursor-grab transition-all relative overflow-hidden bg-cover bg-center ${selectedCampaignId === campaign.id ? 'ring-4 ring-amber-500' : ''} ${!campaign.playerDisplayBackgroundUrl ? 'bg-stone-800 hover:bg-stone-700' : 'hover:shadow-xl'} ${draggedCampaignId === campaign.id ? 'opacity-50 ring-2 ring-yellow-400' : ''}`; return (
{ setDraggedCampaignId(campaign.id); e.dataTransfer.effectAllowed = 'move'; }} + onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }} + onDrop={async (e) => { + e.preventDefault(); + if (!db || !draggedCampaignId || draggedCampaignId === campaign.id) { setDraggedCampaignId(null); return; } + const reordered = [...campaignsWithDetails]; + const fromIdx = reordered.findIndex(c => c.id === draggedCampaignId); + const toIdx = reordered.findIndex(c => c.id === campaign.id); + if (fromIdx === -1 || toIdx === -1) { setDraggedCampaignId(null); return; } + const [moved] = reordered.splice(fromIdx, 1); + reordered.splice(toIdx, 0, moved); + const ops = reordered.map((c, i) => ({ type: 'update', path: getPath.campaign(c.id), data: { order: i } })); + try { await storage.batchWrite(ops); } catch (err) {} + setDraggedCampaignId(null); + }} + onDragEnd={() => setDraggedCampaignId(null)} onClick={() => { if (encounterStartedRef.current) { showToast('End or pause active encounter before switching campaigns.'); @@ -2599,7 +2622,10 @@ function AdminView({ userId }) { className={`relative z-10 flex flex-col justify-between h-full ${campaign.playerDisplayBackgroundUrl ? 'bg-black bg-opacity-60 p-3' : 'p-4'}`} >
-

{campaign.name}

+
+ +

{campaign.name}

+
{campaign.characters?.length || 0} Characters