|
|
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)
|
|
|
}
|
|
|
}
|