This commit is contained in:
Daniel Arantes Loverde
2026-02-15 08:18:06 -03:00
parent d7b8b1864a
commit 0f5ad4c268
35 changed files with 4090 additions and 309 deletions

View File

@@ -0,0 +1,229 @@
import Foundation
import SwiftUI
extension PizzaProductDetailSheet {
var stepSizes: some View {
let sizeItems = sizes
return VStack(alignment: .leading, spacing: 10) {
Text("1. Tamanho")
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textPrimary)
ForEach(Array(sizeItems.indices), id: \.self) { index in
sizeRow(sizeItems[index])
}
}
.padding(12)
.background(AppColors.surface)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
}
var stepDoughs: some View {
VStack(alignment: .leading, spacing: 10) {
Text("2. Massa")
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textPrimary)
if doughs.count <= 1 {
Text(doughs.first?.name ?? "Massa tradicional")
.font(AppTypography.body)
.foregroundStyle(AppColors.textPrimary)
} else {
ForEach(doughs) { dough in
radioRow(
title: dough.name ?? "Massa",
subtitle: nil,
isSelected: selectedDoughId == dough.id
) {
selectedDoughId = dough.id
}
}
}
}
.padding(12)
.background(AppColors.surface)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
}
var stepCrusts: some View {
VStack(alignment: .leading, spacing: 10) {
Text("3. Borda")
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textPrimary)
if crusts.count <= 1 {
Text(crustDescription(crusts.first))
.font(AppTypography.body)
.foregroundStyle(AppColors.textPrimary)
} else {
ForEach(crusts) { crust in
radioRow(
title: crust.name ?? "Borda",
subtitle: crust.priceModifier ?? 0 > 0 ? "+ \(formatCurrency(crust.priceModifier ?? 0))" : nil,
isSelected: selectedCrustId == crust.id
) {
selectedCrustId = crust.id
}
}
}
}
.padding(12)
.background(AppColors.surface)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
}
var stepFlavors: some View {
let flavorItems = flavors
return VStack(alignment: .leading, spacing: 10) {
Text("4. Sabores (\(selectedFlavorIds.count)/\(maxFlavorsAllowed))")
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textPrimary)
Text("Toque no sabor para escolher adicionais.")
.font(.caption)
.foregroundStyle(AppColors.textMuted)
ForEach(Array(flavorItems.indices), id: \.self) { index in
let flavor = flavorItems[index]
let isSelected = selectedFlavorIds.contains(flavor.id)
let price = selectedSizeId.flatMap { flavor.pizzaPrices[$0] } ?? flavor.price
let maxReached = selectedFlavorIds.count >= maxFlavorsAllowed
let disableSwitch = isSelected == false && maxReached
HStack(spacing: 10) {
VStack(alignment: .leading, spacing: 4) {
Text(flavor.name)
.font(AppTypography.body)
.foregroundStyle(AppColors.textPrimary)
if let price {
Text(formatCurrency(price))
.font(.caption)
.foregroundStyle(AppColors.textMuted)
}
}
Spacer()
Toggle("", isOn: Binding(
get: { isSelected },
set: { value in
if value {
addFlavor(flavor.id)
} else {
removeFlavor(flavor.id)
}
}
))
.labelsHidden()
.disabled(disableSwitch)
}
.padding(.vertical, 2)
.contentShape(Rectangle())
.onTapGesture {
guard isSelected else { return }
guard flavor.addonGroups.contains(where: { $0.items.isEmpty == false }) else { return }
selectedFlavorForAddons = flavor
}
}
}
.padding(12)
.background(AppColors.surface)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
}
func sizeRow(_ size: StorePizzaSize) -> some View {
let title = size.name ?? "Tamanho"
let subtitle = "Até \(max(1, size.maxFlavors ?? 1)) sabor(es)"
let selected = selectedSizeId == size.id
return radioRow(
title: title,
subtitle: subtitle,
isSelected: selected
) {
selectedSizeId = size.id
}
}
func radioRow(title: String, subtitle: String?, isSelected: Bool, action: @escaping () -> Void) -> some View {
Button(action: action) {
HStack(spacing: 10) {
ZStack {
Circle()
.stroke(isSelected ? AppColors.primary : AppColors.textMuted.opacity(0.4), lineWidth: 2)
.frame(width: 20, height: 20)
if isSelected {
Circle()
.fill(AppColors.primary)
.frame(width: 10, height: 10)
}
}
VStack(alignment: .leading, spacing: 2) {
Text(title)
.font(AppTypography.body)
.foregroundStyle(AppColors.textPrimary)
if let subtitle, subtitle.isEmpty == false {
Text(subtitle)
.font(.caption)
.foregroundStyle(AppColors.textMuted)
}
}
Spacer()
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
func crustDescription(_ crust: StorePizzaCrust?) -> String {
guard let crust else { return "Sem borda especial" }
let name = crust.name ?? "Borda"
let modifier = crust.priceModifier ?? 0
if modifier > 0 {
return "\(name) (+ \(formatCurrency(modifier)))"
}
return name
}
func applyAutoSelections() {
if selectedSizeId != nil {
if doughs.count == 1 {
selectedDoughId = doughs.first?.id
} else if doughs.isEmpty {
selectedDoughId = "__none__"
}
}
if isDoughReady {
if crusts.count == 1 {
selectedCrustId = crusts.first?.id
} else if crusts.isEmpty {
selectedCrustId = "__none__"
}
}
}
func trimFlavorSelectionByLimit() {
let limit = maxFlavorsAllowed
guard selectedFlavorIds.count > limit else { return }
let sorted = selectedFlavorIds.sorted()
selectedFlavorIds = Set(sorted.prefix(limit))
}
func addFlavor(_ flavorId: String) {
if selectedFlavorIds.contains(flavorId) { return }
if selectedFlavorIds.count >= maxFlavorsAllowed { return }
selectedFlavorIds.insert(flavorId)
}
func removeFlavor(_ flavorId: String) {
selectedFlavorIds.remove(flavorId)
flavorAddonQuantities.removeValue(forKey: flavorId)
}
func formatCurrency(_ value: Double) -> String {
String(format: "R$ %.2f", value).replacingOccurrences(of: ".", with: ",")
}
}