diff --git a/src/features/Queue/Queue.tsx b/src/features/Queue/Queue.tsx index 88ba603..fbaab8a 100644 --- a/src/features/Queue/Queue.tsx +++ b/src/features/Queue/Queue.tsx @@ -1,26 +1,31 @@ -import React from 'react'; -import { IonItem, IonItemSliding, IonItemOptions, IonItemOption, IonIcon, IonLabel } from '@ionic/react'; -import { trash, arrowUp, arrowDown } from 'ionicons/icons'; -import { ActionButton, InfiniteScrollList, PageHeader } from '../../components/common'; +import React, { useState, useEffect } from 'react'; +import { IonButton, IonIcon, IonReorderGroup, IonReorder, IonItem, IonLabel, IonItemSliding, IonItemOptions, IonItemOption } from '@ionic/react'; +import { trash, reorderThreeOutline, reorderTwoOutline, playCircle } from 'ionicons/icons'; +import { PageHeader, ActionButton } from '../../components/common'; import { useQueue } from '../../hooks'; import { useAppSelector } from '../../redux'; -import { selectQueue, selectPlayerState, selectIsAdmin } from '../../redux'; +import { selectQueue, selectPlayerState, selectIsAdmin, selectControllerName } from '../../redux'; import { PlayerState } from '../../types'; +import { queueService } from '../../firebase/services'; import type { QueueItem } from '../../types'; +type QueueMode = 'delete' | 'reorder'; + const Queue: React.FC = () => { + const [queueMode, setQueueMode] = useState('delete'); + const [listItems, setListItems] = useState([]); + const { queueItems, queueStats, canReorder, handleRemoveFromQueue, - handleMoveUp, - handleMoveDown, } = useQueue(); const queue = useAppSelector(selectQueue); const playerState = useAppSelector(selectPlayerState); const isAdmin = useAppSelector(selectIsAdmin); + const controllerName = useAppSelector(selectControllerName); const queueCount = Object.keys(queue).length; // Debug logging @@ -29,6 +34,7 @@ const Queue: React.FC = () => { console.log('Queue component - player state:', playerState); console.log('Queue component - isAdmin:', isAdmin); console.log('Queue component - canReorder:', canReorder); + console.log('Queue component - queueMode:', queueMode); // Check if items can be deleted (admin can delete any item when not playing) const canDeleteItems = isAdmin && (playerState?.state === PlayerState.stopped || playerState?.state === PlayerState.paused); @@ -36,25 +42,81 @@ const Queue: React.FC = () => { console.log('Queue component - canDeleteItems:', canDeleteItems); console.log('Queue component - canReorder:', canReorder); - // Render queue item for InfiniteScrollList + // Determine if currently playing + const isPlaying = playerState?.state === PlayerState.playing; + + // Update list items when queue changes + useEffect(() => { + if (queueItems.length > 0) { + // Skip the first item (currently playing) for reordering + setListItems(queueItems.slice(1)); + } else { + setListItems([]); + } + }, [queueItems]); + + // Toggle between modes + const toggleQueueMode = () => { + setQueueMode(prevMode => prevMode === 'delete' ? 'reorder' : 'delete'); + }; + + // Handle reorder event from IonReorderGroup + const doReorder = async (event: CustomEvent) => { + console.log('Reorder event:', event.detail); + const { from, to, complete } = event.detail; + + if (listItems && controllerName) { + const copy = [...listItems]; + const draggedItem = copy.splice(from, 1)[0]; + copy.splice(to, 0, draggedItem); + + // Complete the reorder animation + complete(); + + // Create the new queue order (first item + reordered items) + const newQueueItems = [queueItems[0], ...copy]; + console.log('New queue order:', newQueueItems); + + try { + // Update all items with their new order values + const updatePromises = newQueueItems.map((item, index) => { + const newOrder = index + 1; + if (item.key && item.order !== newOrder) { + console.log(`Updating item ${item.key} from order ${item.order} to ${newOrder}`); + return queueService.updateQueueItem(controllerName, item.key, { order: newOrder }); + } + return Promise.resolve(); + }); + + await Promise.all(updatePromises); + console.log('Queue reorder completed successfully'); + } catch (error) { + console.error('Failed to reorder queue:', error); + // You might want to show an error toast here + } + } + }; + + // Render queue item const renderQueueItem = (queueItem: QueueItem, index: number) => { console.log(`Queue item ${index}: order=${queueItem.order}, key=${queueItem.key}`); - const canDelete = isAdmin; // Allow admin to delete any item + const canDelete = isAdmin && queueMode === 'delete'; // Only allow delete in delete mode + const isFirstItem = index === 0; return ( - + {/* Order Number */} -
+
{queueItem.order}
{/* Song Info */} -

+

{queueItem.singer.name}

-

+

{queueItem.song.title}

@@ -62,30 +124,10 @@ const Queue: React.FC = () => {

- {/* Admin Controls */} - {(canReorder || isAdmin) && ( -
-
- {queueItem.order > 2 && ( - handleMoveUp(queueItem)} - variant="secondary" - size="sm" - > - - - )} - {queueItem.order > 1 && queueItem.order < queueItems.length && ( - handleMoveDown(queueItem)} - variant="secondary" - size="sm" - > - - - )} -
- {canDelete && ( + {/* Delete Button or Drag Handle */} +
+ {canDelete && ( +
e.stopPropagation()}> handleRemoveFromQueue(queueItem)} variant="danger" @@ -93,9 +135,14 @@ const Queue: React.FC = () => { > - )} -
- )} +
+ )} + {canReorder && queueMode === 'reorder' && ( +
+ +
+ )} +
{/* Swipe Actions */} @@ -113,6 +160,66 @@ const Queue: React.FC = () => { ); }; + // Render first item (currently playing) separately + const renderFirstItem = () => { + if (queueItems.length === 0) return null; + + const firstItem = queueItems[0]; + const canDeleteFirstItem = isAdmin && (playerState?.state === PlayerState.stopped || playerState?.state === PlayerState.paused); + + return ( + + + {/* Order Number */} +
+ {firstItem.order} +
+ + {/* Song Info */} + +

