189 lines
7.2 KiB
Swift
189 lines
7.2 KiB
Swift
import SwiftUI
|
|
|
|
struct ListDetailView: View {
|
|
@Environment(ListStore.self) private var store
|
|
let listID: UUID
|
|
@State private var editorItem: TodoItem?
|
|
@State private var moveItem: TodoItem?
|
|
@State private var showCompleted = true
|
|
@State private var filterUrgent = false
|
|
@State private var filterDue = false
|
|
@State private var selectedSort: ListDetailSort = .oldest
|
|
|
|
private var todoList: TodoList? {
|
|
store.lists.first { $0.id == listID }
|
|
}
|
|
|
|
var body: some View {
|
|
Group {
|
|
if let todoList {
|
|
if todoList.items.isEmpty {
|
|
ContentUnavailableView("Keine Einträge", systemImage: "tray")
|
|
} else {
|
|
let filteredItems = store.filteredOpenItems(
|
|
for: todoList, urgent: filterUrgent, due: filterDue, sort: selectedSort
|
|
)
|
|
|
|
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)
|
|
}
|
|
|
|
Section {
|
|
if filteredItems.isEmpty {
|
|
emptyState
|
|
} else {
|
|
TodoListView(
|
|
items: filteredItems,
|
|
onToggle: { item in
|
|
store.toggleItemCompleted(item.id, in: listID)
|
|
},
|
|
onTap: { item in
|
|
editorItem = item
|
|
},
|
|
onDelete: { item in
|
|
store.deleteItem(item.id, from: listID)
|
|
},
|
|
onMove: { item in
|
|
moveItem = item
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
if !todoList.completedItems.isEmpty {
|
|
Section {
|
|
if showCompleted {
|
|
TodoListView(
|
|
items: todoList.completedItems,
|
|
onToggle: { item in
|
|
store.toggleItemCompleted(item.id, in: listID)
|
|
},
|
|
onTap: { item in
|
|
editorItem = item
|
|
},
|
|
onDelete: { item in
|
|
store.deleteItem(item.id, from: listID)
|
|
},
|
|
onMove: { item in
|
|
moveItem = item
|
|
}
|
|
)
|
|
}
|
|
} header: {
|
|
Button {
|
|
withAnimation {
|
|
showCompleted.toggle()
|
|
}
|
|
} label: {
|
|
HStack {
|
|
Text("Erledigt (\(todoList.completedItems.count))")
|
|
Spacer()
|
|
Image(systemName: showCompleted ? "chevron.down" : "chevron.right")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle(todoList?.name ?? "")
|
|
.sheet(item: $editorItem) { item in
|
|
TodoEditorView(listID: listID, item: item)
|
|
}
|
|
.sheet(item: $moveItem) { item in
|
|
MoveToListView(itemID: item.id, currentListID: listID)
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
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"
|
|
}
|
|
}
|
|
}
|