Files
pulse-mobile/PulseHealth/Views/Finance/AddTransactionView.swift

136 lines
7.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 }
}
}
}