diff --git a/src/frontend/src/components/SortGroupBar.tsx b/src/frontend/src/components/SortGroupBar.tsx new file mode 100644 index 0000000..e452ee8 --- /dev/null +++ b/src/frontend/src/components/SortGroupBar.tsx @@ -0,0 +1,155 @@ +import { ArrowDown, ArrowUp } from 'lucide-react'; + +export type SortKey = 'publishedAt' | 'title' | 'duration' | 'fileSize' | 'status'; +export type GroupByKey = 'none' | 'playlist' | 'year' | 'type'; + +interface SortButton { + key: SortKey; + label: string; +} + +const SORT_BUTTONS: SortButton[] = [ + { key: 'publishedAt', label: 'Date' }, + { key: 'title', label: 'Title' }, + { key: 'duration', label: 'Duration' }, + { key: 'fileSize', label: 'Size' }, + { key: 'status', label: 'Status' }, +]; + +const GROUP_BY_OPTIONS: { value: GroupByKey; label: string; youtubeOnly?: boolean }[] = [ + { value: 'none', label: 'No Grouping' }, + { value: 'playlist', label: 'Playlist', youtubeOnly: true }, + { value: 'year', label: 'Year' }, + { value: 'type', label: 'Type' }, +]; + +interface SortGroupBarProps { + sortKey: string | null; + sortDirection: 'asc' | 'desc'; + onSort: (key: string, direction: 'asc' | 'desc') => void; + groupBy: GroupByKey; + onGroupByChange: (groupBy: GroupByKey) => void; + isYouTube: boolean; +} + +export function SortGroupBar({ + sortKey, + sortDirection, + onSort, + groupBy, + onGroupByChange, + isYouTube, +}: SortGroupBarProps) { + const handleSortClick = (key: SortKey) => { + if (sortKey === key) { + // Toggle direction + onSort(key, sortDirection === 'asc' ? 'desc' : 'asc'); + } else { + // New sort key — default to descending + onSort(key, 'desc'); + } + }; + + return ( +
+ {/* Sort label */} + + Sort + + + {/* Sort buttons */} + {SORT_BUTTONS.map((btn) => { + const isActive = sortKey === btn.key; + return ( + + ); + })} + + {/* Spacer */} +
+ + {/* Group by */} + + Group + + +
+ ); +} diff --git a/src/frontend/src/pages/ChannelDetail.tsx b/src/frontend/src/pages/ChannelDetail.tsx index ccb0c2e..13d66e6 100644 --- a/src/frontend/src/pages/ChannelDetail.tsx +++ b/src/frontend/src/pages/ChannelDetail.tsx @@ -36,6 +36,7 @@ import { SkeletonChannelHeader, SkeletonTable } from '../components/Skeleton'; import { ContentCard } from '../components/ContentCard'; import { ContentListItem } from '../components/ContentListItem'; import { Pagination } from '../components/Pagination'; +import { SortGroupBar, type GroupByKey } from '../components/SortGroupBar'; import { Modal } from '../components/Modal'; import { useToast } from '../components/Toast'; import { useDownloadProgress } from '../contexts/DownloadProgressContext'; @@ -121,8 +122,23 @@ export function ChannelDetail() { const [contentSearch, setContentSearch] = useState(''); const [contentStatusFilter, setContentStatusFilter] = useState(''); const [contentTypeFilter, setContentTypeFilter] = useState(''); - const [sortKey, setSortKey] = useState(null); - const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); + const [sortKey, setSortKey] = useState(() => { + try { return localStorage.getItem('tubearr-sort-key') || null; } catch { return null; } + }); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>(() => { + try { + const stored = localStorage.getItem('tubearr-sort-dir'); + if (stored === 'asc' || stored === 'desc') return stored; + return 'asc'; + } catch { return 'asc'; } + }); + const [groupBy, setGroupBy] = useState(() => { + try { + const stored = localStorage.getItem('tubearr-group-by'); + if (stored === 'none' || stored === 'playlist' || stored === 'year' || stored === 'type') return stored; + return 'none'; + } catch { return 'none'; } + }); const [viewMode, setViewMode] = useState<'table' | 'card' | 'list'>(() => { try { const stored = localStorage.getItem('tubearr-content-view'); @@ -316,6 +332,13 @@ export function ChannelDetail() { setSortKey(key); setSortDirection(direction); setContentPage(1); + try { localStorage.setItem('tubearr-sort-key', key); } catch { /* ignore */ } + try { localStorage.setItem('tubearr-sort-dir', direction); } catch { /* ignore */ } + }, []); + + const handleGroupByChange = useCallback((value: GroupByKey) => { + setGroupBy(value); + try { localStorage.setItem('tubearr-group-by', value); } catch { /* ignore */ } }, []); const handleSetViewMode = useCallback((mode: 'table' | 'card' | 'list') => { @@ -1433,6 +1456,15 @@ export function ChannelDetail() {
+ {/* Sort & Group controls */} + {contentError ? (