From 4b6792ccbe2768fc1181af30aeb365ce9f4ef123 Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Tue, 5 Aug 2025 16:19:50 -0500 Subject: [PATCH] Add ability to expand an episode row to view additional information. --- apps/vod/api_views.py | 2 +- frontend/src/pages/VODs.jsx | 198 ++++++++++++++++++++++------- frontend/src/store/useVODStore.jsx | 3 +- 3 files changed, 156 insertions(+), 47 deletions(-) diff --git a/apps/vod/api_views.py b/apps/vod/api_views.py index 24eca04f..0f593558 100644 --- a/apps/vod/api_views.py +++ b/apps/vod/api_views.py @@ -298,8 +298,8 @@ class SeriesViewSet(viewsets.ReadOnlyModelViewSet): 'release_date': episode.release_date, 'plot': episode.description, 'duration': episode.duration, - 'duration_secs': episode.duration * 60 if episode.duration else None, 'rating': episode.rating, + 'movie_image': episode.custom_properties.get('info', {}).get('movie_image') if episode.custom_properties else None, 'container_extension': episode.container_extension, 'type': 'episode', 'series': { diff --git a/frontend/src/pages/VODs.jsx b/frontend/src/pages/VODs.jsx index 288053ac..912116ff 100644 --- a/frontend/src/pages/VODs.jsx +++ b/frontend/src/pages/VODs.jsx @@ -222,6 +222,7 @@ const SeriesModal = ({ series, opened, onClose }) => { const [detailedSeries, setDetailedSeries] = useState(null); const [loadingDetails, setLoadingDetails] = useState(false); const [activeTab, setActiveTab] = useState(null); + const [expandedEpisode, setExpandedEpisode] = useState(null); useEffect(() => { if (opened && series) { @@ -324,6 +325,10 @@ const SeriesModal = ({ series, opened, onClose }) => { return hours > 0 ? `${hours}h ${mins}m` : `${mins}m`; }; + const handleEpisodeRowClick = (episode) => { + setExpandedEpisode(expandedEpisode === episode.id ? null : episode.id); + }; + if (!series) return null; // Use detailed data if available, otherwise use basic series data @@ -539,52 +544,155 @@ const SeriesModal = ({ series, opened, onClose }) => { {episodesBySeason[season]?.map(episode => ( - handlePlayEpisode(episode)} - > - - - {episode.episode_number || '?'} - - - - - - {episode.name} - - {episode.genre && ( - - {episode.genre} + + handleEpisodeRowClick(episode)} + > + + + {episode.episode_number || '?'} + + + + + + {episode.name} - )} - - - - - {formatDuration(episode.duration)} - - - - - {episode.release_date ? new Date(episode.release_date).toLocaleDateString() : 'N/A'} - - - - { - e.stopPropagation(); - handlePlayEpisode(episode); - }} - > - - - - + {episode.genre && ( + + {episode.genre} + + )} + + + + + {formatDuration(episode.duration)} + + + + + {episode.release_date ? new Date(episode.release_date).toLocaleDateString() : 'N/A'} + + + + { + e.stopPropagation(); + handlePlayEpisode(episode); + }} + > + + + + + {expandedEpisode === episode.id && ( + + + + {/* Episode Image and Description Row */} + + {/* Episode Image */} + {episode.movie_image && ( + + {episode.name} + + )} + + {/* Episode Description */} + + {episode.description && ( + + Description + + {episode.description} + + + )} + + + + {/* Additional Episode Details */} + + {episode.rating && ( + + Rating + {episode.rating} + + )} + + {episode.director && ( + + Director + {episode.director} + + )} + + {episode.actors && ( + + Cast + {episode.actors} + + )} + + + {/* Technical Details */} + {(episode.bitrate || episode.video || episode.audio) && ( + + Technical Details + + {episode.bitrate && episode.bitrate > 0 && ( + + Bitrate: {episode.bitrate} kbps + + )} + {episode.video && Object.keys(episode.video).length > 0 && ( + + Video:{' '} + {episode.video.codec_long_name || episode.video.codec_name} + {episode.video.width && episode.video.height + ? `, ${episode.video.width}x${episode.video.height}` + : ''} + + )} + {episode.audio && Object.keys(episode.audio).length > 0 && ( + + Audio:{' '} + {episode.audio.codec_long_name || episode.audio.codec_name} + {episode.audio.channels + ? `, ${episode.audio.channels} channels` + : ''} + + )} + + + )} + + {/* Provider Information */} + {episode.m3u_account && ( + + Provider: + + {episode.m3u_account.name || episode.m3u_account} + + + )} + + + + )} + ))} diff --git a/frontend/src/store/useVODStore.jsx b/frontend/src/store/useVODStore.jsx index 630d31c6..ceb23ff7 100644 --- a/frontend/src/store/useVODStore.jsx +++ b/frontend/src/store/useVODStore.jsx @@ -307,7 +307,7 @@ const useVODStore = create((set, get) => ({ description: episode.plot || '', season_number: parseInt(seasonNumber) || 0, episode_number: episode.episode_number || 0, - duration: episode.duration_secs ? Math.floor(episode.duration_secs / 60) : null, + duration: episode.duration ? Math.floor(episode.duration / 60) : null, rating: episode.rating || '', container_extension: episode.container_extension || '', series: { @@ -318,6 +318,7 @@ const useVODStore = create((set, get) => ({ uuid: episode.id, // Use the stream ID as UUID for playback logo: episode.movie_image ? { url: episode.movie_image } : null, release_date: episode.release_date || null, + movie_image: episode.movie_image || null, }; episodesData[episode.id] = episodeData; });