Kanban: Design-Fix (padding/header wie andere Tools), Spaltenfarben, Verschieben-nach im Modal
This commit is contained in:
@@ -10,6 +10,7 @@ const router = express.Router();
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
color TEXT NOT NULL DEFAULT '#4ecdc4',
|
||||
position INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
|
||||
);
|
||||
@@ -25,12 +26,16 @@ const router = express.Router();
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
|
||||
);
|
||||
`);
|
||||
// Nachrüsten falls Tabelle schon existiert aber color fehlt
|
||||
const cols = db.pragma('table_info(kanban_columns)').map(c => c.name);
|
||||
if (!cols.includes('color')) {
|
||||
db.exec("ALTER TABLE kanban_columns ADD COLUMN color TEXT NOT NULL DEFAULT '#4ecdc4'");
|
||||
}
|
||||
})();
|
||||
|
||||
const uid = req => req.user.id;
|
||||
|
||||
// ── Alle Spalten + Karten laden ───────────────────────────────────────────────
|
||||
// Feste Routen vor :param-Routen (Lessons Learned)
|
||||
router.get('/board', authenticate, (req, res) => {
|
||||
const me = uid(req);
|
||||
const columns = db.prepare(
|
||||
@@ -39,7 +44,6 @@ router.get('/board', authenticate, (req, res) => {
|
||||
const cards = db.prepare(
|
||||
'SELECT * FROM kanban_cards WHERE user_id=? ORDER BY position ASC, id ASC'
|
||||
).all(me);
|
||||
// Karten in Spalten einsortieren
|
||||
const colMap = {};
|
||||
for (const c of columns) { c.cards = []; colMap[c.id] = c; }
|
||||
for (const card of cards) {
|
||||
@@ -50,29 +54,39 @@ router.get('/board', authenticate, (req, res) => {
|
||||
|
||||
// ── Spalte erstellen ──────────────────────────────────────────────────────────
|
||||
router.post('/columns', authenticate, (req, res) => {
|
||||
const { title } = req.body;
|
||||
const { title, color = '#4ecdc4' } = req.body;
|
||||
if (!title?.trim()) return res.status(400).json({ error: 'Titel fehlt' });
|
||||
const me = uid(req);
|
||||
const maxPos = db.prepare('SELECT MAX(position) as m FROM kanban_columns WHERE user_id=?').get(me);
|
||||
const pos = (maxPos?.m ?? -1) + 1;
|
||||
const r = db.prepare(
|
||||
"INSERT INTO kanban_columns (user_id,title,position) VALUES (?,?,?)"
|
||||
).run(me, title.trim(), pos);
|
||||
"INSERT INTO kanban_columns (user_id,title,color,position) VALUES (?,?,?,?)"
|
||||
).run(me, title.trim(), color, pos);
|
||||
res.json(db.prepare('SELECT * FROM kanban_columns WHERE id=?').get(r.lastInsertRowid));
|
||||
});
|
||||
|
||||
// ── Spalte umbenennen ─────────────────────────────────────────────────────────
|
||||
router.patch('/columns/:id', authenticate, (req, res) => {
|
||||
const { title } = req.body;
|
||||
if (!title?.trim()) return res.status(400).json({ error: 'Titel fehlt' });
|
||||
// ── Spalte aktualisieren (Titel + Farbe) ─────────────────────────────────────
|
||||
// Feste Route vor :param (Lesson Learned)
|
||||
router.post('/columns/reorder', authenticate, (req, res) => {
|
||||
const { order } = req.body;
|
||||
if (!Array.isArray(order)) return res.status(400).json({ error: 'order fehlt' });
|
||||
const me = uid(req);
|
||||
const col = db.prepare('SELECT * FROM kanban_columns WHERE id=? AND user_id=?').get(req.params.id, me);
|
||||
if (!col) return res.status(404).json({ error: 'Spalte nicht gefunden' });
|
||||
db.prepare('UPDATE kanban_columns SET title=? WHERE id=?').run(title.trim(), col.id);
|
||||
const upd = db.prepare('UPDATE kanban_columns SET position=? WHERE id=? AND user_id=?');
|
||||
db.transaction(() => { for (let i = 0; i < order.length; i++) upd.run(i, order[i], me); })();
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
router.patch('/columns/:id', authenticate, (req, res) => {
|
||||
const me = uid(req);
|
||||
const col = db.prepare('SELECT * FROM kanban_columns WHERE id=? AND user_id=?').get(req.params.id, me);
|
||||
if (!col) return res.status(404).json({ error: 'Spalte nicht gefunden' });
|
||||
const title = req.body.title !== undefined ? req.body.title.trim() : col.title;
|
||||
const color = req.body.color !== undefined ? req.body.color : col.color;
|
||||
if (!title) return res.status(400).json({ error: 'Titel fehlt' });
|
||||
db.prepare('UPDATE kanban_columns SET title=?, color=? WHERE id=?').run(title, color, col.id);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// ── Spalte löschen (inkl. aller Karten via CASCADE) ──────────────────────────
|
||||
router.delete('/columns/:id', authenticate, (req, res) => {
|
||||
const me = uid(req);
|
||||
const col = db.prepare('SELECT * FROM kanban_columns WHERE id=? AND user_id=?').get(req.params.id, me);
|
||||
@@ -81,20 +95,7 @@ router.delete('/columns/:id', authenticate, (req, res) => {
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// ── Spalten-Reihenfolge speichern ─────────────────────────────────────────────
|
||||
router.post('/columns/reorder', authenticate, (req, res) => {
|
||||
const { order } = req.body; // Array von column-IDs in neuer Reihenfolge
|
||||
if (!Array.isArray(order)) return res.status(400).json({ error: 'order fehlt' });
|
||||
const me = uid(req);
|
||||
const upd = db.prepare('UPDATE kanban_columns SET position=? WHERE id=? AND user_id=?');
|
||||
const tx = db.transaction(() => {
|
||||
for (let i = 0; i < order.length; i++) upd.run(i, order[i], me);
|
||||
});
|
||||
tx();
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// ── Karte erstellen ───────────────────────────────────────────────────────────
|
||||
// ── Karten ────────────────────────────────────────────────────────────────────
|
||||
router.post('/cards', authenticate, (req, res) => {
|
||||
const { column_id, title, description = '', priority = 'none' } = req.body;
|
||||
if (!column_id || !title?.trim()) return res.status(400).json({ error: 'column_id und title erforderlich' });
|
||||
@@ -109,7 +110,6 @@ router.post('/cards', authenticate, (req, res) => {
|
||||
res.json(db.prepare('SELECT * FROM kanban_cards WHERE id=?').get(r.lastInsertRowid));
|
||||
});
|
||||
|
||||
// ── Karte bearbeiten ──────────────────────────────────────────────────────────
|
||||
router.patch('/cards/:id', authenticate, (req, res) => {
|
||||
const me = uid(req);
|
||||
const card = db.prepare('SELECT * FROM kanban_cards WHERE id=? AND user_id=?').get(req.params.id, me);
|
||||
@@ -124,7 +124,6 @@ router.patch('/cards/:id', authenticate, (req, res) => {
|
||||
res.json(db.prepare('SELECT * FROM kanban_cards WHERE id=?').get(card.id));
|
||||
});
|
||||
|
||||
// ── Karte löschen ─────────────────────────────────────────────────────────────
|
||||
router.delete('/cards/:id', authenticate, (req, res) => {
|
||||
const me = uid(req);
|
||||
const card = db.prepare('SELECT * FROM kanban_cards WHERE id=? AND user_id=?').get(req.params.id, me);
|
||||
@@ -133,38 +132,23 @@ router.delete('/cards/:id', authenticate, (req, res) => {
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// ── Karte verschieben (Spalte + Position) ─────────────────────────────────────
|
||||
// Wird genutzt für: Drag&Drop zwischen Spalten, Pfeil-Buttons, Position hoch/runter
|
||||
router.post('/cards/:id/move', authenticate, (req, res) => {
|
||||
const { column_id, position } = req.body;
|
||||
const me = uid(req);
|
||||
const card = db.prepare('SELECT * FROM kanban_cards WHERE id=? AND user_id=?').get(req.params.id, me);
|
||||
if (!card) return res.status(404).json({ error: 'Karte nicht gefunden' });
|
||||
|
||||
const targetColId = column_id !== undefined ? column_id : card.column_id;
|
||||
|
||||
// Zielspalte gehört dem User?
|
||||
const col = db.prepare('SELECT * FROM kanban_columns WHERE id=? AND user_id=?').get(targetColId, me);
|
||||
if (!col) return res.status(404).json({ error: 'Zielspalte nicht gefunden' });
|
||||
|
||||
// Alle Karten der Zielspalte (ohne die verschobene), dann an position einfügen
|
||||
const siblings = db.prepare(
|
||||
'SELECT id FROM kanban_cards WHERE column_id=? AND id!=? ORDER BY position ASC, id ASC'
|
||||
).all(targetColId, card.id).map(r => r.id);
|
||||
|
||||
const targetPos = position !== undefined
|
||||
? Math.max(0, Math.min(position, siblings.length))
|
||||
: siblings.length; // ans Ende wenn keine position angegeben
|
||||
|
||||
: siblings.length;
|
||||
siblings.splice(targetPos, 0, card.id);
|
||||
|
||||
const upd = db.prepare('UPDATE kanban_cards SET column_id=?, position=? WHERE id=?');
|
||||
const tx = db.transaction(() => {
|
||||
for (let i = 0; i < siblings.length; i++) {
|
||||
upd.run(targetColId, i, siblings[i]);
|
||||
}
|
||||
});
|
||||
tx();
|
||||
db.transaction(() => { for (let i = 0; i < siblings.length; i++) upd.run(targetColId, i, siblings[i]); })();
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user