From d0a83a85f58730ad2b9ff7a421c3d35d64e0a2da Mon Sep 17 00:00:00 2001 From: Geert Rademakes Date: Fri, 8 Aug 2025 14:52:48 +0200 Subject: [PATCH] feat(reorder): add end-of-list drop zone to move track to end; show hover indicator --- .../src/components/PaginatedSongList.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/frontend/src/components/PaginatedSongList.tsx b/packages/frontend/src/components/PaginatedSongList.tsx index 1d2b399..bd0da02 100644 --- a/packages/frontend/src/components/PaginatedSongList.tsx +++ b/packages/frontend/src/components/PaginatedSongList.tsx @@ -170,6 +170,7 @@ export const PaginatedSongList: React.FC = memo(({ const isTriggeringRef = useRef(false); const timeoutRef = useRef(null); const [dragHoverIndex, setDragHoverIndex] = useState(null); + const [endDropHover, setEndDropHover] = useState(false); // Store current values in refs to avoid stale closures const hasMoreRef = useRef(hasMore); @@ -500,6 +501,36 @@ export const PaginatedSongList: React.FC = memo(({ ))} + + {/* Drop zone to move item to end of playlist */} + {onReorder && currentPlaylist && selectedSongs.size === 0 && ( + { + e.preventDefault(); + setDragHoverIndex(null); + setEndDropHover(true); + try { e.dataTransfer.dropEffect = 'move'; } catch {} + }} + onDragLeave={() => setEndDropHover(false)} + onDrop={async (e: React.DragEvent) => { + e.preventDefault(); + const fromId = e.dataTransfer.getData('text/song-id'); + if (!fromId) return; + // Move to end: omit toId + console.debug('[Reorder] move to end request', { playlist: currentPlaylist, fromId }); + await api.moveTrackInPlaylist(currentPlaylist, fromId); + await onReorder(songs.map(s => s.id)); + setEndDropHover(false); + }} + position="relative" + height="28px" + mt={1} + > + {endDropHover && ( + + )} + + )} {/* Loading indicator for infinite scroll or playlist switching */} {(loading || isSwitchingPlaylist) && (