diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 842adf9..f27f611 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -102,6 +102,7 @@ export const App: React.FC = () => { tasksOpenCount: 0, tasksDoneCount: 0, docsCount: 0, + docsTodosOpenCount: 0, listsCount: 0, discardedCount: 0, }); @@ -478,18 +479,14 @@ export const App: React.FC = () => { className={`tab-btn ${activeTab === 'triage' ? 'tab-btn-active' : ''}`} > - Triage - {stats.inboxCount > 0 && ( - - {stats.inboxCount} - - )} + 0 ? 'pr-2' : ''}`}> + Triage + {stats.inboxCount > 0 && ( + + {stats.inboxCount} + + )} + diff --git a/frontend/src/features/workspace/WorkspaceView.tsx b/frontend/src/features/workspace/WorkspaceView.tsx index 486345f..2c26d0a 100644 --- a/frontend/src/features/workspace/WorkspaceView.tsx +++ b/frontend/src/features/workspace/WorkspaceView.tsx @@ -260,6 +260,7 @@ export const WorkspaceView: React.FC = ({ } : prev ); + onStatsChange(); } catch (e: any) { if (e.message && e.message.includes('423')) { api.getDocLock(docToSave.id).then((res) => { @@ -2162,9 +2163,15 @@ export const WorkspaceView: React.FC = ({ type="checkbox" disabled={isReadOnly} checked={!!block.checked} - onChange={(e) => - !isReadOnly && updateBlock(block.id, { checked: e.target.checked }) - } + onChange={(e) => { + if (!isReadOnly && selectedDoc) { + const nextBlocks = selectedDoc.blocks.map((b) => + b.id === block.id ? { ...b, checked: e.target.checked } : b + ); + persistDocument({ ...selectedDoc, blocks: nextBlocks }, true); + onStatsChange(); + } + }} className={`w-4 h-4 checkbox-square flex-shrink-0 ${ isReadOnly ? 'cursor-not-allowed opacity-60' : 'cursor-pointer' }`} diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index 6e2da2b..ea5ebc5 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -233,6 +233,7 @@ export interface DashboardStats { tasksOpenCount: number; tasksDoneCount: number; docsCount: number; + docsTodosOpenCount?: number; listsCount: number; discardedCount: number; } diff --git a/internal/domain/models.go b/internal/domain/models.go index ce2563b..5967949 100644 --- a/internal/domain/models.go +++ b/internal/domain/models.go @@ -330,12 +330,13 @@ type WSMessage struct { } type DashboardStats struct { - InboxCount int `json:"inboxCount"` - TasksOpenCount int `json:"tasksOpenCount"` - TasksDoneCount int `json:"tasksDoneCount"` - DocsCount int `json:"docsCount"` - ListsCount int `json:"listsCount"` - DiscardedCount int `json:"discardedCount"` + InboxCount int `json:"inboxCount"` + TasksOpenCount int `json:"tasksOpenCount"` + TasksDoneCount int `json:"tasksDoneCount"` + DocsCount int `json:"docsCount"` + DocsTodosOpenCount int `json:"docsTodosOpenCount"` + ListsCount int `json:"listsCount"` + DiscardedCount int `json:"discardedCount"` } type FullExport struct { diff --git a/internal/storage/sqlite.go b/internal/storage/sqlite.go index b696e2e..29f885f 100644 --- a/internal/storage/sqlite.go +++ b/internal/storage/sqlite.go @@ -1910,6 +1910,31 @@ func (s *Store) GetDashboardStats(userID string) (*domain.DashboardStats, error) _ = s.db.QueryRow(`SELECT COUNT(*) FROM documents WHERE user_id = ? AND is_archived = 0`, userID).Scan(&st.DocsCount) _ = s.db.QueryRow(`SELECT COUNT(*) FROM thoughts WHERE user_id = ? AND status = 'discarded'`, userID).Scan(&st.DiscardedCount) _ = s.db.QueryRow(`SELECT COUNT(*) FROM lists l JOIN space_members sm ON sm.space_id = l.space_id WHERE sm.user_id = ?`, userID).Scan(&st.ListsCount) + + // Count open (uncompleted) To-Dos across accessible workspace documents (owned or shared) + docRows, err := s.db.Query(` + SELECT blocks_json FROM documents + WHERE (user_id = ? OR id IN (SELECT doc_id FROM doc_shares WHERE user_id = ?)) + AND is_archived = 0`, + userID, userID, + ) + if err == nil { + for docRows.Next() { + var bj string + if err := docRows.Scan(&bj); err == nil && bj != "" { + var blocks []domain.DocumentBlock + if err := json.Unmarshal([]byte(bj), &blocks); err == nil { + for _, b := range blocks { + if b.Type == "todo" && !b.Checked { + st.DocsTodosOpenCount++ + } + } + } + } + } + _ = docRows.Close() + } + return st, nil }