Add pie chart and detailed statistics to Dashboard view
This commit is contained in:
parent
df59aab471
commit
7586c78f55
2 changed files with 140 additions and 4 deletions
|
|
@ -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)
|
||||
|
|
|
|||
80
Coinly/UI/Components/PieChartView.swift
Normal file
80
Coinly/UI/Components/PieChartView.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue