summaryrefslogtreecommitdiff
path: root/DomainDig/ScheduledReportsView.swift
diff options
context:
space:
mode:
Diffstat (limited to 'DomainDig/ScheduledReportsView.swift')
-rw-r--r--DomainDig/ScheduledReportsView.swift130
1 files changed, 130 insertions, 0 deletions
diff --git a/DomainDig/ScheduledReportsView.swift b/DomainDig/ScheduledReportsView.swift
new file mode 100644
index 0000000..f97ee77
--- /dev/null
+++ b/DomainDig/ScheduledReportsView.swift
@@ -0,0 +1,130 @@
+import SwiftUI
+
+struct ScheduledReportsView: View {
+ @Environment(\.appDensity) private var appDensity
+ @State private var settings = ScheduledReportStorage.loadSettings()
+ @State private var logs = ScheduledReportStorage.loadLogs()
+ @State private var isGenerating = false
+ @State private var statusMessage: String?
+
+ var body: some View {
+ List {
+ Section("Overview") {
+ VStack(alignment: .leading, spacing: 8) {
+ if !FeatureAccessService.hasAccess(to: .automatedMonitoring) {
+ Text("Scheduled reports require Pro.")
+ .font(appDensity.font(.caption))
+ .foregroundStyle(.secondary)
+ }
+
+ Toggle("Scheduled Reports", isOn: Binding(
+ get: { settings.isEnabled },
+ set: { newValue in
+ settings.isEnabled = newValue
+ saveSettings()
+ }
+ ))
+ .disabled(!FeatureAccessService.hasAccess(to: .automatedMonitoring))
+
+ Picker("Cadence", selection: Binding(
+ get: { settings.cadence },
+ set: { newValue in
+ settings.cadence = newValue
+ saveSettings()
+ }
+ )) {
+ ForEach(ScheduledReportCadence.allCases) { cadence in
+ Text(cadence.title).tag(cadence)
+ }
+ }
+
+ Picker("Format", selection: Binding(
+ get: { settings.format },
+ set: { newValue in
+ settings.format = newValue
+ saveSettings()
+ }
+ )) {
+ ForEach([DomainExportFormat.markdown, .pdf, .json]) { format in
+ Text(format.title).tag(format)
+ }
+ }
+
+ LabeledContent(
+ "Last Generated",
+ value: settings.lastGeneratedAt?.formatted(date: .abbreviated, time: .shortened) ?? "Never"
+ )
+
+ if let statusMessage {
+ Text(statusMessage)
+ .font(appDensity.font(.caption))
+ .foregroundStyle(.secondary)
+ }
+
+ Button(isGenerating ? "Generating…" : "Generate Now") {
+ Task { await generateNow() }
+ }
+ .disabled(isGenerating)
+ }
+ .padding(.vertical, 4)
+ }
+ .listRowBackground(Color(.systemGray6).opacity(0.5))
+
+ if logs.isEmpty {
+ Section {
+ EmptyStateCardView(
+ title: "No Reports Yet",
+ message: "Generated reports for your watchlist appear here after a manual or scheduled run.",
+ suggestion: "Tap Generate Now, or enable Scheduled Reports above.",
+ systemImage: "doc.text",
+ showsCardBackground: false
+ )
+ }
+ .listRowBackground(Color(.systemGray6).opacity(0.5))
+ } else {
+ Section("Recent Reports") {
+ ForEach(logs) { log in
+ Button {
+ share(log)
+ } label: {
+ VStack(alignment: .leading, spacing: 2) {
+ Text("\(log.domainCount) domains • \(log.format.title)")
+ .foregroundStyle(.primary)
+ Text(log.generatedAt.formatted(date: .abbreviated, time: .shortened))
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ }
+ }
+ }
+ .listRowBackground(Color(.systemGray6).opacity(0.5))
+ }
+ }
+ .scrollContentBackground(.hidden)
+ .background(Color.black)
+ .navigationTitle("Scheduled Reports")
+ }
+
+ private func saveSettings() {
+ ScheduledReportStorage.saveSettings(settings)
+ ScheduledReportScheduler.shared.syncSchedule()
+ }
+
+ private func generateNow() async {
+ isGenerating = true
+ let outcome = await ScheduledReportService.shared.generateReport(trigger: .manual, requireEnabledSetting: false)
+ statusMessage = outcome.message
+ settings = ScheduledReportStorage.loadSettings()
+ logs = ScheduledReportStorage.loadLogs()
+ isGenerating = false
+ }
+
+ private func share(_ log: ScheduledReportLog) {
+ let url = ScheduledReportStorage.reportsDirectory.appendingPathComponent(log.fileName)
+ guard let data = try? Data(contentsOf: url) else {
+ statusMessage = "That report is no longer available on disk."
+ return
+ }
+ ExportPresenter.share(filename: log.fileName, data: data)
+ }
+}