Fix
This commit is contained in:
@@ -18,6 +18,8 @@ struct OrderTrackingView: View {
|
||||
@State var order: PublicOrderResult? = nil
|
||||
@State var tracker = OrderRealtimeTracker()
|
||||
@State var showCancellationReason = false
|
||||
@State var reviewDraft: ReviewDraft? = nil
|
||||
@State var didSaveReviewForCurrentOrder = false
|
||||
|
||||
var body: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
@@ -27,7 +29,11 @@ struct OrderTrackingView: View {
|
||||
statusBanner
|
||||
timelineSection
|
||||
placeholderCard
|
||||
contactButton
|
||||
if shouldShowReviewButton {
|
||||
reviewButton
|
||||
} else {
|
||||
contactButton
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.top, 14)
|
||||
@@ -53,6 +59,16 @@ struct OrderTrackingView: View {
|
||||
.onDisappear {
|
||||
tracker.stop()
|
||||
}
|
||||
.navigationDestination(item: $reviewDraft) { draft in
|
||||
MyReviewsView(initialOrder: draft)
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .orderReviewDidSave)) { payload in
|
||||
guard let orderIdFromEvent = payload.userInfo?["orderId"] as? String else { return }
|
||||
let currentOrderId = (order?.id ?? orderId).trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||
if orderIdFromEvent.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == currentOrderId {
|
||||
didSaveReviewForCurrentOrder = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var topHeader: some View {
|
||||
@@ -217,6 +233,19 @@ struct OrderTrackingView: View {
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
private var reviewButton: some View {
|
||||
Button("AVALIAR PEDIDO") {
|
||||
guard let reviewTargetDraft else { return }
|
||||
reviewDraft = reviewTargetDraft
|
||||
}
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(Color(hex: "#0E1A06"))
|
||||
.frame(maxWidth: .infinity, minHeight: 56)
|
||||
.background(Color(hex: "#7CF02A"))
|
||||
.clipShape(Capsule())
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
private func statusBadge(title: String, fg: Color, bg: Color, icon: String) -> some View {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: icon)
|
||||
@@ -444,12 +473,43 @@ struct OrderTrackingView: View {
|
||||
return customerOtpSubtitle
|
||||
}
|
||||
let label = (eventLabel ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if label.isEmpty == false {
|
||||
if shouldUseTimelineEventLabel(label, fallback: fallback) {
|
||||
return label
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
private func shouldUseTimelineEventLabel(_ label: String, fallback: String) -> Bool {
|
||||
guard label.isEmpty == false else { return false }
|
||||
|
||||
let foldedLabel = label
|
||||
.folding(options: [.diacriticInsensitive, .caseInsensitive], locale: .current)
|
||||
.lowercased()
|
||||
let foldedFallback = fallback
|
||||
.folding(options: [.diacriticInsensitive, .caseInsensitive], locale: .current)
|
||||
.lowercased()
|
||||
|
||||
if foldedLabel == foldedFallback { return false }
|
||||
|
||||
let englishHints = [
|
||||
"order",
|
||||
"confirmed",
|
||||
"in progress",
|
||||
"progress",
|
||||
"delivery",
|
||||
"delivered",
|
||||
"ready",
|
||||
"sent",
|
||||
"out for",
|
||||
"began"
|
||||
]
|
||||
if englishHints.contains(where: { foldedLabel.contains($0) }) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private var customerOtpSubtitle: String {
|
||||
if let otp = customerOtpCode {
|
||||
return "Código para o entregador: \(otp)"
|
||||
@@ -548,6 +608,57 @@ struct OrderTrackingView: View {
|
||||
return isCanceled == false && isWaitingPayment == false && stepIndex >= terminalIndex
|
||||
}
|
||||
|
||||
private var shouldShowReviewButton: Bool {
|
||||
guard isCompletedOrder else { return false }
|
||||
guard isCanceled == false else { return false }
|
||||
guard let reviewTargetDraft else { return false }
|
||||
if didSaveReviewForCurrentOrder { return false }
|
||||
if hasPersistedReviewForCurrentOrder { return false }
|
||||
return order?.review == nil
|
||||
}
|
||||
|
||||
private var hasPersistedReviewForCurrentOrder: Bool {
|
||||
reviewIdCandidates.contains { candidate in
|
||||
SessionStateStore.hasOrderReview(orderId: candidate)
|
||||
}
|
||||
}
|
||||
|
||||
private var reviewIdCandidates: [String] {
|
||||
let values = [
|
||||
orderId,
|
||||
order?.id,
|
||||
order?.realId,
|
||||
order?.shortId
|
||||
]
|
||||
var unique: [String] = []
|
||||
var seen = Set<String>()
|
||||
for raw in values {
|
||||
let normalized = (raw ?? "")
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.lowercased()
|
||||
guard normalized.isEmpty == false, seen.contains(normalized) == false else { continue }
|
||||
seen.insert(normalized)
|
||||
unique.append(normalized)
|
||||
}
|
||||
return unique
|
||||
}
|
||||
|
||||
private var reviewTargetDraft: ReviewDraft? {
|
||||
let idFromOrder = (order?.id ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let id = idFromOrder.isEmpty ? orderId : idFromOrder
|
||||
guard id.isEmpty == false else { return nil }
|
||||
|
||||
return ReviewDraft(
|
||||
orderId: id,
|
||||
storeId: order?.storeId,
|
||||
shortId: order?.shortId ?? initialShortId,
|
||||
storeName: order?.storeName,
|
||||
storeLogoURL: order?.storeLogoURL,
|
||||
createdAt: order?.createdAt,
|
||||
total: order?.total
|
||||
)
|
||||
}
|
||||
|
||||
private var cancellationReasonText: String {
|
||||
let value = (order?.cancellationReason ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return value.isEmpty ? "Sem detalhe informado." : value
|
||||
@@ -590,7 +701,7 @@ struct OrderTrackingView: View {
|
||||
case 1:
|
||||
return "tracking-preparing"
|
||||
case 2:
|
||||
return isPickup ? "tracking-ready" : "tracking-preparing"
|
||||
return "tracking-ready"
|
||||
case 3:
|
||||
return "tracking-delivering"
|
||||
default:
|
||||
@@ -636,24 +747,21 @@ struct OrderTrackingView: View {
|
||||
|
||||
@MainActor
|
||||
private func loadInitialOrder() async {
|
||||
if let cached = SessionStateStore.loadTrackedOrder(orderId: orderId) {
|
||||
order = cached
|
||||
isLoading = false
|
||||
}
|
||||
logger.info("OrderTracking initial fetch orderId=\(orderId, privacy: .public)")
|
||||
|
||||
do {
|
||||
let response = try await ApiService().publicOrder(orderId: orderId)
|
||||
if response.error {
|
||||
errorMessage = response.message ?? "Não foi possível carregar o pedido."
|
||||
logger.error("OrderTracking initial fetch API error orderId=\(orderId, privacy: .public) message=\((response.message ?? "unknown"), privacy: .public)")
|
||||
} else if let result = response.result {
|
||||
order = result
|
||||
SessionStateStore.saveTrackedOrder(result)
|
||||
errorMessage = nil
|
||||
logger.info("OrderTracking initial fetch success orderId=\(orderId, privacy: .public) status=\((result.status ?? "nil"), privacy: .public) paymentStatus=\((result.paymentStatus ?? "nil"), privacy: .public)")
|
||||
}
|
||||
} catch {
|
||||
if order == nil {
|
||||
errorMessage = "Não foi possível carregar o pedido."
|
||||
}
|
||||
errorMessage = "Não foi possível carregar o pedido."
|
||||
logger.error("OrderTracking initial fetch failure orderId=\(orderId, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
isLoading = false
|
||||
|
||||
Reference in New Issue
Block a user