From f03d6a173a7bf84ebd329d1fed30b5567eaa149e Mon Sep 17 00:00:00 2001 From: Dicken Date: Thu, 28 May 2026 18:37:04 +0200 Subject: [PATCH] =?UTF-8?q?Board:=20Eingabe-Fix=20(ItemRow=20au=C3=9Ferhal?= =?UTF-8?q?b),=20Promote-Button=20Wunsch=E2=86=92Roadmap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/db.js | 17 +++-- backend/src/routes/dashboard.js | 14 ++++ frontend/src/App.jsx | 124 +++++++++++++++++++------------- 3 files changed, 98 insertions(+), 57 deletions(-) diff --git a/backend/src/db.js b/backend/src/db.js index f7c774a..c5ad4aa 100644 --- a/backend/src/db.js +++ b/backend/src/db.js @@ -288,15 +288,20 @@ for (const [k, v] of loginDefaults) { if (!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='board_items'").get()) { db.exec(` CREATE TABLE board_items ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - type TEXT NOT NULL CHECK(type IN ('roadmap','wish')), - user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, - title TEXT NOT NULL, - description TEXT NOT NULL DEFAULT '', - created_at DATETIME DEFAULT NULL + id INTEGER PRIMARY KEY AUTOINCREMENT, + type TEXT NOT NULL CHECK(type IN ('roadmap','wish')), + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + title TEXT NOT NULL, + description TEXT NOT NULL DEFAULT '', + promoted_from_wish INTEGER NOT NULL DEFAULT 0, + created_at DATETIME DEFAULT NULL ) `); } +// Migration: promoted_from_wish Spalte +const boardCols = db.prepare("PRAGMA table_info(board_items)").all().map(r => r.name); +if (boardCols.length && !boardCols.includes('promoted_from_wish')) + db.exec("ALTER TABLE board_items ADD COLUMN promoted_from_wish INTEGER NOT NULL DEFAULT 0"); if (!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='board_reads'").get()) { db.exec(` CREATE TABLE board_reads ( diff --git a/backend/src/routes/dashboard.js b/backend/src/routes/dashboard.js index 3b18312..1ccb327 100644 --- a/backend/src/routes/dashboard.js +++ b/backend/src/routes/dashboard.js @@ -189,4 +189,18 @@ router.delete('/board/:id', authenticate, (req, res) => { res.json({ ok: true }); }); +// Wunsch zur Roadmap befördern (Admin only) +router.post('/board/:id/promote', authenticate, (req, res) => { + if (req.user.role !== 'admin') return res.status(403).json({ error: 'Kein Zugriff' }); + const item = db.prepare('SELECT * FROM board_items WHERE id=?').get(req.params.id); + if (!item) return res.status(404).json({ error: 'Nicht gefunden' }); + if (item.type !== 'wish') return res.status(400).json({ error: 'Nur Wünsche können befördert werden' }); + db.prepare('UPDATE board_items SET type=?, promoted_from_wish=1 WHERE id=?').run('roadmap', item.id); + const updated = db.prepare(` + SELECT b.*, u.username as author FROM board_items b + JOIN users u ON u.id=b.user_id WHERE b.id=? + `).get(item.id); + res.json(updated); +}); + module.exports = router; diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 104ab74..e71f5ea 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -822,13 +822,64 @@ function PushoverSettings({ toast }) { } // ── Ideen-Board Modal ───────────────────────────────────────────────────────── +// ── Board ItemRow (außerhalb BoardModal damit Inputs stabil bleiben) ─────────── +function BoardItemRow({ item, isAdmin, userId, editId, editForm, setEditId, setEditForm, onSave, onDelete, onPromote }) { + const isEditing = editId === item.id; + const canEdit = isAdmin || (item.user_id === userId && item.type === 'wish'); + return ( +
+ {isEditing ? ( +
+ setEditForm(p=>({...p,title:e.target.value}))} + style={{...S.inp,marginBottom:6,fontSize:13}} autoFocus/> + setEditForm(p=>({...p,description:e.target.value}))} + placeholder="Beschreibung (optional)" style={{...S.inp,marginBottom:8,fontSize:12}}/> +
+ + +
+
+ ) : ( +
+
+
+
{item.title}
+ {item.promoted_from_wish && ( + ↑ aus Wünschen + )} +
+ {item.description &&
{item.description}
} +
+ {item.author} · {item.created_at ? new Date(item.created_at.replace(' ','T')).toLocaleDateString('de-DE') : ''} +
+
+
+ {isAdmin && item.type==='wish' && ( + + )} + {canEdit && ( + <> + + + + )} +
+
+ )} +
+ ); +} + function BoardModal({ user, toast, onClose, onRead }) { const isAdmin = user?.role === 'admin'; - const [items, setItems] = useState([]); - const [loading, setLoading] = useState(true); - const [form, setForm] = useState({ type:'wish', title:'', description:'' }); - const [editId, setEditId] = useState(null); - const [editForm,setEditForm]= useState({}); + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + const [form, setForm] = useState({ type:'wish', title:'', description:'' }); + const [editId, setEditId] = useState(null); + const [editForm, setEditForm] = useState({ title:'', description:'' }); const isMobile = window.innerWidth < 768; useEffect(()=>{ @@ -857,46 +908,17 @@ function BoardModal({ user, toast, onClose, onRead }) { catch(e){ toast(e.message,'error'); } }; + const promote = async id => { + try { + const r = await api(`/dashboard/board/${id}/promote`,{method:'POST'}); + setItems(p=>p.map(i=>i.id===id?r:i)); toast('Zur Roadmap befördert ✓'); + } catch(e){ toast(e.message,'error'); } + }; + const roadmap = items.filter(i=>i.type==='roadmap'); const wishes = items.filter(i=>i.type==='wish'); - - const ItemRow = ({item}) => { - const canEdit = isAdmin || item.user_id === user?.id; - const isEditing = editId === item.id; - return ( -
- {isEditing ? ( -
- setEditForm(p=>({...p,title:e.target.value}))} - style={{...S.inp,marginBottom:6,fontSize:13}}/> - setEditForm(p=>({...p,description:e.target.value}))} - placeholder="Beschreibung (optional)" style={{...S.inp,marginBottom:8,fontSize:12}}/> -
- - -
-
- ) : ( -
-
-
{item.title}
- {item.description &&
{item.description}
} -
- {item.author} · {item.created_at ? new Date(item.created_at.replace(' ','T')).toLocaleDateString('de-DE') : ''} -
-
- {canEdit && ( -
- - -
- )} -
- )} -
- ); - }; + const rowProps = { isAdmin, userId:user?.id, editId, editForm, setEditId, setEditForm, + onSave:save, onDelete:del, onPromote:promote }; return (
- {/* Header */}
- {isMobile&&
} + display:'flex',justifyContent:'space-between',alignItems:'center',position:'relative'}}> + {isMobile&&
}
📋 Ideen & Roadmap
- {/* Roadmap */}
🗺 ROADMAP{isAdmin&& (Admin)}
{loading ?
Lädt…
: roadmap.length===0 ?
Noch keine Einträge.
- : roadmap.map(i=>)} + : roadmap.map(i=>)} {isAdmin && (
- {/* Wishes */}
-
💡 WÜNSCHE & IDEEN
+
💡 WÜNSCHE & IDEEN
+ {isAdmin &&
↑ befördert Wunsch zur Roadmap
} {loading ? null : wishes.length===0 ?
Noch keine Wünsche.
- : wishes.map(i=>)} + : wishes.map(i=>)}
setForm(f=>({...f,type:'wish'}))} @@ -968,6 +989,7 @@ function BoardModal({ user, toast, onClose, onRead }) { ); } + function Dashboard({ toast, mobile, setActive, unreadMsgs=0, unreadBoard=0, user }) { const [now, setNow] = useState(new Date()); const [showBoard, setShowBoard] = useState(false);