fix: Route-Reihenfolge Favoriten, Dashboard-Badge, TV-Suche, User-gruppierte Admin-Liste

This commit is contained in:
2026-06-18 07:31:10 +02:00
parent 7f1ba07500
commit 3e93188c1d
3 changed files with 121 additions and 73 deletions

View File

@@ -1981,9 +1981,9 @@ function Dashboard({ toast, mobile, setActive, setDevToolsNav, unreadMsgs=0, unr
borderRadius:'50%', background:'#ffe66d', boxShadow:'0 0 5px #ffe66d' }}/>
)}
</button>
{/* Admin: unquittierte Favoriten */}
{user?.role==='admin' && unackedFavs > 0 && (
<button onClick={()=>{ setActive('media'); onAckFavs(); }} style={{
{/* Unquittierte Favoriten für alle User */}
{unackedFavs > 0 && (
<button onClick={()=>{ setActive('media'); onAckFavs?.(); }} style={{
background:'rgba(245,158,11,0.12)', border:'1px solid rgba(245,158,11,0.35)',
borderRadius:7, color:'#f59e0b', cursor:'pointer',
padding:'4px 10px', fontSize:9, fontFamily:'monospace', letterSpacing:1,
@@ -3655,6 +3655,12 @@ export default function App() {
return()=>clearInterval(t);
},[user]);
useEffect(()=>{
if(!user)return;
const poll=()=>api('/tools/media/favorites/unacked').then(d=>setUnackedFavs(d.count||0)).catch(()=>{});
poll(); const ti=setInterval(poll,60000); return()=>clearInterval(ti);
},[user]);
useEffect(()=>{
if(!user)return;
const poll=()=>api('/dashboard/changelog/unread').then(d=>setUnreadChangelog(d.unread||0)).catch(()=>{});
@@ -3711,7 +3717,7 @@ export default function App() {
unreadMsgs={unreadMsgs}/>
)}
<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)} 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}/>}
{activeTool && <activeTool.component toast={toast} mobile={mobile} setActive={setActive} {...(activeTool.id==='devtools'?{nav:devToolsNav}:{})}/>}
</main>

View File

@@ -509,12 +509,17 @@ function FavoritenKalender({ onOpenModal }) {
if (loading) return <StatusBox>⏳ Lädt…</StatusBox>;
if (error) return <ErrorBox msg={error} />;
// Admin-Ansicht: alle Favoriten aller User
// Admin-Ansicht: alle Favoriten nach User gruppiert
if (isAdmin && adminTab) {
const unacked = allFavs.filter(f => !f.acknowledged).length;
const userGroups = {};
allFavs.forEach(f => {
if (!userGroups[f.username]) userGroups[f.username] = [];
userGroups[f.username].push(f);
});
return (
<div>
<div style={{display:'flex',gap:8,marginBottom:16,alignItems:'center'}}>
<div style={{display:'flex',gap:8,marginBottom:16,alignItems:'center',flexWrap:'wrap'}}>
<button onClick={()=>setAdminTab(false)}
style={{background:'rgba(255,255,255,0.06)',border:'1px solid rgba(255,255,255,0.12)',
borderRadius:7,padding:'5px 12px',color:'rgba(255,255,255,0.6)',fontFamily:'monospace',fontSize:11,cursor:'pointer'}}>
@@ -528,47 +533,66 @@ function FavoritenKalender({ onOpenModal }) {
</button>
)}
</div>
<div style={{display:'flex',flexDirection:'column',gap:6}}>
{allFavs.map(f => (
<div key={f.id} style={{display:'flex',alignItems:'center',gap:10,padding:'8px 12px',
background: !f.acknowledged ? 'rgba(245,158,11,0.06)' : 'rgba(255,255,255,0.02)',
border:`1px solid ${!f.acknowledged ? 'rgba(245,158,11,0.25)' : 'rgba(255,255,255,0.06)'}`,
borderRadius:8}}>
{f.poster_path
? <img src={POSTER+f.poster_path} alt={f.title} style={{width:32,height:48,objectFit:'cover',borderRadius:4,flexShrink:0}}/>
: <div style={{width:32,height:48,background:'#111',borderRadius:4,flexShrink:0,display:'flex',alignItems:'center',justifyContent:'center',fontSize:14}}>
{f.media_type==='tv'?'📺':'🎬'}
</div>
}
<div style={{flex:1,minWidth:0}}>
<div style={{color:'rgba(255,255,255,0.85)',fontFamily:'monospace',fontSize:12,
overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>
{f.title}
{f.media_type==='tv' && <span style={{marginLeft:6,color:'#60a5fa',fontSize:9}}>SERIE</span>}
</div>
<div style={{color:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:10,marginTop:2}}>
👤 {f.username} · ⭐ {f.added_at ? new Date(f.added_at).toLocaleDateString('de-DE') : '—'}
{!!f.acknowledged && <span style={{marginLeft:6,color:'rgba(255,255,255,0.2)'}}>✓ quittiert</span>}
</div>
{!allFavs.length
? <StatusBox>Noch keine Favoriten von irgendjemandem.</StatusBox>
: Object.entries(userGroups).map(([username, items]) => (
<div key={username} style={{marginBottom:20}}>
<div style={{display:'flex',alignItems:'center',gap:8,marginBottom:8,
padding:'6px 10px',background:'rgba(255,255,255,0.04)',borderRadius:8}}>
<span style={{color:'rgba(255,255,255,0.6)',fontFamily:'monospace',fontSize:12,fontWeight:700}}>
👤 {username}
</span>
<span style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:10}}>
{items.length} Favorit{items.length!==1?'en':''}
</span>
{items.filter(f=>!f.acknowledged).length > 0 && (
<span style={{background:'rgba(245,158,11,0.2)',border:'1px solid rgba(245,158,11,0.4)',
borderRadius:10,padding:'1px 7px',color:'#f59e0b',fontFamily:'monospace',fontSize:9}}>
{items.filter(f=>!f.acknowledged).length} neu
</span>
)}
</div>
<div style={{display:'flex',flexDirection:'column',gap:4}}>
{items.map(f => (
<div key={f.id} style={{display:'flex',alignItems:'center',gap:10,padding:'8px 12px',
background:!f.acknowledged?'rgba(245,158,11,0.05)':'rgba(255,255,255,0.02)',
border:`1px solid ${!f.acknowledged?'rgba(245,158,11,0.2)':'rgba(255,255,255,0.05)'}`,
borderRadius:8}}>
{f.poster_path
? <img src={POSTER+f.poster_path} alt={f.title} style={{width:32,height:48,objectFit:'cover',borderRadius:4,flexShrink:0}}/>
: <div style={{width:32,height:48,background:'#111',borderRadius:4,flexShrink:0,display:'flex',alignItems:'center',justifyContent:'center',fontSize:14}}>
{f.media_type==='tv'?'📺':'🎬'}
</div>
}
<div style={{flex:1,minWidth:0}}>
<div style={{color:'rgba(255,255,255,0.85)',fontFamily:'monospace',fontSize:12,
overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>
{f.title}
{f.media_type==='tv' && <span style={{marginLeft:6,color:'#60a5fa',fontSize:9}}>SERIE</span>}
</div>
<div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:10,marginTop:2}}>
⭐ {f.added_at ? new Date(f.added_at).toLocaleDateString('de-DE') : '—'}
{!!f.acknowledged && <span style={{marginLeft:6}}>✓</span>}
</div>
</div>
{!f.acknowledged && (
<button onClick={async e=>{e.stopPropagation();
await api(`/tools/media/favorites/${f.id}/acknowledge`,{method:'POST',body:{}});
setAllFavs(prev=>prev.map(x=>x.id===f.id?{...x,acknowledged:1}:x));
}} style={{background:'rgba(245,158,11,0.1)',border:'1px solid rgba(245,158,11,0.3)',
borderRadius:6,padding:'3px 10px',color:'#f59e0b',fontFamily:'monospace',fontSize:9,cursor:'pointer',flexShrink:0}}>
</button>
)}
</div>
))}
</div>
{!f.acknowledged && (
<button onClick={async(e)=>{e.stopPropagation();
await api(`/tools/media/favorites/${f.id}/acknowledge`,{method:'POST',body:{}});
setAllFavs(prev=>prev.map(x=>x.id===f.id?{...x,acknowledged:1}:x));
}}
style={{background:'rgba(245,158,11,0.1)',border:'1px solid rgba(245,158,11,0.3)',
borderRadius:6,padding:'3px 10px',color:'#f59e0b',fontFamily:'monospace',fontSize:9,cursor:'pointer',flexShrink:0}}>
</button>
)}
</div>
))}
{!allFavs.length && <StatusBox>Noch keine Favoriten von irgendjemandem.</StatusBox>}
</div>
))
}
</div>
);
}
if (!favs.length) return (
<div>
{isAdmin && (