Primera build enviada
This commit is contained in:
@@ -10,19 +10,28 @@ struct AddSourceView: View {
|
||||
@State private var initialValue = ""
|
||||
@State private var showingCategoryPicker = false
|
||||
@State private var selectedAccountId: UUID?
|
||||
@State private var duplicateError: String?
|
||||
|
||||
@StateObject private var categoryRepository = CategoryRepository()
|
||||
@StateObject private var sourceRepository = InvestmentSourceRepository()
|
||||
|
||||
private var availableAccounts: [Account] {
|
||||
accountStore.accounts.filter { $0.safeId != nil }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
if accountStore.accounts.count > 1 {
|
||||
if availableAccounts.count > 1 {
|
||||
Section {
|
||||
Picker("Account", selection: $selectedAccountId) {
|
||||
ForEach(accountStore.accounts) { account in
|
||||
Text(account.name).tag(Optional(account.id))
|
||||
ForEach(availableAccounts, id: \.objectID) { account in
|
||||
Text(account.name).tag(Optional(account.safeId))
|
||||
}
|
||||
}
|
||||
.onChange(of: selectedAccountId) { _, _ in
|
||||
validateSourceName(name)
|
||||
}
|
||||
} header: {
|
||||
Text("Account")
|
||||
}
|
||||
@@ -32,6 +41,9 @@ struct AddSourceView: View {
|
||||
Section {
|
||||
TextField("Source Name", text: $name)
|
||||
.textContentType(.organizationName)
|
||||
.onChange(of: name) { _, newValue in
|
||||
validateSourceName(newValue)
|
||||
}
|
||||
|
||||
Button {
|
||||
showingCategoryPicker = true
|
||||
@@ -61,6 +73,11 @@ struct AddSourceView: View {
|
||||
}
|
||||
} header: {
|
||||
Text("Source Information")
|
||||
} footer: {
|
||||
if let error = duplicateError {
|
||||
Text(error)
|
||||
.foregroundColor(.negativeRed)
|
||||
}
|
||||
}
|
||||
|
||||
// Initial Value (Optional)
|
||||
@@ -107,7 +124,7 @@ struct AddSourceView: View {
|
||||
selectedCategory = categoryRepository.categories.first
|
||||
}
|
||||
if selectedAccountId == nil {
|
||||
selectedAccountId = accountStore.selectedAccount?.id ?? accountStore.accounts.first?.id
|
||||
selectedAccountId = accountStore.selectedAccount?.safeId ?? availableAccounts.first?.safeId
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,7 +133,21 @@ struct AddSourceView: View {
|
||||
// MARK: - Validation
|
||||
|
||||
private var isValid: Bool {
|
||||
!name.trimmingCharacters(in: .whitespaces).isEmpty && selectedCategory != nil
|
||||
!name.trimmingCharacters(in: .whitespaces).isEmpty && selectedCategory != nil && duplicateError == nil
|
||||
}
|
||||
|
||||
private func validateSourceName(_ newName: String) {
|
||||
let trimmed = newName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmed.isEmpty else {
|
||||
duplicateError = nil
|
||||
return
|
||||
}
|
||||
|
||||
if sourceRepository.sourceExists(name: trimmed, in: selectedAccount) {
|
||||
duplicateError = "A source with this name already exists."
|
||||
} else {
|
||||
duplicateError = nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Save
|
||||
@@ -177,7 +208,7 @@ struct AddSourceView: View {
|
||||
|
||||
private var selectedAccount: Account? {
|
||||
guard let id = selectedAccountId else { return nil }
|
||||
return accountStore.accounts.first { $0.id == id }
|
||||
return availableAccounts.first { $0.safeId == id }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,17 +279,17 @@ struct EditSourceView: View {
|
||||
self.source = source
|
||||
_name = State(initialValue: source.name)
|
||||
_selectedCategory = State(initialValue: source.category)
|
||||
_selectedAccountId = State(initialValue: source.account?.id)
|
||||
_selectedAccountId = State(initialValue: source.account?.safeId)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
if accountStore.accounts.count > 1 {
|
||||
if availableAccounts.count > 1 {
|
||||
Section {
|
||||
Picker("Account", selection: $selectedAccountId) {
|
||||
ForEach(accountStore.accounts) { account in
|
||||
Text(account.name).tag(Optional(account.id))
|
||||
ForEach(availableAccounts, id: \.objectID) { account in
|
||||
Text(account.name).tag(Optional(account.safeId))
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
@@ -320,6 +351,10 @@ struct EditSourceView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var availableAccounts: [Account] {
|
||||
accountStore.accounts.filter { $0.safeId != nil }
|
||||
}
|
||||
|
||||
private var isValid: Bool {
|
||||
!name.trimmingCharacters(in: .whitespaces).isEmpty && selectedCategory != nil
|
||||
}
|
||||
@@ -343,7 +378,7 @@ struct EditSourceView: View {
|
||||
|
||||
private var selectedAccount: Account? {
|
||||
guard let id = selectedAccountId else { return nil }
|
||||
return accountStore.accounts.first { $0.id == id }
|
||||
return availableAccounts.first { $0.safeId == id }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -424,42 +424,48 @@ struct SourceDetailView: View {
|
||||
.cornerRadius(AppConstants.UI.smallCornerRadius)
|
||||
}
|
||||
|
||||
ForEach(viewModel.visibleSnapshots) { snapshot in
|
||||
SnapshotRowView(snapshot: snapshot, onEdit: {
|
||||
editingSnapshot = snapshot
|
||||
})
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
editingSnapshot = snapshot
|
||||
}
|
||||
.contextMenu {
|
||||
Button {
|
||||
editingSnapshot = snapshot
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
}
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button {
|
||||
editingSnapshot = snapshot
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
// Use LazyVStack for better performance with many snapshots
|
||||
LazyVStack(spacing: 0) {
|
||||
ForEach(viewModel.visibleSnapshots) { snapshot in
|
||||
VStack(spacing: 0) {
|
||||
SnapshotRowView(snapshot: snapshot, onEdit: {
|
||||
editingSnapshot = snapshot
|
||||
})
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
editingSnapshot = snapshot
|
||||
}
|
||||
.contextMenu {
|
||||
Button {
|
||||
editingSnapshot = snapshot
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
}
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button {
|
||||
editingSnapshot = snapshot
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
|
||||
Button(role: .destructive) {
|
||||
viewModel.deleteSnapshot(snapshot)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
Button(role: .destructive) {
|
||||
viewModel.deleteSnapshot(snapshot)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
|
||||
if snapshot.id != viewModel.visibleSnapshots.last?.id {
|
||||
Divider()
|
||||
if snapshot.id != viewModel.visibleSnapshots.last?.id {
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.snapshots.count > 10 {
|
||||
Text("+ \(viewModel.snapshots.count - 10) more snapshots")
|
||||
// Show "more snapshots" only for free users who have limited history
|
||||
if viewModel.isHistoryLimited && viewModel.hiddenSnapshotCount > 0 {
|
||||
Text("+ \(viewModel.hiddenSnapshotCount) older snapshots hidden")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
@@ -234,13 +234,13 @@ struct SourceListView: View {
|
||||
|
||||
Divider()
|
||||
|
||||
ForEach(accountStore.accounts) { account in
|
||||
ForEach(availableAccounts, id: \.objectID) { account in
|
||||
Button {
|
||||
accountStore.selectAccount(account)
|
||||
} label: {
|
||||
HStack {
|
||||
Text(account.name)
|
||||
if accountStore.selectedAccount?.id == account.id && !accountStore.showAllAccounts {
|
||||
if accountStore.selectedAccount?.safeId == account.safeId && !accountStore.showAllAccounts {
|
||||
Image(systemName: "checkmark")
|
||||
}
|
||||
}
|
||||
@@ -257,6 +257,10 @@ struct SourceListView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var availableAccounts: [Account] {
|
||||
accountStore.accounts.filter { $0.safeId != nil }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Source Row View
|
||||
|
||||
Reference in New Issue
Block a user