// Views/Inspection/InspectionHistoryView.swift // -------------------------------------------- // Shows the inspector's synced inspection history fetched from the server. // Only available when online. Displays score, facility, template, and date. import SwiftUI struct InspectionHistoryView: View { @EnvironmentObject private var sync: SyncManager @State private var inspections: [APIInspectionSummary] = [] @State private var isLoading = false @State private var errorMessage: String? @State private var total = 0 @State private var offset = 0 private let limit = 30 var body: some View { Group { if !sync.isOnline && inspections.isEmpty { ContentUnavailableView( "Offline", systemImage: "wifi.slash", description: Text("Inspection history requires an internet connection.") ) } else if isLoading && inspections.isEmpty { ProgressView("Loading history…") .frame(maxWidth: .infinity, maxHeight: .infinity) } else if let error = errorMessage, inspections.isEmpty { ContentUnavailableView( "Could Not Load", systemImage: "exclamationmark.triangle", description: Text(error) ) } else if inspections.isEmpty { ContentUnavailableView( "No History", systemImage: "clock.arrow.circlepath", description: Text("Completed inspections will appear here after syncing.") ) } else { List { ForEach(inspections) { inspection in HistoryRowView(inspection: inspection) } // Load more if inspections.count < total { HStack { Spacer() Button("Load More") { Task { await loadMore() } } .disabled(isLoading) Spacer() } .listRowSeparator(.hidden) } if isLoading { HStack { Spacer() ProgressView() Spacer() } .listRowSeparator(.hidden) } } .refreshable { await load(reset: true) } } } .navigationTitle("Inspection History") .task { if sync.isOnline { await load(reset: true) } } .onChange(of: sync.isOnline) { if sync.isOnline && inspections.isEmpty { Task { await load(reset: true) } } } } private func load(reset: Bool) async { if reset { offset = 0 } isLoading = true errorMessage = nil defer { isLoading = false } do { let result = try await APIClient.shared.fetchInspectionHistory( limit: limit, offset: reset ? 0 : offset ) if reset { inspections = result.inspections } else { inspections.append(contentsOf: result.inspections) } total = result.total offset = inspections.count } catch { errorMessage = error.localizedDescription } } private func loadMore() async { await load(reset: false) } } // MARK: - History Row struct HistoryRowView: View { let inspection: APIInspectionSummary private var scoreColor: Color { guard let score = inspection.overallScore else { return .secondary } return score >= 80 ? .green : score >= 60 ? .orange : .red } private var dateText: String { guard let date = inspection.inspectionDateParsed else { return inspection.inspectionDate ?? "" } return date.formatted(date: .abbreviated, time: .shortened) } var body: some View { VStack(alignment: .leading, spacing: 6) { HStack(alignment: .top) { VStack(alignment: .leading, spacing: 2) { Text(inspection.templateName) .font(.headline) .lineLimit(1) Text(inspection.facilityName) .font(.callout) .foregroundStyle(.secondary) .lineLimit(1) if let area = inspection.areaName { Text(area) .font(.caption) .foregroundStyle(.tertiary) } } Spacer() if let score = inspection.overallScore { VStack(alignment: .trailing, spacing: 2) { Text(String(format: "%.1f%%", score)) .font(.title3.bold()) .foregroundStyle(scoreColor) Text("Score") .font(.caption2) .foregroundStyle(.tertiary) } } } HStack { Image(systemName: "calendar") .font(.caption2) .foregroundStyle(.tertiary) Text(dateText) .font(.caption2) .foregroundStyle(.tertiary) Spacer() // Sync origin badge if inspection.mobileLocalId != nil { Label("Mobile", systemImage: "ipad") .font(.caption2) .foregroundStyle(.blue) .padding(.horizontal, 6) .padding(.vertical, 2) .background(Color.blue.opacity(0.1)) .clipShape(Capsule()) } } } .padding(.vertical, 4) } }