Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Insights group and split into piece, use dataManager to fetch basic stats in Setting #64

Merged
merged 2 commits into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion MMEX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
A3363EE72C93249D004696C7 /* CategoryAddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3363EE62C93249D004696C7 /* CategoryAddView.swift */; };
A3363EE92C9326A1004696C7 /* CategoryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3363EE82C9326A1004696C7 /* CategoryListView.swift */; };
A3363EEB2C93BF62004696C7 /* CurrencyRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3363EEA2C93BF62004696C7 /* CurrencyRepository.swift */; };
A33742882CA8E55400698466 /* IncomeAndExpense.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33742872CA8E55400698466 /* IncomeAndExpense.swift */; };
A337428A2CA8E72C00698466 /* Summary.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33742892CA8E72C00698466 /* Summary.swift */; };
A3462F642C9426F500F79145 /* InsightsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3462F632C9426F500F79145 /* InsightsViewModel.swift */; };
A3462F662C94854800F79145 /* ExportableEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3462F652C94854800F79145 /* ExportableEntity.swift */; };
A3462F6A2C948CDB00F79145 /* ExportableEntityDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3462F692C948CDB00F79145 /* ExportableEntityDocument.swift */; };
Expand Down Expand Up @@ -156,6 +158,8 @@
A3363EE62C93249D004696C7 /* CategoryAddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryAddView.swift; sourceTree = "<group>"; };
A3363EE82C9326A1004696C7 /* CategoryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryListView.swift; sourceTree = "<group>"; };
A3363EEA2C93BF62004696C7 /* CurrencyRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyRepository.swift; sourceTree = "<group>"; };
A33742872CA8E55400698466 /* IncomeAndExpense.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomeAndExpense.swift; sourceTree = "<group>"; };
A33742892CA8E72C00698466 /* Summary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Summary.swift; sourceTree = "<group>"; };
A3462F632C9426F500F79145 /* InsightsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsightsViewModel.swift; sourceTree = "<group>"; };
A3462F652C94854800F79145 /* ExportableEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportableEntity.swift; sourceTree = "<group>"; };
A3462F692C948CDB00F79145 /* ExportableEntityDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportableEntityDocument.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -373,6 +377,16 @@
path = Repositories;
sourceTree = "<group>";
};
A3B51C072CA8E03100BB0681 /* Insights */ = {
isa = PBXGroup;
children = (
A3C142AD2C9134DD00D3CEC0 /* InsightsView.swift */,
A33742872CA8E55400698466 /* IncomeAndExpense.swift */,
A33742892CA8E72C00698466 /* Summary.swift */,
);
path = Insights;
sourceTree = "<group>";
};
A3C1421E2C89751500D3CEC0 = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -452,6 +466,7 @@
A3C142452C89CB1600D3CEC0 /* Views */ = {
isa = PBXGroup;
children = (
A3B51C072CA8E03100BB0681 /* Insights */,
A39B1B3E2C9A6714003E5562 /* Settings */,
A39B1B3D2C9A66F7003E5562 /* Currencies */,
A39B1B392C9A668F003E5562 /* Accounts */,
Expand All @@ -460,7 +475,6 @@
A39B1B3A2C9A66B8003E5562 /* Payees */,
A39B1B3B2C9A66CB003E5562 /* Transactions */,
A3C142A72C906E0A00D3CEC0 /* CustomNumberPadView.swift */,
A3C142AD2C9134DD00D3CEC0 /* InsightsView.swift */,
A37E7D832C9AC14800B4ECFC /* ManagementView.swift */,
);
path = Views;
Expand Down Expand Up @@ -568,6 +582,7 @@
929EF6652C9FF3FB0051A3E6 /* StockRepository.swift in Sources */,
A3C142502C8B366400D3CEC0 /* PayeeListView.swift in Sources */,
A379C1A72CA3B79E00CC8E2C /* AssetAddView.swift in Sources */,
A337428A2CA8E72C00698466 /* Summary.swift in Sources */,
A3C142672C8F2AF500D3CEC0 /* TransactionData.swift in Sources */,
920824042CA4ADC100388AB2 /* ScheduledSplitData.swift in Sources */,
A3462F642C9426F500F79145 /* InsightsViewModel.swift in Sources */,
Expand Down Expand Up @@ -614,6 +629,7 @@
A3462F6A2C948CDB00F79145 /* ExportableEntityDocument.swift in Sources */,
920824122CA4C6A400388AB2 /* BudgetTableRepository.swift in Sources */,
A3C142982C8FE62200D3CEC0 /* TransactionListView.swift in Sources */,
A33742882CA8E55400698466 /* IncomeAndExpense.swift in Sources */,
A3C142542C8B381400D3CEC0 /* PayeeEditView.swift in Sources */,
920823F82CA498B500388AB2 /* CurrencyHistoryRepository.swift in Sources */,
A37E7D8A2C9AC30700B4ECFC /* HelpFAQView.swift in Sources */,
Expand Down
8 changes: 8 additions & 0 deletions MMEX/DatabaseManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ extension DataManager {
currencyFormat = [:]
print("Database connection closed.")
}

/// basic stats
func getDatabaseFileName() -> String? {
return self.databaseURL?.lastPathComponent
}
func getDatabaseUserVersion() -> Int32? {
return self.db?.userVersion
}
}

