diff --git a/MindDump/Models/TodoFilter.swift b/MindDump/Models/TodoFilter.swift index a62e983..f42acfe 100644 --- a/MindDump/Models/TodoFilter.swift +++ b/MindDump/Models/TodoFilter.swift @@ -2,28 +2,4 @@ import Foundation enum TodoFilter: Hashable { case open, urgent, due - - var title: String { - switch self { - case .open: "Offene Aufgaben" - case .urgent: "Dringende Aufgaben" - case .due: "Fällige Aufgaben" - } - } - - var emptyMessage: String { - switch self { - case .open: "Keine offenen Aufgaben" - case .urgent: "Keine dringenden Aufgaben" - case .due: "Keine fälligen Aufgaben" - } - } - - var emptyIcon: String { - switch self { - case .open: "checkmark.circle" - case .urgent: "exclamationmark.triangle" - case .due: "calendar.badge.checkmark" - } - } } diff --git a/MindDump/ViewModels/ListStore.swift b/MindDump/ViewModels/ListStore.swift index ccdfce0..83790b1 100644 --- a/MindDump/ViewModels/ListStore.swift +++ b/MindDump/ViewModels/ListStore.swift @@ -19,29 +19,21 @@ class ListStore { var openCount: Int { allOpenItems.count } var urgentItems: [TodoItem] { - let threeDaysFromNow = Calendar.current.date(byAdding: .day, value: 3, to: Date())! - return allOpenItems.filter { item in - item.priority == .high || (item.deadline != nil && item.deadline! <= threeDaysFromNow) - } + allOpenItems.filter { isUrgent($0) } } var urgentCount: Int { urgentItems.count } var dueItems: [TodoItem] { - let startOfTomorrow = Calendar.current.startOfDay(for: Calendar.current.date(byAdding: .day, value: 1, to: Date())!) - return allOpenItems.filter { item in - item.deadline != nil && item.deadline! < startOfTomorrow - } + allOpenItems.filter { isDue($0) } } var dueCount: Int { dueItems.count } - func items(for filter: TodoFilter) -> [TodoItem] { - switch filter { - case .open: allOpenItems - case .urgent: urgentItems - case .due: dueItems - } + var allCompletedItems: [TodoItem] { + lists.flatMap { $0.items } + .filter { $0.isCompleted } + .sorted { ($0.modifiedAt ?? $0.createdAt) > ($1.modifiedAt ?? $1.createdAt) } } init(modelContext: ModelContext) { @@ -128,21 +120,14 @@ class ListStore { deleteItem(item.id, from: listID) } - func filteredOpenItems(for list: TodoList, urgent: Bool, due: Bool, sort: ListDetailSort) -> [TodoItem] { - var items = list.openItems + func filteredOpenItems(for list: TodoList? = nil, urgent: Bool, due: Bool, sort: ListDetailSort) -> [TodoItem] { + var items = list?.openItems ?? allOpenItems if urgent || due { - let now = Date() - let threeDaysFromNow = Calendar.current.date(byAdding: .day, value: 3, to: now)! - let startOfTomorrow = Calendar.current.startOfDay(for: Calendar.current.date(byAdding: .day, value: 1, to: now)!) - items = items.filter { item in - let isUrgent = item.priority == .high || (item.deadline != nil && item.deadline! <= threeDaysFromNow) - let isDue = item.deadline != nil && item.deadline! < startOfTomorrow - - if urgent && due { return isUrgent && isDue } - if urgent { return isUrgent } - return isDue + if urgent && due { return isUrgent(item) && isDue(item) } + if urgent { return isUrgent(item) } + return isDue(item) } } @@ -203,6 +188,16 @@ class ListStore { lists = inbox + rest } + private func isUrgent(_ item: TodoItem) -> Bool { + let threeDaysFromNow = Calendar.current.date(byAdding: .day, value: 3, to: Date())! + return item.priority == .high || (item.deadline != nil && item.deadline! <= threeDaysFromNow) + } + + private func isDue(_ item: TodoItem) -> Bool { + let startOfTomorrow = Calendar.current.startOfDay(for: Calendar.current.date(byAdding: .day, value: 1, to: Date())!) + return item.deadline != nil && item.deadline! < startOfTomorrow + } + private func save() { try? modelContext.save() } diff --git a/MindDump/Views/ListDetailView.swift b/MindDump/Views/ListDetailView.swift index c6173ae..412f1df 100644 --- a/MindDump/Views/ListDetailView.swift +++ b/MindDump/Views/ListDetailView.swift @@ -26,18 +26,11 @@ struct ListDetailView: View { List { Section { - HStack(spacing: 12) { - chipButton("Dringend", icon: "exclamationmark.triangle", isActive: filterUrgent, color: .orange) { - withAnimation { filterUrgent.toggle() } - } - chipButton("Fällig", icon: "calendar.badge.clock", isActive: filterDue, color: .red) { - withAnimation { filterDue.toggle() } - } - sortMenu - } - .frame(maxWidth: .infinity) - .listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) - .listRowBackground(Color.clear) + FilterChipBar( + filterUrgent: $filterUrgent, + filterDue: $filterDue, + selectedSort: $selectedSort + ) } Section { @@ -108,46 +101,6 @@ struct ListDetailView: View { } } - // MARK: - Filter Chip - - @ViewBuilder - private func chipButton(_ label: String, icon: String, isActive: Bool, color: Color, action: @escaping () -> Void) -> some View { - Button(action: action) { - Image(systemName: icon) - .font(.subheadline.weight(.medium)) - .padding(.horizontal, 12) - .padding(.vertical, 6) - .background(isActive ? color.opacity(0.15) : Color(.systemGray6)) - .foregroundStyle(isActive ? color : .secondary) - .clipShape(Capsule()) - } - .buttonStyle(.plain) - } - - // MARK: - Sort Menu - - private var sortMenu: some View { - Menu { - Picker("Sortierung", selection: $selectedSort) { - ForEach(ListDetailSort.allCases, id: \.self) { sort in - Text(sort.label).tag(sort) - } - } - } label: { - HStack(spacing: 4) { - Image(systemName: "arrow.up.arrow.down") - ZStack(alignment: .leading) { - ForEach(ListDetailSort.allCases, id: \.self) { sort in - Text(sort.shortLabel) - .opacity(sort == selectedSort ? 1 : 0) - } - } - } - .font(.subheadline) - .foregroundStyle(.secondary) - } - } - // MARK: - Empty State private var emptyState: some View { diff --git a/MindDump/Views/TodoListScreen.swift b/MindDump/Views/TodoListScreen.swift index 296c1fc..4232ccd 100644 --- a/MindDump/Views/TodoListScreen.swift +++ b/MindDump/Views/TodoListScreen.swift @@ -5,37 +5,96 @@ struct TodoListScreen: View { let filter: TodoFilter @State private var editorItem: TodoItem? @State private var moveItem: TodoItem? + @State private var showCompleted = true + @State private var filterUrgent: Bool + @State private var filterDue: Bool + @State private var selectedSort: ListDetailSort = .oldest - private var items: [TodoItem] { - store.items(for: filter) + init(filter: TodoFilter) { + self.filter = filter + _filterUrgent = State(initialValue: filter == .urgent) + _filterDue = State(initialValue: filter == .due) } var body: some View { Group { - if items.isEmpty { - ContentUnavailableView(filter.emptyMessage, systemImage: filter.emptyIcon) + let filteredItems = store.filteredOpenItems( + urgent: filterUrgent, due: filterDue, sort: selectedSort + ) + + if store.allOpenItems.isEmpty && store.allCompletedItems.isEmpty { + ContentUnavailableView("Keine Einträge", systemImage: "tray") } else { List { - TodoListView( - items: items, - showListName: true, - onToggle: { item in - store.toggleItemCompleted(item) - }, - onTap: { item in - editorItem = item - }, - onDelete: { item in - store.deleteItem(item) - }, - onMove: { item in - moveItem = item + Section { + FilterChipBar( + filterUrgent: $filterUrgent, + filterDue: $filterDue, + selectedSort: $selectedSort + ) + } + + Section { + if filteredItems.isEmpty { + emptyState + } else { + TodoListView( + items: filteredItems, + showListName: true, + onToggle: { item in + store.toggleItemCompleted(item) + }, + onTap: { item in + editorItem = item + }, + onDelete: { item in + store.deleteItem(item) + }, + onMove: { item in + moveItem = item + } + ) } - ) + } + + if !store.allCompletedItems.isEmpty { + Section { + if showCompleted { + TodoListView( + items: store.allCompletedItems, + showListName: true, + onToggle: { item in + store.toggleItemCompleted(item) + }, + onTap: { item in + editorItem = item + }, + onDelete: { item in + store.deleteItem(item) + }, + onMove: { item in + moveItem = item + } + ) + } + } header: { + Button { + withAnimation { + showCompleted.toggle() + } + } label: { + HStack { + Text("Erledigt (\(store.allCompletedItems.count))") + Spacer() + Image(systemName: showCompleted ? "chevron.down" : "chevron.right") + } + } + } + } } } } - .navigationTitle(filter.title) + .navigationTitle("Aufgaben") .sheet(item: $editorItem) { item in if let listID = item.list?.id { TodoEditorView(listID: listID, item: item) @@ -47,4 +106,42 @@ struct TodoListScreen: View { } } } + + // MARK: - Empty State + + private var emptyState: some View { + VStack(spacing: 8) { + Image(systemName: emptyIcon) + .font(.title2) + .foregroundStyle(.secondary) + Text(emptyMessage) + .font(.subheadline) + .foregroundStyle(.secondary) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 24) + .listRowBackground(Color.clear) + } + + private var emptyMessage: String { + if filterUrgent && filterDue { + "Keine dringenden oder fälligen Aufgaben" + } else if filterUrgent { + "Keine dringenden Aufgaben" + } else if filterDue { + "Keine fälligen Aufgaben" + } else { + "Keine offenen Aufgaben" + } + } + + private var emptyIcon: String { + if filterUrgent { + "exclamationmark.triangle" + } else if filterDue { + "calendar.badge.checkmark" + } else { + "checkmark.circle" + } + } }