diff --git a/packages/backend/src/routes/playlists.ts b/packages/backend/src/routes/playlists.ts index bb454d5..aab14b3 100644 --- a/packages/backend/src/routes/playlists.ts +++ b/packages/backend/src/routes/playlists.ts @@ -125,4 +125,61 @@ router.post('/reorder', async (req: Request, res: Response) => { } }); +// Move a single track before another (stable, full-playlist context) +router.post('/reorder-move', async (req: Request, res: Response) => { + try { + const { playlistName, fromId, toId } = req.body as { playlistName: string; fromId: string; toId?: string }; + if (!playlistName || !fromId) { + return res.status(400).json({ message: 'playlistName and fromId are required' }); + } + + const playlists = await Playlist.find({}); + let updated = false; + + const applyMove = (node: any): any => { + if (!node) return node; + if (node.type === 'playlist' && node.name === playlistName) { + const base: string[] = Array.isArray(node.tracks) ? node.tracks : []; + const order: string[] = Array.isArray(node.order) ? node.order : []; + // Build effective order + const baseSet = new Set(base); + const effective = [ + ...order.filter((id: string) => baseSet.has(id)), + ...base.filter((id: string) => !order.includes(id)) + ]; + + // Remove fromId if present + const without = effective.filter(id => id !== fromId); + let insertIndex = typeof toId === 'string' ? without.indexOf(toId) : without.length; + if (insertIndex < 0) insertIndex = without.length; + without.splice(insertIndex, 0, fromId); + + node.order = without; // store full order overlay + updated = true; + return node; + } + if (Array.isArray(node.children)) { + node.children = node.children.map(applyMove); + } + return node; + }; + + for (const p of playlists) { + applyMove(p as any); + p.markModified('children'); + p.markModified('order'); + await p.save(); + } + + if (!updated) { + return res.status(404).json({ message: `Playlist "${playlistName}" not found` }); + } + + res.json({ message: 'Track moved', playlistName }); + } catch (error) { + console.error('Error moving track in playlist:', error); + res.status(500).json({ error: 'Failed to move track' }); + } +}); + export const playlistsRouter = router; \ No newline at end of file diff --git a/packages/frontend/src/components/PaginatedSongList.tsx b/packages/frontend/src/components/PaginatedSongList.tsx index 42c98d2..b45839a 100644 --- a/packages/frontend/src/components/PaginatedSongList.tsx +++ b/packages/frontend/src/components/PaginatedSongList.tsx @@ -16,6 +16,7 @@ import { import { Search2Icon } from '@chakra-ui/icons'; import { FiPlay } from 'react-icons/fi'; import type { Song, PlaylistNode } from '../types/interfaces'; +import { api } from '../services/api'; import { formatDuration, formatTotalDuration } from '../utils/formatters'; import { useDebounce } from '../hooks/useDebounce'; import { PlaylistSelectionModal } from './PlaylistSelectionModal'; @@ -296,12 +297,10 @@ export const PaginatedSongList: React.FC = memo(({ console.debug('[Reorder] same index drop'); return; } - const ordered = [...songs]; - const [moved] = ordered.splice(fromIndex, 1); - ordered.splice(toIndex, 0, moved); - const orderedIds = ordered.map(s => s.id); - console.debug('[Reorder] persisting order', { fromIndex, toIndex, fromId, toId: songs[index].id }); - await onReorder(orderedIds); + const toId = songs[index].id; + // Simpler and more robust: instruct backend to move fromId before toId + await api.moveTrackInPlaylist(currentPlaylist, fromId, toId); + await onReorder(songs.map(s => s.id)); // trigger refresh via parent setDragHoverIndex(null); }} onRowDragStartCapture={(e: React.DragEvent) => { diff --git a/packages/frontend/src/services/api.ts b/packages/frontend/src/services/api.ts index bdf7c7f..559467f 100644 --- a/packages/frontend/src/services/api.ts +++ b/packages/frontend/src/services/api.ts @@ -106,6 +106,15 @@ class Api { if (!response.ok) throw new Error('Failed to reorder playlist'); } + async moveTrackInPlaylist(playlistName: string, fromId: string, toId?: string): Promise { + const response = await fetch(`${API_BASE_URL}/playlists/reorder-move`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ playlistName, fromId, toId }) + }); + if (!response.ok) throw new Error('Failed to move track in playlist'); + } + async resetDatabase(): Promise { try { const response = await fetch(`${API_BASE_URL}/reset`, {