extension DataManager {
Expand Down
7 changes: 7 additions & 0 deletions MMEX/Models/TransactionData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ extension TransactionData {
}
return 0.0
}
var actual: Double {
return switch transCode {
case .withdrawal: 0 - transAmount;
case .deposit: transAmount;
default: 0.0
}
}
var transfer: Double {
// TODO: in base currency
if transCode == .transfer {
Expand Down
6 changes: 0 additions & 6 deletions MMEX/ViewModels/InfotableViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,6 @@ class InfotableViewModel: ObservableObject {
)
}
}

var userVersion: Int32 { self.dataManager.repository?.userVersion ?? 0 }

func getDatabaseURL() -> URL {
return self.dataManager.databaseURL!
}

// Set up individual bindings for each @Published property
private func setupBindings() {
Expand Down
25 changes: 21 additions & 4 deletions MMEX/ViewModels/InsightsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import Combine
class InsightsViewModel: ObservableObject {
private var dataManager: DataManager


@Published var stats: [TransactionData] = [] // all transactions
// Published properties for the view to observe
@Published var stats: [TransactionData] = []
@Published var recentStats: [TransactionData] = []
@Published var startDate: Date
@Published var endDate: Date

Expand All @@ -25,18 +27,19 @@ class InsightsViewModel: ObservableObject {
self.endDate = Date()

// Load transactions on initialization
loadRecentTransactions()
loadTransactions()

// Automatically reload transactions when date range changes
$startDate
.combineLatest($endDate)
.sink { [weak self] startDate, endDate in
self?.loadTransactions()
self?.loadRecentTransactions()
}
.store(in: &cancellables)
}

func loadTransactions() {
func loadRecentTransactions() {
let repository = dataManager.transactionRepository

// Fetch transactions asynchronously
Expand All @@ -45,7 +48,21 @@ class InsightsViewModel: ObservableObject {

// Update the published stats on the main thread
DispatchQueue.main.async {
self.stats = transactions
self.recentStats = transactions
}
}
}

func loadTransactions() {
if let repository = dataManager.transactionRepository {
// Fetch transactions asynchronously
DispatchQueue.global(qos: .background).async {
let transactions = repository.load()

// Update the published stats on the main thread
DispatchQueue.main.async {
self.stats = transactions
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion MMEX/Views/Currencies/CurrencyEditView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ struct CurrencyEditView: View {
}
}
.labelsHidden()
.pickerStyle(MenuPickerStyle()) // Adjust the style of the picker as needed
.pickerStyle(SegmentedPickerStyle()) // Adjust the style of the picker as needed
}
}
}
Expand Down
53 changes: 53 additions & 0 deletions MMEX/Views/Insights/IncomeAndExpense.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// IncomeAndExpense.swift
// MMEX
//
// Created by Lisheng Guan on 2024/9/29.
//

import SwiftUI
import Charts

struct IncomeAndExpense: View {
@Binding var stats: [TransactionData]
var body: some View {
Chart() {
ForEach(stats) { stat in
Plot {
BarMark(
x: .value("Day", stat.day),
y: .value("Amount", stat.income)
)
// .foregroundStyle(by: .value("Status", $0.status.fullName))
.foregroundStyle(.green)
}
.accessibilityLabel("income")
.accessibilityValue("\(stat.income)")
}

ForEach(stats) { stat in
Plot {
BarMark(
x: .value("Day", stat.day),
y: .value("Amount", 0 - stat.expenses)
)
// .foregroundStyle(by: .value("Status", $0.status.fullName))
.foregroundStyle(.purple)
}
.accessibilityLabel("expenses")
.accessibilityValue("\(stat.expenses)")
}
}
.chartYAxis {
AxisMarks(preset: .automatic, position: .leading)
}
.frame(height: 300)
.chartYAxis(.automatic)
.chartXAxis(.automatic)
.padding(.horizontal)
}
}

#Preview {
IncomeAndExpense(stats: .constant(TransactionData.sampleData))
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ struct InsightsView: View {
NavigationStack {
ScrollView {
VStack(spacing: 20) {
Section {
Summary(stats: $viewModel.stats)
} header: {
Text("Account Income Summary")
.font(.headline)
.padding(.horizontal)
}

// Date Range Filters Section
Section {
Expand All @@ -36,7 +43,7 @@ struct InsightsView: View {
}

Section {
incomeVSexpense
IncomeAndExpense(stats: $viewModel.recentStats)
} header: {
Text("Income vs Expense Over Time")
.font(.headline)
Expand Down Expand Up @@ -68,43 +75,6 @@ struct InsightsView: View {
.navigationBarTitleDisplayMode(.inline) // Ensure title is inline to reduce top space
}
}

private var incomeVSexpense: some View {
Chart() {
ForEach(viewModel.stats) { stat in
Plot {
BarMark(
x: .value("Day", stat.day),
y: .value("Amount", stat.income)
)
// .foregroundStyle(by: .value("Status", $0.status.fullName))
.foregroundStyle(.green)
}
.accessibilityLabel("income")
.accessibilityValue("\(stat.income)")
}

ForEach(viewModel.stats) { stat in
Plot {
BarMark(
x: .value("Day", stat.day),
y: .value("Amount", 0 - stat.expenses)
)
// .foregroundStyle(by: .value("Status", $0.status.fullName))
.foregroundStyle(.purple)
}
.accessibilityLabel("expenses")
.accessibilityValue("\(stat.expenses)")
}
}
.chartYAxis {
AxisMarks(preset: .automatic, position: .leading)
}
.frame(height: 300)
.chartYAxis(.automatic)
.chartXAxis(.automatic)
.padding(.horizontal)
}
}

#Preview {
Expand Down
31 changes: 31 additions & 0 deletions MMEX/Views/Insights/Summary.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Summary.swift
// MMEX
//
// Created by Lisheng Guan on 2024/9/29.
//

import SwiftUI
import Charts

struct Summary: View {
@Binding var stats: [TransactionData]

var body: some View {
Chart(stats) {
BarMark(
x: .value("Amount", $0.income),
y: .value("Account", String($0.accountId))
)
.foregroundStyle(by: .value("Status", $0.status.fullName))
}
.chartXAxis (.automatic)
.chartYAxis (.automatic)
.frame(maxHeight: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/)
.frame(idealHeight: 300)
}
}

#Preview {
Summary(stats: .constant(TransactionData.sampleData))
}
4 changes: 2 additions & 2 deletions MMEX/Views/Settings/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ struct SettingsView: View {
HStack {
Text("Database File")
Spacer()
Text(viewModel.getDatabaseURL().lastPathComponent)
Text(dataManager.getDatabaseFileName() ?? "")
}
HStack {
Text("Schema Version")
Spacer()
Text("\(viewModel.userVersion)")
Text(String(dataManager.getDatabaseUserVersion() ?? 0))
}
HStack {
Text("Date Format")
Expand Down