diff --git a/src/routes/admin/+page.svelte b/src/routes/admin/+page.svelte index 0ea8801..a93d8af 100644 --- a/src/routes/admin/+page.svelte +++ b/src/routes/admin/+page.svelte @@ -27,6 +27,116 @@ let groupFormSnapshot = $state>({}); let groupFieldError = $state(null); // which field has error + // ── Drag-to-reorder state ───────────────────────────────────────────────────── + let dragGroupId = $state(null); // group being dragged + let dragOverGroupId = $state(null); // group being hovered over + let dragUnitId = $state(null); // unit being dragged + let dragUnitGroupId = $state(null); // group of unit being dragged + let dragOverUnitId = $state(null); // unit being hovered over + + // ── Drag handlers: groups ───────────────────────────────────────────────────── + function onGroupDragStart(e: DragEvent, groupId: string) { + dragGroupId = groupId; + e.dataTransfer!.effectAllowed = 'move'; + e.dataTransfer!.setData('text/plain', groupId); + } + + function onGroupDragOver(e: DragEvent, groupId: string) { + if (!dragGroupId || dragGroupId === groupId) return; + e.preventDefault(); + e.dataTransfer!.dropEffect = 'move'; + dragOverGroupId = groupId; + } + + function onGroupDragLeave(groupId: string) { + if (dragOverGroupId === groupId) dragOverGroupId = null; + } + + function onGroupDrop(e: DragEvent, targetGroupId: string) { + e.preventDefault(); + if (!dragGroupId || dragGroupId === targetGroupId) { + dragGroupId = null; + dragOverGroupId = null; + return; + } + // Reorder in local state immediately + const groups = [...unitsData.groups]; + const fromIdx = groups.findIndex((g) => g.id === dragGroupId); + const toIdx = groups.findIndex((g) => g.id === targetGroupId); + if (fromIdx === -1 || toIdx === -1) { dragGroupId = null; dragOverGroupId = null; return; } + const [moved] = groups.splice(fromIdx, 1); + groups.splice(toIdx, 0, moved); + unitsData = { ...unitsData, groups }; + dragGroupId = null; + dragOverGroupId = null; + // Persist + fetch('/admin/api/order', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ groups: groups.map((g) => g.id) }) + }); + } + + function onGroupDragEnd() { + dragGroupId = null; + dragOverGroupId = null; + } + + // ── Drag handlers: units ────────────────────────────────────────────────────── + function onUnitDragStart(e: DragEvent, unitId: string, groupId: string | null) { + dragUnitId = unitId; + dragUnitGroupId = groupId; + e.dataTransfer!.effectAllowed = 'move'; + e.dataTransfer!.setData('text/plain', unitId); + } + + function onUnitDragOver(e: DragEvent, unitId: string, groupId: string | null) { + if (!dragUnitId || dragUnitId === unitId || dragUnitGroupId !== groupId) return; + e.preventDefault(); + e.dataTransfer!.dropEffect = 'move'; + dragOverUnitId = unitId; + } + + function onUnitDragLeave(unitId: string) { + if (dragOverUnitId === unitId) dragOverUnitId = null; + } + + function onUnitDrop(e: DragEvent, targetUnitId: string, groupId: string | null) { + e.preventDefault(); + if (!dragUnitId || dragUnitId === targetUnitId || dragUnitGroupId !== groupId) { + dragUnitId = null; + dragUnitGroupId = null; + dragOverUnitId = null; + return; + } + // Reorder in local state + const units = [...unitsData.units]; + const fromIdx = units.findIndex((u) => u.id === dragUnitId); + const toIdx = units.findIndex((u) => u.id === targetUnitId); + if (fromIdx === -1 || toIdx === -1) { dragUnitId = null; dragUnitGroupId = null; dragOverUnitId = null; return; } + const [moved] = units.splice(fromIdx, 1); + units.splice(toIdx, 0, moved); + unitsData = { ...unitsData, units }; + const gid = dragUnitId; + dragUnitId = null; + dragUnitGroupId = null; + dragOverUnitId = null; + // Build ordered IDs for this group + const groupKey = groupId ?? 'null'; + const groupUnitIds = units.filter((u) => (u.group ?? null) === groupId).map((u) => u.id); + fetch('/admin/api/order', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ units: { [groupKey]: groupUnitIds } }) + }); + } + + function onUnitDragEnd() { + dragUnitId = null; + dragUnitGroupId = null; + dragOverUnitId = null; + } + // ── Load data ──────────────────────────────────────────────────────────────── async function loadData() { const [res, gres] = await Promise.all([ @@ -470,8 +580,17 @@ {#each unitsData.groups as group (group.id)} {@const groupUnits = unitsByGroup.get(group.id) ?? []} -
-
+
onGroupDragStart(e, group.id)} + ondragover={(e) => onGroupDragOver(e, group.id)} + ondragleave={() => onGroupDragLeave(group.id)} + ondrop={(e) => onGroupDrop(e, group.id)} + ondragend={onGroupDragEnd} + > +