diff --git a/apps/epg/serializers.py b/apps/epg/serializers.py
index 1179d934..c0b378a8 100644
--- a/apps/epg/serializers.py
+++ b/apps/epg/serializers.py
@@ -83,38 +83,26 @@ class EPGSourceSerializer(serializers.ModelSerializer):
return instance
class ProgramDataSerializer(serializers.ModelSerializer):
- season = serializers.SerializerMethodField()
- episode = serializers.SerializerMethodField()
-
class Meta:
model = ProgramData
- fields = ['id', 'start_time', 'end_time', 'title', 'sub_title', 'description', 'tvg_id', 'season', 'episode']
+ fields = ['id', 'start_time', 'end_time', 'title', 'sub_title', 'description', 'tvg_id']
- def _parse_onscreen(self, obj):
- """Parse season/episode from onscreen_episode string (e.g. 'S12 E6')."""
- onscreen = (obj.custom_properties or {}).get('onscreen_episode', '')
- match = _ONSCREEN_RE.search(onscreen)
- if match:
- return int(match.group(1)), int(match.group(2))
- return None, None
-
- def get_season(self, obj):
- if obj.custom_properties:
- season = obj.custom_properties.get('season')
- if season is not None:
- return season
- season, _ = self._parse_onscreen(obj)
- return season
- return None
-
- def get_episode(self, obj):
- if obj.custom_properties:
- episode = obj.custom_properties.get('episode')
- if episode is not None:
- return episode
- _, episode = self._parse_onscreen(obj)
- return episode
- return None
+ def to_representation(self, obj):
+ data = super().to_representation(obj)
+ cp = obj.custom_properties or {}
+ season = cp.get('season')
+ episode = cp.get('episode')
+ # Only run the regex if at least one value is still missing
+ if (season is None or episode is None) and cp.get('onscreen_episode'):
+ match = _ONSCREEN_RE.search(cp['onscreen_episode'])
+ if match:
+ if season is None:
+ season = int(match.group(1))
+ if episode is None:
+ episode = int(match.group(2))
+ data['season'] = season
+ data['episode'] = episode
+ return data
class EPGDataSerializer(serializers.ModelSerializer):
"""
diff --git a/frontend/src/pages/Guide.jsx b/frontend/src/pages/Guide.jsx
index edd46fd0..79d9bdb5 100644
--- a/frontend/src/pages/Guide.jsx
+++ b/frontend/src/pages/Guide.jsx
@@ -966,8 +966,13 @@ export default function TVChannelGuide({ startDate, endDate }) {
transition: 'transform 0.1s ease-out',
display: 'flex',
flexDirection: 'column',
- justifyContent: !isExpanded && (program.sub_title || seasonEpisodeLabel) ? 'space-between' : 'flex-start',
- height: '100%',
+ justifyContent:
+ !isExpanded && (program.sub_title || program.description)
+ ? 'space-between'
+ : 'flex-start',
+ flex: '1 1 0',
+ minHeight: 0,
+ overflow: 'hidden',
}}
>
)}
{program.title}
+ {!isExpanded && seasonEpisodeLabel && (
+
+ {seasonEpisodeLabel}
+
+ )}
- {(program.sub_title || seasonEpisodeLabel) && (
+ {!isExpanded && (program.sub_title || program.description) && (
+
+ {program.sub_title || program.description}
+
+ )}
+
+ {format(programStart, timeFormat)} -{' '}
+ {format(programEnd, timeFormat)}
+
+ {isExpanded && (program.sub_title || seasonEpisodeLabel) && (
{program.sub_title && (
{program.sub_title}
@@ -1025,28 +1066,16 @@ export default function TVChannelGuide({ startDate, endDate }) {
)}
)}
-
- {format(programStart, timeFormat)} -{' '}
- {format(programEnd, timeFormat)}
-
- {(isExpanded || (!program.sub_title && !seasonEpisodeLabel)) && program.description && (
+ {isExpanded && program.description && (
{program.description}
diff --git a/frontend/src/pages/guideUtils.js b/frontend/src/pages/guideUtils.js
index b46616a7..4bd53fdc 100644
--- a/frontend/src/pages/guideUtils.js
+++ b/frontend/src/pages/guideUtils.js
@@ -15,7 +15,7 @@ import {
import API from '../api.js';
export const PROGRAM_HEIGHT = 90;
-export const EXPANDED_PROGRAM_HEIGHT = 180;
+export const EXPANDED_PROGRAM_HEIGHT = 200;
/** Layout constants */
export const CHANNEL_WIDTH = 120; // Width of the channel/logo column
export const HOUR_WIDTH = 450; // Increased from 300 to 450 to make each program wider
@@ -214,7 +214,12 @@ export const mapChannelsById = (guideChannels) => {
return map;
};
-const _terminalStatuses = new Set(['stopped', 'completed', 'interrupted', 'failed']);
+const _terminalStatuses = new Set([
+ 'stopped',
+ 'completed',
+ 'interrupted',
+ 'failed',
+]);
export const mapRecordingsByProgramId = (recordings) => {
const map = new Map();