diff --git a/PayfritWorks/Models/OrderLineItem.swift b/PayfritWorks/Models/OrderLineItem.swift index 5190873..af6bc89 100644 --- a/PayfritWorks/Models/OrderLineItem.swift +++ b/PayfritWorks/Models/OrderLineItem.swift @@ -9,6 +9,9 @@ struct OrderLineItem: Identifiable { let quantity: Int let remark: String let isModifier: Bool + let isCheckedByDefault: Bool + let isInvertedGroup: Bool + let removedDefaults: [String] // Array of item names that were removed from inverted groups var id: Int { lineItemId } @@ -26,5 +29,21 @@ struct OrderLineItem: Identifiable { if let b = json["IsModifier"] as? Bool { isModifier = b } else if let i = json["IsModifier"] as? Int { isModifier = i == 1 } else { isModifier = false } + + // Inverted group fields + if let b = json["IsCheckedByDefault"] as? Bool { isCheckedByDefault = b } + else if let i = json["IsCheckedByDefault"] as? Int { isCheckedByDefault = i == 1 } + else { isCheckedByDefault = false } + + if let b = json["IsInvertedGroup"] as? Bool { isInvertedGroup = b } + else if let i = json["IsInvertedGroup"] as? Int { isInvertedGroup = i == 1 } + else { isInvertedGroup = false } + + // Removed defaults array + if let arr = json["RemovedDefaults"] as? [String] { + removedDefaults = arr + } else { + removedDefaults = [] + } } } diff --git a/PayfritWorks/Models/TierStatus.swift b/PayfritWorks/Models/TierStatus.swift index 1d672e9..f4d08fc 100644 --- a/PayfritWorks/Models/TierStatus.swift +++ b/PayfritWorks/Models/TierStatus.swift @@ -1,6 +1,6 @@ import Foundation -// MARK: - Tier Status (from tierStatus.cfm) +// MARK: - Tier Status (from tierStatus.php) struct TierStatus: Codable { var tier: Int @@ -98,7 +98,7 @@ struct TierStatus: Codable { } } -// MARK: - Ledger Response (from ledger.cfm) +// MARK: - Ledger Response (from ledger.php) struct LedgerResponse: Codable { var entries: [LedgerEntry] diff --git a/PayfritWorks/Services/APIService.swift b/PayfritWorks/Services/APIService.swift index 68dc29d..fd15218 100644 --- a/PayfritWorks/Services/APIService.swift +++ b/PayfritWorks/Services/APIService.swift @@ -253,7 +253,7 @@ actor APIService { // MARK: - Auth func login(username: String, password: String) async throws -> LoginResponse { - let json = try await postJSON("/auth/login.cfm", payload: [ + let json = try await postJSON("/auth/login.php", payload: [ "username": username, "password": password ]) @@ -293,7 +293,7 @@ actor APIService { // MARK: - OTP Login func sendLoginOtp(phone: String) async throws -> SendOtpResponse { - let json = try await postJSON("/auth/loginOTP.cfm", payload: [ + let json = try await postJSON("/auth/loginOTP.php", payload: [ "phone": phone ]) @@ -309,7 +309,7 @@ actor APIService { } func verifyLoginOtp(uuid: String, otp: String) async throws -> LoginResponse { - let json = try await postJSON("/auth/verifyLoginOTP.cfm", payload: [ + let json = try await postJSON("/auth/verifyLoginOTP.php", payload: [ "uuid": uuid, "otp": otp ]) @@ -363,7 +363,7 @@ actor APIService { throw APIError.serverError("User not logged in") } - let json = try await postJSON("/auth/profile.cfm", payload: [ + let json = try await postJSON("/auth/profile.php", payload: [ "UserID": uid ]) @@ -393,7 +393,7 @@ actor APIService { if let em = email { payload["Email"] = em } if let ph = phone { payload["Phone"] = ph } - let json = try await postJSON("/auth/profile.cfm", payload: payload) + let json = try await postJSON("/auth/profile.php", payload: payload) guard ok(json) else { throw APIError.serverError("Failed to update profile: \(err(json))") @@ -407,7 +407,7 @@ actor APIService { throw APIError.serverError("User not logged in") } - let json = try await postJSON("/workers/myBusinesses.cfm", payload: [ + let json = try await postJSON("/workers/myBusinesses.php", payload: [ "UserID": uid ]) @@ -429,7 +429,7 @@ actor APIService { payload["CategoryID"] = cid } - let json = try await postJSON("/tasks/listPending.cfm", payload: payload) + let json = try await postJSON("/tasks/listPending.php", payload: payload) guard ok(json) else { throw APIError.serverError("Failed to load tasks: \(err(json))") } @@ -440,7 +440,7 @@ actor APIService { func acceptTask(taskId: Int) async throws { // Flutter only sends TaskID + BusinessID (no UserID) - let json = try await postJSON("/tasks/accept.cfm", payload: [ + let json = try await postJSON("/tasks/accept.php", payload: [ "TaskID": taskId, "BusinessID": businessId ]) @@ -463,7 +463,7 @@ actor APIService { payload["BusinessID"] = businessId } - let json = try await postJSON("/tasks/listMine.cfm", payload: payload) + let json = try await postJSON("/tasks/listMine.php", payload: payload) guard ok(json) else { throw APIError.serverError("Failed to load my tasks: \(err(json))") } @@ -483,7 +483,7 @@ actor APIService { if cancelOrder { payload["CancelOrder"] = true } - let json = try await postJSON("/tasks/complete.cfm", payload: payload) + let json = try await postJSON("/tasks/complete.php", payload: payload) guard ok(json) else { throw APIError.serverError("Failed to complete task: \(err(json))") } @@ -493,14 +493,14 @@ actor APIService { let payload: [String: Any] = [ "TaskID": taskId ] - let json = try await postJSON("/tasks/completeChat.cfm", payload: payload) + let json = try await postJSON("/tasks/completeChat.php", payload: payload) guard ok(json) else { throw APIError.serverError("Close chat failed: \(err(json))") } } func getTaskDetails(taskId: Int) async throws -> TaskDetails { - let json = try await postJSON("/tasks/getDetails.cfm", payload: [ + let json = try await postJSON("/tasks/getDetails.php", payload: [ "TaskID": taskId ]) guard ok(json) else { @@ -553,7 +553,7 @@ actor APIService { payload["AfterMessageID"] = after } - let json = try await postJSON("/chat/getMessages.cfm", payload: payload) + let json = try await postJSON("/chat/getMessages.php", payload: payload) guard ok(json) else { throw APIError.serverError("Failed to load chat messages") } @@ -573,7 +573,7 @@ actor APIService { if let uid = userId { payload["UserID"] = uid } if let st = senderType { payload["SenderType"] = st } - let json = try await postJSON("/chat/sendMessage.cfm", payload: payload) + let json = try await postJSON("/chat/sendMessage.php", payload: payload) guard ok(json) else { throw APIError.serverError("Failed to send message") } @@ -582,7 +582,7 @@ actor APIService { } func markChatMessagesRead(taskId: Int, readerType: String) async throws { - let json = try await postJSON("/chat/markRead.cfm", payload: [ + let json = try await postJSON("/chat/markRead.php", payload: [ "TaskID": taskId, "ReaderType": readerType ]) @@ -594,7 +594,7 @@ actor APIService { // MARK: - Payout / Tier Endpoints func getTierStatus() async throws -> TierStatus { - let json = try await postJSON("/workers/tierStatus.cfm", payload: [ + let json = try await postJSON("/workers/tierStatus.php", payload: [ "UserID": userId ?? 0 ]) guard ok(json) else { @@ -605,7 +605,7 @@ actor APIService { } func createStripeAccount() async throws -> String { - let json = try await postJSON("/workers/createAccount.cfm", payload: [ + let json = try await postJSON("/workers/createAccount.php", payload: [ "UserID": userId ?? 0 ]) guard ok(json) else { @@ -615,7 +615,7 @@ actor APIService { } func getOnboardingLink() async throws -> String { - let json = try await postJSON("/workers/onboardingLink.cfm", payload: [ + let json = try await postJSON("/workers/onboardingLink.php", payload: [ "UserID": userId ?? 0 ]) guard ok(json) else { @@ -625,7 +625,7 @@ actor APIService { } func getEarlyUnlockUrl() async throws -> String { - let json = try await postJSON("/workers/earlyUnlock.cfm", payload: [ + let json = try await postJSON("/workers/earlyUnlock.php", payload: [ "UserID": userId ?? 0 ]) guard ok(json) else { @@ -635,7 +635,7 @@ actor APIService { } func getLedger() async throws -> LedgerResponse { - let json = try await postJSON("/workers/ledger.cfm", payload: [ + let json = try await postJSON("/workers/ledger.php", payload: [ "UserID": userId ?? 0 ]) guard ok(json) else { @@ -648,7 +648,7 @@ actor APIService { // MARK: - Avatar func getAvatarUrl() async throws -> String? { - let json = try await getJSON("/auth/avatar.cfm") + let json = try await getJSON("/auth/avatar.php") print("[Avatar] Response: \(json)") guard ok(json) else { print("[Avatar] Response not OK") @@ -681,7 +681,7 @@ actor APIService { /// Get avatar URL for any user by their userId func getUserAvatarUrl(userId: Int) async throws -> String? { // Try the avatar endpoint with UserID - let json = try await postJSON("/auth/avatar.cfm", payload: ["UserID": userId]) + let json = try await postJSON("/auth/avatar.php", payload: ["UserID": userId]) print("[Avatar] getUserAvatarUrl(\(userId)) Response: \(json)") let data = json["DATA"] as? [String: Any] ?? json @@ -712,7 +712,7 @@ actor APIService { // MARK: - Beacon Sharding func resolveServicePoint(uuid: String, major: Int, minor: Int) async throws -> Int { - let json = try await postJSON("/beacon-sharding/resolve_servicepoint.cfm", payload: [ + let json = try await postJSON("/beacon-sharding/resolve_servicepoint.php", payload: [ "UUID": uuid, "Major": major, "Minor": minor @@ -732,7 +732,7 @@ actor APIService { } func resolveBusiness(uuid: String, major: Int) async throws -> (businessId: Int, businessName: String) { - let json = try await postJSON("/beacon-sharding/resolve_business.cfm", payload: [ + let json = try await postJSON("/beacon-sharding/resolve_business.php", payload: [ "UUID": uuid, "Major": major ]) @@ -774,7 +774,7 @@ actor APIService { // MARK: - About Info func getAboutInfo() async throws -> AboutInfo { - let json = try await getJSON("app/about.cfm") + let json = try await getJSON("app/about.php") guard ok(json) else { throw APIError.serverError(err(json)) diff --git a/PayfritWorks/Views/BusinessSelectionScreen.swift b/PayfritWorks/Views/BusinessSelectionScreen.swift index c4b8e10..6921937 100644 --- a/PayfritWorks/Views/BusinessSelectionScreen.swift +++ b/PayfritWorks/Views/BusinessSelectionScreen.swift @@ -309,15 +309,15 @@ struct BusinessSelectionScreen: View { var text = "=== RAW API RESPONSES ===\n\n" text += "UserID: \(uid), BusinessID: \(bid)\n\n" - text += "--- /workers/myBusinesses.cfm ---\n" + text += "--- /workers/myBusinesses.php ---\n" let bizRaw = await APIService.shared.debugRawJSON( - "/workers/myBusinesses.cfm", payload: ["UserID": uid]) + "/workers/myBusinesses.php", payload: ["UserID": uid]) text += bizRaw + "\n\n" if bid > 0 { - text += "--- /tasks/listPending.cfm ---\n" + text += "--- /tasks/listPending.php ---\n" let taskRaw = await APIService.shared.debugRawJSON( - "/tasks/listPending.cfm", payload: ["BusinessID": bid]) + "/tasks/listPending.php", payload: ["BusinessID": bid]) text += taskRaw + "\n\n" } diff --git a/PayfritWorks/Views/TaskDetailScreen.swift b/PayfritWorks/Views/TaskDetailScreen.swift index 91edef6..ed64745 100644 --- a/PayfritWorks/Views/TaskDetailScreen.swift +++ b/PayfritWorks/Views/TaskDetailScreen.swift @@ -518,14 +518,29 @@ struct TaskDetailScreen: View { } ForEach(modifiers) { mod in + // For inverted groups: show removed defaults as "NO {name}" + if mod.isInvertedGroup && !mod.removedDefaults.isEmpty { + ForEach(mod.removedDefaults, id: \.self) { removedName in + Text("- NO \(removedName)") + .font(.caption) + .foregroundColor(.red) + } + } + let children = d.lineItems.filter { $0.parentLineItemId == mod.lineItemId } if !children.isEmpty { ForEach(children) { child in - Text("- \(mod.itemName): \(child.itemName)") - .font(.caption) - .foregroundColor(.secondary) + // Skip default items in inverted groups (they're unchanged) + if mod.isInvertedGroup && child.isCheckedByDefault { + // Don't display - this is an unchanged default + } else { + Text("- \(mod.itemName): \(child.itemName)") + .font(.caption) + .foregroundColor(.secondary) + } } - } else { + } else if !mod.isInvertedGroup { + // Regular modifier (not inverted group) Text("- \(mod.itemName)") .font(.caption) .foregroundColor(.secondary) diff --git a/test-walkthrough.html b/test-walkthrough.html index e16bb31..6158e9b 100644 --- a/test-walkthrough.html +++ b/test-walkthrough.html @@ -461,7 +461,7 @@ const sections = [ preconditions: 'Logged in, user has businesses.', action: 'After login, observe Business Selection screen.', expected: 'Nav title "Select Business". List of businesses with: initial letter in colored square, business name, address/city, status badge (Pending/Active/Inactive), task count or green checkmark. Toolbar: refresh button and ellipsis menu.', - onFail: 'Screenshot. Check API response from /workers/myBusinesses.cfm.' + onFail: 'Screenshot. Check API response from /workers/myBusinesses.php.' }, { id: 'BS02', @@ -532,7 +532,7 @@ const sections = [ title: 'Toolbar menu — Debug API', preconditions: 'Business Selection screen.', action: 'Tap ellipsis menu → "Debug API".', - expected: 'Modal sheet appears showing raw JSON from /workers/myBusinesses.cfm and /tasks/listPending.cfm. Monospaced text, "Done" button to dismiss.', + expected: 'Modal sheet appears showing raw JSON from /workers/myBusinesses.php and /tasks/listPending.php. Monospaced text, "Done" button to dismiss.', onFail: 'Screenshot.' }, { @@ -570,7 +570,7 @@ const sections = [ preconditions: 'Selected a business with pending tasks.', action: 'Observe the task list screen.', expected: 'Nav title = business name. List of task cards with: color bar, category icon (chat = bubble icon), title, location (mappin or bicycle icon), category badge, chat badge if applicable, time ago label.', - onFail: 'Screenshot. Check /tasks/listPending.cfm response.' + onFail: 'Screenshot. Check /tasks/listPending.php response.' }, { id: 'TL02', @@ -655,7 +655,7 @@ const sections = [ preconditions: 'Tapped a task from task list.', action: 'Observe the task detail screen.', expected: 'Loading spinner, then content: customer section (avatar/name/phone), location section (table or delivery), nav bar colored with category color. "Accept Task" button at bottom.', - onFail: 'Screenshot. Check /tasks/getDetails.cfm response.' + onFail: 'Screenshot. Check /tasks/getDetails.php response.' }, { id: 'TD02', @@ -750,7 +750,7 @@ const sections = [ title: 'Accept Task — confirm', preconditions: 'Accept alert shown.', action: 'Tap "Accept".', - expected: 'API call to /tasks/accept.cfm. On success, screen dismisses back to task list. Task removed from pending list.', + expected: 'API call to /tasks/accept.php. On success, screen dismisses back to task list. Task removed from pending list.', onFail: 'Screenshot. Check API response.' }, { @@ -782,7 +782,7 @@ const sections = [ title: 'Complete Task — confirm', preconditions: 'Complete alert shown.', action: 'Tap "Complete".', - expected: 'API call to /tasks/complete.cfm. On success, dismisses back to My Tasks. Task moves to Completed filter.', + expected: 'API call to /tasks/complete.php. On success, dismisses back to My Tasks. Task moves to Completed filter.', onFail: 'Screenshot.' }, { @@ -844,7 +844,7 @@ const sections = [ preconditions: 'Worker has accepted tasks.', action: 'Select Active tab.', expected: 'Shows tasks with status Accepted/In Progress. Each card shows: color bar, title, location, category badge, time ago, status badge.', - onFail: 'Screenshot. Verify /tasks/listMine.cfm?FilterType=active response.' + onFail: 'Screenshot. Verify /tasks/listMine.php?FilterType=active response.' }, { id: 'MT03', @@ -1008,7 +1008,7 @@ const sections = [ title: 'HTTP fallback when WebSocket disconnected', preconditions: 'WebSocket not connected.', action: 'Send a message.', - expected: 'Message sent via HTTP POST to /chat/sendMessage.cfm. Messages poll via 3-second timer.', + expected: 'Message sent via HTTP POST to /chat/sendMessage.php. Messages poll via 3-second timer.', onFail: 'Note if message fails or gets lost.' }, { @@ -1030,7 +1030,7 @@ const sections = [ preconditions: 'Navigated to Account from menu.', action: 'Observe Account screen.', expected: 'Nav title "Account". Loading spinner, then: Payout Status card, Activation card, Earnings card, Logout button. FAB in bottom-right.', - onFail: 'Screenshot. Check /workers/tierStatus.cfm response.' + onFail: 'Screenshot. Check /workers/tierStatus.php response.' }, { id: 'AC02', @@ -1085,7 +1085,7 @@ const sections = [ title: 'Early unlock button', preconditions: 'Activation not complete.', action: 'Tap "Complete activation now (optional)".', - expected: 'API call to /workers/earlyUnlock.cfm. Opens URL in Safari. Reloads after return.', + expected: 'API call to /workers/earlyUnlock.php. Opens URL in Safari. Reloads after return.', onFail: 'Screenshot.' }, {