segregate files
This commit is contained in:
196
pedi-foods/Sources/PediFoods/Views/Main/ProductDetailSheet.swift
Normal file
196
pedi-foods/Sources/PediFoods/Views/Main/ProductDetailSheet.swift
Normal file
@@ -0,0 +1,196 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct ProductDetailSheet: View {
|
||||
let product: StoreCatalogProduct
|
||||
let imageURL: String?
|
||||
let storeId: String
|
||||
let currentQuantityForItemId: (String) -> Int
|
||||
let onAdd: (CartItemState) -> Void
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@State var selectedAddonItemIds: Set<String> = []
|
||||
@State var quantity: Int = 0
|
||||
|
||||
private var selectedAddonItems: [StoreAddonItem] {
|
||||
product.addonGroups
|
||||
.flatMap(\.items)
|
||||
.filter { selectedAddonItemIds.contains($0.id) }
|
||||
}
|
||||
|
||||
private var addonsTotal: Double {
|
||||
selectedAddonItems.reduce(0) { $0 + ($1.price ?? 0) }
|
||||
}
|
||||
|
||||
private var unitPrice: Double {
|
||||
(product.price ?? 0) + addonsTotal
|
||||
}
|
||||
|
||||
private var totalPrice: Double {
|
||||
unitPrice * Double(quantity)
|
||||
}
|
||||
|
||||
private var cartItemId: String {
|
||||
let addonKey = selectedAddonItemIds.sorted().joined(separator: ",")
|
||||
return "\(storeId)::\(product.id)::\(addonKey)"
|
||||
}
|
||||
|
||||
private var selectedAddonsSummary: String? {
|
||||
let names = selectedAddonItems.map(\.name)
|
||||
if names.isEmpty { return nil }
|
||||
return names.joined(separator: ", ")
|
||||
}
|
||||
|
||||
private var addButtonTitle: String {
|
||||
if quantity <= 0 {
|
||||
return "Remover do carrinho"
|
||||
}
|
||||
return "Atualizar • \(formatCurrency(totalPrice))"
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
AsyncStoreImage(imageURL: imageURL)
|
||||
.frame(height: 220)
|
||||
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusXL, style: .continuous))
|
||||
|
||||
Text(product.name)
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
|
||||
if let description = product.description, description.isEmpty == false {
|
||||
Text(description)
|
||||
.font(AppTypography.body)
|
||||
.foregroundStyle(AppColors.textMuted)
|
||||
}
|
||||
|
||||
Text(formatCurrency(unitPrice))
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.primary)
|
||||
|
||||
if addonsTotal > 0 {
|
||||
Text("Inclui adicionais: \(formatCurrency(addonsTotal))")
|
||||
.font(.caption)
|
||||
.foregroundStyle(AppColors.textMuted)
|
||||
}
|
||||
|
||||
if product.addonGroups.isEmpty == false {
|
||||
Text("Adicionais")
|
||||
.font(AppTypography.heading3)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
|
||||
ForEach(product.addonGroups) { group in
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(group.name)
|
||||
.font(AppTypography.heading3)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
|
||||
ForEach(group.items) { item in
|
||||
Button {
|
||||
if selectedAddonItemIds.contains(item.id) {
|
||||
selectedAddonItemIds.remove(item.id)
|
||||
} else {
|
||||
selectedAddonItemIds.insert(item.id)
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: selectedAddonItemIds.contains(item.id) ? "checkmark.circle.fill" : "circle")
|
||||
.font(.system(size: 18, weight: .semibold))
|
||||
.foregroundStyle(selectedAddonItemIds.contains(item.id) ? AppColors.primary : AppColors.textMuted)
|
||||
|
||||
Text(item.name)
|
||||
.font(AppTypography.body)
|
||||
.foregroundStyle(AppColors.textMuted)
|
||||
Spacer()
|
||||
Text(String(format: "+ R$ %.2f", item.price ?? 0).replacingOccurrences(of: ".", with: ","))
|
||||
.font(AppTypography.body)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
.padding(12)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(20)
|
||||
.padding(.bottom, 80)
|
||||
}
|
||||
.safeAreaInset(edge: .bottom) {
|
||||
HStack(spacing: 12) {
|
||||
HStack(spacing: 10) {
|
||||
Button(action: { if quantity > 0 { quantity -= 1 } }) {
|
||||
Image(systemName: "minus")
|
||||
.font(.system(size: 14, weight: .bold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 32, height: 32)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(quantity <= 0)
|
||||
|
||||
Text("\(quantity)")
|
||||
.font(AppTypography.heading3)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(minWidth: 20)
|
||||
|
||||
Button(action: { quantity += 1 }) {
|
||||
Image(systemName: "plus")
|
||||
.font(.system(size: 14, weight: .bold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 32, height: 32)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.frame(height: 48)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
|
||||
.buttonStyle(.plain)
|
||||
|
||||
PrimaryButton(title: addButtonTitle) {
|
||||
let item = CartItemState(
|
||||
id: cartItemId,
|
||||
productId: product.id,
|
||||
storeId: storeId,
|
||||
name: product.name,
|
||||
details: selectedAddonsSummary,
|
||||
quantity: quantity,
|
||||
unitPrice: unitPrice
|
||||
)
|
||||
onAdd(item)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.top, 8)
|
||||
.padding(.bottom, 12)
|
||||
.background(.ultraThinMaterial)
|
||||
}
|
||||
.background(AppColors.backgroundLight.ignoresSafeArea())
|
||||
.navigationTitle("Detalhes")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button("Fechar") { dismiss() }
|
||||
.foregroundStyle(AppColors.primary)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
quantity = currentQuantityForItemId(cartItemId)
|
||||
}
|
||||
.onChange(of: selectedAddonItemIds) { _, _ in
|
||||
quantity = currentQuantityForItemId(cartItemId)
|
||||
}
|
||||
}
|
||||
|
||||
private func formatCurrency(_ value: Double) -> String {
|
||||
String(format: "R$ %.2f", value).replacingOccurrences(of: ".", with: ",")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user