From 5db2dbe8a8341e85dc2c424ac66c14cd0d791826 Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 6 Jun 2026 22:33:49 +0200 Subject: [PATCH 1/6] refactor(libra): encode status on border, type on signed amount Original styling used the left border for entry-type (green=income, red=expense), which clashed visually with the status icons: a red border on a pending expense suggested "rejected". Split the visual channels so each conveys one thing: - Left border + status icon = workflow state (green accepted, yellow pending, red rejected/voided) - Signed/tinted amount = type (+green income, -red expense) - Strike-through + muted amount = voided - Badges = user-added tags only; income-entry / expense-entry suppressed (redundant with the amount), Voided kept as a high-signal status badge Follows the conventional bank-statement / personal-finance encoding (Wise, Mint, YNAB), where amount sign carries direction and chrome carries state. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../expenses/views/TransactionsPage.vue | 82 +++++++++++++------ 1 file changed, 58 insertions(+), 24 deletions(-) diff --git a/src/modules/expenses/views/TransactionsPage.vue b/src/modules/expenses/views/TransactionsPage.vue index fbec2d9..6c20afe 100644 --- a/src/modules/expenses/views/TransactionsPage.vue +++ b/src/modules/expenses/views/TransactionsPage.vue @@ -123,7 +123,7 @@ function getStatusInfo(transaction: Transaction) { case '*': return { icon: CheckCircle2, color: 'text-green-600', label: 'Cleared' } case '!': - return { icon: Clock, color: 'text-orange-600', label: 'Pending' } + return { icon: Clock, color: 'text-yellow-500', label: 'Pending' } case '#': return { icon: Flag, color: 'text-red-600', label: 'Flagged' } default: @@ -131,6 +131,39 @@ function getStatusInfo(transaction: Transaction) { } } +// Left-border color encodes status (accepted / pending / rejected). +// Entry-type is communicated separately via the signed/colored amount. +function getStatusBorderClass(t: Transaction): string { + if (isVoided(t)) return 'border-l-red-600' + if (t.flag === '*') return 'border-l-green-600' + if (t.flag === '!') return 'border-l-yellow-500' + return 'border-l-transparent' +} + +// Income gets a leading '+', expense a leading '-'. +function getAmountSign(t: Transaction): string { + if (isIncome(t)) return '+' + if (isExpense(t)) return '-' + return '' +} + +// Color tint for the amount text. Voided entries drop to muted regardless +// of type since the strike-through carries the "ignore this" signal. +function getAmountColorClass(t: Transaction): string { + if (isVoided(t)) return 'line-through text-muted-foreground' + if (isIncome(t)) return 'text-green-600 dark:text-green-400' + if (isExpense(t)) return 'text-red-600 dark:text-red-400' + return '' +} + +// Tags that drive other visual channels (border / sign / strike-through) — +// suppressed from the badge row so it only carries user-added tags. +const TYPE_TAGS = new Set(['income-entry', 'expense-entry', 'voided']) + +function getDisplayTags(t: Transaction): string[] { + return (t.tags ?? []).filter(tag => !TYPE_TAGS.has(tag)) +} + // Load transactions async function loadTransactions() { if (!walletKey.value) { @@ -324,9 +357,8 @@ onMounted(() => { v-for="transaction in transactionsToDisplay" :key="transaction.id" :class="[ - 'border-b md:border md:rounded-lg p-4 hover:bg-muted/50 transition-colors', - isIncome(transaction) && 'border-l-4 border-l-green-600', - isExpense(transaction) && 'border-l-4 border-l-red-600' + 'border-b md:border md:rounded-lg p-4 hover:bg-muted/50 transition-colors border-l-4', + getStatusBorderClass(transaction) ]" > @@ -358,22 +390,17 @@ onMounted(() => {
-

- {{ formatAmount(transaction.amount) }} sats +

+ {{ getAmountSign(transaction) }}{{ formatAmount(transaction.amount) }} sats

- {{ transaction.fiat_amount.toFixed(2) }} {{ transaction.fiat_currency }} + {{ getAmountSign(transaction) }}{{ transaction.fiat_amount.toFixed(2) }} {{ transaction.fiat_currency }}

@@ -390,20 +417,27 @@ onMounted(() => { Ref: {{ transaction.reference }} - -
+ +
+ Voided + + - {{ tag === 'voided' ? 'Voided' : tag }} + {{ tag }}
From fd269f97ea710ac2dbb319b3371ada6c547815fe Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 6 Jun 2026 22:43:48 +0200 Subject: [PATCH 2/6] fix(libra): drop colored status border to remove color collision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The status border (green/yellow/red) and the type-tinted amount (green income, red expense) both used the same palette, so cleared expenses showed a green border with a red amount and pending income showed a yellow border with a green amount — same colors carrying two different meanings on the same row. Concentrate the encoding so each meaning has one home: - Status lives in the icon (small, single glyph at the title) - Type lives in the amount (sign + red/green tint) - Voided still wins via strike-through + muted amount + Voided badge Per Wise / Mint / YNAB convention. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/modules/expenses/views/TransactionsPage.vue | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/modules/expenses/views/TransactionsPage.vue b/src/modules/expenses/views/TransactionsPage.vue index 6c20afe..6803aa0 100644 --- a/src/modules/expenses/views/TransactionsPage.vue +++ b/src/modules/expenses/views/TransactionsPage.vue @@ -131,15 +131,6 @@ function getStatusInfo(transaction: Transaction) { } } -// Left-border color encodes status (accepted / pending / rejected). -// Entry-type is communicated separately via the signed/colored amount. -function getStatusBorderClass(t: Transaction): string { - if (isVoided(t)) return 'border-l-red-600' - if (t.flag === '*') return 'border-l-green-600' - if (t.flag === '!') return 'border-l-yellow-500' - return 'border-l-transparent' -} - // Income gets a leading '+', expense a leading '-'. function getAmountSign(t: Transaction): string { if (isIncome(t)) return '+' @@ -356,10 +347,7 @@ onMounted(() => {
From dd71d10564b738a57ab36a30bcad930200888c4d Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 6 Jun 2026 22:51:43 +0200 Subject: [PATCH 3/6] feat(libra): hide voided by default + add Pending badge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Voided transactions are noise in the day-to-day view (the user already saw and rejected them), so default to hiding them. A Switch in the filter row toggles 'Show voided'. When voided are present but hidden, the results-count line shows '(N voided hidden)' so the toggle has a discoverable hook. Pending entries gain a yellow Pending badge symmetric with the red Voided badge — both signal 'needs attention' states in the badge row, while cleared entries stay unmarked (the default, quiet state). Status / type encoding (icon = status, signed/colored amount = type) is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../expenses/views/TransactionsPage.vue | 52 ++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/src/modules/expenses/views/TransactionsPage.vue b/src/modules/expenses/views/TransactionsPage.vue index 6803aa0..7a34476 100644 --- a/src/modules/expenses/views/TransactionsPage.vue +++ b/src/modules/expenses/views/TransactionsPage.vue @@ -11,6 +11,8 @@ import FuzzySearch from '@/components/ui/fuzzy-search/FuzzySearch.vue' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Input } from '@/components/ui/input' +import { Switch } from '@/components/ui/switch' +import { Label } from '@/components/ui/label' import { CheckCircle2, Clock, @@ -31,6 +33,7 @@ const dateRangeType = ref(15) // 15, 30, 60, or 'custom' const customStartDate = ref('') const customEndDate = ref('') const typeFilter = ref<'all' | 'income' | 'expense'>('all') +const showVoided = ref(false) const typeFilterOptions = [ { label: 'All', value: 'all' as const }, @@ -50,6 +53,10 @@ function isVoided(t: Transaction): boolean { return t.tags?.includes('voided') ?? false } +function isPending(t: Transaction): boolean { + return t.flag === '!' && !isVoided(t) +} + const walletKey = computed(() => user.value?.wallets?.[0]?.inkey) // Fuzzy search state and configuration @@ -75,14 +82,25 @@ const searchOptions: FuzzySearchOptions = { matchAllWhenSearchEmpty: true } -// Transactions to display (search results or all transactions), filtered by type +// Transactions to display: search results (or all), with voided hidden by +// default and the type filter applied last. const transactionsToDisplay = computed(() => { - const base = searchResults.value.length > 0 ? searchResults.value : transactions.value + let base = searchResults.value.length > 0 ? searchResults.value : transactions.value + if (!showVoided.value) base = base.filter(t => !isVoided(t)) if (typeFilter.value === 'income') return base.filter(isIncome) if (typeFilter.value === 'expense') return base.filter(isExpense) return base }) +// Count of voided entries that are being hidden right now — surfaced in +// the results-count line so the user knows the toggle has something to +// reveal. +const voidedHiddenCount = computed(() => { + if (showVoided.value) return 0 + const base = searchResults.value.length > 0 ? searchResults.value : transactions.value + return base.filter(isVoided).length +}) + // Handle search results function handleSearchResults(results: Transaction[]) { searchResults.value = results @@ -255,7 +273,7 @@ onMounted(() => {
- +
+
+ + +
@@ -322,7 +350,10 @@ onMounted(() => { Found {{ transactionsToDisplay.length }} matching transaction{{ transactionsToDisplay.length === 1 ? '' : 's' }} - {{ transactions.length }} transaction{{ transactions.length === 1 ? '' : 's' }} + {{ transactionsToDisplay.length }} transaction{{ transactionsToDisplay.length === 1 ? '' : 's' }} + + + ({{ voidedHiddenCount }} voided hidden)
@@ -405,11 +436,11 @@ onMounted(() => { Ref: {{ transaction.reference }} -
{ > Voided + + Pending + Date: Sat, 6 Jun 2026 23:00:28 +0200 Subject: [PATCH 4/6] refactor(libra): collapse type filter + voided switch into category chips MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each chip toggles inclusion of one bucket of rows. Every row belongs to exactly one bucket — voided rows go to the Voided bucket regardless of their underlying type — so the model is straightforward: [Income] [Expenses] [Voided] | | | income expense voided (non-voided only) (any type) Defaults: Income + Expenses on, Voided off. Independent multi-select. Empty selection shows the empty state with a 'select a category' hint instead of an open-ended 'try a different time period'. Replaces the previous 'type radio + voided switch' pair: same axes, one control type, no left/right visual split. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../expenses/views/TransactionsPage.vue | 83 +++++++++---------- 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/src/modules/expenses/views/TransactionsPage.vue b/src/modules/expenses/views/TransactionsPage.vue index 7a34476..fafee56 100644 --- a/src/modules/expenses/views/TransactionsPage.vue +++ b/src/modules/expenses/views/TransactionsPage.vue @@ -11,8 +11,6 @@ import FuzzySearch from '@/components/ui/fuzzy-search/FuzzySearch.vue' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Input } from '@/components/ui/input' -import { Switch } from '@/components/ui/switch' -import { Label } from '@/components/ui/label' import { CheckCircle2, Clock, @@ -32,15 +30,26 @@ const isLoading = ref(false) const dateRangeType = ref(15) // 15, 30, 60, or 'custom' const customStartDate = ref('') const customEndDate = ref('') -const typeFilter = ref<'all' | 'income' | 'expense'>('all') -const showVoided = ref(false) +// Each chip is an inclusion toggle for one bucket of rows. Every row +// belongs to exactly one bucket (voided rows go to 'voided' regardless +// of their income/expense type). Default hides voided. +type Category = 'income' | 'expense' | 'voided' -const typeFilterOptions = [ - { label: 'All', value: 'all' as const }, - { label: 'Income', value: 'income' as const }, - { label: 'Expenses', value: 'expense' as const } +const activeCategories = ref>(new Set(['income', 'expense'])) + +const categoryChips: { label: string; value: Category }[] = [ + { label: 'Income', value: 'income' }, + { label: 'Expenses', value: 'expense' }, + { label: 'Voided', value: 'voided' } ] +function toggleCategory(cat: Category) { + const next = new Set(activeCategories.value) + if (next.has(cat)) next.delete(cat) + else next.add(cat) + activeCategories.value = next +} + function isIncome(t: Transaction): boolean { return t.tags?.includes('income-entry') ?? false } @@ -57,6 +66,14 @@ function isPending(t: Transaction): boolean { return t.flag === '!' && !isVoided(t) } +// Which chip bucket a row falls into. Voided always wins over type. +function getBucket(t: Transaction): Category | null { + if (isVoided(t)) return 'voided' + if (isIncome(t)) return 'income' + if (isExpense(t)) return 'expense' + return null +} + const walletKey = computed(() => user.value?.wallets?.[0]?.inkey) // Fuzzy search state and configuration @@ -82,23 +99,13 @@ const searchOptions: FuzzySearchOptions = { matchAllWhenSearchEmpty: true } -// Transactions to display: search results (or all), with voided hidden by -// default and the type filter applied last. +// Transactions to display: row passes if its bucket's chip is active. const transactionsToDisplay = computed(() => { - let base = searchResults.value.length > 0 ? searchResults.value : transactions.value - if (!showVoided.value) base = base.filter(t => !isVoided(t)) - if (typeFilter.value === 'income') return base.filter(isIncome) - if (typeFilter.value === 'expense') return base.filter(isExpense) - return base -}) - -// Count of voided entries that are being hidden right now — surfaced in -// the results-count line so the user knows the toggle has something to -// reveal. -const voidedHiddenCount = computed(() => { - if (showVoided.value) return 0 const base = searchResults.value.length > 0 ? searchResults.value : transactions.value - return base.filter(isVoided).length + return base.filter(t => { + const bucket = getBucket(t) + return bucket !== null && activeCategories.value.has(bucket) + }) }) // Handle search results @@ -273,30 +280,21 @@ onMounted(() => {
- +
-
- - -
@@ -352,9 +350,6 @@ onMounted(() => { {{ transactionsToDisplay.length }} transaction{{ transactionsToDisplay.length === 1 ? '' : 's' }} - - ({{ voidedHiddenCount }} voided hidden) - @@ -369,7 +364,9 @@ onMounted(() => {

No transactions found

- {{ searchResults.length > 0 ? 'Try a different search term' : 'Try selecting a different time period' }} + + +

From 58cf5cb762adf88e6bbba1a95b2f67d2447b3635 Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 6 Jun 2026 23:05:55 +0200 Subject: [PATCH 5/6] refactor(libra): drop status icons, add Income/Expense badges Per the iteration: the title-row status icons (green check / yellow clock / red X) were doing the same job as the new status badges and amount color, so each row had three signals fighting for the same meaning. Drop the icons and lean on badges instead. Badge row order (left-to-right): Voided > Income/Expense > Pending > user tags. Type badge sits between the high-attention Voided marker and the secondary Pending marker, so the type chip is easy to spot on every row. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../expenses/views/TransactionsPage.vue | 76 +++++++------------ 1 file changed, 26 insertions(+), 50 deletions(-) diff --git a/src/modules/expenses/views/TransactionsPage.vue b/src/modules/expenses/views/TransactionsPage.vue index fafee56..fdf34a8 100644 --- a/src/modules/expenses/views/TransactionsPage.vue +++ b/src/modules/expenses/views/TransactionsPage.vue @@ -12,10 +12,6 @@ import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Input } from '@/components/ui/input' import { - CheckCircle2, - Clock, - Flag, - XCircle, RefreshCw, Calendar, Filter @@ -137,25 +133,6 @@ function formatAmount(amount: number): string { return new Intl.NumberFormat('en-US').format(amount) } -// Get status icon and color. Voided entries (per libra convention) keep -// flag='!' and carry a 'voided' tag — surface that as the icon regardless -// of the underlying flag. -function getStatusInfo(transaction: Transaction) { - if (isVoided(transaction)) { - return { icon: XCircle, color: 'text-muted-foreground', label: 'Voided' } - } - switch (transaction.flag) { - case '*': - return { icon: CheckCircle2, color: 'text-green-600', label: 'Cleared' } - case '!': - return { icon: Clock, color: 'text-yellow-500', label: 'Pending' } - case '#': - return { icon: Flag, color: 'text-red-600', label: 'Flagged' } - default: - return null - } -} - // Income gets a leading '+', expense a leading '-'. function getAmountSign(t: Transaction): string { if (isIncome(t)) return '+' @@ -380,25 +357,14 @@ onMounted(() => {
-
- - -

- {{ transaction.description }} -

-
+

+ {{ transaction.description }} +

{{ formatDate(transaction.date) }}

@@ -433,13 +399,9 @@ onMounted(() => { Ref: {{ transaction.reference }}
- -
+ +
{ Voided + Income + + + Expense + + From 46e255315d5633e6f3b8c1002b807ef9c30a4a18 Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 6 Jun 2026 23:06:58 +0200 Subject: [PATCH 6/6] refactor(libra): redesign transactions list status + type encoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rework how the standalone transactions list communicates entry status and type so each visual channel does one job and the filter UI matches the underlying axes. Encoding: - Type lives in the signed/colored amount (+green income, -red expense) and a matching Income/Expense badge in the badge row. - Status lives in badges only: red Voided (leftmost) and yellow Pending (after the type badge). Cleared entries carry no status badge — the quiet default. - Voided rows additionally strike-through and mute the amount. - Drop the title-row status icons and the colored left border that previously fought with the amount color for the same meaning. Filter UI: - Replace the type radio + voided switch with three category chips — Income, Expenses, Voided — that independently toggle inclusion of one bucket of rows. Each row belongs to exactly one bucket (voided wins over type). Defaults: Income + Expenses on, Voided off. - Empty-selection state nudges the user to enable a category. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../expenses/views/TransactionsPage.vue | 197 ++++++++++-------- 1 file changed, 115 insertions(+), 82 deletions(-) diff --git a/src/modules/expenses/views/TransactionsPage.vue b/src/modules/expenses/views/TransactionsPage.vue index fbec2d9..fdf34a8 100644 --- a/src/modules/expenses/views/TransactionsPage.vue +++ b/src/modules/expenses/views/TransactionsPage.vue @@ -12,10 +12,6 @@ import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Input } from '@/components/ui/input' import { - CheckCircle2, - Clock, - Flag, - XCircle, RefreshCw, Calendar, Filter @@ -30,14 +26,26 @@ const isLoading = ref(false) const dateRangeType = ref(15) // 15, 30, 60, or 'custom' const customStartDate = ref('') const customEndDate = ref('') -const typeFilter = ref<'all' | 'income' | 'expense'>('all') +// Each chip is an inclusion toggle for one bucket of rows. Every row +// belongs to exactly one bucket (voided rows go to 'voided' regardless +// of their income/expense type). Default hides voided. +type Category = 'income' | 'expense' | 'voided' -const typeFilterOptions = [ - { label: 'All', value: 'all' as const }, - { label: 'Income', value: 'income' as const }, - { label: 'Expenses', value: 'expense' as const } +const activeCategories = ref>(new Set(['income', 'expense'])) + +const categoryChips: { label: string; value: Category }[] = [ + { label: 'Income', value: 'income' }, + { label: 'Expenses', value: 'expense' }, + { label: 'Voided', value: 'voided' } ] +function toggleCategory(cat: Category) { + const next = new Set(activeCategories.value) + if (next.has(cat)) next.delete(cat) + else next.add(cat) + activeCategories.value = next +} + function isIncome(t: Transaction): boolean { return t.tags?.includes('income-entry') ?? false } @@ -50,6 +58,18 @@ function isVoided(t: Transaction): boolean { return t.tags?.includes('voided') ?? false } +function isPending(t: Transaction): boolean { + return t.flag === '!' && !isVoided(t) +} + +// Which chip bucket a row falls into. Voided always wins over type. +function getBucket(t: Transaction): Category | null { + if (isVoided(t)) return 'voided' + if (isIncome(t)) return 'income' + if (isExpense(t)) return 'expense' + return null +} + const walletKey = computed(() => user.value?.wallets?.[0]?.inkey) // Fuzzy search state and configuration @@ -75,12 +95,13 @@ const searchOptions: FuzzySearchOptions = { matchAllWhenSearchEmpty: true } -// Transactions to display (search results or all transactions), filtered by type +// Transactions to display: row passes if its bucket's chip is active. const transactionsToDisplay = computed(() => { const base = searchResults.value.length > 0 ? searchResults.value : transactions.value - if (typeFilter.value === 'income') return base.filter(isIncome) - if (typeFilter.value === 'expense') return base.filter(isExpense) - return base + return base.filter(t => { + const bucket = getBucket(t) + return bucket !== null && activeCategories.value.has(bucket) + }) }) // Handle search results @@ -112,23 +133,28 @@ function formatAmount(amount: number): string { return new Intl.NumberFormat('en-US').format(amount) } -// Get status icon and color. Voided entries (per libra convention) keep -// flag='!' and carry a 'voided' tag — surface that as the icon regardless -// of the underlying flag. -function getStatusInfo(transaction: Transaction) { - if (isVoided(transaction)) { - return { icon: XCircle, color: 'text-muted-foreground', label: 'Voided' } - } - switch (transaction.flag) { - case '*': - return { icon: CheckCircle2, color: 'text-green-600', label: 'Cleared' } - case '!': - return { icon: Clock, color: 'text-orange-600', label: 'Pending' } - case '#': - return { icon: Flag, color: 'text-red-600', label: 'Flagged' } - default: - return null - } +// Income gets a leading '+', expense a leading '-'. +function getAmountSign(t: Transaction): string { + if (isIncome(t)) return '+' + if (isExpense(t)) return '-' + return '' +} + +// Color tint for the amount text. Voided entries drop to muted regardless +// of type since the strike-through carries the "ignore this" signal. +function getAmountColorClass(t: Transaction): string { + if (isVoided(t)) return 'line-through text-muted-foreground' + if (isIncome(t)) return 'text-green-600 dark:text-green-400' + if (isExpense(t)) return 'text-red-600 dark:text-red-400' + return '' +} + +// Tags that drive other visual channels (border / sign / strike-through) — +// suppressed from the badge row so it only carries user-added tags. +const TYPE_TAGS = new Set(['income-entry', 'expense-entry', 'voided']) + +function getDisplayTags(t: Transaction): string[] { + return (t.tags ?? []).filter(tag => !TYPE_TAGS.has(tag)) } // Load transactions @@ -231,19 +257,20 @@ onMounted(() => {
- +
@@ -298,7 +325,7 @@ onMounted(() => { Found {{ transactionsToDisplay.length }} matching transaction{{ transactionsToDisplay.length === 1 ? '' : 's' }} - {{ transactions.length }} transaction{{ transactions.length === 1 ? '' : 's' }} + {{ transactionsToDisplay.length }} transaction{{ transactionsToDisplay.length === 1 ? '' : 's' }}
@@ -314,7 +341,9 @@ onMounted(() => {

No transactions found

- {{ searchResults.length > 0 ? 'Try a different search term' : 'Try selecting a different time period' }} + + +

@@ -323,34 +352,19 @@ onMounted(() => {
-
- - -

- {{ transaction.description }} -

-
+

+ {{ transaction.description }} +

{{ formatDate(transaction.date) }}

@@ -358,22 +372,17 @@ onMounted(() => {
-

- {{ formatAmount(transaction.amount) }} sats +

+ {{ getAmountSign(transaction) }}{{ formatAmount(transaction.amount) }} sats

- {{ transaction.fiat_amount.toFixed(2) }} {{ transaction.fiat_currency }} + {{ getAmountSign(transaction) }}{{ transaction.fiat_amount.toFixed(2) }} {{ transaction.fiat_currency }}

@@ -390,20 +399,44 @@ onMounted(() => { Ref: {{ transaction.reference }}
- -
+ +
+ Voided + + + Income + + + Expense + + + Pending + + - {{ tag === 'voided' ? 'Voided' : tag }} + {{ tag }}