From 63e003a718782e2ca3ffcb2d11b0be97560a7259 Mon Sep 17 00:00:00 2001 From: Martin Kagamino Lehoux Date: Wed, 29 Jul 2026 15:01:38 +0200 Subject: feat: drag-to-reorder categories with persistent order --- src/routes/categories/+page.server.ts | 17 ++++++++- src/routes/categories/+page.svelte | 67 +++++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 4 deletions(-) (limited to 'src/routes') diff --git a/src/routes/categories/+page.server.ts b/src/routes/categories/+page.server.ts index ac53ca8..c97b619 100644 --- a/src/routes/categories/+page.server.ts +++ b/src/routes/categories/+page.server.ts @@ -1,5 +1,10 @@ import { fail } from '@sveltejs/kit'; -import { loadAll, add, deleteCategory } from '$lib/server/services/categories'; +import { + loadAll, + add, + deleteCategory, + reorder, +} from '$lib/server/services/categories'; import { numericId, parseFormString } from '$lib/server/services/validation'; import type { Category } from '$lib/server/services/types'; import type { Actions, PageServerLoad } from './$types'; @@ -35,4 +40,14 @@ export const actions: Actions = { } return { success: true }; }, + + reorder: async ({ request }) => { + const data = await request.formData(); + const ids = data.getAll('orderedIds').map(numericId); + if (ids.length === 0) { + return fail(400, { error: 'Aucune catégorie' }); + } + await reorder(ids); + return { success: true }; + }, }; diff --git a/src/routes/categories/+page.svelte b/src/routes/categories/+page.svelte index 1060eaa..9c91578 100644 --- a/src/routes/categories/+page.svelte +++ b/src/routes/categories/+page.svelte @@ -3,6 +3,36 @@ let { data, form } = $props(); let name = $state(''); + let draggedIndex = $state(null); + // eslint-disable-next-line svelte/prefer-writable-derived + let localCategories = $state(data?.categories ?? []); + + // Sync with server data on navigation + $effect(() => { + localCategories = data?.categories ?? []; + }); + + let reorderFormRef: HTMLFormElement; + + function handleDragStart(index: number) { + draggedIndex = index; + } + + function handleDragOver(e: DragEvent, index: number) { + e.preventDefault(); + if (draggedIndex === null || draggedIndex === index) return; + + const next = [...localCategories]; + const [moved] = next.splice(draggedIndex, 1); + next.splice(index, 0, moved); + localCategories = next; + draggedIndex = index; + } + + function handleDrop() { + draggedIndex = null; + reorderFormRef?.requestSubmit(); + }
@@ -30,10 +60,33 @@

{form.error}

{/if} +
{ + return async ({ update }) => { + await update({ reset: false }); + }; + }} + > + {#each localCategories as cat (cat.id)} + + {/each} +
+
    - {#each data?.categories ?? [] as cat (cat.id)} -
  • + {#each localCategories as cat, index (cat.id)} +
  • handleDragStart(index)} + ondragover={(e) => handleDragOver(e, index)} + ondrop={handleDrop} + ondragend={() => (draggedIndex = null)} + > + ⋮⋮ {cat.name}
@@ -61,6 +114,14 @@ color: var(--text); } + .drag-handle { + color: var(--text-secondary); + font-size: 0.875rem; + cursor: grab; + padding-right: 0.5rem; + user-select: none; + } + .error { margin-top: -0.25rem; margin-bottom: 0.5rem; -- cgit v1.3