Add pie chart and detailed statistics to Dashboard view

This commit is contained in:
“SamoilenkoVadym” 2025-03-02 18:56:24 +00:00
parent df59aab471
commit 7586c78f55
2 changed files with 140 additions and 4 deletions

View file

@ -3,18 +3,34 @@ import SwiftUI
struct DashboardView: View {
let transactions: [TransactionModel]
var totalBalance: Double {
private var totalBalance: Double {
transactions.reduce(0) { $0 + $1.signedAmount }
}
var income: Double {
private var income: Double {
transactions.filter { !$0.isExpense }.reduce(0) { $0 + $1.amount }
}
var expenses: Double {
private var expenses: Double {
transactions.filter { $0.isExpense }.reduce(0) { $0 + $1.amount }
}
private var categoryExpenses: [PieChartView.PieSlice] {
// Группируем расходы по категориям
let expensesByCategory = Dictionary(grouping: transactions.filter { $0.isExpense }) { $0.category }
let totalExpenses = expenses
return expensesByCategory.map { category, transactions in
let categoryTotal = transactions.reduce(0) { $0 + $1.amount }
let percentage = totalExpenses > 0 ? categoryTotal / totalExpenses : 0
return PieChartView.PieSlice(
category: CategoryModel.category(for: category),
amount: categoryTotal,
percentage: percentage
)
}.sorted { $0.amount > $1.amount }
}
var body: some View {
NavigationView {
ScrollView {
@ -68,6 +84,44 @@ struct DashboardView: View {
.shadow(radius: 2)
}
// Expenses by Category
if !categoryExpenses.isEmpty {
VStack(alignment: .leading, spacing: 16) {
Text("Expenses by Category")
.font(.headline)
// Pie Chart
PieChartView(slices: categoryExpenses)
.frame(height: 200)
.padding(.vertical)
// Category Legend
VStack(spacing: 12) {
ForEach(categoryExpenses) { slice in
HStack {
Circle()
.fill(Color(slice.category.color))
.frame(width: 12, height: 12)
Text(slice.category.name)
Spacer()
Text(String(format: "$ %.2f", slice.amount))
.foregroundColor(.secondary)
Text(String(format: "(%.1f%%)", slice.percentage * 100))
.foregroundColor(.secondary)
}
}
}
}
.padding()
.background(Color(.systemBackground))
.cornerRadius(12)
.shadow(radius: 2)
}
// Recent Transactions
VStack(alignment: .leading, spacing: 12) {
Text("Recent Transactions")
@ -75,7 +129,9 @@ struct DashboardView: View {
ForEach(Array(transactions.prefix(3))) { transaction in
TransactionRowView(transaction: transaction)
Divider()
if transaction.id != transactions.prefix(3).last?.id {
Divider()
}
}
}
.frame(maxWidth: .infinity)

View file

@ -0,0 +1,80 @@
//
// PieChartView.swift
// Coinly
//
// Created by Vadym Samoilenko on 02/03/2025.
//
import SwiftUI
struct PieChartView: View {
struct PieSlice: Identifiable {
let id = UUID()
let category: CategoryModel
let amount: Double
let percentage: Double
}
let slices: [PieSlice]
var body: some View {
ZStack {
ForEach(slices) { slice in
PieSliceView(
startAngle: startAngle(for: slice),
endAngle: endAngle(for: slice),
color: colorFor(category: slice.category)
)
}
}
.aspectRatio(1, contentMode: .fit)
}
private func startAngle(for slice: PieSlice) -> Double {
let index = slices.firstIndex(where: { $0.id == slice.id }) ?? 0
let precedingSlices = slices.prefix(index)
let precedingPercentages = precedingSlices.map { $0.percentage }
let startPercentage = precedingPercentages.reduce(0, +)
return startPercentage * 360
}
private func endAngle(for slice: PieSlice) -> Double {
startAngle(for: slice) + (slice.percentage * 360)
}
private func colorFor(category: CategoryModel) -> Color {
switch category.color {
case "red": return .red
case "blue": return .blue
case "purple": return .purple
case "orange": return .orange
case "green": return .green
case "gray": return .gray
default: return .gray
}
}
}
struct PieSliceView: View {
let startAngle: Double
let endAngle: Double
let color: Color
var body: some View {
GeometryReader { geometry in
Path { path in
let center = CGPoint(x: geometry.size.width/2, y: geometry.size.height/2)
let radius = min(geometry.size.width, geometry.size.height)/2
path.move(to: center)
path.addArc(center: center,
radius: radius,
startAngle: .degrees(startAngle),
endAngle: .degrees(endAngle),
clockwise: false)
path.closeSubpath()
}
.fill(color)
}
}
}