05/27 Update functionalities

This commit is contained in:
Nguyen Ngo
2026-05-27 17:26:52 -04:00
parent ea7e523f25
commit 7d05c3862d
7 changed files with 800 additions and 27 deletions
+22 -5
View File
@@ -314,9 +314,17 @@ All server URLs built as: `ServerConfig.current + endpoint` — **`Constants.bas
let photoPath: String? // primary evidence photo
let mobilePhotoPaths: [String] // extra evidence photos from iPad (mobile_photo_paths)
let resultPhotos: [String] // resolution photos NOT stored in photoServerPaths on iPad
// Phase A:
let resultNotes: String? // resolution notes from web staff
let verifiedAt: String? // ISO 8601 when fix was verified
let verificationNote: String? // verifier note
let reportedByName: String? // reporter display_name
// Phase E:
let areaName: String? // area the issue was flagged in
let assignedToName: String? // assigned user display_name
```
`SyncManager.pullAssignedIssues` merges `photoPath + mobilePhotoPaths` into `photoServerPaths`. `resultPhotos` is decoded but intentionally excluded from `photoServerPaths` — resolution photos are web-only.
`SyncManager.pullAssignedIssues` merges `photoPath + mobilePhotoPaths` into `photoServerPaths`. `resultPhotos` is decoded but intentionally excluded from `photoServerPaths` — resolution photos are web-only. All Phase A/E optional fields are persisted to `LocalIssue` on both insert and update paths.
---
@@ -329,12 +337,14 @@ JanitorialQCApp
│ └── Server picker (segmented: jqc Primary / jqc1 Secondary)
└── DashboardView (authenticated)
├── Sidebar (NavigationSplitView — no selection: binding)
│ ├── My Inspections → MyInspectionsView [+ button here, NOT in sidebar]
│ ├── Issues → IssuesListView [+ button here → StandaloneIssueView]
│ ├── Dashboard → DashboardStatsView [default landing — KPI tiles]
│ ├── My Inspections → MyInspectionsView [+ button here, NOT in sidebar]
│ ├── Issues → IssuesListView [+ button → StandaloneIssueView; resolved excluded]
│ ├── Facilities → FacilitiesListView
│ ├── Pending Sync → SyncStatusView
│ ├── History → InspectionHistoryView
── Settings → SettingsView [server picker + logout alert]
── Notifications → NotificationsView [red badge when unreadCount > 0]
│ └── Settings → SettingsView [server picker + logout alert]
└── (sidebar has NO + button)
```
@@ -391,7 +401,7 @@ else { continue }
## 13. Issue Flagging Workflow
`FlagIssueView` — presented as sheet from `ExecuteInspectionView`. Sets `inspectionLocalId = inspection.localId` and appends to `inspection.localIssues`. Creates `PendingPhoto` with `entityType = "issue"`.
`FlagIssueView` — presented as sheet from `ExecuteInspectionView`. Sets `inspectionLocalId = inspection.localId`, `areaServerId = inspection.areaServerId`, and appends to `inspection.localIssues`. Creates `PendingPhoto` with `entityType = "issue"`. `areaServerId` is sent as `area_id` in `submitIssue()`.
**Issue sync guard:** If parent `inspection.syncStatus == "failed"`, issue is immediately marked `"failed"` — prevents orphaned server records.
@@ -623,6 +633,13 @@ Deletes `LocalIssue` where `serverId != nil`. Preserves `serverId == nil` record
| 43 | **`processIssueQueue` clears `photoLocalPaths` after successful submit** | Prevents `IssueDetailView` from rendering a duplicate "local photos" section alongside the server photos section for synced issues. |
| 44 | **`StandaloneIssueView` uses `inspectionLocalId = ""`** | Same pattern as server-pulled issues. `processIssueQueue`'s parent-inspection guard evaluates `parent?.syncStatus == "failed"``false` for `""`, so standalone issues submit normally. |
| 45 | **Facility lists deduplicate by `serverId` at both storage and display layers** | Storage: `pullReferenceData()` deduplicates server response before upsert. Display: `filteredFacilities` in both `StartInspectionView` and `StandaloneIssueView` uses `filter { seen.insert($0.serverId).inserted }`. |
| 46 | **`LocalIssue` Phase AE fields all use nil-default optionals** | `String?`, `Date?` optionals default to nil. SwiftData lightweight migration supports nil-default optional properties without a migration plan. |
| 47 | **`IssueDetailView` comments block must be inside `List { }`** | `.navigationTitle` and `.task` must chain on the `List` view. Placing the `if sync.isOnline { }` comments block outside `List` causes `Instance member navigationTitle cannot be used on type View` build error. |
| 48 | **`IssuesListView` filters resolved in Swift, not via `#Predicate`** | `@Query` returns `allIssues`; computed `var issues` filters `$0.issueStatus != "resolved"`. `#Predicate` with string literals on `LocalIssue` is unreliable under Xcode 26 `SWIFT_DEFAULT_ACTOR_ISOLATION` (rule 3). |
| 49 | **`SyncManager.fetchDashboardStats()` is best-effort — never blocks pipeline** | Called last in `triggerSync()`. Failure leaves `dashboardStats` nil; UI shows placeholder. Never propagates errors. |
| 50 | **`NotificationsView.markNotificationsViewed()` on both `.onAppear` and sidebar tap** | Sidebar tap calls `sync.markNotificationsViewed()` inline to clear the badge immediately without waiting for navigation. |
| 51 | **`FlagIssueView.submitIssue()` must copy `inspection.areaServerId` to `issue.areaServerId`** | Without this, the server cannot link the issue to the correct area. `APIClient.submitIssue` sends `area_id` only when `issue.areaServerId` is non-nil. |
| 52 | **`LocalIssue.swift` zip delivery must include all Phase AE fields** | When packaging, copy from the working-tree file and verify every field with `grep` before zipping. Partial field sets cause `SyncManager` build failures. The recurring hotfix pattern traces to this: each phase patched the working tree but the prior phases file was not in working tree. Fix: always `cp` back immediately after creating a phase file. |
---