From 5db2dbe8a8341e85dc2c424ac66c14cd0d791826 Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 6 Jun 2026 22:33:49 +0200 Subject: [PATCH] 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 }}