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
@@ -42,6 +42,19 @@ struct DashboardView: View {
order: .reverse
) private var myInspections: [LocalInspection]
/// Outstanding follow-up requests, counted into the My Inspections badge so
/// the inspector sees there is work waiting from any tab the same reason
/// in-progress inspections are counted there.
@Query private var followUpRequests: [LocalFollowUpRequest]
/// Badge count for My Inspections: in-progress work plus outstanding
/// follow-ups. `fulfilledLocally` rows are excluded in Swift, not in the
/// @Query predicate (CLAUDE.md rule 3), so the badge drops the instant a
/// re-inspection is submitted.
private var myInspectionsBadgeCount: Int {
myInspections.count + followUpRequests.filter { !$0.fulfilledLocally }.count
}
@State private var selectedTab: SidebarTab = .dashboard
/// Each sidebar tap refreshes the UUID for that tab, forcing its
/// NavigationStack to be destroyed and recreated even when the tab
@@ -181,8 +194,8 @@ struct DashboardView: View {
Label("My Inspections", systemImage: "checklist")
.foregroundStyle(tint)
Spacer()
if !myInspections.isEmpty {
Text("\(myInspections.count)")
if myInspectionsBadgeCount > 0 {
Text("\(myInspectionsBadgeCount)")
.font(.caption2)
.padding(.horizontal, 6).padding(.vertical, 2)
.background(Color.blue.opacity(0.15))
@@ -332,6 +345,10 @@ struct DashboardStatsView: View {
/// inspection empties its @Query while the start form is still presented.
@State private var scheduledStartTarget: ScheduledStartTarget? = nil
/// The follow-up request tapped in FollowUpRequestsCard. Held here for the
/// same reason as `scheduledStartTarget` see the covers at the bottom.
@State private var followUpStartTarget: FollowUpStartTarget? = nil
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 20) {
@@ -341,6 +358,15 @@ struct DashboardStatsView: View {
DraftResumeBanner(drafts: draftInspections, context: context)
}
// Follow-up Requests
// Re-inspections a director asked for. Ranked above SCHEDULED:
// a follow-up is remedial work on a facility that already failed
// once, so it is the more urgent of the two. Self-hides when
// there are none. Tap a row to start the linked re-inspection.
FollowUpRequestsCard(onStart: { target in
followUpStartTarget = target
})
// Scheduled Inspections (phase36)
// Planned/recurring assignments for this inspector. Self-hides
// when there are none. Tap a row to start it (facility +
@@ -484,10 +510,34 @@ struct DashboardStatsView: View {
StartInspectionView(
preFillTemplateId: t.templateServerId,
preFillFacilityId: t.facilityServerId,
// phase45 nil for an ordinary schedule; set when this row is a
// planned follow-up, which makes the run a linked re-inspection.
// Must precede preFillScheduleId: argument order follows the
// property declaration order in StartInspectionView.
parentServerId: t.parentServerId,
preFillScheduleId: t.id,
preFillScheduleInstructions: t.instructions
)
}
// Start cover for a tapped follow-up request. Owned here for the same
// reason as the scheduled cover above: FollowUpRequestsCard self-hides
// the instant its last row is invalidated at submit, which is exactly
// when this cover is on screen.
//
// `parentServerId` is what makes this a re-inspection rather than a
// fresh one the server reads it to clear follow_up_required on the
// flagged inspection. `parentLocalId` stays nil: the parent synced long
// ago (that is how it got flagged), so serverId is the reliable handle,
// and clearParentFollowUpFlag()'s fallback-1 matches on it.
.fullScreenCover(item: $followUpStartTarget) { t in
StartInspectionView(
preFillTemplateId: t.templateServerId,
preFillFacilityId: t.facilityServerId,
parentServerId: t.id,
preFillFollowUpNote: t.note,
preFillParentFormDataJSON: t.parentFormDataJSON
)
}
}
// Helpers