feat: Whiteboard Dot in Liste, Favoriten eingeklappt + Modal bei Klick

This commit is contained in:
2026-07-04 12:49:19 +02:00
parent 31cb7f7411
commit 8425688160
3 changed files with 124 additions and 66 deletions

View File

@@ -678,6 +678,50 @@ function FavCard({ f, onOpenModal, onRemove }) {
}
// ── Favoriten Kalender ────────────────────────────────────────────────────────
// ── Admin User Group mit einklappbaren quittierten Favoriten ─────────────────
function AdminUserGroup({ username, unackedItems, ackedItems, FavRow }) {
const [ackedOpen, setAckedOpen] = useState(false);
const total = unackedItems.length + ackedItems.length;
return (
<div 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}}>
{total} Favorit{total!==1?'en':''}
</span>
{unackedItems.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}}>
{unackedItems.length} neu
</span>
)}
</div>
<div style={{display:'flex',flexDirection:'column',gap:4}}>
{unackedItems.map(f => <FavRow key={f.id} f={f} />)}
</div>
{ackedItems.length > 0 && (
<div style={{marginTop:6}}>
<button onClick={() => setAckedOpen(v => !v)}
style={{background:'rgba(255,255,255,0.03)',border:'1px solid rgba(255,255,255,0.08)',
borderRadius:7,padding:'4px 12px',color:'rgba(255,255,255,0.35)',fontFamily:'monospace',
fontSize:10,cursor:'pointer',width:'100%',textAlign:'left',display:'flex',alignItems:'center',gap:6}}>
<span>{ackedOpen ? '▾' : '▸'}</span>
<span>{ackedItems.length} quittiert{ackedItems.length!==1?'e':''}</span>
</button>
{ackedOpen && (
<div style={{display:'flex',flexDirection:'column',gap:4,marginTop:4}}>
{ackedItems.map(f => <FavRow key={f.id} f={f} />)}
</div>
)}
</div>
)}
</div>
);
}
function FavoritenKalender({ onOpenModal }) {
const [favs, setFavs] = useState([]);
const [allFavs, setAllFavs] = useState([]);
@@ -712,7 +756,7 @@ function FavoritenKalender({ onOpenModal }) {
if (loading) return <StatusBox>⏳ Lädt…</StatusBox>;
if (error) return <ErrorBox msg={error} />;
// Admin-Ansicht: alle Favoriten nach User gruppiert
// Admin-Ansicht: alle Favoriten nach User gruppiert, quittierte eingeklappt
if (isAdmin && adminTab) {
const unacked = allFavs.filter(f => !f.acknowledged).length;
const userGroups = {};
@@ -720,6 +764,52 @@ function FavoritenKalender({ onOpenModal }) {
if (!userGroups[f.username]) userGroups[f.username] = [];
userGroups[f.username].push(f);
});
// FavRow: einzelne Favoriten-Zeile mit Modal-Klick
const FavRow = ({ 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, cursor:'pointer'}}
onClick={() => onOpenModal({ id: f.tmdb_id, media_type: f.media_type || 'movie' })}>
{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,color:'#4ade80'}}>✓ quittiert</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>
) : (
<button onClick={async e=>{e.stopPropagation();
await api(`/tools/media/favorites/${f.id}/unacknowledge`,{method:'POST',body:{}});
setAllFavs(prev=>prev.map(x=>x.id===f.id?{...x,acknowledged:0}:x));
}} title="Quittierung rückgängig"
style={{background:'transparent',border:'1px solid rgba(255,255,255,0.1)',
borderRadius:6,padding:'3px 8px',color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:9,cursor:'pointer',flexShrink:0}}>
</button>
)}
</div>
);
return (
<div>
<div style={{display:'flex',gap:8,marginBottom:16,alignItems:'center',flexWrap:'wrap'}}>
@@ -744,69 +834,13 @@ function FavoritenKalender({ onOpenModal }) {
</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>
) : (
<button onClick={async e=>{e.stopPropagation();
await api(`/tools/media/favorites/${f.id}/unacknowledge`,{method:'POST',body:{}});
setAllFavs(prev=>prev.map(x=>x.id===f.id?{...x,acknowledged:0}:x));
}} title="Quittierung rückgängig"
style={{background:'transparent',border:'1px solid rgba(255,255,255,0.1)',
borderRadius:6,padding:'3px 8px',color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:9,cursor:'pointer',flexShrink:0}}>
</button>
)}
</div>
))}
</div>
</div>
))
: Object.entries(userGroups).map(([username, items]) => {
const unackedItems = items.filter(f => !f.acknowledged);
const ackedItems = items.filter(f => f.acknowledged);
return (
<AdminUserGroup key={username} username={username} unackedItems={unackedItems} ackedItems={ackedItems} FavRow={FavRow} />
);
})
}
</div>
);