Add drag-and-drop section reordering
This commit is contained in:
+9
-1
@@ -52,7 +52,9 @@ export default function App() {
|
|||||||
const importRef = useRef<HTMLInputElement>(null)
|
const importRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
const { catalog, loading: catalogLoading, error: catalogError } = useCatalog()
|
const { catalog, loading: catalogLoading, error: catalogError } = useCatalog()
|
||||||
const { carts, activeCart, activeId, storageError, setActiveCart, createCart, deleteCart, renameCart, importCart, exportCart, addSection, removeSection, renameSection, addItem, updateItem, removeItem } = useCarts()
|
const { carts, activeCart, activeId, storageError, setActiveCart, createCart, deleteCart, renameCart, importCart, exportCart, addSection, removeSection, renameSection, reorderSections, addItem, updateItem, removeItem } = useCarts()
|
||||||
|
const [dragSectionId, setDragSectionId] = useState<string | null>(null)
|
||||||
|
const [dragOverId, setDragOverId] = useState<string | null>(null)
|
||||||
|
|
||||||
function commitCartRename() {
|
function commitCartRename() {
|
||||||
if (renamingCartId && renameCartValue.trim()) renameCart(renamingCartId, renameCartValue.trim())
|
if (renamingCartId && renameCartValue.trim()) renameCart(renamingCartId, renameCartValue.trim())
|
||||||
@@ -219,6 +221,12 @@ export default function App() {
|
|||||||
onEditItem={item => updateItem(section.id, item)}
|
onEditItem={item => updateItem(section.id, item)}
|
||||||
onRemoveSection={() => removeSection(section.id)}
|
onRemoveSection={() => removeSection(section.id)}
|
||||||
onRenameSection={label => renameSection(section.id, label)}
|
onRenameSection={label => renameSection(section.id, label)}
|
||||||
|
isDragging={dragSectionId === section.id}
|
||||||
|
isDragOver={dragOverId === section.id}
|
||||||
|
onDragStart={() => setDragSectionId(section.id)}
|
||||||
|
onDragOver={e => { e.preventDefault(); setDragOverId(section.id) }}
|
||||||
|
onDrop={() => { if (dragSectionId) reorderSections(dragSectionId, section.id); setDragOverId(null) }}
|
||||||
|
onDragEnd={() => { setDragSectionId(null); setDragOverId(null) }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -11,9 +11,15 @@ interface Props {
|
|||||||
onEditItem: (item: Product | Drone) => void
|
onEditItem: (item: Product | Drone) => void
|
||||||
onRemoveSection: () => void
|
onRemoveSection: () => void
|
||||||
onRenameSection: (label: string) => void
|
onRenameSection: (label: string) => void
|
||||||
|
onDragStart: () => void
|
||||||
|
onDragOver: (e: React.DragEvent) => void
|
||||||
|
onDrop: () => void
|
||||||
|
onDragEnd: () => void
|
||||||
|
isDragging: boolean
|
||||||
|
isDragOver: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SectionBlock({ section, onRemoveItem, onAddItem, onEditItem, onRemoveSection, onRenameSection }: Props) {
|
export function SectionBlock({ section, onRemoveItem, onAddItem, onEditItem, onRemoveSection, onRenameSection, onDragStart, onDragOver, onDrop, onDragEnd, isDragging, isDragOver }: Props) {
|
||||||
const [showAdd, setShowAdd] = useState(false)
|
const [showAdd, setShowAdd] = useState(false)
|
||||||
const [editingItem, setEditingItem] = useState<Product | Drone | null>(null)
|
const [editingItem, setEditingItem] = useState<Product | Drone | null>(null)
|
||||||
const [editing, setEditing] = useState(false)
|
const [editing, setEditing] = useState(false)
|
||||||
@@ -49,10 +55,27 @@ export function SectionBlock({ section, onRemoveItem, onAddItem, onEditItem, onR
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3 rounded-lg border p-4" style={{ background: 'var(--color-bg1)', borderColor: 'var(--color-bg3)' }}>
|
<div
|
||||||
|
draggable
|
||||||
|
onDragStart={onDragStart}
|
||||||
|
onDragOver={onDragOver}
|
||||||
|
onDrop={onDrop}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
className="flex flex-col gap-3 rounded-lg border p-4 transition-opacity"
|
||||||
|
style={{
|
||||||
|
background: 'var(--color-bg1)',
|
||||||
|
borderColor: isDragOver ? 'var(--color-cyan)' : 'var(--color-bg3)',
|
||||||
|
opacity: isDragging ? 0.4 : 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<span
|
||||||
|
className="cursor-grab select-none text-base leading-none"
|
||||||
|
style={{ color: 'var(--color-bg3)' }}
|
||||||
|
title="Drag to reorder"
|
||||||
|
>⠿</span>
|
||||||
{editing ? (
|
{editing ? (
|
||||||
<form onSubmit={submitRename} className="flex items-center gap-1">
|
<form onSubmit={submitRename} className="flex items-center gap-1">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -158,6 +158,18 @@ export function useCarts() {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reorderSections(fromId: string, toId: string) {
|
||||||
|
mutateActiveCart(cart => {
|
||||||
|
const sections = [...cart.sections]
|
||||||
|
const fromIdx = sections.findIndex(s => s.id === fromId)
|
||||||
|
const toIdx = sections.findIndex(s => s.id === toId)
|
||||||
|
if (fromIdx === -1 || toIdx === -1 || fromIdx === toIdx) return cart
|
||||||
|
const [moved] = sections.splice(fromIdx, 1)
|
||||||
|
sections.splice(toIdx, 0, moved)
|
||||||
|
return { ...cart, sections }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ── Item management ───────────────────────────────────────────────────────
|
// ── Item management ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
function addItem(sectionId: string, item: Product | Drone) {
|
function addItem(sectionId: string, item: Product | Drone) {
|
||||||
@@ -220,6 +232,7 @@ export function useCarts() {
|
|||||||
addSection,
|
addSection,
|
||||||
removeSection,
|
removeSection,
|
||||||
renameSection,
|
renameSection,
|
||||||
|
reorderSections,
|
||||||
addItem,
|
addItem,
|
||||||
updateItem,
|
updateItem,
|
||||||
removeItem,
|
removeItem,
|
||||||
|
|||||||
Reference in New Issue
Block a user