Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d541640c74 | |||
| 30b150a286 | |||
| 4a9bb32b5e | |||
| 2b9346c78b | |||
| 66a7b23f5a |
+159
-177
@@ -523,6 +523,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Alle" : {
|
||||
|
||||
},
|
||||
"Alle %lld Einträge anzeigen" : {
|
||||
"localizations" : {
|
||||
@@ -780,18 +783,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Anstehende Termine" : {
|
||||
"comment" : "TodayView – section title for upcoming reminders",
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Upcoming Events"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Anstehende Erinnerungen" : {
|
||||
"comment" : "TodayView – replaced by 'Anstehende Unternehmungen'",
|
||||
"extractionState" : "stale",
|
||||
@@ -815,6 +806,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Anstehende Termine" : {
|
||||
"comment" : "TodayView – section title for upcoming reminders",
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Upcoming Events"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Anstehende Unternehmungen" : {
|
||||
"comment" : "TodayView – section title for plannable moments (Treffen, Gespräch, Vorhaben) with upcoming reminder dates",
|
||||
"localizations" : {
|
||||
@@ -1529,6 +1532,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Dieser Moment wird unwiderruflich gelöscht." : {
|
||||
"comment" : "EditMomentView – delete confirmation message",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "This moment will be permanently deleted."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Distanzierter" : {
|
||||
"comment" : "RatingQuestion – negative pole for relationship closeness question",
|
||||
"extractionState" : "stale",
|
||||
@@ -2171,6 +2185,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Fällig am" : {
|
||||
"comment" : "AddTodoView – label for due date picker",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Due on"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Fällige Todos" : {
|
||||
"comment" : "TodayView – section header for due todos",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Due Todos"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Falscher Code" : {
|
||||
"comment" : "AppLockView / AppLockSetupView – wrong PIN error",
|
||||
"localizations" : {
|
||||
@@ -3463,6 +3499,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Moment löschen?" : {
|
||||
"comment" : "EditMomentView – delete confirmation dialog title",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Delete Moment?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Moment mit Kalendereintrag löschen?" : {
|
||||
"comment" : "EditMomentView – calendar delete confirmation dialog title",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Delete Moment with Calendar Event?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Moment…" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
@@ -3969,6 +4027,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Noch keine Todos." : {
|
||||
"comment" : "PersonDetailView – empty state message when person has no todos",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "No todos yet."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Noch keine Treffen bewertet" : {
|
||||
"comment" : "VisitHistorySection – empty state title",
|
||||
"extractionState" : "stale",
|
||||
@@ -4768,6 +4837,62 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Todo" : {
|
||||
"comment" : "PersonDetailView – button label to add a new Todo",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Todo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Todo abgeschlossen" : {
|
||||
"comment" : "LogEntryType.todoCompleted raw value label",
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Todo completed"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Todo anlegen" : {
|
||||
"comment" : "AddTodoView – navigation title",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Add Todo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Todo bearbeiten" : {
|
||||
"comment" : "EditTodoView – navigation title",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Edit Todo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Todos" : {
|
||||
"comment" : "PersonDetailView – section header for todos",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Todos"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Touch ID aktiviert" : {
|
||||
"comment" : "SettingsView – biometric label when Touch ID is active",
|
||||
"localizations" : {
|
||||
@@ -5032,6 +5157,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Verlauf & KI-Analyse" : {
|
||||
"comment" : "PersonDetailView – logbuch section header",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "History & AI Analysis"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Version" : {
|
||||
"comment" : "SettingsView – version info row label",
|
||||
"extractionState" : "stale",
|
||||
@@ -5308,6 +5444,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Was möchtest du erledigen?" : {
|
||||
"comment" : "AddTodoView – placeholder text for todo title input",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "What do you want to do?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Was war der Kern des Gesprächs?\nWas möchtest du nicht vergessen?" : {
|
||||
"comment" : "AddMomentView – text editor placeholder",
|
||||
"extractionState" : "stale",
|
||||
@@ -5750,171 +5897,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Todo" : {
|
||||
"comment" : "PersonDetailView – button label to add a new Todo",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Todo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Todo anlegen" : {
|
||||
"comment" : "AddTodoView – navigation title",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Add Todo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Dieser Moment wird unwiderruflich gelöscht." : {
|
||||
"comment" : "EditMomentView – delete confirmation message",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "This moment will be permanently deleted."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Moment löschen" : {
|
||||
"comment" : "EditMomentView – delete button label",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Delete Moment"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Moment löschen?" : {
|
||||
"comment" : "EditMomentView – delete confirmation dialog title",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Delete Moment?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Moment + Kalendereintrag löschen" : {
|
||||
"comment" : "EditMomentView – delete moment + calendar event option",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Delete Moment + Calendar Event"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Moment mit Kalendereintrag löschen?" : {
|
||||
"comment" : "EditMomentView – calendar delete confirmation dialog title",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Delete Moment with Calendar Event?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Nur Moment löschen" : {
|
||||
"comment" : "EditMomentView – delete only the moment, keep calendar event",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Delete Moment Only"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Todo bearbeiten" : {
|
||||
"comment" : "EditTodoView – navigation title",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Edit Todo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Todos" : {
|
||||
"comment" : "PersonDetailView – section header for todos",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Todos"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Was möchtest du erledigen?" : {
|
||||
"comment" : "AddTodoView – placeholder text for todo title input",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "What do you want to do?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Fällig am" : {
|
||||
"comment" : "AddTodoView – label for due date picker",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Due on"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Fällige Todos" : {
|
||||
"comment" : "TodayView – section header for due todos",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Due Todos"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Noch keine Todos." : {
|
||||
"comment" : "PersonDetailView – empty state message when person has no todos",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "No todos yet."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Speichern" : {
|
||||
"comment" : "AddTodoView – save button",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Save"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version" : "1.1"
|
||||
|
||||
@@ -299,12 +299,14 @@ enum LogEntryType: String, Codable {
|
||||
case nextStep = "Schritt abgeschlossen"
|
||||
case calendarEvent = "Termin geplant"
|
||||
case call = "Anruf"
|
||||
case todoCompleted = "Todo abgeschlossen"
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .nextStep: return "checkmark.circle.fill"
|
||||
case .calendarEvent: return "calendar.badge.checkmark"
|
||||
case .call: return "phone.circle.fill"
|
||||
case .todoCompleted: return "checkmark.square.fill"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,6 +315,7 @@ enum LogEntryType: String, Codable {
|
||||
case .nextStep: return "green"
|
||||
case .calendarEvent: return "blue"
|
||||
case .call: return "accent"
|
||||
case .todoCompleted: return "green"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ struct PersonDetailView: View {
|
||||
personHeader
|
||||
momentsSection
|
||||
todosSection
|
||||
if !person.sortedLogEntries.isEmpty { logbuchSection }
|
||||
if !person.sortedMoments.isEmpty || !person.sortedLogEntries.isEmpty { logbuchSection }
|
||||
if hasInfoContent { infoSection }
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
@@ -264,26 +264,52 @@ struct PersonDetailView: View {
|
||||
|
||||
private let logbuchPreviewLimit = 5
|
||||
|
||||
// Lokaler Hilfstyp für die gemischte Vorschau
|
||||
private struct LogPreviewItem: Identifiable {
|
||||
let id: String
|
||||
let icon: String
|
||||
let title: String
|
||||
let typeLabel: String
|
||||
let date: Date
|
||||
}
|
||||
|
||||
private var mergedLogPreview: [LogPreviewItem] {
|
||||
let momentItems = person.sortedMoments.map {
|
||||
LogPreviewItem(id: "m-\($0.id)", icon: $0.type.icon, title: $0.text,
|
||||
typeLabel: $0.type.displayName, date: $0.createdAt)
|
||||
}
|
||||
let entryItems = person.sortedLogEntries.map {
|
||||
LogPreviewItem(id: "e-\($0.id)", icon: $0.type.icon, title: $0.title,
|
||||
typeLabel: $0.type.rawValue, date: $0.loggedAt)
|
||||
}
|
||||
return (momentItems + entryItems).sorted { $0.date > $1.date }
|
||||
}
|
||||
|
||||
private var logbuchSection: some View {
|
||||
let entries = person.sortedLogEntries
|
||||
let preview = Array(entries.prefix(logbuchPreviewLimit))
|
||||
let hasMore = entries.count > logbuchPreviewLimit
|
||||
let allItems = mergedLogPreview
|
||||
let preview = Array(allItems.prefix(logbuchPreviewLimit))
|
||||
let hasMore = allItems.count > logbuchPreviewLimit
|
||||
|
||||
return VStack(alignment: .leading, spacing: 10) {
|
||||
HStack {
|
||||
SectionHeader(title: "Verlauf", icon: "book.closed")
|
||||
SectionHeader(title: "Verlauf & KI-Analyse", icon: "sparkles")
|
||||
Spacer()
|
||||
NavigationLink(destination: LogbuchView(person: person)) {
|
||||
Text("Alle")
|
||||
.font(.system(size: 13))
|
||||
.foregroundStyle(theme.accent)
|
||||
}
|
||||
}
|
||||
|
||||
VStack(spacing: 0) {
|
||||
ForEach(Array(preview.enumerated()), id: \.element.id) { index, entry in
|
||||
logEntryPreviewRow(entry)
|
||||
ForEach(Array(preview.enumerated()), id: \.element.id) { index, item in
|
||||
logPreviewRow(item)
|
||||
if index < preview.count - 1 || hasMore { RowDivider() }
|
||||
}
|
||||
if hasMore {
|
||||
NavigationLink(destination: LogbuchView(person: person)) {
|
||||
HStack {
|
||||
Text("Alle \(entries.count) Einträge anzeigen")
|
||||
Text("Alle \(allItems.count) Einträge anzeigen")
|
||||
.font(.system(size: 14))
|
||||
.foregroundStyle(theme.accent)
|
||||
Spacer()
|
||||
@@ -301,27 +327,28 @@ struct PersonDetailView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func logEntryPreviewRow(_ entry: LogEntry) -> some View {
|
||||
private func logPreviewRow(_ item: LogPreviewItem) -> some View {
|
||||
HStack(spacing: 12) {
|
||||
Image(systemName: entry.type.icon)
|
||||
Image(systemName: item.icon)
|
||||
.font(.system(size: 14, weight: .light))
|
||||
.foregroundStyle(theme.accent)
|
||||
.frame(width: 20)
|
||||
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
Text(entry.title)
|
||||
Text(item.title)
|
||||
.font(.system(size: 15, design: theme.displayDesign))
|
||||
.foregroundStyle(theme.contentPrimary)
|
||||
.lineLimit(2)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
HStack(spacing: 6) {
|
||||
Text(LocalizedStringKey(entry.type.rawValue))
|
||||
Text(LocalizedStringKey(item.typeLabel))
|
||||
.font(.system(size: 12))
|
||||
.foregroundStyle(theme.contentTertiary)
|
||||
Text("·")
|
||||
.font(.system(size: 12))
|
||||
.foregroundStyle(theme.contentTertiary)
|
||||
Text(entry.loggedAt.formatted(.dateTime.day().month(.abbreviated).year()))
|
||||
Text(item.date.formatted(.dateTime.day().month(.abbreviated).year()))
|
||||
.font(.system(size: 12))
|
||||
.foregroundStyle(theme.contentTertiary)
|
||||
}
|
||||
@@ -472,6 +499,11 @@ struct PersonDetailView: View {
|
||||
UNUserNotificationCenter.current()
|
||||
.removePendingNotificationRequests(withIdentifiers: ["todo-\(todo.id)"])
|
||||
|
||||
// Logbuch-Eintrag erstellen
|
||||
let entry = LogEntry(type: .todoCompleted, title: todo.title, person: person)
|
||||
modelContext.insert(entry)
|
||||
person.logEntries?.append(entry)
|
||||
|
||||
// Nach 5 Sek. sanft ausblenden
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
|
||||
withAnimation(.easeOut(duration: 0.35)) {
|
||||
|
||||
@@ -372,6 +372,11 @@ struct TodayView: View {
|
||||
UNUserNotificationCenter.current()
|
||||
.removePendingNotificationRequests(withIdentifiers: ["todo-\(todo.id)"])
|
||||
|
||||
// Logbuch-Eintrag erstellen
|
||||
let entry = LogEntry(type: .todoCompleted, title: todo.title, person: todo.person)
|
||||
modelContext.insert(entry)
|
||||
todo.person?.logEntries?.append(entry)
|
||||
|
||||
do {
|
||||
try modelContext.save()
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user