import SwiftUI import PhotosUI struct AddDiaryEntrySheet: View { let dogId: Int let onSaved: () async -> Void @Environment(\.dismiss) private var dismiss @State private var titel = "" @State private var text = "" @State private var date = Date() @State private var isMilestone = false @State private var locationName = "" @State private var tagsInput = "" @State private var photoSelection: [PhotosPickerItem] = [] @State private var photoData: [Data] = [] @State private var saveState: SaveState = .idle @State private var errorMessage: String? private enum SaveState: Equatable { case idle case savingEntry case uploadingMedia(done: Int, total: Int) } var body: some View { NavigationStack { Form { Section("Titel") { TextField("z. B. Erster Strandbesuch", text: $titel) } Section("Text") { TextField("Was hat dein Hund heute erlebt?", text: $text, axis: .vertical) .lineLimit(4...10) } Section("Datum") { DatePicker("Datum", selection: $date, displayedComponents: .date) .environment(\.locale, Locale(identifier: "de_DE")) } Section { Toggle("Meilenstein", isOn: $isMilestone) TextField("Ort (optional)", text: $locationName) TextField("Tags (komma-getrennt)", text: $tagsInput) .textInputAutocapitalization(.never) .autocorrectionDisabled() } Section { PhotosPicker( selection: $photoSelection, maxSelectionCount: 6, matching: .images ) { Label(photoData.isEmpty ? "Fotos hinzufügen" : "Fotos ändern", systemImage: "photo.badge.plus") } if !photoData.isEmpty { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { ForEach(Array(photoData.enumerated()), id: \.offset) { _, d in if let img = UIImage(data: d) { Image(uiImage: img) .resizable() .scaledToFill() .frame(width: 80, height: 80) .clipShape(RoundedRectangle(cornerRadius: 8)) } } } } } } header: { Text(photoData.isEmpty ? "Fotos" : "Fotos (\(photoData.count))") } if let errorMessage { Section { Text(errorMessage).font(.footnote).foregroundStyle(.red) } } } .navigationTitle("Neuer Eintrag") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Abbrechen") { dismiss() } .disabled(saveState != .idle) } ToolbarItem(placement: .confirmationAction) { saveToolbarItem } } .onChange(of: photoSelection) { _, items in Task { await loadPhotos(from: items) } } .interactiveDismissDisabled(saveState != .idle) } } @ViewBuilder private var saveToolbarItem: some View { switch saveState { case .idle: Button("Sichern") { Task { await save() } } .disabled(titel.trimmingCharacters(in: .whitespaces).isEmpty && text.trimmingCharacters(in: .whitespaces).isEmpty) case .savingEntry: ProgressView() case .uploadingMedia(let done, let total): Text("\(done)/\(total)").font(.caption.monospacedDigit()) } } private func loadPhotos(from items: [PhotosPickerItem]) async { var loaded: [Data] = [] for item in items { if let d = try? await item.loadTransferable(type: Data.self) { loaded.append(d) } } photoData = loaded } private func save() async { errorMessage = nil saveState = .savingEntry let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd" let tags = tagsInput .split(separator: ",") .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } .filter { !$0.isEmpty } let body = DiaryCreateBody( datum: formatter.string(from: date), typ: isMilestone ? "meilenstein" : "eintrag", titel: titel.trimmingCharacters(in: .whitespaces).isEmpty ? nil : titel.trimmingCharacters(in: .whitespaces), text: text.trimmingCharacters(in: .whitespaces).isEmpty ? nil : text.trimmingCharacters(in: .whitespacesAndNewlines), tags: tags.isEmpty ? nil : tags, gpsLat: nil, gpsLon: nil, locationName: locationName.trimmingCharacters(in: .whitespaces).isEmpty ? nil : locationName, isMilestone: isMilestone ) let entry: DiaryEntry do { entry = try await APIClient.shared.post("/api/dogs/\(dogId)/diary", body: body) } catch { errorMessage = error.localizedDescription saveState = .idle return } if !photoData.isEmpty { for (i, raw) in photoData.enumerated() { saveState = .uploadingMedia(done: i, total: photoData.count) let resized = ImageResize.resizedJPEG(from: raw) do { _ = try await APIClient.shared.uploadFile( "/api/dogs/\(dogId)/diary/\(entry.id)/media", filename: "media_\(i + 1).jpg", data: resized ) } catch { errorMessage = "Eintrag gespeichert, Foto \(i + 1) fehlgeschlagen: \(error.localizedDescription)" await onSaved() saveState = .idle return } } } await onSaved() dismiss() } }