100 lines
3.7 KiB
Swift
100 lines
3.7 KiB
Swift
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 }
|
|
}
|