import SwiftUI struct PersonsView: View { @EnvironmentObject var store: Store @State private var newName = "" @FocusState private var newFocused: Bool private let nameWidth: CGFloat = 220 private let genderWidth: CGFloat = 120 private let noteWidth: CGFloat = 180 private let checkWidth: CGFloat = 32 var body: some View { VStack(alignment: .leading, spacing: 0) { HStack(spacing: 10) { TextField("Name der neuen Person", text: $newName) .textFieldStyle(.roundedBorder) .frame(width: 260) .focused($newFocused) .onSubmit(add) Button("Hinzufügen", action: add).buttonStyle(.borderedProminent) .disabled(newName.trimmingCharacters(in: .whitespaces).isEmpty) Text("Häkchen = darf diese Aufgabe übernehmen. Wer mindestens eine Diener-Aufgabe hat, erscheint oben in der Übersicht, wer eine Schulungsaufgabe hat, unten.") .font(.caption).foregroundStyle(Theme.muted) .fixedSize(horizontal: false, vertical: true) } .padding(16) ScrollView([.horizontal, .vertical]) { VStack(alignment: .leading, spacing: 0) { headerRow ForEach(store.personsSorted) { p in if let binding = store.binding(person: p.id) { PersonRow(person: binding, widths: (nameWidth, genderWidth, noteWidth, checkWidth)) } } } .padding([.horizontal, .bottom], 16) } } } private var headerRow: some View { HStack(alignment: .bottom, spacing: 0) { Text("Name").frame(width: nameWidth, alignment: .leading).padding(.leading, 4) Text("Bruder/Schwester").frame(width: genderWidth, alignment: .leading) Text("Notiz").frame(width: noteWidth, alignment: .leading) ForEach(store.db.tasks) { t in Text(t.name) .lineLimit(1) .fixedSize() .rotationEffect(.degrees(-90)) .frame(width: checkWidth, height: 170, alignment: .center) .background(Theme.soft(for: t.group)) .help(t.name) } Spacer().frame(width: 80) } .font(.caption.weight(.semibold)) .foregroundStyle(Theme.muted) .padding(.bottom, 6) .overlay(alignment: .bottom) { Rectangle().fill(Theme.line).frame(height: 1) } } private func add() { if store.addPerson(named: newName) { newName = "" } else { NSSound.beep() } newFocused = true } } struct PersonRow: View { @Binding var person: Person let widths: (name: CGFloat, gender: CGFloat, note: CGFloat, check: CGFloat) @EnvironmentObject var store: Store @State private var name = "" @FocusState private var focused: Bool var body: some View { HStack(spacing: 0) { TextField("Name", text: $name) .textFieldStyle(.roundedBorder) .frame(width: widths.name - 12) .padding(.trailing, 12) .onAppear { name = person.name } .focused($focused) .onSubmit(commitName) .onChange(of: focused) { _, now in if !now { commitName() } } .onDisappear(perform: commitName) Picker("", selection: $person.gender) { Text("–").tag("") Text("Bruder").tag("Bruder") Text("Schwester").tag("Schwester") } .labelsHidden() .frame(width: widths.gender - 12) .padding(.trailing, 12) TextField("", text: $person.note) .textFieldStyle(.roundedBorder) .frame(width: widths.note - 12) .padding(.trailing, 12) ForEach(store.db.tasks) { t in Toggle("", isOn: Binding( get: { person.tasks.contains(t.id) }, set: { on in if on { if !person.tasks.contains(t.id) { person.tasks.append(t.id) } } else { person.tasks.removeAll { $0 == t.id } } })) .toggleStyle(.checkbox) .labelsHidden() .frame(width: widths.check) .help(t.name) } Button("Löschen", role: .destructive, action: delete) .buttonStyle(.link) .foregroundStyle(Theme.bengara) .frame(width: 80) } .padding(.vertical, 5) .overlay(alignment: .bottom) { Rectangle().fill(Theme.line).frame(height: 1) } } private func commitName() { let n = name.trimmingCharacters(in: .whitespaces) guard n != person.name else { return } if n.isEmpty || store.db.persons.contains(where: { $0.id != person.id && $0.name.lowercased() == n.lowercased() }) { NSSound.beep(); name = person.name; return } person.name = n } private func delete() { let count = store.entryCount(person: person.id) let alert = NSAlert() alert.messageText = "\(person.name) löschen?" alert.informativeText = count > 0 ? "\(count) Einträge im Verlauf gehören zu dieser Person und werden ebenfalls gelöscht." : "Die Person hat noch keine Einträge im Verlauf." alert.addButton(withTitle: "Löschen").hasDestructiveAction = true alert.addButton(withTitle: "Abbrechen") guard alert.runModal() == .alertFirstButtonReturn else { return } store.deletePerson(person.id) } }