feat: Dashboard-Badge → Favoriten-Tab, ❤ Button im Modal, xREL für TV ausblenden

This commit is contained in:
2026-06-18 11:55:22 +02:00
parent 3244ac7644
commit 940e1860b7
2 changed files with 28 additions and 10 deletions

View File

@@ -1983,7 +1983,7 @@ function Dashboard({ toast, mobile, setActive, setDevToolsNav, unreadMsgs=0, unr
</button> </button>
{/* Unquittierte Favoriten für alle User */} {/* Unquittierte Favoriten für alle User */}
{unackedFavs > 0 && ( {unackedFavs > 0 && (
<button onClick={()=>{ setActive('media'); onAckFavs?.(); }} style={{ <button onClick={()=>{ setActive('media'); setMediaInitTab('favoriten'); onAckFavs?.(); }} style={{
background:'rgba(245,158,11,0.12)', border:'1px solid rgba(245,158,11,0.35)', background:'rgba(245,158,11,0.12)', border:'1px solid rgba(245,158,11,0.35)',
borderRadius:7, color:'#f59e0b', cursor:'pointer', borderRadius:7, color:'#f59e0b', cursor:'pointer',
padding:'4px 10px', fontSize:9, fontFamily:'monospace', letterSpacing:1, padding:'4px 10px', fontSize:9, fontFamily:'monospace', letterSpacing:1,
@@ -3619,6 +3619,7 @@ export default function App() {
const [unreadPromoted, setUnreadPromoted] = useState(0); const [unreadPromoted, setUnreadPromoted] = useState(0);
const [unreadChangelog,setUnreadChangelog]= useState(0); const [unreadChangelog,setUnreadChangelog]= useState(0);
const [unackedFavs, setUnackedFavs] = useState(0); const [unackedFavs, setUnackedFavs] = useState(0);
const [mediaInitTab, setMediaInitTab] = useState(null);
const [authChecked, setAuthChecked] = useState(false); const [authChecked, setAuthChecked] = useState(false);
useEffect(()=>{ useEffect(()=>{
@@ -3719,7 +3720,7 @@ export default function App() {
<main ref={mainRef} style={mainStyle}> <main ref={mainRef} style={mainStyle}>
{active==='dashboard' && <Dashboard toast={toast} mobile={mobile} setActive={setActive} setDevToolsNav={setDevToolsNav} unreadMsgs={unreadMsgs} unreadBoard={unreadBoard} unreadPromoted={unreadPromoted} onBoardRead={()=>{ setUnreadBoard(0); setUnreadPromoted(0); }} unreadChangelog={unreadChangelog} onChangelogRead={()=>setUnreadChangelog(0)} unackedFavs={unackedFavs} onAckFavs={()=>api('/tools/media/favorites/acknowledge-all',{method:'POST',body:{}}).then(()=>setUnackedFavs(0)).catch(()=>{})} user={user}/>} {active==='dashboard' && <Dashboard toast={toast} mobile={mobile} setActive={setActive} setDevToolsNav={setDevToolsNav} unreadMsgs={unreadMsgs} unreadBoard={unreadBoard} unreadPromoted={unreadPromoted} onBoardRead={()=>{ setUnreadBoard(0); setUnreadPromoted(0); }} unreadChangelog={unreadChangelog} onChangelogRead={()=>setUnreadChangelog(0)} unackedFavs={unackedFavs} onAckFavs={()=>api('/tools/media/favorites/acknowledge-all',{method:'POST',body:{}}).then(()=>setUnackedFavs(0)).catch(()=>{})} user={user}/>}
{active==='admin' && <AdminPanel toast={toast} mobile={mobile} user={user} nav={devToolsNav}/>} {active==='admin' && <AdminPanel toast={toast} mobile={mobile} user={user} nav={devToolsNav}/>}
{activeTool && <activeTool.component toast={toast} mobile={mobile} setActive={setActive} {...(activeTool.id==='devtools'?{nav:devToolsNav}:{})}/>} {activeTool && <activeTool.component toast={toast} mobile={mobile} setActive={setActive} {...(activeTool.id==='devtools'?{nav:devToolsNav}:{})} {...(activeTool.id==='media' && mediaInitTab ? {initTab:mediaInitTab, onInitTabConsumed:()=>setMediaInitTab(null)} : {})}/>}
</main> </main>
</div> </div>
{mobile && ( {mobile && (

View File

@@ -38,8 +38,14 @@ const FSK_COLOR = fsk => {
return { bg:'#444', color:'#fff' }; return { bg:'#444', color:'#fff' };
}; };
export default function Media({ toast, mobile }) { export default function Media({ toast, mobile, initTab, onInitTabConsumed }) {
const [activeTool, setActiveTool] = useState('kino'); const [activeTool, setActiveTool] = useState('kino');
const [favIds, toggleFav] = useFavIds();
// Wenn von außen ein Tab vorgegeben wird (z.B. Dashboard-Badge)
useEffect(() => {
if (initTab) { setActiveTool(initTab); onInitTabConsumed?.(); }
}, [initTab]);
const [modal, setModal] = useState(null); // { id, media_type } const [modal, setModal] = useState(null); // { id, media_type }
const [xrelIds, setXrelIds] = useState(new Set()); // tmdb_ids mit bekannten Releases const [xrelIds, setXrelIds] = useState(new Set()); // tmdb_ids mit bekannten Releases
@@ -126,10 +132,10 @@ export default function Media({ toast, mobile }) {
<span style={{ color:'rgba(255,255,255,0.5)', fontFamily:'monospace', fontSize:10 }}>{active?.desc}</span> <span style={{ color:'rgba(255,255,255,0.5)', fontFamily:'monospace', fontSize:10 }}>{active?.desc}</span>
</div> </div>
{activeTool === 'kino' && <KinoGrid onOpenModal={setModal} xrelIds={xrelIds} onXrelLoaded={ids => setXrelIds(prev => new Set([...prev, ...ids]))} />} {activeTool === 'kino' && <KinoGrid onOpenModal={setModal} xrelIds={xrelIds} onXrelLoaded={ids => setXrelIds(prev => new Set([...prev, ...ids]))} favIds={favIds} onToggleFav={toggleFav}/>}
{activeTool === 'demnächst' && <DemnächstGrid onOpenModal={setModal} xrelIds={xrelIds} onXrelLoaded={ids => setXrelIds(prev => new Set([...prev, ...ids]))} />} {activeTool === 'demnächst' && <DemnächstGrid onOpenModal={setModal} xrelIds={xrelIds} onXrelLoaded={ids => setXrelIds(prev => new Set([...prev, ...ids]))} favIds={favIds} onToggleFav={toggleFav}/>}
{activeTool === 'favoriten' && <FavoritenKalender onOpenModal={setModal} />} {activeTool === 'favoriten' && <FavoritenKalender onOpenModal={setModal} />}
{activeTool === 'suchen' && <SucheGrid onOpenModal={setModal} xrelIds={xrelIds} onXrelLoaded={ids => setXrelIds(prev => new Set([...prev, ...ids]))} />} {activeTool === 'suchen' && <SucheGrid onOpenModal={setModal} xrelIds={xrelIds} onXrelLoaded={ids => setXrelIds(prev => new Set([...prev, ...ids]))} favIds={favIds} onToggleFav={toggleFav}/>}
</div> </div>
{modal && createPortal( {modal && createPortal(
@@ -140,6 +146,8 @@ export default function Media({ toast, mobile }) {
mobile={mobile} mobile={mobile}
onHasXrel={id => setXrelIds(prev => new Set([...prev, id]))} onHasXrel={id => setXrelIds(prev => new Set([...prev, id]))}
noYearFilter={activeTool === 'suchen'} noYearFilter={activeTool === 'suchen'}
isFav={favIds.has(modal?.id || modal)}
onToggleFav={movie => toggleFav(movie, favIds.has(modal?.id || modal))}
/>, />,
document.body document.body
)} )}
@@ -809,7 +817,7 @@ function FavoritenKalender({ onOpenModal }) {
} }
// ── Film-Detail-Modal (via Portal) ──────────────────────────────────────────── // ── Film-Detail-Modal (via Portal) ────────────────────────────────────────────
function MovieModal({ tmdbId, mediaType='movie', onClose, mobile, onHasXrel, noYearFilter }) { function MovieModal({ tmdbId, mediaType='movie', onClose, mobile, onHasXrel, noYearFilter, isFav=false, onToggleFav }) {
const [detail, setDetail] = useState(null); const [detail, setDetail] = useState(null);
const [xrel, setXrel] = useState(null); // null=nicht gesucht, []=gesucht const [xrel, setXrel] = useState(null); // null=nicht gesucht, []=gesucht
const [xrelDates, setXrelDates] = useState([]); const [xrelDates, setXrelDates] = useState([]);
@@ -917,13 +925,22 @@ function MovieModal({ tmdbId, mediaType='movie', onClose, mobile, onHasXrel, noY
)} )}
</div> </div>
{/* Schließen sticky außerhalb des Scroll-Containers */} {/* Schließen + Favorit sticky außerhalb des Scroll-Containers */}
<button onClick={onClose} style={{ <button onClick={onClose} style={{
position:'absolute', top:12, right:12, position:'absolute', top:12, right:12,
background:'rgba(0,0,0,0.75)', border:'none', borderRadius:'50%', background:'rgba(0,0,0,0.75)', border:'none', borderRadius:'50%',
width:34, height:34, cursor:'pointer', zIndex:10, width:34, height:34, cursor:'pointer', zIndex:10,
color:'#fff', fontSize:18, display:'flex', alignItems:'center', justifyContent:'center', color:'#fff', fontSize:18, display:'flex', alignItems:'center', justifyContent:'center',
}}>✕</button> }}>✕</button>
{onToggleFav && detail && (
<button onClick={() => onToggleFav(detail)} style={{
position:'absolute', top:12, right:54,
background:'rgba(0,0,0,0.75)', border:'none', borderRadius:'50%',
width:34, height:34, cursor:'pointer', zIndex:10,
color: isFav ? '#f87171' : 'rgba(255,255,255,0.6)',
fontSize:18, display:'flex', alignItems:'center', justifyContent:'center',
}}>{isFav ? '❤' : '🤍'}</button>
)}
{/* Scrollbarer Inhalt */} {/* Scrollbarer Inhalt */}
<div style={{ overflowY:'auto', WebkitOverflowScrolling:'touch', flex:1 }}> <div style={{ overflowY:'auto', WebkitOverflowScrolling:'touch', flex:1 }}>
@@ -1042,7 +1059,7 @@ function MovieModal({ tmdbId, mediaType='movie', onClose, mobile, onHasXrel, noY
</div> </div>
) : null; ) : null;
})()} })()}
<div style={{ display:'flex', alignItems:'center', gap:8, marginBottom: xrel?.length ? 8 : 0 }}> {mediaType !== 'tv' && <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom: xrel?.length ? 8 : 0 }}>
<div style={{ ...S.head, marginBottom:0, flex:1 }}>RELEASES (XREL.TO)</div> <div style={{ ...S.head, marginBottom:0, flex:1 }}>RELEASES (XREL.TO)</div>
<button <button
onClick={() => searchXrel(xrel !== null && xrel.length > 0)} onClick={() => searchXrel(xrel !== null && xrel.length > 0)}
@@ -1057,7 +1074,7 @@ function MovieModal({ tmdbId, mediaType='movie', onClose, mobile, onHasXrel, noY
{xrelLoading ? '⏳ Suche…' : xrel === null ? '🔍 Nach Releases suchen' : '🔄 Erneut suchen'} {xrelLoading ? '⏳ Suche…' : xrel === null ? '🔍 Nach Releases suchen' : '🔄 Erneut suchen'}
</button> </button>
</div> </div>
{xrel !== null && xrel.length === 0 && ( {mediaType !== 'tv' && xrel !== null && xrel.length === 0 && (
<div style={{ fontSize:11, color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontStyle:'italic' }}> <div style={{ fontSize:11, color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontStyle:'italic' }}>
Keine deutschen Releases gefunden Keine deutschen Releases gefunden
</div> </div>