Skip to content

Commit ef5d544

Browse files
committed
Add node type filter, Tasks view, workspace fixes, graph improvements
- Add multi-select dropdown to filter node types in graph view - Add TasksView component for viewing tasks with filters - Add getTasks API with workspace, completion, due date filters - Fix sidebar tree workspace filtering - Fix graph viewport reset on node creation - Fix localStorage key mismatch for node positions - Add getAncestors deleted_at filter - Remove sidebar tasks section - Show only leaf organizations in person detail - Continuous relax now uses current layout mode
1 parent 6e806cb commit ef5d544

File tree

10 files changed

+1600
-411
lines changed

10 files changed

+1600
-411
lines changed

electron/database.js

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,65 @@ class Database {
768768
return this._query(sql, values).map(r => this._rowToNode(r))
769769
}
770770

771-
getChildren(id, type = null) {
771+
/**
772+
* Get tasks with optional filtering
773+
* @param {Object} params - Filter parameters
774+
* @param {string} params.workspaceId - Filter by workspace
775+
* @param {boolean} params.completed - Filter by completion status
776+
* @param {string} params.dueDateFrom - Filter by due date >= this value (YYYY-MM-DD)
777+
* @param {string} params.dueDateTo - Filter by due date <= this value (YYYY-MM-DD)
778+
* @param {number} params.importance - Filter by importance level (1-5)
779+
* @param {number} params.parentId - Filter by parent/project ID
780+
* @returns {Array} List of task nodes
781+
*/
782+
getTasks(params = {}) {
783+
let sql = "SELECT * FROM nodes WHERE type = 'task' AND deleted_at IS NULL"
784+
const values = []
785+
786+
// Workspace filtering
787+
if (params.workspaceId === null || params.workspaceId === 'null') {
788+
sql += ' AND workspace_id IS NULL'
789+
} else if (params.workspaceId !== undefined) {
790+
sql += ' AND workspace_id = ?'
791+
values.push(params.workspaceId)
792+
}
793+
794+
// Completion filtering
795+
if (params.completed !== undefined) {
796+
sql += ' AND completed = ?'
797+
values.push(params.completed ? 1 : 0)
798+
}
799+
800+
// Due date range filtering
801+
if (params.dueDateFrom) {
802+
sql += ' AND due_date >= ?'
803+
values.push(params.dueDateFrom)
804+
}
805+
if (params.dueDateTo) {
806+
sql += ' AND due_date <= ?'
807+
values.push(params.dueDateTo)
808+
}
809+
810+
// Importance filtering
811+
if (params.importance !== undefined) {
812+
sql += ' AND importance = ?'
813+
values.push(params.importance)
814+
}
815+
816+
// Parent/project filtering
817+
if (params.parentId !== undefined) {
818+
sql += ' AND parent_id = ?'
819+
values.push(params.parentId)
820+
}
821+
822+
// Order: due_date first (nulls last), then sort_order, then created_at
823+
sql += ' ORDER BY CASE WHEN due_date IS NULL THEN 1 ELSE 0 END, due_date, sort_order, created_at'
824+
825+
return this._query(sql, values).map(r => this._rowToNode(r))
826+
}
827+
828+
getChildren(id, type = null, workspaceId = undefined) {
829+
const parent = this.getNode(id)
772830
let sql = 'SELECT * FROM nodes WHERE parent_id = ? AND deleted_at IS NULL'
773831
const values = [id]
774832

@@ -777,6 +835,24 @@ class Database {
777835
values.push(type)
778836
}
779837

838+
// Filter by workspace if provided or if parent has a workspace
839+
if (workspaceId !== undefined) {
840+
if (workspaceId === null) {
841+
sql += ' AND workspace_id IS NULL'
842+
} else {
843+
sql += ' AND workspace_id = ?'
844+
values.push(workspaceId)
845+
}
846+
} else if (parent) {
847+
// Use parent's workspace for consistency
848+
if (parent.workspace_id === null) {
849+
sql += ' AND workspace_id IS NULL'
850+
} else if (parent.workspace_id) {
851+
sql += ' AND workspace_id = ?'
852+
values.push(parent.workspace_id)
853+
}
854+
}
855+
780856
sql += ' ORDER BY sort_order, created_at'
781857
return this._query(sql, values).map(r => this._rowToNode(r))
782858
}
@@ -790,6 +866,14 @@ class Database {
790866
let sql = "SELECT * FROM nodes WHERE (path = ? OR path LIKE ?) AND deleted_at IS NULL"
791867
const values = [pathPrefix, `${pathPrefix}/%`]
792868

869+
// Filter by workspace to ensure descendants match parent's workspace
870+
if (node.workspace_id === null) {
871+
sql += ' AND workspace_id IS NULL'
872+
} else if (node.workspace_id) {
873+
sql += ' AND workspace_id = ?'
874+
values.push(node.workspace_id)
875+
}
876+
793877
if (maxDepth !== null) {
794878
sql += ' AND depth <= ?'
795879
values.push(node.depth + maxDepth)
@@ -808,7 +892,7 @@ class Database {
808892

809893
const placeholders = ancestorIds.map(() => '?').join(', ')
810894
return this._query(
811-
`SELECT * FROM nodes WHERE id IN (${placeholders}) ORDER BY depth`,
895+
`SELECT * FROM nodes WHERE id IN (${placeholders}) AND deleted_at IS NULL ORDER BY depth`,
812896
ancestorIds
813897
).map(r => this._rowToNode(r))
814898
}
@@ -1101,6 +1185,37 @@ class Database {
11011185
[`%"${tag}"%`]
11021186
).map(r => this._rowToNode(r))
11031187
}
1188+
1189+
// Repair workspace_id for all descendants to match their root ancestor
1190+
repairWorkspaces() {
1191+
const roots = this._query('SELECT * FROM nodes WHERE parent_id IS NULL AND deleted_at IS NULL')
1192+
let fixed = 0
1193+
1194+
for (const root of roots) {
1195+
const rootWorkspace = root.workspace_id
1196+
// Get all descendants of this root
1197+
const pathPrefix = root.path ? `${root.path}/${root.id}` : `${root.id}`
1198+
const descendants = this._query(
1199+
"SELECT id, workspace_id FROM nodes WHERE (path = ? OR path LIKE ?) AND deleted_at IS NULL",
1200+
[pathPrefix, `${pathPrefix}/%`]
1201+
)
1202+
1203+
for (const desc of descendants) {
1204+
// Fix if workspace_id doesn't match root
1205+
const descWorkspace = desc.workspace_id
1206+
const needsFix = (rootWorkspace === null && descWorkspace !== null) ||
1207+
(rootWorkspace !== null && descWorkspace !== rootWorkspace)
1208+
if (needsFix) {
1209+
this._run('UPDATE nodes SET workspace_id = ? WHERE id = ?', [rootWorkspace, desc.id])
1210+
fixed++
1211+
}
1212+
}
1213+
}
1214+
1215+
console.log(`repairWorkspaces: fixed ${fixed} nodes`)
1216+
if (fixed > 0) this._save()
1217+
return { fixed }
1218+
}
11041219
}
11051220

11061221
module.exports = Database

electron/main.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ ipcMain.handle('db:getProjects', () => db.getProjects())
176176
ipcMain.handle('db:getInbox', () => db.getInbox())
177177
ipcMain.handle('db:getRecent', (event, limit, workspaceId) => db.getRecent(limit, workspaceId))
178178
ipcMain.handle('db:getFavorites', (event, workspaceId) => db.getFavorites(workspaceId))
179+
ipcMain.handle('db:getTasks', (event, params) => db.getTasks(params))
179180
ipcMain.handle('db:getChildren', (event, id, type) => db.getChildren(id, type))
180181
ipcMain.handle('db:getDescendants', (event, id, maxDepth) => db.getDescendants(id, maxDepth))
181182
ipcMain.handle('db:getAncestors', (event, id) => db.getAncestors(id))
@@ -228,6 +229,7 @@ ipcMain.handle('db:backup', (event, suffix) => db.backup(suffix))
228229
ipcMain.handle('db:listBackups', () => db.listBackups())
229230
ipcMain.handle('db:restoreBackup', (event, backupPath) => db.restoreBackup(backupPath))
230231
ipcMain.handle('db:reload', () => db.reload())
232+
ipcMain.handle('db:repairWorkspaces', () => db.repairWorkspaces())
231233

232234
// =========================================
233235
// SHELL

electron/preload.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
1515
getInbox: () => ipcRenderer.invoke('db:getInbox'),
1616
getRecent: (limit, workspaceId) => ipcRenderer.invoke('db:getRecent', limit, workspaceId),
1717
getFavorites: (workspaceId) => ipcRenderer.invoke('db:getFavorites', workspaceId),
18+
getTasks: (params) => ipcRenderer.invoke('db:getTasks', params),
1819
getChildren: (id, type) => ipcRenderer.invoke('db:getChildren', id, type),
1920
getDescendants: (id, maxDepth) => ipcRenderer.invoke('db:getDescendants', id, maxDepth),
2021
getAncestors: (id) => ipcRenderer.invoke('db:getAncestors', id),
@@ -63,6 +64,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
6364
listBackups: () => ipcRenderer.invoke('db:listBackups'),
6465
restoreBackup: (backupPath) => ipcRenderer.invoke('db:restoreBackup', backupPath),
6566
reload: () => ipcRenderer.invoke('db:reload'),
67+
repairWorkspaces: () => ipcRenderer.invoke('db:repairWorkspaces'),
6668

6769
// Shell
6870
openExternal: (url) => ipcRenderer.invoke('shell:openExternal', url),

0 commit comments

Comments
 (0)