Jul 30 - Update iPad - inspector can re-inspect and create follow-up

This commit is contained in:
Nguyen Ngo
2026-07-30 16:16:15 -04:00
parent b7990cc9ba
commit dac7e6c597
16 changed files with 961 additions and 19 deletions
+60
View File
@@ -278,6 +278,7 @@ class SyncManager: ObservableObject {
await pullReferenceData()
await pullAssignedIssues(context: context)
await pullScheduledInspections(context: context)
await pullFollowUpRequests(context: context)
// Poll notifications immediately on every sync rather than waiting
// for the 60-second timer ensures the inspector sees assignments
@@ -918,6 +919,65 @@ class SyncManager: ObservableObject {
}
}
// Follow-up Requests
// Read-only pull of inspections a director flagged for follow-up, for the
// Dashboard and My Inspections "Follow-up Requested" section. Upsert by
// serverId, then delete rows the server no longer returns (the follow-up was
// fulfilled by a linked re-inspection, or the director cleared the flag).
// Best-effort never blocks the pipeline.
func pullFollowUpRequests(context: ModelContext) async {
guard isOnline, AuthManager.shared.isAuthenticated else { return }
do {
let apiRows = try await APIClient.shared.fetchFollowUpRequests()
// Fetch-all + filter/map in Swift no #Predicate (CLAUDE.md rule 3).
let allLocal = (try? context.fetch(FetchDescriptor<LocalFollowUpRequest>())) ?? []
var byServerId: [Int: LocalFollowUpRequest] = [:]
for row in allLocal { byServerId[row.serverId] = row }
for api in apiRows {
if let existing = byServerId[api.id] {
existing.update(from: api)
} else {
context.insert(LocalFollowUpRequest(from: api))
}
}
// Delete rows the server no longer returns.
let returnedIds = Set(apiRows.map { $0.id })
for row in allLocal where !returnedIds.contains(row.serverId) {
context.delete(row)
}
// Keep the local copy of the flagged inspection in step, so the
// follow-up badge in My Inspections / history detail agrees with the
// card without waiting for the inspector to open that detail view
// (which was previously the only thing that wrote these fields).
var noteByServerId: [Int: String] = [:]
for api in apiRows {
if let note = api.followUpNote { noteByServerId[api.id] = note }
}
for local in (try? context.fetch(FetchDescriptor<LocalInspection>())) ?? [] {
guard let sid = local.serverId else { continue }
if returnedIds.contains(sid) {
local.followUpRequired = true
local.followUpNote = noteByServerId[sid]
} else if local.followUpRequired {
local.followUpRequired = false
local.followUpNote = nil
}
}
try? context.save()
} catch APIError.notAuthenticated {
// Let AuthManager handle session expiry
} catch {
// Non-fatal stale follow-up rows stay visible until next pull
}
}
// Dashboard Stats
// Best-effort fetch a network failure silently leaves dashboardStats nil
// so the UI falls back to a placeholder card. Never blocks the sync pipeline.