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