+ {firstItem.singer.name} +

+

+ {firstItem.song.title} +

+

+ {firstItem.song.artist} +

+
+ + {/* Delete Button */} +
+ {canDeleteFirstItem && queueMode === 'delete' && ( +
e.stopPropagation()}> + handleRemoveFromQueue(firstItem)} + variant="danger" + size="sm" + className="opacity-100" + > + + +
+ )} +
+
+ + {/* Swipe Actions */} + {canDeleteFirstItem && queueMode === 'delete' && ( + + handleRemoveFromQueue(firstItem)} + > + + + + )} +
+ ); + }; + return ( <> { subtitle={`${queueStats.totalSongs} song${queueStats.totalSongs !== 1 ? 's' : ''} in queue`} /> - -
- {/* Queue List */} - - items={queueItems} - isLoading={queueCount === 0} - hasMore={false} - onLoadMore={() => {}} - renderItem={renderQueueItem} - emptyTitle="Queue is empty" - emptyMessage="Add songs from search, history, or favorites to get started" - loadingTitle="Loading queue..." - loadingMessage="Please wait while queue data is being loaded" - /> + {/* Mode Toggle Button */} + {canReorder && ( +
+ + + {queueMode === 'delete' ? 'Reorder Mode' : 'Delete Mode'} + +
+ )} + + {/* Mode Instructions */} + {canReorder && ( +
+

+ {queueMode === 'reorder' + ? (isPlaying + ? "💡 Drag and drop to reorder songs (first song is locked while playing)" + : "💡 Drag and drop to reorder songs" + ) + : "🗑️ Click the trash icon to delete songs from the queue" + } +

+
+ )} + + {/* First Item (Currently Playing) */} + {renderFirstItem()} + + {/* Queue List with Reorder */} + {canReorder && queueMode === 'reorder' ? ( + + {listItems.map((queueItem, index) => ( + + {renderQueueItem(queueItem, index)} + + ))} + + ) : ( +
+ {listItems.map((queueItem, index) => renderQueueItem(queueItem, index))} +
+ )}
); diff --git a/src/hooks/useQueue.ts b/src/hooks/useQueue.ts index 73ba501..97e1e0c 100644 --- a/src/hooks/useQueue.ts +++ b/src/hooks/useQueue.ts @@ -171,6 +171,76 @@ export const useQueue = () => { } }, [controllerName, queueItems, showSuccess, showError]); + const handleReorder = useCallback(async (oldIndex: number, newIndex: number) => { + console.log('handleReorder called with:', { oldIndex, newIndex }); + console.log('Current queueItems:', queueItems); + console.log('Controller name:', controllerName); + + if (!controllerName || oldIndex === newIndex) { + console.log('Early return - conditions not met:', { + controllerName: !!controllerName, + oldIndex, + newIndex + }); + return; + } + + try { + const itemToMove = queueItems[oldIndex]; + if (!itemToMove || !itemToMove.key) { + console.log('No item to move found'); + showError('Cannot reorder item'); + return; + } + + console.log('Moving item:', { + item: { key: itemToMove.key, order: itemToMove.order }, + fromIndex: oldIndex, + toIndex: newIndex + }); + + // Calculate the new order for the moved item + const newOrder = newIndex + 1; + + // Update all affected items' orders + const updatePromises: Promise[] = []; + + if (oldIndex < newIndex) { + // Moving down: shift items between oldIndex and newIndex up by 1 + for (let i = oldIndex + 1; i <= newIndex; i++) { + const item = queueItems[i]; + if (item && item.key) { + updatePromises.push( + queueService.updateQueueItem(controllerName, item.key, { order: item.order - 1 }) + ); + } + } + } else { + // Moving up: shift items between newIndex and oldIndex down by 1 + for (let i = newIndex; i < oldIndex; i++) { + const item = queueItems[i]; + if (item && item.key) { + updatePromises.push( + queueService.updateQueueItem(controllerName, item.key, { order: item.order + 1 }) + ); + } + } + } + + // Update the moved item's order + updatePromises.push( + queueService.updateQueueItem(controllerName, itemToMove.key, { order: newOrder }) + ); + + await Promise.all(updatePromises); + console.log('Reorder completed successfully'); + showSuccess('Queue reordered successfully'); + } catch (error) { + console.error('Failed to reorder queue:', error); + showError('Failed to reorder queue'); + } + }, [controllerName, queueItems, showSuccess, showError]); + return { queueItems, queueStats, @@ -179,5 +249,6 @@ export const useQueue = () => { handleToggleFavorite, handleMoveUp, handleMoveDown, + handleReorder, }; }; \ No newline at end of file