feat: Added groupedContent useMemo and renderGroupedContent to unify pl…
- "src/frontend/src/pages/ChannelDetail.tsx" GSD-Task: S04/T02
This commit is contained in:
parent
79b2a3a566
commit
1711389d9c
1 changed files with 77 additions and 11 deletions
|
|
@ -177,7 +177,7 @@ export function ChannelDetail() {
|
|||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
const [showFullDescription, setShowFullDescription] = useState(false);
|
||||
const [scanInProgress, setScanInProgress] = useState(false);
|
||||
const [expandedPlaylists, setExpandedPlaylists] = useState<Set<number | 'uncategorized'>>(new Set());
|
||||
const [expandedGroups, setExpandedGroups] = useState<Set<string | number>>(new Set());
|
||||
const [selectedIds, setSelectedIds] = useState<Set<number>>(new Set());
|
||||
const [localCheckInterval, setLocalCheckInterval] = useState<number | ''>('');
|
||||
const [checkIntervalSaved, setCheckIntervalSaved] = useState(false);
|
||||
|
|
@ -341,13 +341,18 @@ export function ChannelDetail() {
|
|||
try { localStorage.setItem('tubearr-group-by', value); } catch { /* ignore */ }
|
||||
}, []);
|
||||
|
||||
// Reset expanded groups when groupBy changes
|
||||
useEffect(() => {
|
||||
setExpandedGroups(new Set());
|
||||
}, [groupBy]);
|
||||
|
||||
const handleSetViewMode = useCallback((mode: 'table' | 'card' | 'list') => {
|
||||
setViewMode(mode);
|
||||
try { localStorage.setItem('tubearr-content-view', mode); } catch { /* ignore */ }
|
||||
}, []);
|
||||
|
||||
const togglePlaylist = useCallback((id: number | 'uncategorized') => {
|
||||
setExpandedPlaylists((prev) => {
|
||||
const toggleGroup = useCallback((id: string | number) => {
|
||||
setExpandedGroups((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(id)) {
|
||||
next.delete(id);
|
||||
|
|
@ -450,6 +455,68 @@ export function ChannelDetail() {
|
|||
return groups.length > 0 ? groups : null;
|
||||
}, [channel, playlistData, content]);
|
||||
|
||||
// ── Unified group-by logic ──
|
||||
|
||||
const groupedContent = useMemo<{ id: string | number; title: string; items: ContentItem[] }[] | null>(() => {
|
||||
if (groupBy === 'none') return null;
|
||||
|
||||
if (groupBy === 'playlist') {
|
||||
// Delegate to existing playlist grouping for YouTube; null for others
|
||||
return playlistGroups;
|
||||
}
|
||||
|
||||
if (groupBy === 'year') {
|
||||
const yearMap = new Map<string, ContentItem[]>();
|
||||
for (const item of content) {
|
||||
const year = item.publishedAt ? new Date(item.publishedAt).getFullYear().toString() : 'Unknown';
|
||||
const arr = yearMap.get(year);
|
||||
if (arr) {
|
||||
arr.push(item);
|
||||
} else {
|
||||
yearMap.set(year, [item]);
|
||||
}
|
||||
}
|
||||
// Sort groups by year descending, 'Unknown' last
|
||||
const groups = Array.from(yearMap.entries())
|
||||
.sort((a, b) => {
|
||||
if (a[0] === 'Unknown') return 1;
|
||||
if (b[0] === 'Unknown') return -1;
|
||||
return Number(b[0]) - Number(a[0]);
|
||||
})
|
||||
.map(([year, items]) => ({ id: year, title: year, items }));
|
||||
return groups.length > 0 ? groups : null;
|
||||
}
|
||||
|
||||
if (groupBy === 'type') {
|
||||
const shorts: ContentItem[] = [];
|
||||
const longform: ContentItem[] = [];
|
||||
const livestream: ContentItem[] = [];
|
||||
const audio: ContentItem[] = [];
|
||||
|
||||
for (const item of content) {
|
||||
if (item.contentType === 'livestream') {
|
||||
livestream.push(item);
|
||||
} else if (item.contentType === 'audio') {
|
||||
audio.push(item);
|
||||
} else if (item.contentType === 'video' && item.duration != null && item.duration <= 60) {
|
||||
shorts.push(item);
|
||||
} else {
|
||||
// video with duration > 60, null duration, or any other video
|
||||
longform.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
const groups: { id: string; title: string; items: ContentItem[] }[] = [];
|
||||
if (shorts.length > 0) groups.push({ id: 'short', title: 'Short', items: shorts });
|
||||
if (longform.length > 0) groups.push({ id: 'longform', title: 'Longform', items: longform });
|
||||
if (livestream.length > 0) groups.push({ id: 'livestream', title: 'Livestream', items: livestream });
|
||||
if (audio.length > 0) groups.push({ id: 'audio', title: 'Audio', items: audio });
|
||||
return groups.length > 0 ? groups : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [groupBy, content, playlistGroups]);
|
||||
|
||||
// ── Content table columns ──
|
||||
|
||||
const contentColumns = useMemo<Column<ContentItem>[]>(
|
||||
|
|
@ -742,15 +809,15 @@ export function ChannelDetail() {
|
|||
[selectedIds, toggleSelect, toggleMonitored, downloadContent],
|
||||
);
|
||||
|
||||
const renderPlaylistGroups = useCallback(
|
||||
(groups: { id: number | 'uncategorized'; title: string; items: ContentItem[] }[]) => (
|
||||
const renderGroupedContent = useCallback(
|
||||
(groups: { id: string | number; title: string; items: ContentItem[] }[]) => (
|
||||
<div>
|
||||
{groups.map((group) => {
|
||||
const isExpanded = expandedPlaylists.has(group.id);
|
||||
const isExpanded = expandedGroups.has(group.id);
|
||||
return (
|
||||
<div key={group.id} style={{ borderBottom: '1px solid var(--border)' }}>
|
||||
<button
|
||||
onClick={() => togglePlaylist(group.id)}
|
||||
onClick={() => toggleGroup(group.id)}
|
||||
aria-expanded={isExpanded}
|
||||
style={{
|
||||
display: 'flex',
|
||||
|
|
@ -793,7 +860,7 @@ export function ChannelDetail() {
|
|||
})}
|
||||
</div>
|
||||
),
|
||||
[expandedPlaylists, togglePlaylist, renderTable, renderCardGrid, renderListView, viewMode],
|
||||
[expandedGroups, toggleGroup, renderTable, renderCardGrid, renderListView, viewMode],
|
||||
);
|
||||
|
||||
// ── Loading / Error states ──
|
||||
|
|
@ -844,7 +911,6 @@ export function ChannelDetail() {
|
|||
}
|
||||
|
||||
const isYouTube = channel.platform === 'youtube';
|
||||
const hasPlaylistGroups = isYouTube && playlistGroups !== null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -1498,8 +1564,8 @@ export function ChannelDetail() {
|
|||
) : null}
|
||||
{contentLoading ? (
|
||||
<SkeletonTable rows={8} columns={8} />
|
||||
) : hasPlaylistGroups ? (
|
||||
renderPlaylistGroups(playlistGroups!)
|
||||
) : groupedContent ? (
|
||||
renderGroupedContent(groupedContent)
|
||||
) : viewMode === 'card' ? (
|
||||
renderCardGrid(content)
|
||||
) : viewMode === 'list' ? (
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue