import SwiftUI struct AddTransactionView: View { @Binding var isPresented: Bool @EnvironmentObject var authManager: AuthManager let categories: [FinanceCategory] let onAdded: () async -> Void @State private var amount = "" @State private var description = "" @State private var type = "expense" @State private var selectedCategoryId: Int? = nil @State private var isLoading = false var filteredCategories: [FinanceCategory] { categories.filter { $0.type == type } } var isExpense: Bool { type == "expense" } var body: some View { ZStack { Color(hex: "0a0a1a").ignoresSafeArea() VStack(spacing: 0) { // Handle RoundedRectangle(cornerRadius: 3) .fill(Color.white.opacity(0.2)) .frame(width: 40, height: 4) .padding(.top, 12) // Header HStack { Button("Отмена") { isPresented = false }.foregroundColor(Color(hex: "8888aa")) Spacer() Text("Новая операция").font(.headline).foregroundColor(.white) Spacer() Button(action: save) { if isLoading { ProgressView().tint(Color(hex: "00d4aa")).scaleEffect(0.8) } else { Text("Добавить").foregroundColor(amount.isEmpty ? Color(hex: "8888aa") : Color(hex: "00d4aa")).fontWeight(.semibold) } }.disabled(amount.isEmpty || isLoading) } .padding(.horizontal, 20).padding(.vertical, 16) Divider().background(Color.white.opacity(0.1)) ScrollView { VStack(spacing: 20) { // Type toggle HStack(spacing: 0) { Button(action: { type = "expense" }) { Text("Расход") .font(.callout.bold()) .foregroundColor(isExpense ? .black : Color(hex: "ff4757")) .frame(maxWidth: .infinity).padding(.vertical, 12) .background(isExpense ? Color(hex: "ff4757") : Color.clear) } Button(action: { type = "income" }) { Text("Доход") .font(.callout.bold()) .foregroundColor(!isExpense ? .black : Color(hex: "00d4aa")) .frame(maxWidth: .infinity).padding(.vertical, 12) .background(!isExpense ? Color(hex: "00d4aa") : Color.clear) } } .background(Color.white.opacity(0.07)) .cornerRadius(12) // Amount VStack(spacing: 8) { Text(isExpense ? "Сумма расхода" : "Сумма дохода") .font(.caption).foregroundColor(Color(hex: "8888aa")) HStack { Text(isExpense ? "−" : "+") .font(.title.bold()) .foregroundColor(isExpense ? Color(hex: "ff4757") : Color(hex: "00d4aa")) TextField("0", text: $amount) .keyboardType(.decimalPad) .font(.system(size: 36, weight: .bold)) .foregroundColor(.white) .multilineTextAlignment(.center) Text("₽") .font(.title.bold()) .foregroundColor(Color(hex: "8888aa")) } .padding(20) .background(RoundedRectangle(cornerRadius: 16).fill(Color.white.opacity(0.07))) } // Description VStack(alignment: .leading, spacing: 8) { Label("Описание", systemImage: "text.alignleft").font(.caption).foregroundColor(Color(hex: "8888aa")) TextField("Комментарий...", text: $description) .foregroundColor(.white).padding(14) .background(RoundedRectangle(cornerRadius: 12).fill(Color.white.opacity(0.07))) } // Categories if !filteredCategories.isEmpty { VStack(alignment: .leading, spacing: 8) { Label("Категория", systemImage: "tag.fill").font(.caption).foregroundColor(Color(hex: "8888aa")) LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))], spacing: 8) { ForEach(filteredCategories) { cat in Button(action: { selectedCategoryId = selectedCategoryId == cat.id ? nil : cat.id }) { HStack(spacing: 6) { Text(cat.icon ?? "").font(.callout) Text(cat.name).font(.caption).lineLimit(1) } .foregroundColor(selectedCategoryId == cat.id ? .black : .white) .padding(.horizontal, 10).padding(.vertical, 8) .frame(maxWidth: .infinity) .background( RoundedRectangle(cornerRadius: 10) .fill(selectedCategoryId == cat.id ? Color(hex: "00d4aa") : Color.white.opacity(0.07)) ) } } } } } } .padding(20) } } } } func save() { guard let a = Double(amount.replacingOccurrences(of: ",", with: ".")) else { return } isLoading = true Task { let req = CreateTransactionRequest(amount: a, categoryId: selectedCategoryId, description: description.isEmpty ? nil : description, type: type) try? await APIService.shared.createTransaction(token: authManager.token, request: req) await onAdded() await MainActor.run { isPresented = false } } } }