Jul 27 - Update code for scheduled tasks 3

This commit is contained in:
Nguyen Ngo
2026-07-27 15:56:19 -04:00
parent ad91a92ff1
commit 4132373fee
6 changed files with 91 additions and 41 deletions
@@ -770,6 +770,7 @@ struct ExecuteInspectionView: View {
$0.templateServerId == tid &&
$0.facilityServerId == fid &&
($0.inspectorId == nil || $0.inspectorId == uid) &&
!$0.fulfilledLocally && // already satisfied, awaiting sync
!$0.dueDateString.isEmpty &&
$0.dueDateString <= today // ISO strings sort chronologically
}
@@ -781,12 +782,21 @@ struct ExecuteInspectionView: View {
}
}
// Clear the cached row for whichever schedule this submission fulfils.
// Hide the row until the server confirms what happened to it.
//
// NOT a delete. A recurring schedule comes back from the server on its
// next occurrence, so deleting turned every completion into a
// delete-then-reinsert against the `@Attribute(.unique)` serverId, and
// the reinserted row did not reliably pick up the new due date a
// daily schedule kept showing today's date after being completed.
// One-time schedules masked it, because the server stops returning them
// and they are never reinserted. Flagging leaves `update(from:)` as the
// single path that ever writes a cached schedule's dates.
guard let schedId = inspection.scheduledInspectionServerId,
let sched = all.first(where: { $0.serverId == schedId })
else { return }
context.delete(sched)
sched.fulfilledLocally = true
}
/// "yyyy-MM-dd", matching `LocalScheduledInspection.dueDateString`.
@@ -19,6 +19,11 @@ struct MyInspectionsView: View {
@Query(sort: \LocalScheduledInspection.dueDateString, order: .forward)
private var scheduledAll: [LocalScheduledInspection]
/// Rows still awaiting action see ScheduledInspectionsCard.visible.
private var scheduledVisible: [LocalScheduledInspection] {
scheduledAll.filter { !$0.fulfilledLocally }
}
@Environment(\.modelContext) private var context
@State private var showNewInspection = false
@@ -30,7 +35,7 @@ struct MyInspectionsView: View {
var body: some View {
Group {
if inspections.isEmpty && scheduledAll.isEmpty {
if inspections.isEmpty && scheduledVisible.isEmpty {
ContentUnavailableView(
"No Inspections",
systemImage: "checklist",
@@ -39,9 +44,9 @@ struct MyInspectionsView: View {
} else {
List {
// Scheduled assignments (phase36) self-hides when empty.
if !scheduledAll.isEmpty {
if !scheduledVisible.isEmpty {
Section("Scheduled") {
ForEach(scheduledAll) { s in
ForEach(scheduledVisible) { s in
Button { scheduledStartTarget = ScheduledStartTarget(s) } label: {
ScheduledRow(schedule: s)
}
@@ -136,6 +136,14 @@ struct ScheduledInspectionsCard: View {
@Query(sort: \LocalScheduledInspection.dueDateString, order: .forward)
private var scheduled: [LocalScheduledInspection]
/// Rows still awaiting action. Filtered in Swift rather than in the @Query
/// predicate, per CLAUDE.md rule 3. `fulfilledLocally` is set at submit and
/// cleared by the next pull, so a completed schedule leaves the card at
/// once and reappears only when the server says it is due again.
private var visible: [LocalScheduledInspection] {
scheduled.filter { !$0.fulfilledLocally }
}
/// Tap handler. The `.fullScreenCover` deliberately lives in the PARENT
/// (`DashboardStatsView`, on its always-present ScrollView) rather than here:
/// this card self-hides, and submitting the last scheduled inspection removes
@@ -146,14 +154,14 @@ struct ScheduledInspectionsCard: View {
let onStart: (ScheduledStartTarget) -> Void
var body: some View {
if !scheduled.isEmpty {
if !visible.isEmpty {
VStack(alignment: .leading, spacing: 10) {
Text("SCHEDULED")
.font(.caption.bold())
.foregroundStyle(.secondary)
.tracking(1)
ForEach(scheduled) { s in
ForEach(visible) { s in
Button { onStart(ScheduledStartTarget(s)) } label: {
ScheduledRow(schedule: s)
.padding(12)