feat(libra/balance): split Net Balance card per direction per currency
When a user has entries in multiple currencies that go in opposite directions — e.g. an income entry in EUR (user owes the org) and an expense entry in CAD (org owes user) — the previous Net Balance hero collapsed both into a single "You owe" / "Owed to you" label driven by the net sats. The fiat amounts were displayed via Math.abs(), hiding the per-currency signs the backend already returns, so the hero was actively misleading: it showed €200 and CA$300 under one direction when in reality they point in opposite directions. Render up to two grouped sections — "You owe" with the user-owes currencies, "Owed to you" with the libra-owes currencies — using new youOweFiatEntries / libraOwesFiatEntries computeds that filter the signed fiat_balances dict by sign. Net sats moves to a small caption labelled "Net at current rates", since sats can be netted but distinct fiat currencies can't without a spot rate. Falls back to the old single-amount sats display when there are no fiat balances.
This commit is contained in:
parent
9e3de6ce16
commit
124cad1249
1 changed files with 67 additions and 17 deletions
|
|
@ -25,8 +25,24 @@ const totalIncomeFiat = ref<Record<string, number>>({})
|
||||||
const pendingTransactions = ref<Transaction[]>([])
|
const pendingTransactions = ref<Transaction[]>([])
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
|
|
||||||
const fiatBalanceEntries = computed(() =>
|
// Per-currency split: sign convention from the user perspective:
|
||||||
Object.entries(fiatBalances.value).filter(([, amount]) => Math.abs(amount) > 0.005)
|
// positive fiat_balance = user owes Libra, negative = Libra owes user.
|
||||||
|
// Distinct currencies can't be netted across each other (no spot rate),
|
||||||
|
// so we render them grouped by direction instead of one collapsed label.
|
||||||
|
const youOweFiatEntries = computed(() =>
|
||||||
|
Object.entries(fiatBalances.value)
|
||||||
|
.filter(([, amount]) => amount > 0.005)
|
||||||
|
.map(([currency, amount]) => [currency, amount] as [string, number])
|
||||||
|
)
|
||||||
|
|
||||||
|
const libraOwesFiatEntries = computed(() =>
|
||||||
|
Object.entries(fiatBalances.value)
|
||||||
|
.filter(([, amount]) => amount < -0.005)
|
||||||
|
.map(([currency, amount]) => [currency, Math.abs(amount)] as [string, number])
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasAnyFiatBalance = computed(() =>
|
||||||
|
youOweFiatEntries.value.length > 0 || libraOwesFiatEntries.value.length > 0
|
||||||
)
|
)
|
||||||
|
|
||||||
const expensesFiatEntries = computed(() =>
|
const expensesFiatEntries = computed(() =>
|
||||||
|
|
@ -126,30 +142,64 @@ function formatFiat(amount: number, currency: string): string {
|
||||||
<div class="rounded-xl border bg-card p-6 mb-6">
|
<div class="rounded-xl border bg-card p-6 mb-6">
|
||||||
<p class="text-sm text-muted-foreground mb-1">{{ t('libra.balance.netBalance') }}</p>
|
<p class="text-sm text-muted-foreground mb-1">{{ t('libra.balance.netBalance') }}</p>
|
||||||
|
|
||||||
<div v-if="balance !== null" class="space-y-1">
|
<div v-if="balance !== null" class="space-y-3">
|
||||||
<div class="flex items-center gap-2">
|
<!-- Fiat split by direction (real per-currency state) -->
|
||||||
|
<div v-if="hasAnyFiatBalance" class="space-y-2">
|
||||||
|
<div v-if="youOweFiatEntries.length > 0">
|
||||||
|
<p class="text-xs uppercase tracking-wide text-red-600 dark:text-red-400 font-medium mb-0.5">
|
||||||
|
{{ t('libra.balance.youOwe') }}
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-wrap gap-x-3 gap-y-0.5">
|
||||||
|
<span
|
||||||
|
v-for="[currency, amount] in youOweFiatEntries"
|
||||||
|
:key="'yo-' + currency"
|
||||||
|
class="text-2xl font-bold text-foreground"
|
||||||
|
>
|
||||||
|
{{ formatFiat(amount, currency) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="libraOwesFiatEntries.length > 0">
|
||||||
|
<p class="text-xs uppercase tracking-wide text-green-600 dark:text-green-400 font-medium mb-0.5">
|
||||||
|
{{ t('libra.balance.owedToYou') }}
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-wrap gap-x-3 gap-y-0.5">
|
||||||
|
<span
|
||||||
|
v-for="[currency, amount] in libraOwesFiatEntries"
|
||||||
|
:key="'lo-' + currency"
|
||||||
|
class="text-2xl font-bold text-foreground"
|
||||||
|
>
|
||||||
|
{{ formatFiat(amount, currency) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sats-only fallback when no fiat balances are present -->
|
||||||
|
<div v-else class="flex items-center gap-2">
|
||||||
<component
|
<component
|
||||||
:is="libraOwesUser ? ArrowDown : ArrowUp"
|
:is="libraOwesUser ? ArrowDown : ArrowUp"
|
||||||
class="w-5 h-5"
|
class="w-5 h-5"
|
||||||
:class="libraOwesUser ? 'text-green-500' : 'text-red-500'"
|
:class="libraOwesUser ? 'text-green-500' : 'text-red-500'"
|
||||||
/>
|
/>
|
||||||
<span class="text-3xl font-bold text-foreground">
|
<span class="text-3xl font-bold text-foreground">{{ formatAmount(balance) }}</span>
|
||||||
{{ formatAmount(balance) }}
|
|
||||||
</span>
|
|
||||||
<span class="text-lg text-muted-foreground">{{ balanceCurrency }}</span>
|
<span class="text-lg text-muted-foreground">{{ balanceCurrency }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="fiatBalanceEntries.length > 0" class="flex flex-wrap items-center gap-x-3 gap-y-1 pl-7">
|
|
||||||
<span
|
<!-- Net sats caption (always shown when there's a balance; distinct
|
||||||
v-for="[currency, amount] in fiatBalanceEntries"
|
currencies can't be netted into a single fiat number, but sats
|
||||||
:key="currency"
|
can — informational only, depends on current BTC rates) -->
|
||||||
class="text-base text-muted-foreground"
|
<div class="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||||
>
|
<component
|
||||||
{{ formatFiat(Math.abs(amount), currency) }}
|
:is="libraOwesUser ? ArrowDown : ArrowUp"
|
||||||
|
class="w-3 h-3"
|
||||||
|
:class="libraOwesUser ? 'text-green-500' : 'text-red-500'"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
Net at current rates: {{ formatAmount(balance) }} {{ balanceCurrency }}
|
||||||
|
({{ libraOwesUser ? t('libra.balance.owedToYou').toLowerCase() : t('libra.balance.youOwe').toLowerCase() }})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm" :class="libraOwesUser ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'">
|
|
||||||
{{ libraOwesUser ? t('libra.balance.owedToYou') : t('libra.balance.youOwe') }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="text-muted-foreground">
|
<div v-else class="text-muted-foreground">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue