05/02 Phase C

This commit is contained in:
Nguyen Ngo
2026-05-03 08:31:25 -04:00
parent 8bdcbecd73
commit 59e698b5f7
6 changed files with 451 additions and 147 deletions
+20 -19
View File
@@ -1,7 +1,7 @@
// API/APIClient.swift
// -------------------
// Central HTTP client for all JQC API calls.
// Phase B adds: uploadPhoto(), submitInspection(), submitIssue()
// Phase C adds: fetchInspectionHistory()
import Foundation
import Combine
@@ -99,10 +99,24 @@ actor APIClient {
return try await request(endpoint, method: "POST", body: body)
}
// Inspection History (Phase C)
/// Fetch the inspector's synced inspection history from the server.
/// Returns up to `limit` records starting at `offset`.
func fetchInspectionHistory(
limit: Int = 50,
offset: Int = 0,
facilityId: Int? = nil
) async throws -> InspectionHistoryResponseData {
var endpoint = "/api/v1/inspections?limit=\(limit)&offset=\(offset)&status=completed"
if let fid = facilityId {
endpoint += "&facility_id=\(fid)"
}
return try await request(endpoint)
}
// Photo Upload (multipart/form-data)
/// Upload a local photo file to the server.
/// Returns the server_path string on success.
func uploadPhoto(localPath: String, entityType: String) async throws -> String {
guard let url = URL(string: baseURL + "/api/v1/photos/upload") else {
throw APIError.invalidURL
@@ -115,12 +129,10 @@ actor APIClient {
let boundary = "Boundary-\(UUID().uuidString)"
var body = Data()
// -- entity_type field
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"entity_type\"\r\n\r\n".data(using: .utf8)!)
body.append("\(entityType)\r\n".data(using: .utf8)!)
// -- file field
let filename = URL(fileURLWithPath: localPath).lastPathComponent
let ext = (filename as NSString).pathExtension.lowercased()
let mimeType = ext == "png" ? "image/png" : "image/jpeg"
@@ -171,8 +183,6 @@ actor APIClient {
// Submit Inspection
/// Submit a completed LocalInspection to the server.
/// Returns the server-assigned inspection ID.
func submitInspection(_ inspection: LocalInspection) async throws -> Int {
var body: [String: Any] = [
"template_id": inspection.templateServerId,
@@ -183,12 +193,8 @@ actor APIClient {
"overall_score": inspection.overallScore as Any,
]
if let areaId = inspection.areaServerId {
body["area_id"] = areaId
}
if !inspection.inspectorNotes.isEmpty {
body["notes"] = inspection.inspectorNotes
}
if let areaId = inspection.areaServerId { body["area_id"] = areaId }
if !inspection.inspectorNotes.isEmpty { body["notes"] = inspection.inspectorNotes }
let formatter = ISO8601DateFormatter()
body["inspection_date"] = formatter.string(from: inspection.inspectionDate)
@@ -207,8 +213,6 @@ actor APIClient {
// Submit Issue
/// Submit a LocalIssue to the server.
/// Returns the server-assigned issue ID.
func submitIssue(_ issue: LocalIssue) async throws -> Int {
var body: [String: Any] = [
"area_id": issue.areaServerId,
@@ -217,12 +221,9 @@ actor APIClient {
"mobile_local_id": issue.localId,
]
// Link to server inspection if already synced
// (inspection must be synced before its issues)
if let inspServerId = (issue.inspection?.serverId) {
if let inspServerId = issue.inspection?.serverId {
body["inspection_id"] = inspServerId
}
if let serverPhotoPath = issue.photoServerPath {
body["photo_path"] = serverPhotoPath
}