// Views/Dashboard/FlagIssueView.swift // ------------------------------------ // Sheet for flagging an issue during an inspection. // Saves locally immediately; syncs to server when online. // // CHANGED: Area picker removed. Facility is derived directly from the // inspection (inspection.facilityServerId) and displayed as read-only info, // matching the web app's flag_issue.html behaviour where facility_id is // a hidden field populated from the inspection context. import SwiftUI import SwiftData struct FlagIssueView: View { @Environment(\.modelContext) private var context @Environment(\.dismiss) private var dismiss @EnvironmentObject private var sync: SyncManager let inspection: LocalInspection @State private var severity = "medium" @State private var description = "" @State private var selectedImage: UIImage? @State private var photoLocalPath: String? @State private var showChoice = false @State private var showCamera = false @State private var showLibrary = false private var cameraAvailable: Bool { UIImagePickerController.isSourceTypeAvailable(.camera) } private let severities = ["low", "medium", "high", "critical"] private var facility: LocalFacility? { let id = inspection.facilityServerId return try? context.fetch( FetchDescriptor( predicate: #Predicate { $0.serverId == id } ) ).first } private var canSubmit: Bool { !description.trimmingCharacters(in: .whitespaces).isEmpty } var body: some View { NavigationStack { Form { // ── Facility (read-only) — matches web alert banner ──────── Section { HStack(spacing: 10) { Image(systemName: "building.2") .foregroundStyle(.secondary) VStack(alignment: .leading, spacing: 2) { Text("Facility") .font(.caption) .foregroundStyle(.secondary) Text(facility?.name ?? "—") .font(.body) } } .padding(.vertical, 2) } header: { Text("Inspection Context") } footer: { Text("Issue will be logged against this facility.") .font(.caption) } // ── Severity ─────────────────────────────────────────────── Section("Severity") { Picker("Severity", selection: $severity) { ForEach(severities, id: \.self) { s in Text(s.capitalized).tag(s) } } .pickerStyle(.segmented) } // ── Description ──────────────────────────────────────────── Section("Description") { TextEditor(text: $description) .frame(minHeight: 100) } // ── Photo ────────────────────────────────────────────────── Section("Photo (Optional)") { if let img = selectedImage { Image(uiImage: img) .resizable() .scaledToFit() .frame(maxHeight: 160) .clipShape(RoundedRectangle(cornerRadius: 8)) } Button { if cameraAvailable { showChoice = true } else { showLibrary = true } } label: { Label(selectedImage == nil ? "Attach Photo" : "Replace Photo", systemImage: "camera") } } // ── Offline notice ───────────────────────────────────────── if !sync.isOnline { Section { Label("You're offline — this issue will sync automatically.", systemImage: "wifi.slash") .font(.callout) .foregroundStyle(.orange) } } } .navigationTitle("Flag Issue") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .confirmationAction) { Button("Submit") { submitIssue() } .disabled(!canSubmit) .fontWeight(.semibold) } } .confirmationDialog("Add Photo", isPresented: $showChoice, titleVisibility: .visible) { Button("Take Photo") { showCamera = true } Button("Photo Library") { showLibrary = true } Button("Cancel", role: .cancel) {} } .fullScreenCover(isPresented: $showCamera) { CameraPickerView(image: $selectedImage, onSelected: savePhoto) .ignoresSafeArea() } .sheet(isPresented: $showLibrary) { LibraryPickerView(image: $selectedImage, onSelected: savePhoto) } } } private func savePhoto(_ img: UIImage) { guard let data = img.jpegData(compressionQuality: 0.8) else { return } let docsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let photosDir = docsDir.appendingPathComponent("JQC/Photos", isDirectory: true) try? FileManager.default.createDirectory(at: photosDir, withIntermediateDirectories: true) let filename = "\(UUID().uuidString).jpg" let fileURL = photosDir.appendingPathComponent(filename) try? data.write(to: fileURL) photoLocalPath = fileURL.path selectedImage = img } private func submitIssue() { let issue = LocalIssue( inspectionLocalId: inspection.localId, facilityServerId: inspection.facilityServerId, severity: severity, description: description.trimmingCharacters(in: .whitespaces) ) issue.photoLocalPath = photoLocalPath issue.inspection = inspection inspection.localIssues.append(issue) context.insert(issue) if let path = photoLocalPath { let photo = PendingPhoto( localFilePath: path, entityType: "issue", entityLocalId: issue.localId ) context.insert(photo) } try? context.save() if sync.isOnline { Task { await sync.triggerSync() } } dismiss() } }