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.
This commit is contained in:
+28
-2
@@ -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 (
|
||||
<div
|
||||
key={campaign.id}
|
||||
draggable
|
||||
onDragStart={(e) => { 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'}`}
|
||||
>
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-white">{campaign.name}</h3>
|
||||
<div className="flex items-center gap-1">
|
||||
<ChevronsUpDown size={14} className="text-white/60 flex-shrink-0" title="Drag to reorder" />
|
||||
<h3 className="text-xl font-semibold text-white">{campaign.name}</h3>
|
||||
</div>
|
||||
<div className="text-xs text-stone-100 mt-1 space-x-3">
|
||||
<span className="inline-flex items-center">
|
||||
<Users size={12} className="mr-1" /> {campaign.characters?.length || 0} Characters
|
||||
|
||||
Reference in New Issue
Block a user