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 } helpFooter } .padding(.horizontal, 20) .padding(.top, 14) .padding(.bottom, UIDevice.bottomNotch) } .background(AppColors.backgroundLight) .navigationTitle("Detalhes do Pedido") .navigationBarTitleDisplayMode(.inline) .safeAreaInset(edge: .bottom) { VStack { reorderButton .padding(.horizontal, 20) .padding(.top, 10) .padding(.bottom, UIDevice.bottomNotch + 60) } .background(AppColors.backgroundLight.opacity(0.94)) } } 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(storeSubtitle) .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(subtotalValue)) .font(AppTypography.body) .foregroundStyle(AppColors.textMuted) } HStack { Text("Taxa de entrega") .font(AppTypography.body) .foregroundStyle(AppColors.textMuted) Spacer() Text(formatCurrency(deliveryFeeValue)) .font(AppTypography.body) .foregroundStyle(AppColors.textMuted) } HStack { Text("Desconto") .font(AppTypography.body) .foregroundStyle(AppColors.textMuted) Spacer() Text("- \(formatCurrency(discountValue))") .font(AppTypography.body) .foregroundStyle(Color(hex: "#18A957")) } Divider() HStack { Text("Total") .font(AppTypography.heading2) .foregroundStyle(AppColors.textPrimary) Spacer() Text(formatCurrency(totalValue)) .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 subtotalValue: Double { order.subtotal ?? subtotal } private var deliveryFeeValue: Double { max(0, order.deliveryFee ?? 0) } private var discountValue: Double { max(0, order.discount ?? 0) } private var totalValue: Double { if let total = order.total { return total } let calculated = subtotalValue + deliveryFeeValue - discountValue return max(0, calculated) } 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 storeSubtitle: String { if deliveryAddressLine2.isEmpty == false { return deliveryAddressLine2 } return "Pedido #\(displayOrderTitle)" } 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) } }