iOS parity fixes from Android comparison
- Add profile endpoints (getProfile/updateProfile) to APIService - Fix beacon dwell time: 5 → 30 samples to match Android (~3s) - Make chat WebSocket dev-aware (uses IS_DEV flag) - Add activeTaskCount field to Employment model - Fix categoryName: remove incorrect fallback to taskTypeName Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c71b9f7dea
commit
d69270a4af
5 changed files with 62 additions and 3 deletions
|
|
@ -8,6 +8,7 @@ struct Employment: Identifiable {
|
|||
let businessCity: String
|
||||
let employeeStatusId: Int
|
||||
let pendingTaskCount: Int
|
||||
let activeTaskCount: Int
|
||||
|
||||
var id: Int { employeeId }
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ struct Employment: Identifiable {
|
|||
// Match Flutter: read EmployeeStatusID first (server sends StatusID, which may differ)
|
||||
employeeStatusId = WorkTask.parseInt(json["EmployeeStatusID"] ?? json["StatusID"]) ?? 0
|
||||
pendingTaskCount = WorkTask.parseInt(json["PendingTaskCount"]) ?? 0
|
||||
activeTaskCount = WorkTask.parseInt(json["ActiveTaskCount"]) ?? 0
|
||||
}
|
||||
|
||||
var statusName: String {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ struct WorkTask: Identifiable {
|
|||
sourceType = (json["SourceType"] as? String) ?? (json["TaskSourceType"] as? String) ?? ""
|
||||
sourceId = Self.parseInt(json["SourceID"] ?? json["TaskSourceID"]) ?? 0
|
||||
// Server key varies: CategoryName, Name, TaskCategoryName — try all
|
||||
categoryName = Self.nonEmpty(json["CategoryName"]) ?? Self.nonEmpty(json["Name"]) ?? Self.nonEmpty(json["TaskCategoryName"]) ?? Self.nonEmpty(json["TaskTypeName"]) ?? "Uncategorized"
|
||||
// NOTE: Do NOT fallback to TaskTypeName as it's a different field
|
||||
categoryName = Self.nonEmpty(json["CategoryName"]) ?? Self.nonEmpty(json["Name"]) ?? Self.nonEmpty(json["TaskCategoryName"]) ?? "Uncategorized"
|
||||
// Category color as fallback
|
||||
categoryColor = Self.nonEmpty(json["CategoryColor"]) ?? Self.nonEmpty(json["Color"]) ?? Self.nonEmpty(json["TaskCategoryColor"]) ?? "#888888"
|
||||
taskTypeName = (json["TaskTypeName"] as? String) ?? ""
|
||||
|
|
|
|||
|
|
@ -347,6 +347,59 @@ actor APIService {
|
|||
businessId = 0
|
||||
}
|
||||
|
||||
// MARK: - Profile
|
||||
|
||||
struct UserProfile {
|
||||
let userId: Int
|
||||
let firstName: String
|
||||
let lastName: String
|
||||
let email: String
|
||||
let phone: String
|
||||
let photoUrl: String
|
||||
}
|
||||
|
||||
func getProfile() async throws -> UserProfile {
|
||||
guard let uid = userId, uid > 0 else {
|
||||
throw APIError.serverError("User not logged in")
|
||||
}
|
||||
|
||||
let json = try await postJSON("/auth/profile.cfm", payload: [
|
||||
"UserID": uid
|
||||
])
|
||||
|
||||
guard ok(json) else {
|
||||
throw APIError.serverError("Failed to load profile: \(err(json))")
|
||||
}
|
||||
|
||||
let data = json["DATA"] as? [String: Any] ?? json
|
||||
return UserProfile(
|
||||
userId: (data["UserID"] as? Int) ?? (data["USERID"] as? Int) ?? uid,
|
||||
firstName: (data["FirstName"] as? String) ?? (data["FIRSTNAME"] as? String) ?? "",
|
||||
lastName: (data["LastName"] as? String) ?? (data["LASTNAME"] as? String) ?? "",
|
||||
email: (data["Email"] as? String) ?? (data["EMAIL"] as? String) ?? (data["EmailAddress"] as? String) ?? "",
|
||||
phone: (data["Phone"] as? String) ?? (data["PHONE"] as? String) ?? (data["ContactNumber"] as? String) ?? "",
|
||||
photoUrl: Self.resolvePhotoUrl((data["PhotoUrl"] as? String) ?? (data["PHOTOURL"] as? String) ?? "")
|
||||
)
|
||||
}
|
||||
|
||||
func updateProfile(firstName: String? = nil, lastName: String? = nil, email: String? = nil, phone: String? = nil) async throws {
|
||||
guard let uid = userId, uid > 0 else {
|
||||
throw APIError.serverError("User not logged in")
|
||||
}
|
||||
|
||||
var payload: [String: Any] = ["UserID": uid]
|
||||
if let fn = firstName { payload["FirstName"] = fn }
|
||||
if let ln = lastName { payload["LastName"] = ln }
|
||||
if let em = email { payload["Email"] = em }
|
||||
if let ph = phone { payload["Phone"] = ph }
|
||||
|
||||
let json = try await postJSON("/auth/profile.cfm", payload: payload)
|
||||
|
||||
guard ok(json) else {
|
||||
throw APIError.serverError("Failed to update profile: \(err(json))")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Businesses
|
||||
|
||||
func getMyBusinesses() async throws -> [Employment] {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ final class BeaconScanner: NSObject, ObservableObject {
|
|||
|
||||
// RSSI samples for dwell time enforcement
|
||||
private var rssiSamples: [Int] = []
|
||||
private let minSamplesToConfirm = 5 // ~5 seconds at 1Hz ranging
|
||||
private let minSamplesToConfirm = 30 // ~3 seconds to match Android (30 samples at ~100ms)
|
||||
private let rssiThreshold = -75
|
||||
|
||||
// Track resolved beacons to avoid repeated API calls
|
||||
|
|
|
|||
|
|
@ -28,7 +28,10 @@ struct TypingEvent {
|
|||
|
||||
@MainActor
|
||||
final class ChatService: ObservableObject {
|
||||
private static let wsBaseURL = "wss://app.payfrit.com:3001"
|
||||
// Dev-aware WebSocket URL - matches API dev/prod switching
|
||||
private static var wsBaseURL: String {
|
||||
IS_DEV ? "wss://dev.payfrit.com:3001" : "wss://app.payfrit.com:3001"
|
||||
}
|
||||
|
||||
@Published var isConnected = false
|
||||
@Published var chatClosed = false
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue