feat(ui): open list items counter on Listen, none on Workspace

New stat listItemsOpenCount: unchecked items in all lists the user sees
(own, shared directly or through a group), shown as accent counter on
Listen in the header and the mobile bottom bar. Workspace loses its
counter. Stats refresh on list events and after deleting an item.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-29 12:43:56 +01:00
1 parent 6d720c0c36
commit c4fc455850
8 files changed
+75 -19

No files matched your search

+1
View File
@@ -520,6 +520,7 @@ type DashboardStats struct {
DocsCount int `json:"docsCount"`
DocsTodosOpenCount int `json:"docsTodosOpenCount"`
ListsCount int `json:"listsCount"`
ListItemsOpenCount int `json:"listItemsOpenCount"` // unchecked items in all lists the user sees
DiscardedCount int `json:"discardedCount"`
}
+8 -5
View File
@@ -18,15 +18,18 @@ func (s *Store) GetDashboardStats(userID string) (*domain.DashboardStats, error)
_ = s.db.QueryRow(`SELECT COUNT(*) FROM thoughts WHERE `+taskScope+` AND is_completed = 1`, userID, userID, userID, userID).Scan(&st.TasksDoneCount)
_ = 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(DISTINCT l.id)
FROM lists l
// Own lists and lists shared with the user directly or through a group
visibleLists := `
SELECT l.id
FROM lists l
LEFT JOIN list_shares ls ON ls.list_id = l.id AND (
(ls.target_type = 'user' AND ls.target_id = ?)
OR (ls.target_type = 'team' AND ls.target_id IN (SELECT team_id FROM team_members WHERE user_id = ?))
)
WHERE l.user_id = ? OR ls.id IS NOT NULL`,
userID, userID, userID).Scan(&st.ListsCount)
WHERE l.user_id = ? OR ls.id IS NOT NULL`
_ = s.db.QueryRow(`SELECT COUNT(DISTINCT id) FROM (`+visibleLists+`)`, userID, userID, userID).Scan(&st.ListsCount)
_ = s.db.QueryRow(`SELECT COUNT(*) FROM list_items WHERE is_completed = 0 AND list_id IN (`+visibleLists+`)`,
userID, userID, userID).Scan(&st.ListItemsOpenCount)
// Count open (uncompleted) To-Dos across accessible workspace documents (owned or shared)
docRows, err := s.db.Query(`
+49
View File
@@ -0,0 +1,49 @@
package storage
import (
"path/filepath"
"testing"
)
func TestOpenListItemsCountCoversSharedLists(t *testing.T) {
store, err := NewStore(filepath.Join(t.TempDir(), "stats.db"))
if err != nil {
t.Fatalf("NewStore: %v", err)
}
defer store.Close()
openSharing(t, store)
anna, _ := store.CreateUser("anna@example.com", "hash", "Anna", "member")
ben, _ := store.CreateUser("ben@example.com", "hash", "Ben", "member")
carla, _ := store.CreateUser("carla@example.com", "hash", "Carla", "member")
addItems := func(owner, listID string, open, done int) {
for i := 0; i < open+done; i++ {
item, _ := store.CreateListItem(listID, "Eintrag", "", "", i, owner)
if i >= open {
_, _ = store.ToggleListItem(item.ID, true, owner, "")
}
}
}
own, _ := store.CreateList(anna.ID, "Einkauf", "🛒", "shopping")
addItems(anna.ID, own.ID, 2, 1)
direct, _ := store.CreateList(ben.ID, "Bens Liste", "", "shopping")
addItems(ben.ID, direct.ID, 1, 2)
if _, err := store.AddListShare(ben.ID, direct.ID, "user", anna.ID, "viewer"); err != nil {
t.Fatalf("share: %v", err)
}
viaGroup, _ := store.CreateList(carla.ID, "WG-Putzplan", "", "shopping")
addItems(carla.ID, viaGroup.ID, 3, 0)
team, _ := store.CreateTeam("", "WG", "")
_ = store.AddTeamMember(team.ID, anna.ID, "member")
_ = store.AddTeamMember(team.ID, carla.ID, "admin")
if _, err := store.AddListShare(carla.ID, viaGroup.ID, "team", team.ID, "editor"); err != nil {
t.Fatalf("team share: %v", err)
}
foreign, _ := store.CreateList(ben.ID, "Privat", "", "shopping")
addItems(ben.ID, foreign.ID, 5, 0)
st, _ := store.GetDashboardStats(anna.ID)
if st.ListItemsOpenCount != 6 || st.ListsCount != 3 {
t.Errorf("want 6 open items in 3 lists (own 2 + direct 1 + group 3), got %d in %d", st.ListItemsOpenCount, st.ListsCount)
}
}