feat(orders): separar reviews e criar detalhe de pedido

This commit is contained in:
Daniel Arantes Loverde
2026-03-05 10:10:03 -03:00
parent 56d6ff7223
commit a8e3631177
3 changed files with 586 additions and 0 deletions

View File

@@ -4,6 +4,7 @@ struct AppOrderSummary: Decodable, Identifiable {
let id: String
let orderId: String?
let realId: String?
let storeId: String?
let shortId: String?
let total: Double?
let status: String?
@@ -14,6 +15,7 @@ struct AppOrderSummary: Decodable, Identifiable {
let paymentMethod: String?
let deliveryType: String?
let storeName: String?
let storeLogoURL: String?
let createdAt: String?
let updatedAt: String?
@@ -21,6 +23,8 @@ struct AppOrderSummary: Decodable, Identifiable {
case id
case orderId
case realId
case storeId
case store_id
case shortId
case total
case status
@@ -31,6 +35,9 @@ struct AppOrderSummary: Decodable, Identifiable {
case paymentMethod
case deliveryType
case storeName
case storeLogo
case store_logo
case logo
case date
case createdAt
case updatedAt
@@ -41,6 +48,7 @@ struct AppOrderSummary: Decodable, Identifiable {
orderId = ApiService.decodeFlexibleString(from: container, keys: [.orderId])
id = ApiService.decodeFlexibleString(from: container, keys: [.orderId, .realId, .id]) ?? UUID().uuidString
realId = ApiService.decodeFlexibleString(from: container, keys: [.realId])
storeId = ApiService.decodeFlexibleString(from: container, keys: [.storeId, .store_id])
shortId = ApiService.decodeFlexibleString(from: container, keys: [.shortId])
total = ApiService.decodeFlexibleDouble(from: container, keys: [.total])
status = ApiService.decodeFlexibleString(from: container, keys: [.status])
@@ -51,6 +59,7 @@ struct AppOrderSummary: Decodable, Identifiable {
paymentMethod = ApiService.decodeFlexibleString(from: container, keys: [.paymentMethod])
deliveryType = ApiService.decodeFlexibleString(from: container, keys: [.deliveryType])
storeName = ApiService.decodeFlexibleString(from: container, keys: [.storeName])
storeLogoURL = ApiService.decodeFlexibleString(from: container, keys: [.storeLogo, .store_logo, .logo])
let fallbackDate = ApiService.decodeFlexibleString(from: container, keys: [.date])
createdAt = ApiService.decodeFlexibleString(from: container, keys: [.createdAt]) ?? fallbackDate
updatedAt = ApiService.decodeFlexibleString(from: container, keys: [.updatedAt]) ?? fallbackDate
@@ -61,6 +70,7 @@ struct PublicOrderResult: Codable, Identifiable {
let id: String
let shortId: String?
let realId: String?
let storeId: String?
let status: String?
let paymentStatus: String?
let paymentConfirmed: Bool?
@@ -68,16 +78,20 @@ struct PublicOrderResult: Codable, Identifiable {
let paymentMethodCode: String?
let paymentPayload: CreateOrderPaymentPayload?
let payment: CreateOrderPaymentInfo?
let nextAction: String?
let deliveryType: String?
let deliveryTypeLabel: String?
let total: Double?
let storeName: String?
let storeLogoURL: String?
let createdAt: String?
let updatedAt: String?
let otp: String?
let customerOtp: String?
let confirmOtp: String?
let cancellationReason: String?
let deliveryAddress: PublicOrderDeliveryAddress?
let review: PublicOrderReview?
let items: [PublicOrderItem]
let timeline: [PublicOrderTimelineEvent]
@@ -85,6 +99,8 @@ struct PublicOrderResult: Codable, Identifiable {
case id
case shortId
case realId
case storeId
case store_id
case status
case paymentStatus
case paymentConfirmed
@@ -92,16 +108,27 @@ struct PublicOrderResult: Codable, Identifiable {
case paymentMethodCode
case paymentPayload
case payment
case nextAction
case deliveryType
case deliveryTypeLabel
case total
case storeName
case storeLogo
case store_logo
case logo
case createdAt
case updatedAt
case otp
case customerOtp
case confirmOtp
case cancellationReason
case address
case deliveryAddress
case delivery_address
case customerAddress
case customer_address
case review
case orderReview
case items
case timeline
}
@@ -110,6 +137,7 @@ struct PublicOrderResult: Codable, Identifiable {
id: String,
shortId: String? = nil,
realId: String? = nil,
storeId: String? = nil,
status: String? = nil,
paymentStatus: String? = nil,
paymentConfirmed: Bool? = nil,
@@ -117,22 +145,27 @@ struct PublicOrderResult: Codable, Identifiable {
paymentMethodCode: String? = nil,
paymentPayload: CreateOrderPaymentPayload? = nil,
payment: CreateOrderPaymentInfo? = nil,
nextAction: String? = nil,
deliveryType: String? = nil,
deliveryTypeLabel: String? = nil,
total: Double? = nil,
storeName: String? = nil,
storeLogoURL: String? = nil,
createdAt: String? = nil,
updatedAt: String? = nil,
otp: String? = nil,
customerOtp: String? = nil,
confirmOtp: String? = nil,
cancellationReason: String? = nil,
deliveryAddress: PublicOrderDeliveryAddress? = nil,
review: PublicOrderReview? = nil,
items: [PublicOrderItem] = [],
timeline: [PublicOrderTimelineEvent] = []
) {
self.id = id
self.shortId = shortId
self.realId = realId
self.storeId = storeId
self.status = status
self.paymentStatus = paymentStatus
self.paymentConfirmed = paymentConfirmed
@@ -140,16 +173,20 @@ struct PublicOrderResult: Codable, Identifiable {
self.paymentMethodCode = paymentMethodCode
self.paymentPayload = paymentPayload
self.payment = payment
self.nextAction = nextAction
self.deliveryType = deliveryType
self.deliveryTypeLabel = deliveryTypeLabel
self.total = total
self.storeName = storeName
self.storeLogoURL = storeLogoURL
self.createdAt = createdAt
self.updatedAt = updatedAt
self.otp = otp
self.customerOtp = customerOtp
self.confirmOtp = confirmOtp
self.cancellationReason = cancellationReason
self.deliveryAddress = deliveryAddress
self.review = review
self.items = items
self.timeline = timeline
}
@@ -159,11 +196,13 @@ struct PublicOrderResult: Codable, Identifiable {
id = ApiService.decodeFlexibleString(from: container, keys: [.id]) ?? UUID().uuidString
shortId = ApiService.decodeFlexibleString(from: container, keys: [.shortId])
realId = ApiService.decodeFlexibleString(from: container, keys: [.realId])
storeId = ApiService.decodeFlexibleString(from: container, keys: [.storeId, .store_id])
status = ApiService.decodeFlexibleString(from: container, keys: [.status])
paymentStatus = ApiService.decodeFlexibleString(from: container, keys: [.paymentStatus])
paymentConfirmed = try? container.decode(Bool.self, forKey: .paymentConfirmed)
paymentMethod = ApiService.decodeFlexibleString(from: container, keys: [.paymentMethod])
paymentMethodCode = ApiService.decodeFlexibleString(from: container, keys: [.paymentMethodCode])
nextAction = ApiService.decodeFlexibleString(from: container, keys: [.nextAction])
payment = try? container.decode(CreateOrderPaymentInfo.self, forKey: .payment)
if let objectPayload = try? container.decode(CreateOrderPaymentPayload.self, forKey: .paymentPayload) {
paymentPayload = objectPayload
@@ -180,16 +219,54 @@ struct PublicOrderResult: Codable, Identifiable {
deliveryTypeLabel = ApiService.decodeFlexibleString(from: container, keys: [.deliveryTypeLabel])
total = ApiService.decodeFlexibleDouble(from: container, keys: [.total])
storeName = ApiService.decodeFlexibleString(from: container, keys: [.storeName])
storeLogoURL = ApiService.decodeFlexibleString(from: container, keys: [.storeLogo, .store_logo, .logo])
createdAt = ApiService.decodeFlexibleString(from: container, keys: [.createdAt])
updatedAt = ApiService.decodeFlexibleString(from: container, keys: [.updatedAt])
otp = ApiService.decodeFlexibleString(from: container, keys: [.otp])
customerOtp = ApiService.decodeFlexibleString(from: container, keys: [.customerOtp])
confirmOtp = ApiService.decodeFlexibleString(from: container, keys: [.confirmOtp])
cancellationReason = ApiService.decodeFlexibleString(from: container, keys: [.cancellationReason])
deliveryAddress = (try? container.decode(PublicOrderDeliveryAddress.self, forKey: .address))
?? (try? container.decode(PublicOrderDeliveryAddress.self, forKey: .deliveryAddress))
?? (try? container.decode(PublicOrderDeliveryAddress.self, forKey: .delivery_address))
?? (try? container.decode(PublicOrderDeliveryAddress.self, forKey: .customerAddress))
?? (try? container.decode(PublicOrderDeliveryAddress.self, forKey: .customer_address))
review = (try? container.decode(PublicOrderReview.self, forKey: .review))
?? (try? container.decode(PublicOrderReview.self, forKey: .orderReview))
items = (try? container.decode([PublicOrderItem].self, forKey: .items)) ?? []
timeline = (try? container.decode([PublicOrderTimelineEvent].self, forKey: .timeline)) ?? []
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encodeIfPresent(shortId, forKey: .shortId)
try container.encodeIfPresent(realId, forKey: .realId)
try container.encodeIfPresent(storeId, forKey: .storeId)
try container.encodeIfPresent(status, forKey: .status)
try container.encodeIfPresent(paymentStatus, forKey: .paymentStatus)
try container.encodeIfPresent(paymentConfirmed, forKey: .paymentConfirmed)
try container.encodeIfPresent(paymentMethod, forKey: .paymentMethod)
try container.encodeIfPresent(paymentMethodCode, forKey: .paymentMethodCode)
try container.encodeIfPresent(paymentPayload, forKey: .paymentPayload)
try container.encodeIfPresent(payment, forKey: .payment)
try container.encodeIfPresent(nextAction, forKey: .nextAction)
try container.encodeIfPresent(deliveryType, forKey: .deliveryType)
try container.encodeIfPresent(deliveryTypeLabel, forKey: .deliveryTypeLabel)
try container.encodeIfPresent(total, forKey: .total)
try container.encodeIfPresent(storeName, forKey: .storeName)
try container.encodeIfPresent(createdAt, forKey: .createdAt)
try container.encodeIfPresent(updatedAt, forKey: .updatedAt)
try container.encodeIfPresent(otp, forKey: .otp)
try container.encodeIfPresent(customerOtp, forKey: .customerOtp)
try container.encodeIfPresent(confirmOtp, forKey: .confirmOtp)
try container.encodeIfPresent(cancellationReason, forKey: .cancellationReason)
try container.encodeIfPresent(deliveryAddress, forKey: .address)
try container.encodeIfPresent(review, forKey: .review)
try container.encode(items, forKey: .items)
try container.encode(timeline, forKey: .timeline)
}
var displayOtpCode: String? {
let values = [customerOtp, otp, confirmOtp]
for value in values {
@@ -251,6 +328,180 @@ struct PublicOrderResult: Codable, Identifiable {
}
}
struct PublicOrderDeliveryAddress: Codable {
let label: String?
let street: String?
let number: String?
let neighborhood: String?
let city: String?
let state: String?
let zip: String?
let complement: String?
enum CodingKeys: String, CodingKey {
case label
case street
case address
case number
case neighborhood
case district
case city
case state
case zip
case zipCode
case zipcode
case complement
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
label = ApiService.decodeFlexibleString(from: container, keys: [.label])
street = ApiService.decodeFlexibleString(from: container, keys: [.street, .address])
number = ApiService.decodeFlexibleString(from: container, keys: [.number])
neighborhood = ApiService.decodeFlexibleString(from: container, keys: [.neighborhood, .district])
city = ApiService.decodeFlexibleString(from: container, keys: [.city])
state = ApiService.decodeFlexibleString(from: container, keys: [.state])
zip = ApiService.decodeFlexibleString(from: container, keys: [.zip, .zipCode, .zipcode])
complement = ApiService.decodeFlexibleString(from: container, keys: [.complement])
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(label, forKey: .label)
try container.encodeIfPresent(street, forKey: .street)
try container.encodeIfPresent(number, forKey: .number)
try container.encodeIfPresent(neighborhood, forKey: .neighborhood)
try container.encodeIfPresent(city, forKey: .city)
try container.encodeIfPresent(state, forKey: .state)
try container.encodeIfPresent(zip, forKey: .zip)
try container.encodeIfPresent(complement, forKey: .complement)
}
}
struct PublicOrderReview: Codable {
let id: String?
let orderId: String?
let rate: Int?
let message: String?
let orderRate: Int?
let orderComment: String?
let orderPositiveTags: [String]?
let orderImprovementTags: [String]?
let deliverySentiment: String?
let deliveryPositiveTags: [String]?
let deliveryNegativeTags: [String]?
let appNps: Int?
let platform: String?
let date: String?
enum CodingKeys: String, CodingKey {
case id
case orderId
case rate
case message
case orderRate
case orderComment
case orderPositiveTags
case orderImprovementTags
case itemFeedback
case improvementFeedback
case deliverySentiment
case deliveryFeedback
case deliveryPositiveTags
case deliveryNegativeTags
case appNps
case app_nps
case platform
case date
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = ApiService.decodeFlexibleString(from: container, keys: [.id])
orderId = ApiService.decodeFlexibleString(from: container, keys: [.orderId])
rate = ApiService.decodeFlexibleInt(from: container, keys: [.rate])
message = ApiService.decodeFlexibleString(from: container, keys: [.message])
orderRate = ApiService.decodeFlexibleInt(from: container, keys: [.orderRate, .rate])
orderComment = ApiService.decodeFlexibleString(from: container, keys: [.orderComment, .message])
orderPositiveTags = Self.decodeStringList(
from: container,
keys: [.orderPositiveTags, .itemFeedback]
)
orderImprovementTags = Self.decodeStringList(
from: container,
keys: [.orderImprovementTags, .improvementFeedback]
)
deliverySentiment = ApiService.decodeFlexibleString(from: container, keys: [.deliverySentiment, .deliveryFeedback])
deliveryPositiveTags = Self.decodeStringList(from: container, keys: [.deliveryPositiveTags])
deliveryNegativeTags = Self.decodeStringList(from: container, keys: [.deliveryNegativeTags])
appNps = Self.decodeNps(from: container, keys: [.appNps, .app_nps])
platform = ApiService.decodeFlexibleString(from: container, keys: [.platform])
date = ApiService.decodeFlexibleString(from: container, keys: [.date])
}
private static func decodeStringList(
from container: KeyedDecodingContainer<CodingKeys>,
keys: [CodingKeys]
) -> [String]? {
for key in keys {
if let list = try? container.decode([String].self, forKey: key) {
return list
}
if let single = try? container.decode(String.self, forKey: key) {
let normalized = single.trimmingCharacters(in: .whitespacesAndNewlines)
if normalized.isEmpty == false {
return [normalized]
}
}
}
return nil
}
private static func decodeNps(
from container: KeyedDecodingContainer<CodingKeys>,
keys: [CodingKeys]
) -> Int? {
for key in keys {
if let value = try? container.decode(Int.self, forKey: key) {
return value
}
if let value = try? container.decode(Double.self, forKey: key) {
return Int(value.rounded())
}
if let raw = try? container.decode(String.self, forKey: key) {
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.isEmpty { continue }
if let asInt = Int(trimmed) {
return asInt
}
let normalized = trimmed.replacingOccurrences(of: ",", with: ".")
if let asDouble = Double(normalized) {
return Int(asDouble.rounded())
}
}
}
return nil
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(id, forKey: .id)
try container.encodeIfPresent(orderId, forKey: .orderId)
try container.encodeIfPresent(rate, forKey: .rate)
try container.encodeIfPresent(message, forKey: .message)
try container.encodeIfPresent(orderRate, forKey: .orderRate)
try container.encodeIfPresent(orderComment, forKey: .orderComment)
try container.encodeIfPresent(orderPositiveTags, forKey: .orderPositiveTags)
try container.encodeIfPresent(orderImprovementTags, forKey: .orderImprovementTags)
try container.encodeIfPresent(deliverySentiment, forKey: .deliverySentiment)
try container.encodeIfPresent(deliveryPositiveTags, forKey: .deliveryPositiveTags)
try container.encodeIfPresent(deliveryNegativeTags, forKey: .deliveryNegativeTags)
try container.encodeIfPresent(appNps, forKey: .appNps)
try container.encodeIfPresent(platform, forKey: .platform)
try container.encodeIfPresent(date, forKey: .date)
}
}
struct PublicOrderItem: Codable, Identifiable {
let id: String
let name: String?

View File

@@ -0,0 +1,321 @@
import SwiftUI
struct OrderDetailsView: View {
let order: PublicOrderResult
let orderId: String
let initialShortId: String?
var body: some View {
ScrollView(showsIndicators: false) {
VStack(spacing: 14) {
statusCard
storeCard
itemsCard
totalsCard
if hasAddressInfo {
addressCard
}
reorderButton
helpFooter
}
.padding(.horizontal, 20)
.padding(.top, 14)
.padding(.bottom, UIDevice.bottomNotch + 24)
}
.background(AppColors.backgroundLight)
.navigationTitle("Detalhes do Pedido")
.navigationBarTitleDisplayMode(.inline)
}
private var statusCard: some View {
HStack(spacing: 14) {
Circle()
.fill(AppColors.brandSoft)
.frame(width: 54, height: 54)
.overlay(
Image(systemName: statusIcon)
.font(.system(size: 22, weight: .bold))
.foregroundStyle(statusColor)
)
VStack(alignment: .leading, spacing: 2) {
Text(statusTitle)
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textPrimary)
Text(statusDateText)
.font(AppTypography.body)
.foregroundStyle(AppColors.textMuted)
}
Spacer(minLength: 0)
}
.padding(16)
.background(AppColors.surface)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
}
private var storeCard: some View {
HStack(spacing: 12) {
AsyncStoreImage(imageURL: resolvedMediaURL(order.storeLogoURL))
.frame(width: 54, height: 54)
.clipShape(Circle())
.background(AppColors.brandSoft, in: Circle())
VStack(alignment: .leading, spacing: 4) {
Text(order.storeName?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false ? (order.storeName ?? "Loja") : "Loja")
.font(AppTypography.heading2)
.foregroundStyle(AppColors.textPrimary)
Text("Pedido #\(displayOrderTitle)")
.font(AppTypography.body)
.foregroundStyle(AppColors.textMuted)
}
Spacer(minLength: 0)
Text("Ver loja")
.font(AppTypography.body)
.foregroundStyle(Color(hex: "#A5D645"))
}
.padding(16)
.background(AppColors.surface)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
}
private var itemsCard: some View {
VStack(alignment: .leading, spacing: 14) {
Text("Itens do Pedido")
.font(AppTypography.heading2)
.foregroundStyle(AppColors.textPrimary)
ForEach(order.items) { item in
HStack(alignment: .top, spacing: 12) {
Text("\(max(1, item.qty ?? 1))")
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textPrimary)
.frame(width: 30, height: 30)
.background(AppColors.backgroundLight)
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
VStack(alignment: .leading, spacing: 2) {
Text(item.name?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false ? (item.name ?? "Item") : "Item")
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textPrimary)
}
Spacer(minLength: 0)
if let price = item.price {
Text(formatCurrency(price))
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textPrimary)
}
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(16)
.background(AppColors.surface)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
}
private var totalsCard: some View {
VStack(alignment: .leading, spacing: 10) {
Text("Resumo de Valores")
.font(AppTypography.heading2)
.foregroundStyle(AppColors.textPrimary)
HStack {
Text("Subtotal")
.font(AppTypography.body)
.foregroundStyle(AppColors.textMuted)
Spacer()
Text(formatCurrency(subtotal))
.font(AppTypography.body)
.foregroundStyle(AppColors.textMuted)
}
Divider()
HStack {
Text("Total")
.font(AppTypography.heading2)
.foregroundStyle(AppColors.textPrimary)
Spacer()
Text(formatCurrency(order.total ?? subtotal))
.font(AppTypography.heading1)
.foregroundStyle(AppColors.textPrimary)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(16)
.background(AppColors.surface)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
}
private var addressCard: some View {
VStack(alignment: .leading, spacing: 12) {
HStack(spacing: 10) {
Circle()
.fill(AppColors.backgroundLight)
.frame(width: 34, height: 34)
.overlay(
Image(systemName: "mappin.circle.fill")
.font(.system(size: 18, weight: .semibold))
.foregroundStyle(AppColors.textMuted)
)
Text("ENDEREÇO DE ENTREGA")
.font(AppTypography.caption)
.foregroundStyle(AppColors.textMuted)
Spacer(minLength: 0)
}
Text(deliveryAddressLine)
.font(AppTypography.body)
.foregroundStyle(AppColors.textPrimary)
if deliveryAddressLine2.isEmpty == false {
Text(deliveryAddressLine2)
.font(AppTypography.body)
.foregroundStyle(AppColors.textMuted)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(16)
.background(AppColors.surface)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
}
private var reorderButton: some View {
Button("Pedir Novamente") {
SnackbarCenter.shared.show(
title: "Recompra será integrada com o catálogo em breve.",
style: .info,
icon: "cart.badge.plus",
duration: 2.0
)
}
.font(AppTypography.heading2)
.foregroundStyle(Color(hex: "#0E1A06"))
.frame(maxWidth: .infinity, minHeight: 56)
.background(Color(hex: "#C8F06E"))
.clipShape(Capsule())
.buttonStyle(.plain)
}
private var helpFooter: some View {
Text("Precisa de ajuda com esse pedido?")
.font(AppTypography.body)
.foregroundStyle(Color(hex: "#A5D645"))
.frame(maxWidth: .infinity, alignment: .center)
.padding(.vertical, 8)
}
private var subtotal: Double {
order.items.reduce(0) { partial, item in
partial + (Double(max(1, item.qty ?? 1)) * (item.price ?? 0))
}
}
private var hasAddressInfo: Bool {
deliveryAddressLine.isEmpty == false || deliveryAddressLine2.isEmpty == false
}
private var deliveryAddressLine: String {
guard let address = order.deliveryAddress else { return "" }
let street = normalizedText(address.street)
let number = normalizedText(address.number)
let base = [street, number].filter { $0.isEmpty == false }.joined(separator: ", ")
if base.isEmpty == false { return base }
return normalizedText(address.label)
}
private var deliveryAddressLine2: String {
guard let address = order.deliveryAddress else { return "" }
let neighborhood = normalizedText(address.neighborhood)
let city = normalizedText(address.city)
let state = normalizedText(address.state)
let zip = normalizedText(address.zip)
return [neighborhood, city, state, zip]
.filter { $0.isEmpty == false }
.joined(separator: "")
}
private var statusTitle: String {
let status = normalized(order.status)
if status.contains("CANCEL") { return "Pedido cancelado" }
if status.contains("COMPLETED") || status.contains("DELIVERED") {
return normalized(order.deliveryType ?? order.deliveryTypeLabel).contains("PICKUP") ? "Pedido retirado" : "Pedido concluído"
}
if status.contains("DELIVER") || status.contains("ROTA") { return "Pedido em rota" }
if status.contains("READY") { return "Pedido pronto" }
if status.contains("PREPAR") { return "Pedido em produção" }
return "Pedido confirmado"
}
private var statusDateText: String {
if let formatted = formatDate(order.updatedAt ?? order.createdAt) {
return "\(statusDatePrefix) \(formatted)"
}
return statusDatePrefix
}
private var statusDatePrefix: String {
if statusTitle.contains("cancelado") { return "Cancelado em" }
if statusTitle.contains("retirado") { return "Retirado em" }
if statusTitle.contains("concluído") { return "Entregue em" }
return "Atualizado em"
}
private var statusIcon: String {
statusTitle.contains("cancelado") ? "xmark" : "checkmark"
}
private var statusColor: Color {
statusTitle.contains("cancelado") ? Color.red : AppColors.primary
}
private var displayOrderTitle: String {
if let short = order.shortId, short.isEmpty == false { return short }
let orderIdValue = order.id.trimmingCharacters(in: .whitespacesAndNewlines)
if orderIdValue.isEmpty == false { return orderIdValue }
if let initialShortId, initialShortId.isEmpty == false { return initialShortId }
return String(orderId.prefix(6))
}
private func formatCurrency(_ value: Double) -> String {
String(format: "R$ %.2f", value).replacingOccurrences(of: ".", with: ",")
}
private func formatDate(_ isoValue: String?) -> String? {
guard let isoValue, isoValue.isEmpty == false else { return nil }
let iso = ISO8601DateFormatter()
iso.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
var date = iso.date(from: isoValue)
if date == nil {
iso.formatOptions = [.withInternetDateTime]
date = iso.date(from: isoValue)
}
guard let date else { return nil }
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "pt_BR")
formatter.dateFormat = "dd MMM, HH:mm"
return formatter.string(from: date)
}
private func normalized(_ value: String?) -> String {
(value ?? "")
.folding(options: [.diacriticInsensitive, .caseInsensitive], locale: .current)
.uppercased()
.trimmingCharacters(in: .whitespacesAndNewlines)
}
private func resolvedMediaURL(_ raw: String?) -> String? {
ImageSourceResolver.resolve(raw)
}
private func normalizedText(_ value: String?) -> String {
(value ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
}
}

View File

@@ -479,6 +479,7 @@ struct OrderEntryDestinationView: View {
@State var pixContext: PixPaymentContext? = nil
@State var cardContext: CardPaymentContext? = nil
@State var orderTrackingContext: OrderTrackingContext? = nil
@State var orderDetails: PublicOrderResult? = nil
var body: some View {
Group {
@@ -511,6 +512,8 @@ struct OrderEntryDestinationView: View {
orderTrackingContext = OrderTrackingContext(orderId: cardContext.orderId, shortId: cardContext.shortId)
}
)
} else if let orderDetails {
OrderDetailsView(order: orderDetails, orderId: orderId, initialShortId: initialShortId)
} else {
OrderTrackingView(orderId: orderId, initialShortId: initialShortId)
}
@@ -531,6 +534,10 @@ struct OrderEntryDestinationView: View {
let order = await fetchOrderForRouting()
guard let order else { return }
if shouldOpenOrderDetails(for: order) {
orderDetails = order
return
}
guard shouldOpenPaymentScreen(for: order) else { return }
let normalizedMethod = normalizePaymentMethod(order)
@@ -592,6 +599,13 @@ struct OrderEntryDestinationView: View {
return method == "PIX" || method == "CREDIT_CARD" || method == "DEBIT_CARD"
}
func shouldOpenOrderDetails(for order: PublicOrderResult) -> Bool {
let status = normalize(order.status)
if status.contains("CANCEL") { return true }
if status.contains("COMPLETED") || status.contains("DELIVERED") { return true }
return false
}
func shouldOpenPaymentScreen(for order: PublicOrderResult) -> Bool {
guard order.isPaymentConfirmed == false else { return false }
guard isOnlinePaymentMethod(order) else { return false }