Primera build enviada

This commit is contained in:
2026-01-19 14:40:43 +01:00
parent c6be398e5a
commit b03d35194f
36 changed files with 1641 additions and 561 deletions
@@ -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