(e: React.DragEvent, targetSession: Session, projectId: number)
| 1472 | }; |
| 1473 | |
| 1474 | const handleSessionDrop = async (e: React.DragEvent, targetSession: Session, projectId: number) => { |
| 1475 | e.preventDefault(); |
| 1476 | e.stopPropagation(); |
| 1477 | |
| 1478 | const project = projectsWithSessions.find(p => p.id === projectId); |
| 1479 | if (!project) return; |
| 1480 | |
| 1481 | // Handle both session-to-session and folder-to-session reordering |
| 1482 | if (dragState.type === 'session' && |
| 1483 | dragState.sessionId && |
| 1484 | dragState.projectId === projectId && |
| 1485 | dragState.sessionId !== targetSession.id && |
| 1486 | !targetSession.folderId && // Only reorder at root level |
| 1487 | !dragState.folderId) { // Only if dragged session is also at root level |
| 1488 | |
| 1489 | // Get root-level items only (sessions and folders without parents) |
| 1490 | const rootSessions = project.sessions.filter(s => !s.folderId); |
| 1491 | const rootFolders = project.folders ? buildFolderTree(project.folders) : []; |
| 1492 | |
| 1493 | // Find indices in the root-level combined list |
| 1494 | const sourceSessionIndex = rootSessions.findIndex(s => s.id === dragState.sessionId); |
| 1495 | const targetSessionIndex = rootSessions.findIndex(s => s.id === targetSession.id); |
| 1496 | |
| 1497 | if (sourceSessionIndex !== -1 && targetSessionIndex !== -1) { |
| 1498 | // Update display orders for root sessions and folders together |
| 1499 | // Build a combined list with CURRENT displayOrder values, sorted by displayOrder |
| 1500 | type RootItem = { type: 'session' | 'folder'; id: string; displayOrder: number; createdAt: string; originalIndex: number }; |
| 1501 | const rootItems: RootItem[] = [ |
| 1502 | ...rootFolders.map((f, idx) => ({ type: 'folder' as const, id: f.id, displayOrder: f.displayOrder ?? 0, createdAt: f.createdAt, originalIndex: idx })), |
| 1503 | ...rootSessions.map((s, idx) => ({ type: 'session' as const, id: s.id, displayOrder: s.displayOrder ?? 0, createdAt: s.createdAt, originalIndex: idx })) |
| 1504 | ]; |
| 1505 | |
| 1506 | // Sort by current displayOrder to get the current visual order |
| 1507 | // Use createdAt as a tiebreaker to ensure stable sorting when displayOrder values are duplicated |
| 1508 | rootItems.sort((a, b) => { |
| 1509 | const orderDiff = a.displayOrder - b.displayOrder; |
| 1510 | if (orderDiff !== 0) return orderDiff; |
| 1511 | // If displayOrder is equal, sort by createdAt (older items first) |
| 1512 | return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(); |
| 1513 | }); |
| 1514 | |
| 1515 | // Find positions of source and target in the current order |
| 1516 | const sourceItemIndex = rootItems.findIndex(item => item.type === 'session' && item.id === dragState.sessionId); |
| 1517 | const targetItemIndex = rootItems.findIndex(item => item.type === 'session' && item.id === targetSession.id); |
| 1518 | |
| 1519 | if (sourceItemIndex !== -1 && targetItemIndex !== -1) { |
| 1520 | // Remove the source item and insert it at the target position |
| 1521 | const [removedItem] = rootItems.splice(sourceItemIndex, 1); |
| 1522 | rootItems.splice(targetItemIndex, 0, removedItem); |
| 1523 | |
| 1524 | // Reassign displayOrder values sequentially to reflect the new order |
| 1525 | rootItems.forEach((item, index) => { |
| 1526 | item.displayOrder = index; |
| 1527 | }); |
| 1528 | } |
| 1529 | |
| 1530 | // Prepare updates for API |
| 1531 | const sessionOrders = rootItems |
no test coverage detected