summaryrefslogtreecommitdiff
path: root/DomainDig/AuditModeView.swift
blob: b3e363e31d7eaf58f468c778046b6ab44f42c8e4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import SwiftUI

struct AuditModeView: View {
    @Bindable var viewModel: DomainViewModel
    @State private var auditStore = AuditStore()
    @State private var reviewer = "Local Reviewer"
    @State private var selectedSession: AuditSession?
    @State private var exportMessage: String?

    var body: some View {
        List {
            Section("Start Audit") {
                TextField("Domain", text: $viewModel.domain)
                    .textInputAutocapitalization(.never)
                    .autocorrectionDisabled(true)
                TextField("Reviewer", text: $reviewer)
                Button("Start Audit") {
                    let domain = viewModel.domain.trimmingCharacters(in: .whitespacesAndNewlines)
                    guard !domain.isEmpty else { return }
                    auditStore.startAudit(domain: domain, reviewer: reviewer, source: viewModel)
                }
                .disabled(viewModel.domain.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
            }

            Section("Audit Timeline") {
                if auditStore.sessions.isEmpty {
                    Text("No audits yet.")
                        .foregroundStyle(.secondary)
                } else {
                    ForEach(auditStore.sessions) { session in
                        Button {
                            selectedSession = session
                        } label: {
                            VStack(alignment: .leading) {
                                Text(session.domain).font(.headline)
                                Text("\(session.createdAt.formatted(date: .abbreviated, time: .shortened))\(session.status.title)")
                                    .font(.caption)
                                    .foregroundStyle(.secondary)
                            }
                        }
                    }
                }
            }
        }
        .navigationTitle("Audit Mode")
        .sheet(item: $selectedSession) { session in
            AuditSessionDetailView(session: session) { updated in
                auditStore.update(session: updated)
                selectedSession = updated
            } onExport: { updated, format in
                do {
                    let url = try await auditStore.export(session: updated, format: format)
                    exportMessage = "Exported to \(url.lastPathComponent)"
                } catch {
                    exportMessage = "Export failed: \(error.localizedDescription)"
                }
            }
        }
        .alert("Audit Export", isPresented: Binding(get: { exportMessage != nil }, set: { if !$0 { exportMessage = nil } })) {
            Button("OK", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ }
        } message: {
            Text(exportMessage ?? "")
        }
    }
}

private struct AuditSessionDetailView: View {
    @Environment(\.dismiss) private var dismiss
    @State var session: AuditSession
    let onSave: (AuditSession) -> Void
    let onExport: (AuditSession, AuditExportFormat) async -> Void

    var body: some View {
        NavigationStack {
            List {
                Section("Session") {
                    Text(session.domain)
                    Picker("Status", selection: $session.status) {
                        ForEach(AuditStatus.allCases, id: \.self) { Text($0.title).tag($0) }
                    }
                    TextField("Notes", text: $session.notes, axis: .vertical)
                }
                Section("Checklist") {
                    ForEach($session.checklist) { $item in
                        Toggle(item.title, isOn: $item.isComplete)
                    }
                }
                Section("Findings") {
                    ForEach($session.findings) { $finding in
                        VStack(alignment: .leading) {
                            TextField("Title", text: $finding.title)
                            Picker("Severity", selection: $finding.severity) {
                                ForEach(AuditFindingSeverity.allCases, id: \.self) { Text($0.rawValue.capitalized).tag($0) }
                            }
                            TextField("Summary", text: $finding.summary, axis: .vertical)
                        }
                    }
                    Button("Add Finding") {
                        session.findings.append(.init(title: "New finding", severity: .informational, summary: ""))
                    }
                }
                Section("Export") {
                    ForEach(AuditExportFormat.allCases) { format in
                        Button("Export \(format.rawValue.uppercased())") {
                            Task { await onExport(session, format) }
                        }
                    }
                }
            }
            .navigationTitle("Audit Session")
            .toolbar {
                ToolbarItem(placement: .topBarLeading) {
                    Button("Close") { dismiss() }
                }
                ToolbarItem(placement: .topBarTrailing) {
                    Button("Save") {
                        onSave(session)
                        dismiss()
                    }
                }
            }
        }
    }
}