import SwiftUI struct GassiTreffenList: View { @State private var location = OneShotLocation() @State private var meetings: [WalkMeeting] = [] @State private var isLoading = false @State private var errorMessage: String? @State private var showAdd = false var body: some View { content .toolbar { ToolbarItem(placement: .topBarTrailing) { Button { showAdd = true } label: { Label("Planen", systemImage: "plus") } } } .sheet(isPresented: $showAdd) { AddWalkSheet(coord: location.coordinate) { await load() } } .task { location.request() await load() } .onChange(of: location.coordinate?.latitude) { _, _ in Task { await load() } } .refreshable { await load() } } @ViewBuilder private var content: some View { if isLoading && meetings.isEmpty { ProgressView() } else if let errorMessage, meetings.isEmpty { ContentUnavailableView( "Konnte nicht laden", systemImage: "wifi.slash", description: Text(errorMessage) ) } else if meetings.isEmpty { ContentUnavailableView( "Noch keine Treffen", systemImage: "person.2", description: Text("In 20 km Umkreis sind aktuell keine Gassi-Treffen geplant. Tippe oben rechts auf +, um eins zu planen.") ) } else { List(meetings) { meeting in NavigationLink { GassiTreffenDetail(meetingId: meeting.id, fallbackTitle: meeting.titel) { await load() } } label: { TreffenRow(meeting: meeting) } } } } private func load() async { isLoading = true errorMessage = nil defer { isLoading = false } do { var path = "/api/walks?radius=20000" if let coord = location.coordinate { path = "/api/walks?lat=\(coord.latitude)&lon=\(coord.longitude)&radius=20000" } meetings = try await APIClient.shared.get(path) } catch { errorMessage = error.localizedDescription } } } private struct TreffenRow: View { let meeting: WalkMeeting var body: some View { HStack(spacing: 12) { Image(systemName: "pawprint.circle.fill") .font(.title) .foregroundStyle(.white, Color.accentColor) .frame(width: 40) VStack(alignment: .leading, spacing: 4) { Text(meeting.titel).font(.headline) HStack(spacing: 6) { Image(systemName: "calendar") Text(formatDate(meeting.datum)) Text(meeting.uhrzeit) } .font(.caption.monospacedDigit()) .foregroundStyle(.secondary) if let ort = meeting.ortName, !ort.isEmpty { HStack(spacing: 4) { Image(systemName: "mappin.and.ellipse") Text(ort) } .font(.caption) .foregroundStyle(.secondary) .lineLimit(1) } } Spacer() VStack(alignment: .trailing, spacing: 2) { Text("\(meeting.teilnehmerCount ?? 0)/\(meeting.maxTeilnehmer)") .font(.caption.bold().monospacedDigit()) Text("Teilnehmer").font(.caption2).foregroundStyle(.secondary) } } .padding(.vertical, 4) } private func formatDate(_ s: String) -> String { let parser = DateFormatter() parser.locale = Locale(identifier: "en_US_POSIX") parser.dateFormat = "yyyy-MM-dd" if let d = parser.date(from: String(s.prefix(10))) { let out = DateFormatter() out.locale = Locale(identifier: "de_DE") out.dateFormat = "EEE d. MMM" return out.string(from: d) } return s } }