import SwiftUI /// Presented from `ReceiptsListView`'s toolbar. Exports the given receipts as /// CSV, XLSX, or PDF via `ExportAPI`, writes the result to a temp file (so /// the receiving app sees the right filename/extension), and hands it to the /// iOS share sheet. struct ExportSheet: View { let receipts: [Receipt] @Environment(\.dismiss) private var dismiss @State private var isExporting = false @State private var errorMessage: String? @State private var shareFile: ShareFile? var body: some View { NavigationStack { VStack(spacing: ZenithSpacing.sm) { if receipts.isEmpty { Text("Keine Belege zum Exportieren.") .zenithBodyStyle(color: .zenithMuted) .padding(.top, 40) } else { Text("\(receipts.count) Beleg\(receipts.count == 1 ? "" : "e") exportieren") .zenithHeadlineMdStyle() .padding(.top, ZenithSpacing.md) VStack(spacing: ZenithSpacing.xs) { exportButton(title: "Als CSV exportieren", systemImage: "tablecells", format: .csv) exportButton(title: "Als Excel exportieren", systemImage: "tablecells.fill", format: .excel) exportButton(title: "Als PDF exportieren", systemImage: "doc.richtext", format: .pdf) } .padding(.horizontal, ZenithSpacing.sm) } if isExporting { ProgressView() } if let errorMessage { Text(errorMessage) .zenithBodySmStyle(color: .zenithError) .multilineTextAlignment(.center) .padding(.horizontal, ZenithSpacing.sm) } Spacer() } .background(Color.zenithBg.ignoresSafeArea()) .navigationTitle("Exportieren") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Fertig") { dismiss() } .buttonStyle(.zenithPlain) } } .sheet(item: $shareFile) { file in ShareSheet(activityItems: [file.url]) } } } private func exportButton(title: String, systemImage: String, format: ExportAPI.Format) -> some View { Button { Task { await export(format) } } label: { Label(title, systemImage: systemImage) .frame(maxWidth: .infinity) } .buttonStyle(.zenithPrimary) .disabled(isExporting) } private func export(_ format: ExportAPI.Format) async { isExporting = true errorMessage = nil defer { isExporting = false } do { let result = try await ExportAPI.export(format, receipts: receipts, locale: "de") let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(result.fileName) try result.data.write(to: tempURL, options: .atomic) shareFile = ShareFile(url: tempURL) } catch let apiError as APIError { if case .server(let code, _, _) = apiError, code == "pro_required" { errorMessage = "Export ist Pro-Nutzern vorbehalten." } else { errorMessage = apiError.localizedDescription } } catch { errorMessage = error.localizedDescription } } } /// Wraps a temp-file URL so it can drive `.sheet(item:)`. private struct ShareFile: Identifiable { let url: URL var id: String { url.absoluteString } }