drag reorder boards in admin, position respected on public site

This commit is contained in:
2026-03-22 07:55:08 +02:00
parent 393001c07c
commit a530ce67b0
4 changed files with 61 additions and 8 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'
import { useState, useEffect, useCallback } from 'react'
import { Link } from 'react-router-dom'
import { api } from '../../lib/api'
import { useDocumentTitle } from '../../hooks/useDocumentTitle'
@@ -25,6 +25,7 @@ interface Board {
rssEnabled: boolean
rssFeedCount: number
staleDays: number
position: number
}
export default function AdminBoards() {
@@ -35,6 +36,8 @@ export default function AdminBoards() {
const [boards, setBoards] = useState<Board[]>([])
const [loading, setLoading] = useState(true)
const [editBoard, setEditBoard] = useState<Board | null>(null)
const [dragIdx, setDragIdx] = useState<number | null>(null)
const [insertAt, setInsertAt] = useState<number | null>(null)
const [showCreate, setShowCreate] = useState(false)
const [form, setForm] = useState({
name: '',
@@ -62,6 +65,21 @@ export default function AdminBoards() {
useEffect(() => { fetchBoards() }, [])
const handleDrop = useCallback(() => {
if (dragIdx === null || insertAt === null) return
setBoards((prev) => {
const next = [...prev]
const [moved] = next.splice(dragIdx, 1)
next.splice(insertAt > dragIdx ? insertAt - 1 : insertAt, 0, moved)
api.put('/admin/boards/reorder', { boardIds: next.map((b) => b.id) }).catch(() => {
toast.error('Failed to save order')
})
return next
})
setDragIdx(null)
setInsertAt(null)
}, [dragIdx, insertAt, toast])
const resetForm = () => {
setForm({ name: '', slug: '', description: '', iconName: null, iconColor: null, voteBudget: 10, voteBudgetReset: 'monthly', rssEnabled: true, rssFeedCount: 50, staleDays: 0 })
setEditBoard(null)
@@ -158,13 +176,30 @@ export default function AdminBoards() {
))}
</div>
) : (
<div className="flex flex-col gap-3">
{boards.map((board) => (
<div className="flex flex-col gap-1">
{boards.map((board, i) => (
<div key={board.id}>
{dragIdx !== null && insertAt === i && dragIdx !== i && dragIdx !== i - 1 && (
<div style={{ height: 2, background: 'var(--admin-accent)', borderRadius: 1, margin: '0 12px' }} />
)}
<div
key={board.id}
className="card p-4 flex items-center gap-4"
style={{ opacity: board.isArchived ? 0.5 : 1 }}
className="card p-4 flex items-center gap-3"
style={{ opacity: board.isArchived ? 0.5 : dragIdx === i ? 0.4 : 1 }}
draggable
onDragStart={() => setDragIdx(i)}
onDragOver={(e) => { e.preventDefault(); setInsertAt(i) }}
onDragEnd={handleDrop}
>
<div
style={{ cursor: 'grab', color: 'var(--text-tertiary)', padding: '4px 0', touchAction: 'none' }}
aria-label="Drag to reorder"
>
<svg width="10" height="16" viewBox="0 0 10 16" fill="currentColor">
<circle cx="2" cy="2" r="1.5" /><circle cx="8" cy="2" r="1.5" />
<circle cx="2" cy="8" r="1.5" /><circle cx="8" cy="8" r="1.5" />
<circle cx="2" cy="14" r="1.5" /><circle cx="8" cy="14" r="1.5" />
</svg>
</div>
<BoardIcon name={board.name} iconName={board.iconName} iconColor={board.iconColor} size={40} />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
@@ -203,6 +238,10 @@ export default function AdminBoards() {
)}
</div>
</div>
{dragIdx !== null && insertAt === boards.length && i === boards.length - 1 && dragIdx !== i && (
<div style={{ height: 2, background: 'var(--admin-accent)', borderRadius: 1, margin: '0 12px' }} />
)}
</div>
))}
{boards.length === 0 && (