Fix transaction creation and editing functionality. Update TransactionModel initialization and improve AddTransactionView
This commit is contained in:
parent
ce09f987c9
commit
79e649756a
7 changed files with 392 additions and 220 deletions
|
|
@ -9,7 +9,20 @@ struct TransactionModel: Identifiable, Codable {
|
|||
var note: String?
|
||||
var originalCurrency: AppSettings.Currency
|
||||
|
||||
init(amount: Double, date: Date, type: TransactionType, category: String, note: String?, originalCurrency: AppSettings.Currency) {
|
||||
var isExpense: Bool {
|
||||
type == .expense
|
||||
}
|
||||
|
||||
var signedAmount: Double {
|
||||
isExpense ? -amount : amount
|
||||
}
|
||||
|
||||
init(amount: Double,
|
||||
date: Date,
|
||||
type: TransactionType,
|
||||
category: String,
|
||||
note: String? = nil,
|
||||
originalCurrency: AppSettings.Currency) {
|
||||
self.id = UUID().uuidString
|
||||
self.amount = amount
|
||||
self.date = date
|
||||
|
|
@ -19,14 +32,6 @@ struct TransactionModel: Identifiable, Codable {
|
|||
self.originalCurrency = originalCurrency
|
||||
}
|
||||
|
||||
var isExpense: Bool {
|
||||
type == .expense
|
||||
}
|
||||
|
||||
var signedAmount: Double {
|
||||
isExpense ? -amount : amount
|
||||
}
|
||||
|
||||
func amountInCurrentCurrency() -> Double {
|
||||
let settings = AppSettings.shared
|
||||
return settings.convert(amount, from: originalCurrency, to: settings.currency)
|
||||
|
|
|
|||
|
|
@ -3,22 +3,72 @@ import Foundation
|
|||
class TransactionsStore: ObservableObject {
|
||||
@Published private(set) var transactions: [TransactionModel] = TransactionModel.sampleData
|
||||
|
||||
init() {
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(currencyDidChange),
|
||||
name: .currencyDidChange,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
// CRUD операции
|
||||
func addTransaction(_ transaction: TransactionModel) {
|
||||
transactions.insert(transaction, at: 0)
|
||||
}
|
||||
|
||||
func updateTransaction(_ transaction: TransactionModel) {
|
||||
if let index = transactions.firstIndex(where: { $0.id == transaction.id }) {
|
||||
transactions[index] = transaction
|
||||
}
|
||||
}
|
||||
|
||||
func deleteTransaction(_ transaction: TransactionModel) {
|
||||
transactions.removeAll { $0.id == transaction.id }
|
||||
}
|
||||
|
||||
func deleteTransaction(at indexSet: IndexSet) {
|
||||
transactions.remove(atOffsets: indexSet)
|
||||
}
|
||||
|
||||
@objc private func currencyDidChange() {
|
||||
objectWillChange.send()
|
||||
// Фильтрация
|
||||
func filterTransactions(searchText: String, filter: TransactionFilter) -> [TransactionModel] {
|
||||
var filteredTransactions = transactions
|
||||
|
||||
// Фильтр по поисковому запросу
|
||||
if !searchText.isEmpty {
|
||||
filteredTransactions = filteredTransactions.filter { transaction in
|
||||
let searchString = searchText.lowercased()
|
||||
return transaction.category.lowercased().contains(searchString) ||
|
||||
transaction.note?.lowercased().contains(searchString) ?? false
|
||||
}
|
||||
}
|
||||
|
||||
// Фильтр по типу транзакции
|
||||
if let type = filter.transactionType {
|
||||
filteredTransactions = filteredTransactions.filter { $0.type == type }
|
||||
}
|
||||
|
||||
// Фильтр по категориям
|
||||
if !filter.selectedCategories.isEmpty {
|
||||
filteredTransactions = filteredTransactions.filter { filter.selectedCategories.contains($0.category) }
|
||||
}
|
||||
|
||||
// Фильтр по периоду
|
||||
filteredTransactions = filteredTransactions.filter { filter.period.filter($0.date) }
|
||||
|
||||
return filteredTransactions
|
||||
}
|
||||
|
||||
// Сортировка
|
||||
func sortTransactions(_ transactions: [TransactionModel], by sortOrder: SortOrder = .dateDescending) -> [TransactionModel] {
|
||||
switch sortOrder {
|
||||
case .dateAscending:
|
||||
return transactions.sorted { $0.date < $1.date }
|
||||
case .dateDescending:
|
||||
return transactions.sorted { $0.date > $1.date }
|
||||
case .amountAscending:
|
||||
return transactions.sorted { $0.amount < $1.amount }
|
||||
case .amountDescending:
|
||||
return transactions.sorted { $0.amount > $1.amount }
|
||||
}
|
||||
}
|
||||
|
||||
enum SortOrder {
|
||||
case dateAscending
|
||||
case dateDescending
|
||||
case amountAscending
|
||||
case amountDescending
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,20 +4,75 @@ struct AddTransactionView: View {
|
|||
@Environment(\.dismiss) private var dismiss
|
||||
@EnvironmentObject private var settings: AppSettings
|
||||
|
||||
@State private var amount = ""
|
||||
@State private var note = ""
|
||||
@State private var category = CategoryModel.categories[0].name
|
||||
@State private var date = Date()
|
||||
@State private var type: TransactionType = .expense
|
||||
@State private var amount: String
|
||||
@State private var note: String
|
||||
@State private var category: String
|
||||
@State private var date: Date
|
||||
@State private var type: TransactionType
|
||||
@State private var showingCategories = false
|
||||
@State private var showingDeleteConfirmation = false
|
||||
|
||||
let addTransaction: (TransactionModel) -> Void
|
||||
let editingTransaction: TransactionModel?
|
||||
let onDelete: (() -> Void)?
|
||||
|
||||
// Инициализатор для создания новой транзакции
|
||||
init(addTransaction: @escaping (TransactionModel) -> Void) {
|
||||
self.addTransaction = addTransaction
|
||||
self.editingTransaction = nil
|
||||
self.onDelete = nil
|
||||
_amount = State(initialValue: "")
|
||||
_note = State(initialValue: "")
|
||||
_category = State(initialValue: CategoryModel.categories[0].name)
|
||||
_date = State(initialValue: Date())
|
||||
_type = State(initialValue: .expense)
|
||||
}
|
||||
|
||||
// Инициализатор для редактирования существующей транзакции
|
||||
init(editingTransaction: TransactionModel,
|
||||
addTransaction: @escaping (TransactionModel) -> Void,
|
||||
onDelete: (() -> Void)? = nil) {
|
||||
self.addTransaction = addTransaction
|
||||
self.editingTransaction = editingTransaction
|
||||
self.onDelete = onDelete
|
||||
_amount = State(initialValue: String(format: "%.2f", editingTransaction.amount))
|
||||
_note = State(initialValue: editingTransaction.note ?? "")
|
||||
_category = State(initialValue: editingTransaction.category)
|
||||
_date = State(initialValue: editingTransaction.date)
|
||||
_type = State(initialValue: editingTransaction.type)
|
||||
}
|
||||
|
||||
private var isValidAmount: Bool {
|
||||
guard let amountDouble = Double(amount) else { return false }
|
||||
return amountDouble > 0
|
||||
}
|
||||
|
||||
private func createTransaction() -> TransactionModel? {
|
||||
guard let amountDouble = Double(amount) else { return nil }
|
||||
return TransactionModel(
|
||||
amount: amountDouble,
|
||||
date: date,
|
||||
type: type,
|
||||
category: category,
|
||||
note: note.isEmpty ? nil : note,
|
||||
originalCurrency: settings.currency
|
||||
)
|
||||
}
|
||||
|
||||
private var navigationTitle: String {
|
||||
editingTransaction == nil ? "New Transaction" : "Edit Transaction"
|
||||
}
|
||||
|
||||
private var saveButtonTitle: String {
|
||||
editingTransaction == nil ? "Add" : "Save"
|
||||
}
|
||||
|
||||
private func handleSave() {
|
||||
guard let transaction = createTransaction() else { return }
|
||||
addTransaction(transaction)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack {
|
||||
|
|
@ -56,9 +111,9 @@ struct AddTransactionView: View {
|
|||
|
||||
// Details
|
||||
VStack(spacing: 0) {
|
||||
Button(action: {
|
||||
Button {
|
||||
showingCategories = true
|
||||
}) {
|
||||
} label: {
|
||||
HStack {
|
||||
let selectedCategory = CategoryModel.category(for: category)
|
||||
Image(systemName: selectedCategory.icon)
|
||||
|
|
@ -103,11 +158,28 @@ struct AddTransactionView: View {
|
|||
.background(Color(uiColor: .secondarySystemGroupedBackground))
|
||||
.cornerRadius(12)
|
||||
.padding(.horizontal)
|
||||
|
||||
if editingTransaction != nil {
|
||||
Button {
|
||||
showingDeleteConfirmation = true
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "trash")
|
||||
Text("Delete Transaction")
|
||||
}
|
||||
.foregroundColor(.red)
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color(uiColor: .secondarySystemGroupedBackground))
|
||||
.cornerRadius(12)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
.padding(.top, 20)
|
||||
}
|
||||
}
|
||||
.navigationTitle("New Transaction")
|
||||
.navigationTitle(navigationTitle)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
|
|
@ -117,22 +189,21 @@ struct AddTransactionView: View {
|
|||
}
|
||||
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Add") {
|
||||
guard let amountDouble = Double(amount) else { return }
|
||||
let transaction = TransactionModel(
|
||||
amount: amountDouble,
|
||||
date: date,
|
||||
type: type,
|
||||
category: category,
|
||||
note: note.isEmpty ? nil : note,
|
||||
originalCurrency: settings.currency
|
||||
)
|
||||
addTransaction(transaction)
|
||||
dismiss()
|
||||
Button(saveButtonTitle) {
|
||||
handleSave()
|
||||
}
|
||||
.disabled(!isValidAmount)
|
||||
}
|
||||
}
|
||||
.alert("Delete Transaction", isPresented: $showingDeleteConfirmation) {
|
||||
Button("Cancel", role: .cancel) { }
|
||||
Button("Delete", role: .destructive) {
|
||||
onDelete?()
|
||||
dismiss()
|
||||
}
|
||||
} message: {
|
||||
Text("Are you sure you want to delete this transaction? This action cannot be undone.")
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showingCategories) {
|
||||
CategoryPickerView(selectedCategory: $category)
|
||||
|
|
@ -140,57 +211,17 @@ struct AddTransactionView: View {
|
|||
}
|
||||
}
|
||||
|
||||
// Вспомогательное view для выбора категории
|
||||
struct CategoryPickerView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Binding var selectedCategory: String
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
ForEach(CategoryModel.categories) { category in
|
||||
Button {
|
||||
selectedCategory = category.name
|
||||
dismiss()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: category.icon)
|
||||
.foregroundColor(Color(category.color))
|
||||
.frame(width: 30)
|
||||
|
||||
Text(category.name)
|
||||
.foregroundColor(AppStyle.labelPrimary)
|
||||
|
||||
Spacer()
|
||||
|
||||
if selectedCategory == category.name {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Select Category")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AddTransactionView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
AddTransactionView(addTransaction: { _ in })
|
||||
.preferredColorScheme(.light)
|
||||
|
||||
AddTransactionView(addTransaction: { _ in })
|
||||
AddTransactionView(
|
||||
editingTransaction: TransactionModel.sampleData[0],
|
||||
addTransaction: { _ in },
|
||||
onDelete: { }
|
||||
)
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
.environmentObject(AppSettings.shared)
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@ import SwiftUI
|
|||
struct TransactionFilterView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Binding var filter: TransactionFilter
|
||||
@State private var selectedPeriodIndex = 0
|
||||
|
||||
private let periods: [(name: String, period: TransactionFilter.Period)] = [
|
||||
private let periods: [(title: String, period: TransactionFilter.Period)] = [
|
||||
("All Time", .all),
|
||||
("Today", .today),
|
||||
("Last 7 Days", .week),
|
||||
|
|
@ -17,14 +16,19 @@ struct TransactionFilterView: View {
|
|||
List {
|
||||
// Period Section
|
||||
Section {
|
||||
Picker("Time Period", selection: $selectedPeriodIndex) {
|
||||
ForEach(0..<periods.count, id: \.self) { index in
|
||||
Text(periods[index].name).tag(index)
|
||||
ForEach(periods, id: \.title) { period in
|
||||
HStack {
|
||||
Text(period.title)
|
||||
Spacer()
|
||||
if filter.period == period.period {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
filter.period = period.period
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.onChange(of: selectedPeriodIndex) { newValue in
|
||||
filter.period = periods[newValue].period
|
||||
}
|
||||
} header: {
|
||||
Text("Time Period")
|
||||
|
|
@ -32,46 +36,24 @@ struct TransactionFilterView: View {
|
|||
|
||||
// Type Section
|
||||
Section {
|
||||
HStack {
|
||||
Text("All")
|
||||
Spacer()
|
||||
if filter.transactionType == nil {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundColor(.accentColor)
|
||||
ForEach([
|
||||
("All Transactions", nil),
|
||||
("Income", TransactionType.income),
|
||||
("Expense", TransactionType.expense)
|
||||
], id: \.0) { title, type in
|
||||
HStack {
|
||||
Text(title)
|
||||
Spacer()
|
||||
if filter.transactionType == type {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
filter.transactionType = nil
|
||||
}
|
||||
|
||||
HStack {
|
||||
Label("Income", systemImage: "arrow.down.circle.fill")
|
||||
.foregroundColor(.green)
|
||||
Spacer()
|
||||
if filter.transactionType == .income {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundColor(.accentColor)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
filter.transactionType = type
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
filter.transactionType = .income
|
||||
}
|
||||
|
||||
HStack {
|
||||
Label("Expense", systemImage: "arrow.up.circle.fill")
|
||||
.foregroundColor(.red)
|
||||
Spacer()
|
||||
if filter.transactionType == .expense {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
filter.transactionType = .expense
|
||||
}
|
||||
} header: {
|
||||
Text("Transaction Type")
|
||||
}
|
||||
|
|
@ -79,22 +61,24 @@ struct TransactionFilterView: View {
|
|||
// Categories Section
|
||||
Section {
|
||||
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)
|
||||
}
|
||||
}
|
||||
)) {
|
||||
let isSelected = filter.selectedCategories.contains(category.name)
|
||||
HStack {
|
||||
HStack {
|
||||
Image(systemName: category.icon)
|
||||
.foregroundColor(Color(category.color))
|
||||
.frame(width: 24)
|
||||
Text(category.name)
|
||||
}
|
||||
Spacer()
|
||||
Toggle("", isOn: Binding(
|
||||
get: { isSelected },
|
||||
set: { newValue in
|
||||
if newValue {
|
||||
filter.selectedCategories.insert(category.name)
|
||||
} else {
|
||||
filter.selectedCategories.remove(category.name)
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
|
|
@ -115,7 +99,6 @@ struct TransactionFilterView: View {
|
|||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Reset") {
|
||||
filter = TransactionFilter()
|
||||
selectedPeriodIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,31 +112,9 @@ struct TransactionFilterView: View {
|
|||
}
|
||||
}
|
||||
|
||||
// Preview
|
||||
struct TransactionFilterView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
TransactionFilterView(
|
||||
filter: .constant(TransactionFilter())
|
||||
)
|
||||
.preferredColorScheme(.light)
|
||||
|
||||
TransactionFilterView(
|
||||
filter: .constant(TransactionFilter())
|
||||
)
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper extension для красивого отображения периодов
|
||||
extension TransactionFilter.Period {
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .all: return "All Time"
|
||||
case .today: return "Today"
|
||||
case .week: return "Last 7 Days"
|
||||
case .month: return "Last 30 Days"
|
||||
}
|
||||
TransactionFilterView(filter: .constant(TransactionFilter()))
|
||||
.environmentObject(AppSettings.shared)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,20 @@ struct TransactionsView: View {
|
|||
@State private var showingAddTransaction = false
|
||||
@State private var showingFilters = false
|
||||
@State private var searchText = ""
|
||||
@State private var filter = TransactionFilter()
|
||||
@State private var sortOrder = TransactionsStore.SortOrder.dateDescending
|
||||
@State private var transactionToEdit: TransactionModel?
|
||||
|
||||
private var filteredAndSortedTransactions: [TransactionModel] {
|
||||
let filtered = store.filterTransactions(searchText: searchText, filter: filter)
|
||||
return store.sortTransactions(filtered, by: sortOrder)
|
||||
}
|
||||
|
||||
private var groupedTransactions: [(String, [TransactionModel])] {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "MMMM yyyy"
|
||||
|
||||
let grouped = Dictionary(grouping: store.transactions) { transaction in
|
||||
let grouped = Dictionary(grouping: filteredAndSortedTransactions) { transaction in
|
||||
formatter.string(from: transaction.date)
|
||||
}
|
||||
|
||||
|
|
@ -23,36 +31,81 @@ struct TransactionsView: View {
|
|||
Color(uiColor: .systemBackground)
|
||||
.ignoresSafeArea()
|
||||
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 24) {
|
||||
ForEach(groupedTransactions, id: \.0) { month, transactions in
|
||||
VStack(spacing: 8) {
|
||||
// Month Header
|
||||
HStack {
|
||||
Text(month)
|
||||
.font(.system(size: 15, weight: .semibold))
|
||||
.foregroundColor(Color(uiColor: .secondaryLabel))
|
||||
.textCase(.uppercase)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
// Transactions
|
||||
VStack(spacing: 1) {
|
||||
ForEach(transactions) { transaction in
|
||||
TransactionRowView(transaction: transaction)
|
||||
if filteredAndSortedTransactions.isEmpty {
|
||||
EmptyTransactionsView()
|
||||
} else {
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 24) {
|
||||
ForEach(groupedTransactions, id: \.0) { month, transactions in
|
||||
VStack(spacing: 8) {
|
||||
// Month Header
|
||||
HStack {
|
||||
Text(month)
|
||||
.font(.system(size: 15, weight: .semibold))
|
||||
.foregroundColor(Color(uiColor: .secondaryLabel))
|
||||
.textCase(.uppercase)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
// Transactions
|
||||
VStack(spacing: 1) {
|
||||
ForEach(transactions) { transaction in
|
||||
TransactionRowView(transaction: transaction)
|
||||
.contextMenu {
|
||||
Button {
|
||||
transactionToEdit = transaction
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
|
||||
Button(role: .destructive) {
|
||||
store.deleteTransaction(transaction)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
Button(role: .destructive) {
|
||||
store.deleteTransaction(transaction)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
|
||||
Button {
|
||||
transactionToEdit = transaction
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
.tint(.orange)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top)
|
||||
}
|
||||
.padding(.top)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Transactions")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Menu {
|
||||
Picker("Sort by", selection: $sortOrder) {
|
||||
Label("Latest First", systemImage: "arrow.down").tag(TransactionsStore.SortOrder.dateDescending)
|
||||
Label("Oldest First", systemImage: "arrow.up").tag(TransactionsStore.SortOrder.dateAscending)
|
||||
Label("Largest Amount", systemImage: "arrow.down").tag(TransactionsStore.SortOrder.amountDescending)
|
||||
Label("Smallest Amount", systemImage: "arrow.up").tag(TransactionsStore.SortOrder.amountAscending)
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "arrow.up.arrow.down")
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
showingFilters = true
|
||||
} label: {
|
||||
|
|
@ -79,42 +132,14 @@ struct TransactionsView: View {
|
|||
.sheet(isPresented: $showingAddTransaction) {
|
||||
AddTransactionView(addTransaction: store.addTransaction)
|
||||
}
|
||||
.sheet(item: $transactionToEdit) { transaction in
|
||||
AddTransactionView(
|
||||
editingTransaction: transaction,
|
||||
addTransaction: store.updateTransaction
|
||||
)
|
||||
}
|
||||
.sheet(isPresented: $showingFilters) {
|
||||
TransactionFilterView(filter: .constant(TransactionFilter()))
|
||||
TransactionFilterView(filter: $filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Вспомогательное view для пустого состояния
|
||||
struct EmptyTransactionsView: View {
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
Image(systemName: "creditcard")
|
||||
.font(.system(size: 48))
|
||||
.foregroundColor(Color(uiColor: .systemGray))
|
||||
|
||||
Text("No Transactions")
|
||||
.font(.title3)
|
||||
.fontWeight(.semibold)
|
||||
|
||||
Text("Start adding your transactions to track your expenses and income")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(Color(uiColor: .secondaryLabel))
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
struct TransactionsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
TransactionsView(store: TransactionsStore())
|
||||
.preferredColorScheme(.light)
|
||||
|
||||
TransactionsView(store: TransactionsStore())
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
.environmentObject(AppSettings.shared)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
58
Coinly/UI/Components/CategoryPickerView.swift
Normal file
58
Coinly/UI/Components/CategoryPickerView.swift
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
//
|
||||
// CategoryPickerView.swift
|
||||
// Coinly
|
||||
//
|
||||
// Created by Vadym Samoilenko on 02/03/2025.
|
||||
//
|
||||
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CategoryPickerView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Binding var selectedCategory: String
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
ForEach(CategoryModel.categories) { category in
|
||||
Button {
|
||||
selectedCategory = category.name
|
||||
dismiss()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: category.icon)
|
||||
.foregroundColor(Color(category.color))
|
||||
.frame(width: 30)
|
||||
|
||||
Text(category.name)
|
||||
.foregroundColor(AppStyle.labelPrimary)
|
||||
|
||||
Spacer()
|
||||
|
||||
if selectedCategory == category.name {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Select Category")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CategoryPickerView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CategoryPickerView(selectedCategory: .constant(CategoryModel.categories[0].name))
|
||||
}
|
||||
}
|
||||
42
Coinly/UI/Components/EmptyTransactionsView.swift
Normal file
42
Coinly/UI/Components/EmptyTransactionsView.swift
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// EmptyTransactionsView.swift
|
||||
// Coinly
|
||||
//
|
||||
// Created by Vadym Samoilenko on 02/03/2025.
|
||||
//
|
||||
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct EmptyTransactionsView: View {
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
Image(systemName: "creditcard")
|
||||
.font(.system(size: 48))
|
||||
.foregroundColor(Color(uiColor: .systemGray))
|
||||
|
||||
Text("No Transactions")
|
||||
.font(.title3)
|
||||
.fontWeight(.semibold)
|
||||
|
||||
Text("Start adding your transactions to track your expenses and income")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(Color(uiColor: .secondaryLabel))
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, 32)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
struct EmptyTransactionsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
EmptyTransactionsView()
|
||||
.preferredColorScheme(.light)
|
||||
|
||||
EmptyTransactionsView()
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue