diff --git a/CalenTask/DesignSystem/Components/WorkspaceScopePicker.swift b/CalenTask/DesignSystem/Components/WorkspaceScopePicker.swift index 75aa6fa..fa84571 100644 --- a/CalenTask/DesignSystem/Components/WorkspaceScopePicker.swift +++ b/CalenTask/DesignSystem/Components/WorkspaceScopePicker.swift @@ -165,7 +165,7 @@ struct WorkspaceScopePicker: View { .buttonStyle(.plain) .dsHoverHighlight() .popover(isPresented: $showsSwitcher, arrowEdge: .bottom) { - OpenCountsReader { counts, total in + OpenCountsReader(workspaceIDs: workspaces.map(\.id)) { counts, total in switcherPopover(counts: counts, total: total) } } @@ -299,16 +299,23 @@ struct WorkspaceScopePicker: View { .modelContainer(PreviewSampleData.make().container) } -/// #5 — I contatori per spazio servono solo a popover aperto: la query di -/// tutte le attività aperte vive qui, non in ogni selettore montato. +/// #5 — I contatori per spazio servono solo a popover aperto e si calcolano +/// con `fetchCount` nello store, senza caricare le attività. private struct OpenCountsReader: View { - @Query(filter: TodoTask.openPredicate) private var openTasks: [TodoTask] + @Environment(\.modelContext) private var modelContext + let workspaceIDs: [UUID] let content: (_ byWorkspace: [UUID: Int], _ total: Int) -> Content var body: some View { - content( - openTasks.reduce(into: [:]) { $0[$1.workspaceID, default: 0] += 1 }, - openTasks.count - ) + let total = count(TodoTask.openPredicate) + var byWorkspace: [UUID: Int] = [:] + for id in workspaceIDs { + byWorkspace[id] = count(TodoTask.openPredicate(workspaceID: id)) + } + return content(byWorkspace, total) + } + + private func count(_ predicate: Predicate) -> Int { + (try? modelContext.fetchCount(FetchDescriptor(predicate: predicate))) ?? 0 } } diff --git a/CalenTask/Features/Team/TeamView.swift b/CalenTask/Features/Team/TeamView.swift index 4661d21..4954c4d 100644 --- a/CalenTask/Features/Team/TeamView.swift +++ b/CalenTask/Features/Team/TeamView.swift @@ -11,14 +11,10 @@ struct TeamView: View { sort: \UserProfile.createdAt) private var people: [UserProfile] - @Query(filter: TodoTask.openPredicate) - private var openTasks: [TodoTask] - - /// Attività aperte per persona, contate in un solo passaggio. - private var loadByPerson: [UUID: Int] { - openTasks.reduce(into: [:]) { counts, task in - if let id = task.assigneeID { counts[id, default: 0] += 1 } - } + /// Attività aperte di una persona, contate nello store. + private func load(for person: UserProfile) -> Int { + let predicate = TodoTask.openAssignedPredicate(assigneeID: person.id) + return (try? modelContext.fetchCount(FetchDescriptor(predicate: predicate))) ?? 0 } @State private var newName = "" @@ -26,7 +22,6 @@ struct TeamView: View { var body: some View { NavigationStack { - let load = loadByPerson List { ForEach(people, id: \.id) { person in HStack(spacing: DS.m) { @@ -41,7 +36,8 @@ struct TeamView: View { } } Spacer() - if let count = load[person.id], count > 0 { + let count = load(for: person) + if count > 0 { Text("\(count) attività") .font(.dsNumeric) .foregroundStyle(.secondary) diff --git a/CalenTask/Models/TodoTask+Predicates.swift b/CalenTask/Models/TodoTask+Predicates.swift index ed05f19..a0efe3b 100644 --- a/CalenTask/Models/TodoTask+Predicates.swift +++ b/CalenTask/Models/TodoTask+Predicates.swift @@ -26,6 +26,16 @@ extension TodoTask { } } + /// Attività aperte assegnate a una persona (per i contatori di carico). + static func openAssignedPredicate(assigneeID: UUID) -> Predicate { + let doneRaw = TaskStatus.done.rawValue + let phaseRaw = TaskKind.phase.rawValue + return #Predicate { task in + task.assigneeID == assigneeID && task.deletedAt == nil + && task.statusRaw != doneRaw && task.kindRaw != phaseRaw && !task.isTemplate + } + } + static func inboxPredicate(workspaceID: UUID?) -> Predicate { let doneRaw = TaskStatus.done.rawValue let phaseRaw = TaskKind.phase.rawValue