fix(reorder): backend move endpoint and frontend use precise move to persist order reliably; refresh after move

This commit is contained in:
Geert Rademakes 2025-08-08 14:30:58 +02:00
parent 61d4ca16de
commit 8136bbb959
3 changed files with 71 additions and 6 deletions

View File

@ -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; export const playlistsRouter = router;

View File

@ -16,6 +16,7 @@ import {
import { Search2Icon } from '@chakra-ui/icons'; import { Search2Icon } from '@chakra-ui/icons';
import { FiPlay } from 'react-icons/fi'; import { FiPlay } from 'react-icons/fi';
import type { Song, PlaylistNode } from '../types/interfaces'; import type { Song, PlaylistNode } from '../types/interfaces';
import { api } from '../services/api';
import { formatDuration, formatTotalDuration } from '../utils/formatters'; import { formatDuration, formatTotalDuration } from '../utils/formatters';
import { useDebounce } from '../hooks/useDebounce'; import { useDebounce } from '../hooks/useDebounce';
import { PlaylistSelectionModal } from './PlaylistSelectionModal'; import { PlaylistSelectionModal } from './PlaylistSelectionModal';
@ -296,12 +297,10 @@ export const PaginatedSongList: React.FC<PaginatedSongListProps> = memo(({
console.debug('[Reorder] same index drop'); console.debug('[Reorder] same index drop');
return; return;
} }
const ordered = [...songs]; const toId = songs[index].id;
const [moved] = ordered.splice(fromIndex, 1); // Simpler and more robust: instruct backend to move fromId before toId
ordered.splice(toIndex, 0, moved); await api.moveTrackInPlaylist(currentPlaylist, fromId, toId);
const orderedIds = ordered.map(s => s.id); await onReorder(songs.map(s => s.id)); // trigger refresh via parent
console.debug('[Reorder] persisting order', { fromIndex, toIndex, fromId, toId: songs[index].id });
await onReorder(orderedIds);
setDragHoverIndex(null); setDragHoverIndex(null);
}} }}
onRowDragStartCapture={(e: React.DragEvent) => { onRowDragStartCapture={(e: React.DragEvent) => {

View File

@ -106,6 +106,15 @@ class Api {
if (!response.ok) throw new Error('Failed to reorder playlist'); if (!response.ok) throw new Error('Failed to reorder playlist');
} }
async moveTrackInPlaylist(playlistName: string, fromId: string, toId?: string): Promise<void> {
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<boolean> { async resetDatabase(): Promise<boolean> {
try { try {
const response = await fetch(`${API_BASE_URL}/reset`, { const response = await fetch(`${API_BASE_URL}/reset`, {