fix: TV-Modal schwarze Seite (xREL skip), aufklappbare Sektionen, TV-Staffelinfo

This commit is contained in:
2026-06-18 11:27:10 +02:00
parent 5c4bbaa9ab
commit 4708f27912

View File

@@ -387,6 +387,47 @@ function DemnächstGrid({ onOpenModal, xrelIds, onXrelLoaded }) {
} }
// ── Suche ──────────────────────────────────────────────────────────────────── // ── Suche ────────────────────────────────────────────────────────────────────
// ── Aufklappbare Film/Serien-Sektion ─────────────────────────────────────────
function CollapsibleSection({ label, count, children, defaultOpen=true }) {
const [open, setOpen] = useState(defaultOpen);
return (
<div style={{marginBottom:16}}>
<button onClick={()=>setOpen(v=>!v)} style={{
display:'flex',alignItems:'center',gap:8,background:'none',border:'none',cursor:'pointer',
width:'100%',padding:'6px 0',marginBottom: open?10:0,
}}>
<span style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:11,letterSpacing:1}}>
{label} ({count})
</span>
<div style={{flex:1,height:1,background:'rgba(255,255,255,0.07)'}}/>
<span style={{color:'rgba(255,255,255,0.3)',fontSize:11,transform:open?'rotate(90deg)':'none',transition:'transform 0.2s'}}></span>
</button>
{open && children}
</div>
);
}
function SectionedGrid({ films, serien, favIds, xrelIds, onToggle, onOpenModal }) {
return (
<div>
{films.length > 0 && (
<CollapsibleSection label="🎬 FILME" count={films.length}>
<div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(130px,1fr))',gap:12}}>
{films.map(m => <MovieCard key={'m'+m.id} movie={m} favIds={favIds} hasXrel={xrelIds?.has(m.id)} onToggleFav={onToggle} onOpenModal={onOpenModal}/>)}
</div>
</CollapsibleSection>
)}
{serien.length > 0 && (
<CollapsibleSection label="📺 SERIEN" count={serien.length}>
<div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(130px,1fr))',gap:12}}>
{serien.map(m => <MovieCard key={'t'+m.id} movie={m} favIds={favIds} hasXrel={xrelIds?.has(m.id)} onToggleFav={onToggle} onOpenModal={onOpenModal}/>)}
</div>
</CollapsibleSection>
)}
</div>
);
}
function SucheGrid({ onOpenModal, xrelIds, onXrelLoaded }) { function SucheGrid({ onOpenModal, xrelIds, onXrelLoaded }) {
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const [results, setResults] = useState(null); // null=noch nicht gesucht const [results, setResults] = useState(null); // null=noch nicht gesucht
@@ -465,26 +506,7 @@ function SucheGrid({ onOpenModal, xrelIds, onXrelLoaded }) {
{results !== null && results.length > 0 && (() => { {results !== null && results.length > 0 && (() => {
const films = results.filter(m => m.media_type !== 'tv'); const films = results.filter(m => m.media_type !== 'tv');
const serien = results.filter(m => m.media_type === 'tv'); const serien = results.filter(m => m.media_type === 'tv');
return ( return <SectionedGrid films={films} serien={serien} favIds={favIds} xrelIds={xrelIds} onToggle={onToggle} onOpenModal={onOpenModal}/>;
<div>
{films.length > 0 && (
<div style={{marginBottom:20}}>
<div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10,letterSpacing:1,marginBottom:8}}>🎬 FILME ({films.length})</div>
<div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(130px,1fr))',gap:12}}>
{films.map(m => <MovieCard key={'m'+m.id} movie={m} favIds={favIds} hasXrel={xrelIds?.has(m.id)} onToggleFav={onToggle} onOpenModal={onOpenModal}/>)}
</div>
</div>
)}
{serien.length > 0 && (
<div>
<div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10,letterSpacing:1,marginBottom:8}}>📺 SERIEN ({serien.length})</div>
<div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(130px,1fr))',gap:12}}>
{serien.map(m => <MovieCard key={'t'+m.id} movie={m} favIds={favIds} hasXrel={xrelIds?.has(m.id)} onToggleFav={onToggle} onOpenModal={onOpenModal}/>)}
</div>
</div>
)}
</div>
);
})()} })()}
</div> </div>
); );
@@ -700,45 +722,38 @@ function FavoritenKalender({ onOpenModal }) {
)} )}
</button> </button>
)} )}
{/* Filme */}
{filmeFavs.length > 0 && ( {filmeFavs.length > 0 && (
<div style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:11,fontWeight:700,letterSpacing:1, <CollapsibleSection label="🎬 FILME" count={filmeFavs.length}>
borderBottom:'1px solid rgba(255,255,255,0.07)',paddingBottom:6}}>
🎬 FILME ({filmeFavs.length})
</div>
)}
{Object.entries(filmGroups).sort(([a],[b]) => a.localeCompare(b)).map(([key, group]) => ( {Object.entries(filmGroups).sort(([a],[b]) => a.localeCompare(b)).map(([key, group]) => (
<div key={'film-'+key}> <div key={'film-'+key} style={{marginBottom:12}}>
<div style={{ ...S.head, marginBottom:10, display:'flex', alignItems:'center', gap:8 }}> <div style={{...S.head,marginBottom:6,display:'flex',alignItems:'center',gap:6}}>
<span>{group.label.toUpperCase()}</span> <span>{group.label.toUpperCase()}</span>
<div style={{flex:1,height:1,background:'rgba(255,255,255,0.07)'}}/> <div style={{flex:1,height:1,background:'rgba(255,255,255,0.07)'}}/>
<span style={{ fontSize:9 }}>{group.items.length} Film{group.items.length!==1?'e':''}</span> <span style={{fontSize:9}}>{group.items.length}×</span>
</div> </div>
<div style={{ display:'flex', flexDirection:'column', gap:8 }}> <div style={{display:'flex',flexDirection:'column',gap:6}}>
{group.items.map(f => <FavCard key={f.tmdb_id} f={f} onOpenModal={onOpenModal} onRemove={removeFav}/>)} {group.items.map(f => <FavCard key={f.tmdb_id} f={f} onOpenModal={onOpenModal} onRemove={removeFav}/>)}
</div> </div>
</div> </div>
))} ))}
{/* Serien */} </CollapsibleSection>
{serienFavs.length > 0 && (
<div style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:11,fontWeight:700,letterSpacing:1,
borderBottom:'1px solid rgba(255,255,255,0.07)',paddingBottom:6,marginTop:8}}>
📺 SERIEN ({serienFavs.length})
</div>
)} )}
{serienFavs.length > 0 && (
<CollapsibleSection label="📺 SERIEN" count={serienFavs.length}>
{Object.entries(serienGroups).sort(([a],[b]) => a.localeCompare(b)).map(([key, group]) => ( {Object.entries(serienGroups).sort(([a],[b]) => a.localeCompare(b)).map(([key, group]) => (
<div key={'serie-'+key}> <div key={'serie-'+key} style={{marginBottom:12}}>
<div style={{ ...S.head, marginBottom:10, display:'flex', alignItems:'center', gap:8 }}> <div style={{...S.head,marginBottom:6,display:'flex',alignItems:'center',gap:6}}>
<span>{group.label.toUpperCase()}</span> <span>{group.label.toUpperCase()}</span>
<div style={{flex:1,height:1,background:'rgba(255,255,255,0.07)'}}/> <div style={{flex:1,height:1,background:'rgba(255,255,255,0.07)'}}/>
<span style={{ fontSize:9 }}>{group.items.length} Serie{group.items.length!==1?'n':''}</span> <span style={{fontSize:9}}>{group.items.length}×</span>
</div> </div>
<div style={{ display:'flex', flexDirection:'column', gap:8 }}> <div style={{display:'flex',flexDirection:'column',gap:6}}>
{group.items.map(f => <FavCard key={f.tmdb_id} f={f} onOpenModal={onOpenModal} onRemove={removeFav}/>)} {group.items.map(f => <FavCard key={f.tmdb_id} f={f} onOpenModal={onOpenModal} onRemove={removeFav}/>)}
</div> </div>
</div> </div>
))} ))}
{/* Alte groups-Schleife entfernt DUMMY damit Code nicht bricht */} </CollapsibleSection>
)}
{false && Object.entries({}).map(([key, group]) => ( {false && Object.entries({}).map(([key, group]) => (
<div key={key}> <div key={key}>
<div style={{ ...S.head, marginBottom:10, display:'flex', alignItems:'center', gap:8 }}> <div style={{ ...S.head, marginBottom:10, display:'flex', alignItems:'center', gap:8 }}>
@@ -813,7 +828,8 @@ function MovieModal({ tmdbId, mediaType='movie', onClose, mobile, onHasXrel, noY
setDetail(d); setDetail(d);
detailRef.current = d; detailRef.current = d;
setLoading(false); setLoading(false);
// Gecachte Releases laden sobald wir den Titel kennen // Gecachte Releases laden (nur für Filme)
if (mediaType !== 'tv') {
const params = new URLSearchParams({ const params = new URLSearchParams({
title: d.title || '', title: d.title || '',
orig: d.original_title || '', orig: d.original_title || '',
@@ -826,12 +842,13 @@ function MovieModal({ tmdbId, mediaType='movie', onClose, mobile, onHasXrel, noY
if (r.xrel?.length > 0) { setXrel(r.xrel); onHasXrel?.(tmdbId); } if (r.xrel?.length > 0) { setXrel(r.xrel); onHasXrel?.(tmdbId); }
}) })
.catch(() => {}); .catch(() => {});
}
}) })
.catch(e => { setError(e.message); setLoading(false); }); .catch(e => { setError(e.message); setLoading(false); });
const prev = document.body.style.overflow; const prev = document.body.style.overflow;
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
return () => { document.body.style.overflow = prev; }; return () => { document.body.style.overflow = prev; };
}, [tmdbId]); }, [tmdbId, mediaType]);
const searchXrel = async (forceRefresh = false) => { const searchXrel = async (forceRefresh = false) => {
const d = detailRef.current; const d = detailRef.current;
@@ -931,7 +948,9 @@ function MovieModal({ tmdbId, mediaType='movie', onClose, mobile, onHasXrel, noY
)} )}
<div style={{ display:'flex', gap:5, flexWrap:'wrap', marginBottom:8 }}> <div style={{ display:'flex', gap:5, flexWrap:'wrap', marginBottom:8 }}>
{detail.vote_average > 0 && <Badge bg="rgba(255,230,109,0.15)" color="#ffe66d">★ {detail.vote_average.toFixed(1)}</Badge>} {detail.vote_average > 0 && <Badge bg="rgba(255,230,109,0.15)" color="#ffe66d">★ {detail.vote_average.toFixed(1)}</Badge>}
{detail.runtime > 0 && <Badge bg="rgba(255,255,255,0.07)" color="rgba(255,255,255,0.7)">⏱ {detail.runtime} Min</Badge>} {detail.media_type !== 'tv' && detail.runtime > 0 && <Badge bg="rgba(255,255,255,0.07)" color="rgba(255,255,255,0.7)">⏱ {detail.runtime} Min</Badge>}
{detail.media_type === 'tv' && detail.number_of_seasons > 0 && <Badge bg="rgba(96,165,250,0.15)" color="#60a5fa">📺 {detail.number_of_seasons} Staffel{detail.number_of_seasons!==1?'n':''}</Badge>}
{detail.media_type === 'tv' && detail.number_of_episodes > 0 && <Badge bg="rgba(255,255,255,0.07)" color="rgba(255,255,255,0.7)">{detail.number_of_episodes} Ep.</Badge>}
{fskStyle && <Badge bg={fskStyle.bg} color={fskStyle.color}>FSK {detail.fsk}</Badge>} {fskStyle && <Badge bg={fskStyle.bg} color={fskStyle.color}>FSK {detail.fsk}</Badge>}
{detail.release_date && <Badge bg="rgba(255,255,255,0.07)" color="rgba(255,255,255,0.55)">🇩🇪 {fmtDE(detail.release_date)}</Badge>} {detail.release_date && <Badge bg="rgba(255,255,255,0.07)" color="rgba(255,255,255,0.55)">🇩🇪 {fmtDE(detail.release_date)}</Badge>}
{detail.countries?.length > 0 && <Badge bg="rgba(255,255,255,0.07)" color="rgba(255,255,255,0.4)">🌍 {detail.countries.slice(0,2).join(' · ')}</Badge>} {detail.countries?.length > 0 && <Badge bg="rgba(255,255,255,0.07)" color="rgba(255,255,255,0.4)">🌍 {detail.countries.slice(0,2).join(' · ')}</Badge>}