WIP: Implement add transactions
Signed-off-by: Billy Brawner <billy@wbrawner.com>
This commit is contained in:
parent
02ca740ad1
commit
3da85c51c3
13 changed files with 231 additions and 18 deletions
|
@ -13,6 +13,7 @@
|
|||
284102302342D97300EAFA29 /* BudgetListsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2841022F2342D97300EAFA29 /* BudgetListsView.swift */; };
|
||||
284102322342E12F00EAFA29 /* CategoryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284102312342E12F00EAFA29 /* CategoryListView.swift */; };
|
||||
2857EAED233DA30B0026BC83 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2857EAEC233DA30B0026BC83 /* LoadingView.swift */; };
|
||||
28A1E95A235006A300CA57FE /* AddTransactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A1E959235006A300CA57FE /* AddTransactionView.swift */; };
|
||||
28AC94EE233C373900BFB70A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AC94ED233C373900BFB70A /* AppDelegate.swift */; };
|
||||
28AC94F0233C373900BFB70A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AC94EF233C373900BFB70A /* SceneDelegate.swift */; };
|
||||
28AC94F2233C373900BFB70A /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AC94F1233C373900BFB70A /* LoginView.swift */; };
|
||||
|
@ -64,6 +65,7 @@
|
|||
2841022F2342D97300EAFA29 /* BudgetListsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BudgetListsView.swift; sourceTree = "<group>"; };
|
||||
284102312342E12F00EAFA29 /* CategoryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryListView.swift; sourceTree = "<group>"; };
|
||||
2857EAEC233DA30B0026BC83 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = "<group>"; };
|
||||
28A1E959235006A300CA57FE /* AddTransactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTransactionView.swift; sourceTree = "<group>"; };
|
||||
28AC94EA233C373900BFB70A /* Budget.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Budget.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
28AC94ED233C373900BFB70A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
28AC94EF233C373900BFB70A /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
|
@ -233,6 +235,7 @@
|
|||
28FE6B012344331B00D5543E /* TransactionDataStore.swift */,
|
||||
28FE6B03234449DC00D5543E /* TransactionListView.swift */,
|
||||
28FE6B0523444A9800D5543E /* TransactionDetailsView.swift */,
|
||||
28A1E959235006A300CA57FE /* AddTransactionView.swift */,
|
||||
);
|
||||
path = Transaction;
|
||||
sourceTree = "<group>";
|
||||
|
@ -389,6 +392,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
28FE6AFE234428BF00D5543E /* DataStoreProvider.swift in Sources */,
|
||||
28A1E95A235006A300CA57FE /* AddTransactionView.swift in Sources */,
|
||||
28AC94EE233C373900BFB70A /* AppDelegate.swift in Sources */,
|
||||
28AC94F0233C373900BFB70A /* SceneDelegate.swift in Sources */,
|
||||
28FE6AFC23441E4500D5543E /* CategoryRepository.swift in Sources */,
|
||||
|
|
|
@ -46,8 +46,8 @@
|
|||
filePath = "Budget/Transaction/TransactionListView.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "52"
|
||||
endingLineNumber = "52"
|
||||
startingLineNumber = "53"
|
||||
endingLineNumber = "53"
|
||||
landmarkName = "body"
|
||||
landmarkType = "24">
|
||||
<Locations>
|
||||
|
@ -413,8 +413,8 @@
|
|||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "23"
|
||||
endingLineNumber = "23"
|
||||
landmarkName = "getTransactions(_:)"
|
||||
landmarkType = "7">
|
||||
landmarkName = "transaction"
|
||||
landmarkType = "24">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
|
@ -429,8 +429,8 @@
|
|||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "22"
|
||||
endingLineNumber = "22"
|
||||
landmarkName = "getTransactions(_:)"
|
||||
landmarkType = "7">
|
||||
landmarkName = "transaction"
|
||||
landmarkType = "24">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
|
@ -513,5 +513,21 @@
|
|||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "64E00C07-F5B8-44C5-A75B-76A8F00496E8"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "Budget/Transaction/AddTransactionView.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "27"
|
||||
endingLineNumber = "27"
|
||||
landmarkName = "stateContent"
|
||||
landmarkType = "24">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
</Breakpoints>
|
||||
</Bucket>
|
||||
|
|
|
@ -27,8 +27,8 @@ class DataStoreProvider {
|
|||
return CategoryDataStore(categoryRepository, budget: budget)
|
||||
}
|
||||
|
||||
func transactionDataStore(_ category: Category? = nil) -> TransactionDataStore {
|
||||
return TransactionDataStore(transactionRepository, category: category)
|
||||
func transactionDataStore() -> TransactionDataStore {
|
||||
return TransactionDataStore(transactionRepository)
|
||||
}
|
||||
|
||||
func userDataStore() -> UserDataStore {
|
||||
|
|
|
@ -10,6 +10,7 @@ import SwiftUI
|
|||
|
||||
struct TabbedBudgetView: View {
|
||||
@ObservedObject var userData: UserDataStore
|
||||
@State var isAddingTransaction = false
|
||||
|
||||
var body: some View {
|
||||
TabView {
|
||||
|
@ -20,18 +21,26 @@ struct TabbedBudgetView: View {
|
|||
leading: NavigationLink(destination: EmptyView()) {
|
||||
Text("filter")
|
||||
},
|
||||
trailing: NavigationLink(destination: EmptyView().navigationBarTitle("add_transaction")) {
|
||||
Text("add")
|
||||
trailing: Button(action: {
|
||||
self.isAddingTransaction = true
|
||||
}) {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
)
|
||||
}.tabItem {
|
||||
.sheet(isPresented: $isAddingTransaction, content: {
|
||||
AddTransactionView(self.dataStoreProvider)
|
||||
})
|
||||
}
|
||||
.tabItem {
|
||||
Image(systemName: "dollarsign.circle.fill")
|
||||
Text("transactions")
|
||||
}
|
||||
|
||||
BudgetListsView(dataStoreProvider).tabItem {
|
||||
Image(systemName: "chart.pie.fill")
|
||||
Text("budgets")
|
||||
}
|
||||
|
||||
Text("Profile here").tabItem {
|
||||
Image(systemName: "person.circle.fill")
|
||||
Text("profile")
|
||||
|
|
98
Budget/Transaction/AddTransactionView.swift
Normal file
98
Budget/Transaction/AddTransactionView.swift
Normal file
|
@ -0,0 +1,98 @@
|
|||
//
|
||||
// AddTransactionView.swift
|
||||
// Budget
|
||||
//
|
||||
// Created by Billy Brawner on 10/10/19.
|
||||
// Copyright © 2019 William Brawner. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AddTransactionView: View {
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@State var id: Int? = nil
|
||||
@State var title: String = ""
|
||||
@State var description: String = ""
|
||||
@State var date: Date = Date()
|
||||
@State var amount: String = ""
|
||||
@State var categoryId: Int = 0
|
||||
@State var type: TransactionType = .expense
|
||||
@State var budgetId: Int = 0
|
||||
let createdBy: Int
|
||||
|
||||
var stateContent: AnyView {
|
||||
switch transactionDataStore.transaction {
|
||||
case .success(_):
|
||||
// TODO: Figure out how to pass transaction up to previous view
|
||||
self.presentationMode.wrappedValue.dismiss()
|
||||
return AnyView(EmptyView())
|
||||
|
||||
case .failure(.loading):
|
||||
return AnyView(VStack {
|
||||
ActivityIndicator(isAnimating: .constant(true), style: .large)
|
||||
})
|
||||
default:
|
||||
// TODO: Handle each network failure type
|
||||
return AnyView(Form {
|
||||
Section {
|
||||
TextField("prompt_name", text: self.$title)
|
||||
TextField("prompt_description", text: self.$description)
|
||||
DatePicker(selection: self.$date, label: { Text("prompt_date") })
|
||||
TextField("prompt_amount", text: self.$amount)
|
||||
.keyboardType(.decimalPad)
|
||||
Picker("prompt_type", selection: self.$type) {
|
||||
ForEach(TransactionType.allCases) { type in
|
||||
Text(type.localizedKey)
|
||||
}
|
||||
}
|
||||
// TODO: Figure out how to load budgets dynamically
|
||||
Picker("prompt_budget", selection: self.$type) {
|
||||
ForEach(TransactionType.allCases) { type in
|
||||
Text(type.localizedKey)
|
||||
}
|
||||
}
|
||||
// TODO: Figure out how to load categories dynamically
|
||||
Picker("prompt_category", selection: self.$type) {
|
||||
ForEach(TransactionType.allCases) { type in
|
||||
Text(type.localizedKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
stateContent
|
||||
.navigationBarTitle("add_transaction")
|
||||
.navigationBarItems(leading: Button("cancel") {
|
||||
self.presentationMode.wrappedValue.dismiss()
|
||||
}, trailing: Button("save") {
|
||||
self.transactionDataStore.createTransaction(Transaction(
|
||||
id: self.id,
|
||||
title: self.title,
|
||||
description: self.description,
|
||||
date: self.date,
|
||||
amount: Int(Double(self.amount) ?? 0.0 * 100.0),
|
||||
categoryId: self.categoryId,
|
||||
expense: self.type == TransactionType.expense,
|
||||
createdBy: self.createdBy,
|
||||
budgetId: self.budgetId
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ObservedObject var transactionDataStore: TransactionDataStore
|
||||
init(_ dataStoreProvider: DataStoreProvider) {
|
||||
self.transactionDataStore = dataStoreProvider.transactionDataStore()
|
||||
self.createdBy = try! dataStoreProvider.userDataStore().currentUser.get().id!
|
||||
}
|
||||
}
|
||||
|
||||
//struct AddTransactionView_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// AddTransactionView()
|
||||
// }
|
||||
//}
|
|
@ -7,6 +7,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct Transaction: Identifiable, Codable {
|
||||
let id: Int?
|
||||
|
@ -15,7 +16,25 @@ struct Transaction: Identifiable, Codable {
|
|||
let date: Date
|
||||
let amount: Int
|
||||
let categoryId: Int?
|
||||
let expense: Bool = true
|
||||
let expense: Bool
|
||||
let createdBy: Int
|
||||
let budgetId: Int
|
||||
}
|
||||
|
||||
enum TransactionType: Int, CaseIterable, Identifiable, Hashable {
|
||||
case expense
|
||||
case income
|
||||
|
||||
var localizedKey: LocalizedStringKey {
|
||||
var key: String
|
||||
switch self {
|
||||
case .expense:
|
||||
key = "type_expense"
|
||||
case .income:
|
||||
key = "type_income"
|
||||
}
|
||||
return LocalizedStringKey(key)
|
||||
}
|
||||
|
||||
var id: TransactionType { self }
|
||||
}
|
||||
|
|
|
@ -16,6 +16,12 @@ class TransactionDataStore: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
var transaction: Result<Transaction, NetworkError> = .failure(.unknown) {
|
||||
didSet {
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
func getTransactions(_ category: Category? = nil) {
|
||||
self.transactions = .failure(.loading)
|
||||
|
||||
|
@ -37,10 +43,26 @@ class TransactionDataStore: ObservableObject {
|
|||
})
|
||||
}
|
||||
|
||||
func createTransaction(_ transaction: Transaction) {
|
||||
self.transaction = .failure(.loading)
|
||||
|
||||
_ = self.transactionRepository.createTransaction(transaction)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveCompletion: { (completion) in
|
||||
switch completion {
|
||||
case .finished:
|
||||
return
|
||||
case .failure(let error):
|
||||
self.transaction = .failure(error)
|
||||
}
|
||||
}, receiveValue: { (transaction) in
|
||||
self.transaction = .success(transaction)
|
||||
})
|
||||
}
|
||||
|
||||
let objectWillChange = ObservableObjectPublisher()
|
||||
private let transactionRepository: TransactionRepository
|
||||
init(_ transactionRepository: TransactionRepository, category: Category? = nil) {
|
||||
init(_ transactionRepository: TransactionRepository) {
|
||||
self.transactionRepository = transactionRepository
|
||||
self.getTransactions(category)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,12 +9,31 @@
|
|||
import SwiftUI
|
||||
|
||||
struct TransactionDetailsView: View {
|
||||
@State var title: String = ""
|
||||
@State var description: String? = nil
|
||||
@State var date: Date = Date()
|
||||
@State var amount: Int = 0
|
||||
@State var category: Category? = nil
|
||||
@State var expense: Bool = true
|
||||
@State var createdBy: User? = nil
|
||||
@State var budget: Budget? = nil
|
||||
|
||||
var body: some View {
|
||||
Text(/*@START_MENU_TOKEN@*/"Hello World!"/*@END_MENU_TOKEN@*/)
|
||||
List {
|
||||
TextField("prompt_title", text: self.$title)
|
||||
// DatePicker(
|
||||
}
|
||||
}
|
||||
|
||||
init(_ dataStoreProvider: DataStoreProvider, transaction: Transaction) {
|
||||
|
||||
// self.title = transaction.title
|
||||
// self.description = transaction.description
|
||||
// self.date = transaction.date
|
||||
// self.amount = transaction.amount
|
||||
// self.category = transaction.category
|
||||
// self.expense = transaction.expense
|
||||
// self.createdBy = transaction.createdBy
|
||||
// self.budget = transaction.budget
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,8 @@ struct TransactionListView: View {
|
|||
let dataStoreProvider: DataStoreProvider
|
||||
init(_ dataStoreProvider: DataStoreProvider, category: Category? = nil) {
|
||||
self.dataStoreProvider = dataStoreProvider
|
||||
self.transactionDataStore = dataStoreProvider.transactionDataStore(category)
|
||||
self.transactionDataStore = dataStoreProvider.transactionDataStore()
|
||||
self.transactionDataStore.getTransactions(category)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,4 +19,8 @@ class TransactionRepository {
|
|||
func getTransactions(categoryIds: [Int]? = nil, from: Date? = nil, count: Int? = nil, page: Int? = nil) -> AnyPublisher<[Transaction], NetworkError> {
|
||||
return apiService.getTransactions(categoryIds: categoryIds, from: from, count: count, page: page)
|
||||
}
|
||||
|
||||
func createTransaction(_ transaction: Transaction) -> AnyPublisher<Transaction, NetworkError> {
|
||||
return apiService.newTransaction(transaction)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,18 @@
|
|||
// MARK: Generic
|
||||
"add" = "Add";
|
||||
"filter" = "Filter";
|
||||
"save" = "Save";
|
||||
"cancel" = "Cancel";
|
||||
"loading_default" = "Loading...";
|
||||
"prompt_name" = "Name";
|
||||
"prompt_description" = "Description";
|
||||
"prompt_amount" = "Amount";
|
||||
"prompt_date" = "Date";
|
||||
"prompt_category" = "Category";
|
||||
"prompt_budget" = "Budget";
|
||||
"prompt_type" = "Type";
|
||||
"type_income" = "Income";
|
||||
"type_expense" = "Expense";
|
||||
|
||||
// MARK: Login
|
||||
"info_login" = "Login to start managing your budget";
|
||||
|
|
|
@ -9,7 +9,18 @@
|
|||
// MARK: Generic
|
||||
"add" = "Agregar";
|
||||
"filter" = "Filtrar";
|
||||
"save" = "Guardar";
|
||||
"cancel" = "Cancelar";
|
||||
"loading_default" = "Cargando...";
|
||||
"prompt_name" = "Nombre";
|
||||
"prompt_description" = "Descripción";
|
||||
"prompt_amount" = "Monto";
|
||||
"prompt_date" = "Fecha";
|
||||
"prompt_category" = "Categoría";
|
||||
"prompt_budget" = "Presupuesto";
|
||||
"prompt_type" = "Tipo";
|
||||
"type_income" = "Ingreso";
|
||||
"type_expense" = "Gasto";
|
||||
|
||||
// MARK: Login
|
||||
"info_login" = "Inicia sesión para empezar a manejar su presupuseto";
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Budget
|
||||
|
||||
class BudgetTests: XCTestCase {
|
||||
|
||||
|
|
Loading…
Reference in a new issue