import SwiftUI struct RouteDetailView: View { let routeId: Int let fallbackName: String @State private var detail: RouteDetail? @State private var isLoading = false @State private var errorMessage: String? var body: some View { ScrollView { VStack(alignment: .leading, spacing: 16) { if let detail { MiniRouteMap(track: detail.gpsTrack, lineWidth: 4) .frame(height: 320) .clipShape(RoundedRectangle(cornerRadius: 16)) .padding(.horizontal) HStack(spacing: 12) { StatTile(value: formatKm(detail.distanzKm), label: "Distanz", icon: "ruler") StatTile(value: formatMin(detail.dauerMin), label: "Dauer", icon: "clock") StatTile(value: "\(detail.gpsTrack.count)", label: "Punkte", icon: "point.3.connected.trianglepath.dotted") } .padding(.horizontal) if let beschreibung = detail.beschreibung, !beschreibung.isEmpty { Text(beschreibung) .font(.body) .padding(.horizontal) } if let urls = detail.fotoUrls, !urls.isEmpty { VStack(alignment: .leading, spacing: 8) { Text("Fotos").font(.headline) ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 10) { ForEach(urls, id: \.self) { path in photoThumb(path) } } } } .padding(.horizontal) } Spacer(minLength: 24) } else if isLoading { ProgressView().padding(.top, 80) } else if let error = errorMessage { ContentUnavailableView( "Fehler", systemImage: "exclamationmark.triangle", description: Text(error) ) .padding(.top, 60) } } } .navigationTitle(detail?.name ?? fallbackName) .navigationBarTitleDisplayMode(.inline) .task { await load() } } private func photoThumb(_ path: String) -> some View { let url = URL(string: "https://banyaro.app\(path)") return AsyncImage(url: url) { phase in switch phase { case .success(let img): img.resizable().scaledToFill() default: Rectangle().fill(.gray.opacity(0.15)) } } .frame(width: 160, height: 160) .clipShape(RoundedRectangle(cornerRadius: 10)) } private func load() async { isLoading = true errorMessage = nil defer { isLoading = false } do { detail = try await APIClient.shared.get("/api/routes/\(routeId)") } catch { errorMessage = error.localizedDescription } } private func formatKm(_ km: Double?) -> String { guard let km else { return "—" } return String(format: "%.2f km", km) } private func formatMin(_ mins: Int?) -> String { guard let mins else { return "—" } if mins >= 60 { return "\(mins / 60) h \(mins % 60) min" } return "\(mins) min" } } private struct StatTile: View { let value: String let label: String let icon: String var body: some View { VStack(spacing: 6) { Image(systemName: icon) .foregroundStyle(Color.accentColor) Text(value) .font(.headline) Text(label) .font(.caption) .foregroundStyle(.secondary) } .frame(maxWidth: .infinity) .padding(.vertical, 12) .background(.background.secondary, in: RoundedRectangle(cornerRadius: 12)) } }