feat: dashboard 3-column layout — project folders treeview, project cards with open button, info panel

This commit is contained in:
A0 Orchestrator
2026-07-03 17:21:42 +02:00
parent d80e5990e4
commit aae93fe36b
11 changed files with 1454 additions and 193 deletions
+62 -4
View File
@@ -11,6 +11,7 @@ import type {
DBElement, DBBlock, DBSetting, DBUser, DBSession,
DBNotification, DBProjectShare,
DBGlobalBlockFolder, DBGlobalBlock,
DBProjectFolder,
} from './DatabaseInterface.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -28,6 +29,11 @@ export class SqliteAdapter implements DatabaseInterface {
const schemaPath = join(__dirname, 'schema.sql');
const schema = readFileSync(schemaPath, 'utf-8');
this.db.exec(schema);
// Add folder_id column to projects table for existing databases
const columns = this.db.prepare("PRAGMA table_info('projects')").all() as { name: string }[];
if (!columns.some(col => col.name === 'folder_id')) {
this.db.exec('ALTER TABLE projects ADD COLUMN folder_id TEXT REFERENCES project_folders(id) ON DELETE SET NULL');
}
}
close(): void {
@@ -74,10 +80,22 @@ export class SqliteAdapter implements DatabaseInterface {
}
// ─── Projects ──────────────────────────────────────
listProjects(ownerId?: string): DBProject[] {
listProjects(ownerId?: string, folderId?: string | null): DBProject[] {
if (ownerId && folderId !== undefined) {
if (folderId === null) {
return this.db.prepare('SELECT * FROM projects WHERE owner_id = ? AND folder_id IS NULL ORDER BY updated_at DESC').all(ownerId) as DBProject[];
}
return this.db.prepare('SELECT * FROM projects WHERE owner_id = ? AND folder_id = ? ORDER BY updated_at DESC').all(ownerId, folderId) as DBProject[];
}
if (ownerId) {
return this.db.prepare('SELECT * FROM projects WHERE owner_id = ? ORDER BY updated_at DESC').all(ownerId) as DBProject[];
}
if (folderId !== undefined) {
if (folderId === null) {
return this.db.prepare('SELECT * FROM projects WHERE folder_id IS NULL ORDER BY updated_at DESC').all() as DBProject[];
}
return this.db.prepare('SELECT * FROM projects WHERE folder_id = ? ORDER BY updated_at DESC').all(folderId) as DBProject[];
}
return this.db.prepare('SELECT * FROM projects ORDER BY updated_at DESC').all() as DBProject[];
}
@@ -88,16 +106,17 @@ export class SqliteAdapter implements DatabaseInterface {
createProject(data: Partial<DBProject>): DBProject {
const id = data.id ?? `proj-${Date.now()}`;
this.db.prepare(
'INSERT INTO projects (id, name, description, owner_id) VALUES (?, ?, ?, ?)',
).run(id, data.name ?? 'Unbenannt', data.description ?? null, data.owner_id ?? 'user-default');
'INSERT INTO projects (id, name, description, owner_id, folder_id) VALUES (?, ?, ?, ?, ?)',
).run(id, data.name ?? 'Unbenannt', data.description ?? null, data.owner_id ?? 'user-default', data.folder_id ?? null);
return this.getProject(id)!;
}
updateProject(id: string, data: Partial<DBProject>): DBProject | null {
const sets: string[] = [];
const vals: any[] = [];
const vals: unknown[] = [];
if (data.name !== undefined) { sets.push('name = ?'); vals.push(data.name); }
if (data.description !== undefined) { sets.push('description = ?'); vals.push(data.description); }
if (data.folder_id !== undefined) { sets.push('folder_id = ?'); vals.push(data.folder_id); }
sets.push("updated_at = datetime('now')");
if (sets.length > 1) {
this.db.prepare(`UPDATE projects SET ${sets.join(', ')} WHERE id = ?`).run(...vals, id);
@@ -109,6 +128,45 @@ export class SqliteAdapter implements DatabaseInterface {
return this.db.prepare('DELETE FROM projects WHERE id = ?').run(id).changes > 0;
}
moveProjectToFolder(projectId: string, folderId: string | null): DBProject | null {
this.db.prepare('UPDATE projects SET folder_id = ?, updated_at = datetime(\'now\') WHERE id = ?').run(folderId, projectId);
return this.getProject(projectId);
}
// ─── Project Folders ─────────────────────────────────
listProjectFolders(ownerId: string): DBProjectFolder[] {
return this.db.prepare('SELECT * FROM project_folders WHERE owner_id = ? ORDER BY name').all(ownerId) as DBProjectFolder[];
}
getProjectFolder(id: string): DBProjectFolder | null {
return (this.db.prepare('SELECT * FROM project_folders WHERE id = ?').get(id) as DBProjectFolder) ?? null;
}
createProjectFolder(data: Partial<DBProjectFolder>): DBProjectFolder {
const id = data.id ?? `pfolder-${Date.now()}`;
this.db.prepare(
'INSERT INTO project_folders (id, name, parent_id, owner_id) VALUES (?, ?, ?, ?)',
).run(id, data.name ?? 'Neuer Ordner', data.parent_id ?? null, data.owner_id ?? 'user-default');
return this.getProjectFolder(id)!;
}
updateProjectFolder(id: string, data: Partial<DBProjectFolder>): DBProjectFolder | null {
const sets: string[] = [];
const vals: unknown[] = [];
if (data.name !== undefined) { sets.push('name = ?'); vals.push(data.name); }
if (data.parent_id !== undefined) { sets.push('parent_id = ?'); vals.push(data.parent_id); }
if (sets.length > 0) {
this.db.prepare(`UPDATE project_folders SET ${sets.join(', ')} WHERE id = ?`).run(...vals, id);
}
return this.getProjectFolder(id);
}
deleteProjectFolder(id: string): boolean {
// Move projects inside this folder to root (folder_id = NULL) before deleting
this.db.prepare('UPDATE projects SET folder_id = NULL WHERE folder_id = ?').run(id);
return this.db.prepare('DELETE FROM project_folders WHERE id = ?').run(id).changes > 0;
}
// ─── Drawings ──────────────────────────────────────
listDrawings(projectId: string): DBDrawing[] {
return this.db.prepare('SELECT * FROM drawings WHERE project_id = ? ORDER BY updated_at DESC').all(projectId) as DBDrawing[];