Add transaction filtering by period, type and categories
This commit is contained in:
parent
30afdbb5f5
commit
df59aab471
3 changed files with 166 additions and 6 deletions
57
Coinly/Features/Models/TransactionFilter.swift
Normal file
57
Coinly/Features/Models/TransactionFilter.swift
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// TransactionFilter.swift
|
||||
// Coinly
|
||||
//
|
||||
// Created by Vadym Samoilenko on 02/03/2025.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TransactionFilter {
|
||||
enum Period {
|
||||
case all
|
||||
case today
|
||||
case week
|
||||
case month
|
||||
|
||||
func filter(_ date: Date) -> Bool {
|
||||
let calendar = Calendar.current
|
||||
let now = Date()
|
||||
|
||||
switch self {
|
||||
case .all:
|
||||
return true
|
||||
case .today:
|
||||
return calendar.isDate(date, inSameDayAs: now)
|
||||
case .week:
|
||||
let weekAgo = calendar.date(byAdding: .day, value: -7, to: now)!
|
||||
return date >= weekAgo
|
||||
case .month:
|
||||
let monthAgo = calendar.date(byAdding: .month, value: -1, to: now)!
|
||||
return date >= monthAgo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var period: Period = .all
|
||||
var selectedCategories: Set<String> = []
|
||||
var transactionType: TransactionType? = nil
|
||||
|
||||
func applies(to transaction: TransactionModel) -> Bool {
|
||||
// Check period
|
||||
guard period.filter(transaction.date) else { return false }
|
||||
|
||||
// Check categories
|
||||
if !selectedCategories.isEmpty && !selectedCategories.contains(transaction.category) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check type
|
||||
if let type = transactionType, transaction.type != type {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
71
Coinly/Features/Transactions/TransactionFilterView.swift
Normal file
71
Coinly/Features/Transactions/TransactionFilterView.swift
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import SwiftUI
|
||||
|
||||
struct TransactionFilterView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Binding var filter: TransactionFilter
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
// Period Filter
|
||||
Section("Time Period") {
|
||||
Picker("Period", selection: $filter.period) {
|
||||
Text("All Time").tag(TransactionFilter.Period.all)
|
||||
Text("Today").tag(TransactionFilter.Period.today)
|
||||
Text("This Week").tag(TransactionFilter.Period.week)
|
||||
Text("This Month").tag(TransactionFilter.Period.month)
|
||||
}
|
||||
}
|
||||
|
||||
// Type Filter
|
||||
Section("Transaction Type") {
|
||||
Picker("Type", selection: Binding(
|
||||
get: { filter.transactionType ?? .expense },
|
||||
set: { filter.transactionType = $0 }
|
||||
)) {
|
||||
Text("All").tag(TransactionType.expense)
|
||||
Text("Income").tag(TransactionType.income)
|
||||
Text("Expense").tag(TransactionType.expense)
|
||||
}
|
||||
}
|
||||
|
||||
// Categories Filter
|
||||
Section("Categories") {
|
||||
ForEach(CategoryModel.categories) { category in
|
||||
Toggle(isOn: Binding(
|
||||
get: { filter.selectedCategories.contains(category.name) },
|
||||
set: { isSelected in
|
||||
if isSelected {
|
||||
filter.selectedCategories.insert(category.name)
|
||||
} else {
|
||||
filter.selectedCategories.remove(category.name)
|
||||
}
|
||||
}
|
||||
)) {
|
||||
HStack {
|
||||
Image(systemName: category.icon)
|
||||
.foregroundColor(Color(category.color))
|
||||
Text(category.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Filters")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Reset") {
|
||||
filter = TransactionFilter()
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Done") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,28 +3,60 @@ import SwiftUI
|
|||
struct TransactionsView: View {
|
||||
@ObservedObject var store: TransactionsStore
|
||||
@State private var showingAddTransaction = false
|
||||
@State private var showingFilters = false
|
||||
@State private var filter = TransactionFilter()
|
||||
|
||||
var filteredTransactions: [TransactionModel] {
|
||||
store.transactions.filter { filter.applies(to: $0) }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
ForEach(store.transactions) { transaction in
|
||||
ForEach(filteredTransactions) { transaction in
|
||||
TransactionRowView(transaction: transaction)
|
||||
}
|
||||
.onDelete { indexSet in
|
||||
store.deleteTransaction(at: indexSet)
|
||||
// Преобразуем индексы отфильтрованного списка в индексы полного списка
|
||||
let transactionsToDelete = indexSet.map { filteredTransactions[$0] }
|
||||
for transaction in transactionsToDelete {
|
||||
if let index = store.transactions.firstIndex(where: { $0.id == transaction.id }) {
|
||||
store.deleteTransaction(at: IndexSet([index]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Transactions")
|
||||
.toolbar {
|
||||
Button {
|
||||
showingAddTransaction = true
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button {
|
||||
showingFilters = true
|
||||
} label: {
|
||||
Image(systemName: "line.3.horizontal.decrease.circle")
|
||||
.foregroundColor(hasActiveFilters ? .blue : .gray)
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
showingAddTransaction = true
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showingAddTransaction) {
|
||||
AddTransactionView(addTransaction: store.addTransaction)
|
||||
}
|
||||
.sheet(isPresented: $showingFilters) {
|
||||
TransactionFilterView(filter: $filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var hasActiveFilters: Bool {
|
||||
filter.period != .all ||
|
||||
!filter.selectedCategories.isEmpty ||
|
||||
filter.transactionType != nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue