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 isLoading = ref(true)
|
||||
|
||||
const fiatBalanceEntries = computed(() =>
|
||||
Object.entries(fiatBalances.value).filter(([, amount]) => Math.abs(amount) > 0.005)
|
||||
// Per-currency split: sign convention from the user perspective:
|
||||
// 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(() =>
|
||||
|
|
@ -126,30 +142,64 @@ function formatFiat(amount: number, currency: string): string {
|
|||
<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>
|
||||
|
||||
<div v-if="balance !== null" class="space-y-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<div v-if="balance !== null" class="space-y-3">
|
||||
<!-- 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
|
||||
:is="libraOwesUser ? ArrowDown : ArrowUp"
|
||||
class="w-5 h-5"
|
||||
:class="libraOwesUser ? 'text-green-500' : 'text-red-500'"
|
||||
/>
|
||||
<span class="text-3xl font-bold text-foreground">
|
||||
{{ formatAmount(balance) }}
|
||||
</span>
|
||||
<span class="text-3xl font-bold text-foreground">{{ formatAmount(balance) }}</span>
|
||||
<span class="text-lg text-muted-foreground">{{ balanceCurrency }}</span>
|
||||
</div>
|
||||
<div v-if="fiatBalanceEntries.length > 0" class="flex flex-wrap items-center gap-x-3 gap-y-1 pl-7">
|
||||
<span
|
||||
v-for="[currency, amount] in fiatBalanceEntries"
|
||||
:key="currency"
|
||||
class="text-base text-muted-foreground"
|
||||
>
|
||||
{{ formatFiat(Math.abs(amount), currency) }}
|
||||
|
||||
<!-- Net sats caption (always shown when there's a balance; distinct
|
||||
currencies can't be netted into a single fiat number, but sats
|
||||
can — informational only, depends on current BTC rates) -->
|
||||
<div class="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<component
|
||||
: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>
|
||||
</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 v-else class="text-muted-foreground">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue