feat(gui): Ordner-Ebene eines Projekts umbenennbar

Der erste Reiter hieß fest "Ordner" und trägt jetzt einen frei wählbaren
Namen. Er steht in der neuen Spalte projects.root_tab_name (NULL = "Ordner")
statt als Zeile in project_tabs: die Ordner-Ebene ist kein echter Reiter --
es gibt sie immer, sie hat keine eigene id und ist nicht löschbar. Eine
eigene Zeile hätte eine id gebraucht, die in project_pins.tab_id landen
müsste, womit die "0 = Ordner-Ebene"-Regel und damit der UNIQUE-Index
gefallen wären.

PATCH /api/projects/:id/tabs/0 fängt die 0 ab und schreibt ins Projekt;
leerer Name setzt auf "Ordner" zurück. Ebenen-Auswahl im Pin-Dialog und die
Liste bestehender Anheftungen zeigen den Namen mit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
schakal00783
2026-08-02 23:11:12 +02:00
co-authored by Claude Opus 5
parent 772385713e
commit d0e02eb976
5 changed files with 57 additions and 11 deletions
+13 -3
View File
@@ -89,6 +89,10 @@ export async function ensureSchema() {
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(190) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)`);
// Beschriftung der Ordner-Ebene (tab_id 0). Die liegt am Projekt statt in
// project_tabs, weil sie keine echte Reiter-Zeile ist — es gibt sie immer,
// sie lässt sich nicht löschen und hat keine eigene id. NULL = „Ordner".
await pool.query('ALTER TABLE projects ADD COLUMN IF NOT EXISTS root_tab_name VARCHAR(190) NULL');
await pool.query(`CREATE TABLE IF NOT EXISTS project_tabs (
id INT AUTO_INCREMENT PRIMARY KEY,
project_id INT NOT NULL,
@@ -113,7 +117,7 @@ export async function ensureSchema() {
// der (nicht gelöschten) Pins über alle Ebenen.
export async function listProjects() {
const [rows] = await pool.query(`
SELECT p.id, p.name, p.created_at,
SELECT p.id, p.name, p.root_tab_name, p.created_at,
(SELECT COUNT(*) FROM project_tabs t WHERE t.project_id = p.id) AS tabs,
(SELECT COUNT(*) FROM project_pins pin JOIN recordings r ON r.id = pin.recording_id
WHERE pin.project_id = p.id AND r.deleted_at IS NULL) AS pins
@@ -128,6 +132,12 @@ export async function createProject(name) {
export async function renameProject(id, name) {
await pool.execute('UPDATE projects SET name=? WHERE id=?', [String(name).slice(0, 190), id]);
}
// Ordner-Ebene umbenennen. Leerer Name setzt auf die Vorgabe „Ordner" zurück.
export async function renameProjectRoot(id, name) {
const n = String(name || '').trim().slice(0, 190);
await pool.execute('UPDATE projects SET root_tab_name=? WHERE id=?', [n || null, id]);
return n || null;
}
// Der Ordner verschwindet mitsamt Reitern und Pins — die Aufnahmen selbst
// bleiben unberührt (ein Pin ist nur eine Referenz).
export async function deleteProject(id) {
@@ -153,7 +163,7 @@ export async function deleteProjectTab(id) {
// (im Papierkorb liegende) Aufnahmen werden ausgeblendet, nicht entfernt: eine
// Wiederherstellung bringt den Pin ohne Zutun zurück.
export async function getProject(id) {
const [pr] = await pool.query('SELECT id, name, created_at FROM projects WHERE id=?', [id]);
const [pr] = await pool.query('SELECT id, name, root_tab_name, created_at FROM projects WHERE id=?', [id]);
if (!pr.length) return null;
const [tabs] = await pool.query(
'SELECT id, name, created_at FROM project_tabs WHERE project_id=? ORDER BY id', [id]);
@@ -179,7 +189,7 @@ export async function unpinRecording(pinId) {
export async function listPinsForRecording(recId) {
const [rows] = await pool.query(`
SELECT pin.id AS pin_id, pin.project_id, p.name AS project_name,
pin.tab_id, t.name AS tab_name
pin.tab_id, COALESCE(t.name, p.root_tab_name) AS tab_name
FROM project_pins pin
JOIN projects p ON p.id = pin.project_id
LEFT JOIN project_tabs t ON t.id = pin.tab_id