feat: Media-Requests im Dashboard mit Neu/Gesehen-Unterscheidung statt nur Favoriten-Zaehler
This commit is contained in:
@@ -566,6 +566,8 @@ if (!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='mov
|
|||||||
db.exec("ALTER TABLE movie_favorites ADD COLUMN user_notified INTEGER NOT NULL DEFAULT 0");
|
db.exec("ALTER TABLE movie_favorites ADD COLUMN user_notified INTEGER NOT NULL DEFAULT 0");
|
||||||
if (!cols.includes('media_type'))
|
if (!cols.includes('media_type'))
|
||||||
db.exec("ALTER TABLE movie_favorites ADD COLUMN media_type TEXT NOT NULL DEFAULT 'movie'");
|
db.exec("ALTER TABLE movie_favorites ADD COLUMN media_type TEXT NOT NULL DEFAULT 'movie'");
|
||||||
|
if (!cols.includes('admin_seen'))
|
||||||
|
db.exec("ALTER TABLE movie_favorites ADD COLUMN admin_seen INTEGER NOT NULL DEFAULT 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
// xREL Badge Cache (persistent, überlebt Container-Restarts)
|
// xREL Badge Cache (persistent, überlebt Container-Restarts)
|
||||||
|
|||||||
@@ -866,12 +866,13 @@ router.post('/favorites/user-notify', authenticate, (req, res) => {
|
|||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /favorites/unacked – unquittierte Zählung (Admin: neue Favoriten; User: quittierte aber unbestätigte)
|
// GET /favorites/unacked – unquittierte Zählung (Admin: neue Requests; User: quittierte aber unbestätigte)
|
||||||
router.get('/favorites/unacked', authenticate, (req, res) => {
|
router.get('/favorites/unacked', authenticate, (req, res) => {
|
||||||
const isAdmin = req.user.role === 'admin';
|
const isAdmin = req.user.role === 'admin';
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
const count = db.prepare('SELECT COUNT(*) as n FROM movie_favorites WHERE acknowledged=0').get();
|
const count = db.prepare('SELECT COUNT(*) as n FROM movie_favorites WHERE acknowledged=0').get();
|
||||||
res.json({ count: count.n, isAdmin: true });
|
const newCount = db.prepare('SELECT COUNT(*) as n FROM movie_favorites WHERE acknowledged=0 AND admin_seen=0').get();
|
||||||
|
res.json({ count: count.n, newCount: newCount.n, isAdmin: true });
|
||||||
} else {
|
} else {
|
||||||
// User: Favoriten die vom Admin quittiert wurden aber User noch nicht gesehen hat
|
// User: Favoriten die vom Admin quittiert wurden aber User noch nicht gesehen hat
|
||||||
const count = db.prepare('SELECT COUNT(*) as n FROM movie_favorites WHERE user_id=? AND acknowledged=1 AND user_notified=0').get(req.user.id);
|
const count = db.prepare('SELECT COUNT(*) as n FROM movie_favorites WHERE user_id=? AND acknowledged=1 AND user_notified=0').get(req.user.id);
|
||||||
@@ -879,6 +880,14 @@ router.get('/favorites/unacked', authenticate, (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// POST /favorites/mark-seen – Admin hat die offenen Requests angesehen (noch
|
||||||
|
// nicht zwingend quittiert) — nimmt ihnen das "neu"-Label im Dashboard
|
||||||
|
router.post('/favorites/mark-seen', authenticate, (req, res) => {
|
||||||
|
if (req.user.role !== 'admin') return res.status(403).json({ error: 'Kein Zugriff' });
|
||||||
|
db.prepare('UPDATE movie_favorites SET admin_seen=1 WHERE acknowledged=0 AND admin_seen=0').run();
|
||||||
|
res.json({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ── Quittier-Logik (wiederverwendbar: HTTP-Routen + MQTT-Buttons) ───────────
|
// ── Quittier-Logik (wiederverwendbar: HTTP-Routen + MQTT-Buttons) ───────────
|
||||||
@@ -993,7 +1002,7 @@ router.post('/favorites/:id/acknowledge', authenticate, requireAdmin, (req, res)
|
|||||||
router.post('/favorites/:id/unacknowledge', authenticate, requireAdmin, (req, res) => {
|
router.post('/favorites/:id/unacknowledge', authenticate, requireAdmin, (req, res) => {
|
||||||
const fav = db.prepare('SELECT * FROM movie_favorites WHERE id=?').get(req.params.id);
|
const fav = db.prepare('SELECT * FROM movie_favorites WHERE id=?').get(req.params.id);
|
||||||
if (!fav) return res.status(404).json({ error: 'Nicht gefunden' });
|
if (!fav) return res.status(404).json({ error: 'Nicht gefunden' });
|
||||||
db.prepare('UPDATE movie_favorites SET acknowledged=0, acknowledged_at=NULL, user_notified=0 WHERE id=?').run(fav.id);
|
db.prepare('UPDATE movie_favorites SET acknowledged=0, acknowledged_at=NULL, user_notified=0, admin_seen=0 WHERE id=?').run(fav.id);
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
// Pushover an den User — Quittierung zurückgezogen (fire & forget)
|
// Pushover an den User — Quittierung zurückgezogen (fire & forget)
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -2070,7 +2070,9 @@ function Dashboard({ toast, mobile, setActive, setDevToolsNav, unreadMsgs=0, unr
|
|||||||
padding:'4px 10px', fontSize:9, fontFamily:'monospace', letterSpacing:1,
|
padding:'4px 10px', fontSize:9, fontFamily:'monospace', letterSpacing:1,
|
||||||
}}>
|
}}>
|
||||||
{unackedFavs?.isAdmin
|
{unackedFavs?.isAdmin
|
||||||
? `⭐ ${unackedFavs.count} neue Favorit${unackedFavs.count!==1?'en':''}`
|
? ((unackedFavs.newCount || 0) > 0
|
||||||
|
? `🆕 ${unackedFavs.count} neue Request${unackedFavs.count!==1?'s':''}`
|
||||||
|
: `📋 ${unackedFavs.count} Request${unackedFavs.count!==1?'s':''}`)
|
||||||
: `✅ ${unackedFavs.count} Favorit${unackedFavs.count!==1?'en':' wurde'} bestätigt`
|
: `✅ ${unackedFavs.count} Favorit${unackedFavs.count!==1?'en':' wurde'} bestätigt`
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
@@ -3784,7 +3786,7 @@ export default function App() {
|
|||||||
const [hiddenTools,setHiddenTools]=useState([]);
|
const [hiddenTools,setHiddenTools]=useState([]);
|
||||||
const [active,setActiveRaw]=useState('dashboard');
|
const [active,setActiveRaw]=useState('dashboard');
|
||||||
const [devToolsNav, setDevToolsNav] = useState(null);
|
const [devToolsNav, setDevToolsNav] = useState(null);
|
||||||
const pollUnackedFavs = () => api('/tools/media/favorites/unacked').then(d=>setUnackedFavs({count:d.count||0,isAdmin:d.isAdmin})).catch(()=>{});
|
const pollUnackedFavs = () => api('/tools/media/favorites/unacked').then(d=>setUnackedFavs({count:d.count||0,newCount:d.newCount||0,isAdmin:d.isAdmin})).catch(()=>{});
|
||||||
|
|
||||||
const setActive = (page) => {
|
const setActive = (page) => {
|
||||||
if (page === 'dashboard' && window.__newBuildAvailable && window.__safeReloadIfNewBuild?.()) return;
|
if (page === 'dashboard' && window.__newBuildAvailable && window.__safeReloadIfNewBuild?.()) return;
|
||||||
@@ -3801,7 +3803,7 @@ export default function App() {
|
|||||||
const [unreadBoard, setUnreadBoard] = useState(0);
|
const [unreadBoard, setUnreadBoard] = useState(0);
|
||||||
const [unreadPromoted, setUnreadPromoted] = useState(0);
|
const [unreadPromoted, setUnreadPromoted] = useState(0);
|
||||||
const [unreadChangelog,setUnreadChangelog]= useState(0);
|
const [unreadChangelog,setUnreadChangelog]= useState(0);
|
||||||
const [unackedFavs, setUnackedFavs] = useState({count:0,isAdmin:false});
|
const [unackedFavs, setUnackedFavs] = useState({count:0,newCount:0,isAdmin:false});
|
||||||
const [mediaInitTab, setMediaInitTab] = useState(null);
|
const [mediaInitTab, setMediaInitTab] = useState(null);
|
||||||
const [dashboardKey, setDashboardKey] = useState(0);
|
const [dashboardKey, setDashboardKey] = useState(0);
|
||||||
|
|
||||||
@@ -3856,7 +3858,9 @@ export default function App() {
|
|||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
if(!user)return;
|
if(!user)return;
|
||||||
pollUnackedFavs(); const ti=setInterval(pollUnackedFavs,30000); return()=>clearInterval(ti);
|
pollUnackedFavs(); const ti=setInterval(pollUnackedFavs,30000);
|
||||||
|
window.addEventListener('favorites-seen', pollUnackedFavs);
|
||||||
|
return()=>{ clearInterval(ti); window.removeEventListener('favorites-seen', pollUnackedFavs); };
|
||||||
},[user]);
|
},[user]);
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
|
|||||||
@@ -768,7 +768,15 @@ function FavoritenKalender({ onOpenModal }) {
|
|||||||
api('/tools/media/favorites/all').catch(() => null),
|
api('/tools/media/favorites/all').catch(() => null),
|
||||||
]).then(([mine, all]) => {
|
]).then(([mine, all]) => {
|
||||||
setFavs(mine);
|
setFavs(mine);
|
||||||
if (all) { setAllFavs(all); setIsAdmin(true); }
|
if (all) {
|
||||||
|
setAllFavs(all);
|
||||||
|
setIsAdmin(true);
|
||||||
|
// Admin hat die offenen Requests jetzt gesehen — nimmt ihnen das
|
||||||
|
// "neu"-Label im Dashboard, bis sie tatsächlich quittiert werden
|
||||||
|
api('/tools/media/favorites/mark-seen', { method:'POST', body:{} })
|
||||||
|
.then(() => window.dispatchEvent(new CustomEvent('favorites-seen')))
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
}).catch(e => setError(e.message)).finally(() => setLoading(false));
|
}).catch(e => setError(e.message)).finally(() => setLoading(false));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user