diff --git a/backend/src/db.js b/backend/src/db.js index 7fcb899..7c536af 100644 --- a/backend/src/db.js +++ b/backend/src/db.js @@ -342,6 +342,15 @@ const clCols = db.prepare("PRAGMA table_info(changelog)").all().map(r => r.name) if (clCols.length && !clCols.includes('build_time')) db.exec("ALTER TABLE changelog ADD COLUMN build_time TEXT DEFAULT NULL"); +if (!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='changelog_reads'").get()) { + db.exec(` + CREATE TABLE changelog_reads ( + user_id INTEGER PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, + last_read DATETIME DEFAULT NULL + ) + `); +} + // ── Push-Zeitplaner ─────────────────────────────────────────────────────────── if (!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='push_schedules'").get()) { db.exec(` diff --git a/backend/src/routes/dashboard.js b/backend/src/routes/dashboard.js index fa97a1a..a206579 100644 --- a/backend/src/routes/dashboard.js +++ b/backend/src/routes/dashboard.js @@ -211,6 +211,21 @@ router.get('/changelog', authenticate, (req, res) => { res.json(db.prepare('SELECT * FROM changelog ORDER BY created_at DESC').all()); }); +router.get('/changelog/unread', authenticate, (req, res) => { + const u = req.user.id; + const lastRead = db.prepare('SELECT last_read FROM changelog_reads WHERE user_id=?').get(u)?.last_read; + const count = lastRead + ? db.prepare('SELECT COUNT(*) c FROM changelog WHERE created_at > ?').get(lastRead).c + : db.prepare('SELECT COUNT(*) c FROM changelog').get().c; + res.json({ unread: count }); +}); + +router.post('/changelog/read', authenticate, (req, res) => { + db.prepare(`INSERT OR REPLACE INTO changelog_reads (user_id, last_read) VALUES (?, datetime('now','localtime'))`) + .run(req.user.id); + res.json({ ok: true }); +}); + router.post('/changelog', authenticate, requireAdmin, (req, res) => { const { version, title, body, build_time } = req.body; if (!version?.trim() || !title?.trim()) return res.status(400).json({ error: 'Version und Titel erforderlich' }); diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 6bbd37c..960d101 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1172,7 +1172,7 @@ function PushScheduler({ toast }) { } // ── Changelog Modal ─────────────────────────────────────────────────────────── -function ChangelogModal({ user, toast, onClose }) { +function ChangelogModal({ user, toast, onClose, onRead }) { const isAdmin = user?.role === 'admin'; const [entries, setEntries] = useState([]); const [form, setForm] = useState({ version:'', title:'', body:'' }); @@ -1181,6 +1181,7 @@ function ChangelogModal({ user, toast, onClose }) { useEffect(()=>{ api('/dashboard/changelog').then(setEntries).catch(()=>{}); + api('/dashboard/changelog/read',{method:'POST'}).then(()=>{ if(onRead) onRead(); }).catch(()=>{}); },[]); const add = async () => { @@ -1286,7 +1287,7 @@ function ChangelogModal({ user, toast, onClose }) { ); } -function Dashboard({ toast, mobile, setActive, unreadMsgs=0, unreadBoard=0, unreadPromoted=0, onBoardRead, user }) { +function Dashboard({ toast, mobile, setActive, unreadMsgs=0, unreadBoard=0, unreadPromoted=0, onBoardRead, unreadChangelog=0, onChangelogRead, user }) { const [now, setNow] = useState(new Date()); const [showBoard, setShowBoard] = useState(false); const [showChangelog, setShowChangelog] = useState(false); @@ -1294,10 +1295,21 @@ function Dashboard({ toast, mobile, setActive, unreadMsgs=0, unreadBoard=0, unre return (
{showBoard && setShowBoard(false)} onRead={onBoardRead}/>} - {showChangelog && setShowChangelog(false)}/>} + {showChangelog && setShowChangelog(false)} onRead={onChangelogRead}/>}
-

Dashboard

+
+

Dashboard

+ {unreadChangelog > 0 && ( + + )} +

{now.toLocaleDateString('de-DE', { weekday:'long', day:'numeric', month:'long' })} {' · '} @@ -2158,6 +2170,7 @@ export default function App() { const [unreadMsgs, setUnreadMsgs] = useState(0); const [unreadBoard, setUnreadBoard] = useState(0); const [unreadPromoted, setUnreadPromoted] = useState(0); + const [unreadChangelog,setUnreadChangelog]= useState(0); const [authChecked, setAuthChecked] = useState(false); useEffect(()=>{ @@ -2198,6 +2211,14 @@ export default function App() { return()=>clearInterval(t); },[user]); + useEffect(()=>{ + if(!user)return; + const poll=()=>api('/dashboard/changelog/unread').then(d=>setUnreadChangelog(d.unread||0)).catch(()=>{}); + poll(); + const t=setInterval(poll,60*1000); + return()=>clearInterval(t); + },[user]); + useEffect(()=>{ if(!user)return; const poll=()=>api('/tools/nachrichten/unread').then(d=>setUnreadMsgs(d.count||0)).catch(()=>{}); @@ -2245,7 +2266,7 @@ export default function App() { unreadMsgs={unreadMsgs}/> )}

- {active==='dashboard' && { setUnreadBoard(0); setUnreadPromoted(0); }} user={user}/>} + {active==='dashboard' && { setUnreadBoard(0); setUnreadPromoted(0); }} unreadChangelog={unreadChangelog} onChangelogRead={()=>setUnreadChangelog(0)} user={user}/>} {active==='admin' && } {activeTool && }