import React, { ChangeEvent } from 'react'; import { Box, Flex, Text, Button, IconButton, HStack, Menu, MenuButton, MenuList, MenuItem, MenuDivider, Checkbox } from '@chakra-ui/react'; import { Input, InputGroup, InputLeftElement } from "@chakra-ui/input"; import { Search2Icon, ChevronDownIcon } from "@chakra-ui/icons"; import type { Song, PlaylistNode } from "../types/interfaces"; import { useState, useCallback, useMemo } from "react"; import type { MouseEvent } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { formatDuration, formatTotalDuration } from '../utils/formatters'; interface SongListProps { songs: Song[]; onAddToPlaylist: (songIds: string[], playlistName: string) => void; onRemoveFromPlaylist?: (songIds: string[]) => void; playlists: PlaylistNode[]; onSongSelect: (song: Song) => void; selectedSongId: string | null; currentPlaylist: string | null; depth?: number; } export const SongList: React.FC = ({ songs, onAddToPlaylist, onRemoveFromPlaylist, playlists, onSongSelect, selectedSongId, currentPlaylist, depth = 0 }) => { const [selectedSongs, setSelectedSongs] = useState>(new Set()); const [searchQuery, setSearchQuery] = useState(""); // Helper function to get all playlists (excluding folders) from the playlist tree const getAllPlaylists = useCallback((nodes: PlaylistNode[]): PlaylistNode[] => { let result: PlaylistNode[] = []; for (const node of nodes) { if (node.type === 'playlist') { result.push(node); } else if (node.type === 'folder' && node.children) { result = result.concat(getAllPlaylists(node.children)); } } return result; }, []); // Get flattened list of all playlists const allPlaylists = useMemo(() => getAllPlaylists(playlists), [playlists, getAllPlaylists]); const filteredSongs = useMemo(() => { if (!searchQuery) return songs; const query = searchQuery.toLowerCase(); return songs.filter( song => song.title.toLowerCase().includes(query) || song.artist.toLowerCase().includes(query) ); }, [songs, searchQuery]); const toggleSelection = useCallback((songId: string) => { setSelectedSongs(prev => { const newSelection = new Set(prev); if (newSelection.has(songId)) { newSelection.delete(songId); } else { newSelection.add(songId); } return newSelection; }); }, []); const toggleSelectAll = useCallback(() => { setSelectedSongs(prev => prev.size === songs.length ? new Set() : new Set(songs.map(s => s.id)) ); }, [songs]); const handleBulkAddToPlaylist = (playlistName: string) => { if (selectedSongs.size > 0) { onAddToPlaylist(Array.from(selectedSongs), playlistName); setSelectedSongs(new Set()); // Clear selection after action } }; // Calculate total duration const totalDuration = useMemo(() => { return filteredSongs.reduce((total, song) => { if (!song.totalTime) return total; // Convert to seconds, handling milliseconds if present const seconds = Math.floor(Number(song.totalTime) / (song.totalTime.length > 4 ? 1000 : 1)); return total + seconds; }, 0); }, [filteredSongs]); return ( {/* Sticky Header */} {/* Search Bar */} ) => setSearchQuery(e.target.value)} bg="gray.800" borderColor="gray.600" _hover={{ borderColor: "gray.500" }} _focus={{ borderColor: "blue.300", boxShadow: "0 0 0 1px var(--chakra-colors-blue-300)" }} /> {/* Bulk Actions Toolbar */} 0 && selectedSongs.size < filteredSongs.length} onChange={toggleSelectAll} colorScheme="blue" sx={{ '& > span:first-of-type': { opacity: 1, border: '2px solid', borderColor: 'gray.500' } }} > {selectedSongs.size === 0 ? "Select All" : `Selected ${selectedSongs.size} song${selectedSongs.size === 1 ? '' : 's'}`} {filteredSongs.length} song{filteredSongs.length === 1 ? '' : 's'} • {formatTotalDuration(totalDuration)} {selectedSongs.size > 0 && ( } size="sm" colorScheme="blue" > Actions {allPlaylists.map((playlist) => ( { handleBulkAddToPlaylist(playlist.name); }} > Add to {playlist.name} ))} {currentPlaylist !== "All Songs" && onRemoveFromPlaylist && ( <> { onRemoveFromPlaylist(Array.from(selectedSongs)); setSelectedSongs(new Set()); }} > Remove from {currentPlaylist} )} )} {/* Scrollable Song List */} {filteredSongs.map((song) => ( 0 ? 4 + (depth * 4) : 2} borderBottom="1px" borderColor="gray.700" bg={selectedSongId === song.id ? "gray.700" : "transparent"} _hover={{ bg: "gray.800", cursor: "pointer" }} onClick={() => onSongSelect(song)} > ) => { e.stopPropagation(); toggleSelection(song.id); }} mr={4} onClick={(e) => e.stopPropagation()} size={depth > 0 ? "sm" : "md"} /> 0 ? "sm" : "md"} > {song.title} 0 ? "xs" : "sm"} color={selectedSongId === song.id ? "gray.300" : "gray.500"} > {song.artist} • {formatDuration(song.totalTime)} } variant="ghost" onClick={(e) => e.stopPropagation()} size={depth > 0 ? "xs" : "sm"} ml="auto" /> {allPlaylists.map((playlist) => ( { e.stopPropagation(); onAddToPlaylist([song.id], playlist.name); }} > Add to {playlist.name} ))} {currentPlaylist !== "All Songs" && onRemoveFromPlaylist && ( <> { e.stopPropagation(); onRemoveFromPlaylist([song.id]); }} > Remove from {currentPlaylist} )} ))} ); };