// Views/Inspection/FlagIssueView.swift // ------------------------------------- // Sheet for flagging an issue during an inspection. // Saves locally immediately; syncs to server when online. 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 selectedAreaId: Int? @State private var severity = "medium" @State private var description = "" @State private var selectedImage: UIImage? @State private var photoLocalPath: String? @State private var showImagePicker = false private let severities = ["low", "medium", "high", "critical"] private var areas: [LocalArea] { let facilityId = inspection.facilityServerId let results = try? context.fetch( FetchDescriptor( predicate: #Predicate { $0.facilityServerId == facilityId }, sortBy: [SortDescriptor(\.name)] ) ) return results ?? [] } private var canSubmit: Bool { selectedAreaId != nil && !description.trimmingCharacters(in: .whitespaces).isEmpty } var body: some View { NavigationStack { Form { // ── Area ─────────────────────────────────────────────────── Section("Area") { Picker("Area", selection: $selectedAreaId) { Text("Select area…").tag(Optional(nil)) ForEach(areas) { area in Text(area.name).tag(Optional(area.serverId)) } } .pickerStyle(.navigationLink) } // ── 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 { showImagePicker = 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) } } .sheet(isPresented: $showImagePicker) { ImagePickerView(image: $selectedImage) { img in savePhoto(img) } } } } 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() { guard let areaId = selectedAreaId else { return } let issue = LocalIssue( inspectionLocalId: inspection.localId, areaServerId: areaId, severity: severity, description: description.trimmingCharacters(in: .whitespaces) ) issue.photoLocalPath = photoLocalPath issue.inspection = inspection inspection.localIssues.append(issue) context.insert(issue) // Create PendingPhoto if a photo was attached 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() } }