diff --git a/backend/src/tools/gebietseroberung/routes.js b/backend/src/tools/gebietseroberung/routes.js index 9a29d09..26f047d 100644 --- a/backend/src/tools/gebietseroberung/routes.js +++ b/backend/src/tools/gebietseroberung/routes.js @@ -32,6 +32,7 @@ const router = express.Router(); ['opp_head', 'TEXT'], ['owner_scouts', 'TEXT'], ['opp_scouts', 'TEXT'], + ['seen_by', 'TEXT'], ]) { if (!cols.includes(col)) db.exec(`ALTER TABLE geo_games ADD COLUMN ${col} ${def}`); } @@ -281,14 +282,25 @@ router.get('/', authenticate, (req, res) => { res.json(games); }); -// ── Meine Züge: Anzahl Spiele wo ich dran bin ──────────────────────────────── +// ── Meine Züge + ungesehene Spielenden ────────────────────────────────────── router.get('/my-turns', authenticate, (req, res) => { const me = uid(req); - const { count } = db.prepare(` + // Aktive Spiele wo ich dran bin + const { count: turns } = db.prepare(` SELECT COUNT(*) as count FROM geo_games WHERE status='active' AND current_turn=? `).get(me); - res.json({ count }); + // Beendete Spiele wo ich beteiligt bin aber noch nicht gesehen habe + const games = db.prepare(` + SELECT id, seen_by FROM geo_games + WHERE status='finished' AND move_count >= 2 + AND (owner_id=? OR opponent_id=?) + `).all(me, me); + const unseen = games.filter(g => { + const seen = g.seen_by ? JSON.parse(g.seen_by) : []; + return !seen.includes(me); + }).length; + res.json({ count: turns + unseen }); }); // ── Topliste ────────────────────────────────────────────────────────────────── @@ -502,6 +514,20 @@ router.post('/:id/move', authenticate, (req, res) => { res.json(getGameForUser(game.id, me)); }); +// ── Spiel als gesehen markieren ────────────────────────────────────────────── +router.post('/:id/seen', authenticate, (req, res) => { + const me = uid(req); + const game = db.prepare('SELECT * FROM geo_games WHERE id=?').get(req.params.id); + if (!game) return res.status(404).json({ error: 'Nicht gefunden' }); + if (game.owner_id !== me && game.opponent_id !== me) return res.status(403).json({ error: 'Kein Zugriff' }); + const seen = game.seen_by ? JSON.parse(game.seen_by) : []; + if (!seen.includes(me)) { + seen.push(me); + db.prepare('UPDATE geo_games SET seen_by=? WHERE id=?').run(JSON.stringify(seen), game.id); + } + res.json({ ok: true }); +}); + // ── Scout: 3x3 Bereich aufdecken (kostet Zug) ─────────────────────────────── router.post('/:id/scout', authenticate, (req, res) => { const me = uid(req); diff --git a/frontend/src/tools/gebietseroberung.jsx b/frontend/src/tools/gebietseroberung.jsx index 5c9f635..fe0b439 100644 --- a/frontend/src/tools/gebietseroberung.jsx +++ b/frontend/src/tools/gebietseroberung.jsx @@ -541,7 +541,13 @@ export default function HexWars({ toast }) { useEffect(() => { if (!activeId) { setGame(null); return; } - api(`/tools/gebietseroberung/${activeId}`).then(setGame).catch(() => {}); + api(`/tools/gebietseroberung/${activeId}`).then(g => { + setGame(g); + // Beendetes Spiel als gesehen markieren → Badge verschwindet + if (g.status === 'finished') { + api(`/tools/gebietseroberung/${activeId}/seen`, { body: {} }).catch(() => {}); + } + }).catch(() => {}); }, [activeId]); // Polling wenn Gegner dran @@ -567,6 +573,9 @@ export default function HexWars({ toast }) { const handleMove = async (row, col) => { const updated = await api(`/tools/gebietseroberung/${activeId}/move`, { body: { row, col } }); setGame(updated); + if (updated.status === 'finished') { + api(`/tools/gebietseroberung/${activeId}/seen`, { body: {} }).catch(() => {}); + } // Direkt den games-State patchen damit die Liste sofort stimmt (kein Warten auf loadGames) setGames(prev => prev.map(g => g.id === updated.id ? { ...g, current_turn: updated.current_turn, move_count: updated.move_count, status: updated.status, winner_id: updated.winner_id }