// Views/Inspection/StartInspectionView.swift // ------------------------------------------ // Screen where the inspector chooses a template, facility, and optional area // before starting a new inspection. Creates the LocalInspection record // immediately so the form can be resumed if the app is backgrounded. import SwiftUI import SwiftData struct StartInspectionView: View { @Environment(\.modelContext) private var context @Environment(\.dismiss) private var dismiss @Query(sort: \LocalTemplate.name) private var templates: [LocalTemplate] @Query(sort: \LocalFacility.name) private var facilities: [LocalFacility] @State private var selectedTemplateId: Int? @State private var selectedFacilityId: Int? @State private var selectedAreaId: Int? @State private var navigateToExecution = false @State private var createdInspection: LocalInspection? @EnvironmentObject private var auth: AuthManager // ── Pre-fill for re-inspections ─────────────────────────────────────── /// When launching from a "Start Re-inspection" button, these are set so /// the form opens with the parent's template and facility pre-selected. var preFillTemplateId: Int? = nil var preFillFacilityId: Int? = nil var parentServerId: Int? = nil /// Local UUID of the parent — always available, used by SyncManager to /// clear the parent's followUpRequired badge after the re-inspection syncs. var parentLocalId: String? = nil private var selectedFacility: LocalFacility? { facilities.first { $0.serverId == selectedFacilityId } } private var areas: [LocalArea] { selectedFacility?.areas.sorted { $0.name < $1.name } ?? [] } private var canStart: Bool { selectedTemplateId != nil && selectedFacilityId != nil } var body: some View { NavigationStack { Form { // ── Re-inspection notice ─────────────────────────────────── if parentServerId != nil { Section { HStack(spacing: 10) { Image(systemName: "arrow.uturn.right.circle.fill") .foregroundStyle(.orange) VStack(alignment: .leading, spacing: 2) { Text("Re-inspection") .font(.callout.bold()) Text("This will be linked to inspection #\(parentServerId!).") .font(.caption) .foregroundStyle(.secondary) } } .padding(.vertical, 4) } } // ── Template picker ──────────────────────────────────────── Section("Inspection Template") { if templates.isEmpty { Text("No templates available. Sync required.") .foregroundStyle(.secondary) .font(.callout) } else { Picker("Template", selection: $selectedTemplateId) { Text("Select a template…").tag(Optional(nil)) ForEach(templates) { template in VStack(alignment: .leading) { Text(template.name) if !template.frequency.isEmpty { Text(template.frequencyLabel) .font(.caption) .foregroundStyle(.secondary) } } .tag(Optional(template.serverId)) } } .pickerStyle(.navigationLink) } } // ── Facility picker ──────────────────────────────────────── Section("Facility") { if facilities.isEmpty { Text("No facilities available. Sync required.") .foregroundStyle(.secondary) .font(.callout) } else { Picker("Facility", selection: $selectedFacilityId) { Text("Select a facility…").tag(Optional(nil)) ForEach(facilities) { facility in Text(facility.name).tag(Optional(facility.serverId)) } } .pickerStyle(.navigationLink) .onChange(of: selectedFacilityId) { selectedAreaId = nil // reset area when facility changes } } } // ── Area picker (optional) ───────────────────────────────── if selectedFacilityId != nil { Section("Area (Optional)") { Picker("Area", selection: $selectedAreaId) { Text("No specific area").tag(Optional(nil)) ForEach(areas) { area in Text(area.name).tag(Optional(area.serverId)) } } .pickerStyle(.navigationLink) .disabled(areas.isEmpty) } } // ── Start button ─────────────────────────────────────────── Section { Button { startInspection() } label: { HStack { Spacer() Label( parentServerId != nil ? "Start Re-inspection" : "Start Inspection", systemImage: parentServerId != nil ? "arrow.uturn.right.circle.fill" : "play.circle.fill" ) .font(.headline) Spacer() } } .disabled(!canStart) } } .navigationTitle(parentServerId != nil ? "Re-inspection" : "New Inspection") .navigationBarTitleDisplayMode(.large) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } } } .navigationDestination(isPresented: $navigateToExecution) { if let inspection = createdInspection { ExecuteInspectionView(inspection: inspection) } } .onAppear { // Apply pre-fill from re-inspection launch if let tid = preFillTemplateId { selectedTemplateId = tid } if let fid = preFillFacilityId { selectedFacilityId = fid } } } } private func startInspection() { guard let templateId = selectedTemplateId, let facilityId = selectedFacilityId else { return } let inspection = LocalInspection( templateServerId: templateId, facilityServerId: facilityId, areaServerId: selectedAreaId, inspectorUserId: auth.currentUserId ) // Link to parent if this is a re-inspection inspection.parentServerId = parentServerId inspection.parentLocalId = parentLocalId // ── Pre-fill from parent (mirrors web app behaviour) ─────────────── // Copy non-scoring field values from the parent inspection so the // inspector doesn't re-enter static data. Scoring fields (rating, // pass_fail) and media fields (image, signature) are always left blank // so every scoreable item must be re-evaluated fresh. if let parentId = parentServerId { let allInspections = try? context.fetch(FetchDescriptor()) if let parent = allInspections?.first(where: { $0.serverId == parentId }), !parent.formData.isEmpty { // Fetch the template schema to identify field types let tid = templateId let schema = (try? context.fetch( FetchDescriptor(predicate: #Predicate { $0.serverId == tid }) ).first?.formSchema) ?? [] // Build the set of field IDs that must NOT be carried over let excludeTypes: Set = ["rating", "pass_fail", "image", "signature"] var excludeIds = Set() for field in schema { if let type_ = field["type"] as? String, excludeTypes.contains(type_), let id = field["id"] { excludeIds.insert("\(id)") } } // Copy all parent values except excluded fields let parentData = parent.formData var prefilled: [String: Any] = [:] for (key, value) in parentData { if !excludeIds.contains(key) { prefilled[key] = value } } if !prefilled.isEmpty { inspection.formData = prefilled } } } context.insert(inspection) try? context.save() createdInspection = inspection navigateToExecution = true } }