05/04 Update the app functionalities

This commit is contained in:
Nguyen Ngo
2026-05-04 17:36:55 -04:00
parent 09715e9c11
commit 9d6b5e5bcb
7 changed files with 980 additions and 131 deletions
@@ -1,7 +1,12 @@
// Views/Inspection/FlagIssueView.swift
// -------------------------------------
// 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
@@ -14,42 +19,55 @@ struct FlagIssueView: View {
let inspection: LocalInspection
@State private var selectedAreaId: Int?
@State private var severity = "medium"
@State private var description = ""
@State private var severity = "medium"
@State private var description = ""
@State private var selectedImage: UIImage?
@State private var photoLocalPath: String?
@State private var showImagePicker = false
@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 areas: [LocalArea] {
let facilityId = inspection.facilityServerId
let results = try? context.fetch(
FetchDescriptor<LocalArea>(
predicate: #Predicate { $0.facilityServerId == facilityId },
sortBy: [SortDescriptor(\.name)]
private var facility: LocalFacility? {
let id = inspection.facilityServerId
return try? context.fetch(
FetchDescriptor<LocalFacility>(
predicate: #Predicate { $0.serverId == id }
)
)
return results ?? []
).first
}
private var canSubmit: Bool {
selectedAreaId != nil && !description.trimmingCharacters(in: .whitespaces).isEmpty
!description.trimmingCharacters(in: .whitespaces).isEmpty
}
var body: some View {
NavigationStack {
Form {
// Area
Section("Area") {
Picker("Area", selection: $selectedAreaId) {
Text("Select area…").tag(Optional<Int>(nil))
ForEach(areas) { area in
Text(area.name).tag(Optional(area.serverId))
// 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)
}
}
.pickerStyle(.navigationLink)
.padding(.vertical, 2)
} header: {
Text("Inspection Context")
} footer: {
Text("Issue will be logged against this facility.")
.font(.caption)
}
// Severity
@@ -78,7 +96,7 @@ struct FlagIssueView: View {
.clipShape(RoundedRectangle(cornerRadius: 8))
}
Button {
showImagePicker = true
if cameraAvailable { showChoice = true } else { showLibrary = true }
} label: {
Label(selectedImage == nil ? "Attach Photo" : "Replace Photo",
systemImage: "camera")
@@ -107,10 +125,17 @@ struct FlagIssueView: View {
.fontWeight(.semibold)
}
}
.sheet(isPresented: $showImagePicker) {
ImagePickerView(image: $selectedImage) { img in
savePhoto(img)
}
.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)
}
}
}
@@ -129,11 +154,9 @@ struct FlagIssueView: View {
}
private func submitIssue() {
guard let areaId = selectedAreaId else { return }
let issue = LocalIssue(
inspectionLocalId: inspection.localId,
areaServerId: areaId,
facilityServerId: inspection.facilityServerId,
severity: severity,
description: description.trimmingCharacters(in: .whitespaces)
)
@@ -142,7 +165,6 @@ struct FlagIssueView: View {
inspection.localIssues.append(issue)
context.insert(issue)
// Create PendingPhoto if a photo was attached
if let path = photoLocalPath {
let photo = PendingPhoto(
localFilePath: path,