From b4013ea82179dee9f6ab62a7b83c94291067e0df Mon Sep 17 00:00:00 2001 From: nfebe Date: Sat, 27 Jun 2026 13:41:58 +0100 Subject: [PATCH] feat: Add financial position, holdings, and intent tagging UI - Add the Financial Position screen: net worth, a money in/out ledger by intent, and a drill-down into the underlying transactions. - Add a Holdings screen and form backed by the holdings API, with live crypto pricing and manual assets; holdings feed total net worth. - Add an intent selector to the transaction form so income and expenses can be tagged as loans, debt, investments or gifts. - Refine navigation: the dashboard is the default landing, the AI assistant is one entry with a Discussions sub-item, and less-used items move down. - Let budgets be created in any supported currency, not just the currencies of existing wallets. - Use Solar duotone icons (via Iconify) for the assistant landing stat cards so the large art reads richly instead of as thin lines. --- assets/scss/_surfaces.scss | 33 + assets/scss/_transaction-form.scss | 44 + components/TSidebar.vue | 88 +- components/TransactionForm.vue | 59 +- components/ai/ChatLandingInsights.vue | 92 +- components/budgets/BudgetForm.vue | 32 +- .../FinancialPositionDrill.vue | 499 ++++++++++ .../FinancialPositionView.vue | 856 ++++++++++++++++++ components/holdings/HoldingForm.vue | 391 ++++++++ components/settings/SettingsDisplay.vue | 13 +- composables/useLanding.ts | 9 +- i18n/locales/en.json | 91 +- nuxt.config.ts | 4 + package-lock.json | 83 ++ package.json | 2 + pages/financial-position/index.vue | 17 + pages/holdings/index.vue | 394 ++++++++ pages/login.vue | 4 +- pages/reports.vue | 22 +- services/api/holdingsApi.ts | 62 ++ services/api/statsApi.ts | 21 +- services/transactionApi.ts | 6 + tests/components/TransactionForm.test.ts | 51 +- tests/components/budgetForm.test.ts | 31 + .../components/financialPositionView.test.ts | 157 ++++ tests/pages/holdings.test.ts | 102 +++ tests/utils/transactionMapper.test.ts | 27 + types/holding.ts | 44 + types/icons.d.ts | 1 + types/transaction.ts | 33 + utils/transactionMapper.ts | 3 + vitest.config.ts | 3 +- 32 files changed, 3167 insertions(+), 107 deletions(-) create mode 100644 components/financial-position/FinancialPositionDrill.vue create mode 100644 components/financial-position/FinancialPositionView.vue create mode 100644 components/holdings/HoldingForm.vue create mode 100644 pages/financial-position/index.vue create mode 100644 pages/holdings/index.vue create mode 100644 services/api/holdingsApi.ts create mode 100644 tests/components/budgetForm.test.ts create mode 100644 tests/components/financialPositionView.test.ts create mode 100644 tests/pages/holdings.test.ts create mode 100644 types/holding.ts create mode 100644 types/icons.d.ts diff --git a/assets/scss/_surfaces.scss b/assets/scss/_surfaces.scss index ba23dd2..68bf92e 100644 --- a/assets/scss/_surfaces.scss +++ b/assets/scss/_surfaces.scss @@ -34,6 +34,24 @@ --surface-accent: var(--color-border-medium); --surface-deep: var(--color-text-secondary); } + &--investment { + --surface-bg: rgba(14, 165, 233, 0.12); + --surface-ink: var(--color-text-primary); + --surface-accent: rgba(14, 165, 233, 0.5); + --surface-deep: #0284c7; + } + &--loan { + --surface-bg: rgba(217, 119, 6, 0.12); + --surface-ink: var(--color-text-primary); + --surface-accent: rgba(217, 119, 6, 0.5); + --surface-deep: #b45309; + } + &--gift { + --surface-bg: rgba(124, 58, 237, 0.12); + --surface-ink: var(--color-text-primary); + --surface-accent: rgba(124, 58, 237, 0.5); + --surface-deep: #6d28d9; + } } // In dark mode the surface bg becomes a subtle tint so it sits on the page @@ -65,6 +83,21 @@ --surface-accent: var(--color-border-medium); --surface-deep: var(--color-text-secondary); } + .surface--investment { + --surface-bg: rgba(14, 165, 233, 0.16); + --surface-accent: rgba(56, 189, 248, 0.5); + --surface-deep: #38bdf8; + } + .surface--loan { + --surface-bg: rgba(217, 119, 6, 0.16); + --surface-accent: rgba(251, 191, 36, 0.5); + --surface-deep: #fbbf24; + } + .surface--gift { + --surface-bg: rgba(124, 58, 237, 0.16); + --surface-accent: rgba(167, 139, 250, 0.5); + --surface-deep: #a78bfa; + } } .display-1 { diff --git a/assets/scss/_transaction-form.scss b/assets/scss/_transaction-form.scss index 54a2161..340a117 100644 --- a/assets/scss/_transaction-form.scss +++ b/assets/scss/_transaction-form.scss @@ -675,6 +675,50 @@ font-size: $font-size-sm; } +.intent-pills { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.intent-pill { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 14px; + border: 1px solid $border-color; + border-radius: 999px; + background: $bg-white; + color: $text-secondary; + font-size: $font-size-sm; + font-weight: $font-medium; + cursor: pointer; + transition: + background 0.15s ease, + border-color 0.15s ease, + color 0.15s ease; + + &:hover { + border-color: $primary; + color: $text-primary; + } + + .intent-pill-icon { + width: 16px; + height: 16px; + } +} + +.intent-pill--active { + background: $primary; + border-color: $primary; + color: white; + + &:hover { + color: white; + } +} + .wallet-field-wrapper, .group-field-wrapper { position: relative; diff --git a/components/TSidebar.vue b/components/TSidebar.vue index 55135e6..b304c57 100644 --- a/components/TSidebar.vue +++ b/components/TSidebar.vue @@ -10,7 +10,7 @@ }" >