Reorders cards on the dashboard

Moves the pending expense approvals card to the top of the dashboard
and the user balance card to a later position for better UX.
This commit is contained in:
padreug 2025-11-01 09:21:49 +01:00
parent c7bc0c7904
commit d1f22dfda8

View file

@ -62,6 +62,60 @@
</template> </template>
</q-banner> </q-banner>
<!-- Pending Expense Entries (Super User Only) -->
<q-card v-if="isSuperUser && pendingExpenses.length > 0">
<q-card-section>
<h6 class="q-my-none q-mb-md">Pending Expense Approvals</h6>
<q-list separator>
<q-item v-for="entry in pendingExpenses" :key="entry.id">
<q-item-section avatar>
<q-icon name="pending" color="orange" size="sm">
<q-tooltip>Pending approval</q-tooltip>
</q-icon>
</q-item-section>
<q-item-section>
<q-item-label>{% raw %}{{ entry.description }}{% endraw %}</q-item-label>
<q-item-label caption>
{% raw %}{{ formatDate(entry.entry_date) }}{% endraw %}
</q-item-label>
<q-item-label caption v-if="entry.meta && entry.meta.user_id">
User: {% raw %}{{ entry.meta.user_id.substring(0, 16) }}...{% endraw %}
</q-item-label>
<q-item-label caption v-if="entry.reference" class="text-grey">
Ref: {% raw %}{{ entry.reference }}{% endraw %}
</q-item-label>
</q-item-section>
<q-item-section side>
<q-item-label>{% raw %}{{ formatSats(getTotalAmount(entry)) }} sats{% endraw %}</q-item-label>
<q-item-label caption v-if="getEntryFiatAmount(entry)">
{% raw %}{{ getEntryFiatAmount(entry) }}{% endraw %}
</q-item-label>
</q-item-section>
<q-item-section side>
<div class="q-gutter-xs">
<q-btn
size="sm"
color="positive"
@click="approveExpense(entry.id)"
:loading="entry.approving"
>
Approve
</q-btn>
<q-btn
size="sm"
color="negative"
@click="rejectExpense(entry.id)"
:loading="entry.rejecting"
>
Reject
</q-btn>
</div>
</q-item-section>
</q-item>
</q-list>
</q-card-section>
</q-card>
<!-- Quick Actions --> <!-- Quick Actions -->
<q-card> <q-card>
<q-card-section> <q-card-section>
@ -101,58 +155,6 @@
</q-card-section> </q-card-section>
</q-card> </q-card>
<!-- User Balance Card -->
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-sm">
<div class="col">
<h6 class="q-my-none">Your Balance</h6>
</div>
<div class="col-auto">
<q-btn flat round icon="refresh" @click="loadBalance">
<q-tooltip>Refresh balance</q-tooltip>
</q-btn>
</div>
</div>
<div v-if="balance !== null">
<div class="text-h4" :class="isSuperUser ? (balance.balance >= 0 ? 'text-negative' : 'text-positive') : (balance.balance >= 0 ? 'text-positive' : 'text-negative')">
{% raw %}{{ formatSats(Math.abs(balance.balance)) }} sats{% endraw %}
</div>
<div v-if="balance.fiat_balances && Object.keys(balance.fiat_balances).length > 0" class="text-h6 q-mt-sm">
<span v-for="(amount, currency) in balance.fiat_balances" :key="currency" class="q-mr-md">
{% raw %}{{ formatFiat(Math.abs(amount), currency) }}{% endraw %}
</span>
</div>
<div class="text-subtitle2" v-if="isSuperUser">
{% raw %}{{ balance.balance > 0 ? 'Total you owe' : balance.balance < 0 ? 'Total owed to you' : 'No outstanding balances' }}{% endraw %}
</div>
<div class="text-subtitle2" v-else>
{% raw %}{{ balance.balance >= 0 ? 'Castle owes you' : 'You owe Castle' }}{% endraw %}
</div>
<div class="q-mt-md q-gutter-sm">
<q-btn
v-if="balance.balance < 0 && !isSuperUser"
color="primary"
@click="showPayBalanceDialog"
>
Pay Balance
</q-btn>
<q-btn
v-if="balance.balance > 0 && !isSuperUser"
color="secondary"
@click="showManualPaymentDialog"
>
Request Manual Payment
</q-btn>
</div>
</div>
<div v-else>
<q-spinner color="primary" size="md"></q-spinner>
Loading balance...
</div>
</q-card-section>
</q-card>
<!-- User Balances Breakdown (Super User Only) --> <!-- User Balances Breakdown (Super User Only) -->
<q-card v-if="isSuperUser && outstandingUserBalances.length > 0"> <q-card v-if="isSuperUser && outstandingUserBalances.length > 0">
<q-card-section> <q-card-section>
@ -222,57 +224,55 @@
</q-card-section> </q-card-section>
</q-card> </q-card>
<!-- Pending Expense Entries (Super User Only) --> <!-- User Balance Card -->
<q-card v-if="isSuperUser && pendingExpenses.length > 0"> <q-card>
<q-card-section> <q-card-section>
<h6 class="q-my-none q-mb-md">Pending Expense Approvals</h6> <div class="row items-center no-wrap q-mb-sm">
<q-list separator> <div class="col">
<q-item v-for="entry in pendingExpenses" :key="entry.id"> <h6 class="q-my-none">Your Balance</h6>
<q-item-section avatar> </div>
<q-icon name="pending" color="orange" size="sm"> <div class="col-auto">
<q-tooltip>Pending approval</q-tooltip> <q-btn flat round icon="refresh" @click="loadBalance">
</q-icon> <q-tooltip>Refresh balance</q-tooltip>
</q-item-section> </q-btn>
<q-item-section> </div>
<q-item-label>{% raw %}{{ entry.description }}{% endraw %}</q-item-label> </div>
<q-item-label caption> <div v-if="balance !== null">
{% raw %}{{ formatDate(entry.entry_date) }}{% endraw %} <div class="text-h4" :class="isSuperUser ? (balance.balance >= 0 ? 'text-negative' : 'text-positive') : (balance.balance >= 0 ? 'text-positive' : 'text-negative')">
</q-item-label> {% raw %}{{ formatSats(Math.abs(balance.balance)) }} sats{% endraw %}
<q-item-label caption v-if="entry.meta && entry.meta.user_id"> </div>
User: {% raw %}{{ entry.meta.user_id.substring(0, 16) }}...{% endraw %} <div v-if="balance.fiat_balances && Object.keys(balance.fiat_balances).length > 0" class="text-h6 q-mt-sm">
</q-item-label> <span v-for="(amount, currency) in balance.fiat_balances" :key="currency" class="q-mr-md">
<q-item-label caption v-if="entry.reference" class="text-grey"> {% raw %}{{ formatFiat(Math.abs(amount), currency) }}{% endraw %}
Ref: {% raw %}{{ entry.reference }}{% endraw %} </span>
</q-item-label> </div>
</q-item-section> <div class="text-subtitle2" v-if="isSuperUser">
<q-item-section side> {% raw %}{{ balance.balance > 0 ? 'Total you owe' : balance.balance < 0 ? 'Total owed to you' : 'No outstanding balances' }}{% endraw %}
<q-item-label>{% raw %}{{ formatSats(getTotalAmount(entry)) }} sats{% endraw %}</q-item-label> </div>
<q-item-label caption v-if="getEntryFiatAmount(entry)"> <div class="text-subtitle2" v-else>
{% raw %}{{ getEntryFiatAmount(entry) }}{% endraw %} {% raw %}{{ balance.balance >= 0 ? 'Castle owes you' : 'You owe Castle' }}{% endraw %}
</q-item-label> </div>
</q-item-section> <div class="q-mt-md q-gutter-sm">
<q-item-section side> <q-btn
<div class="q-gutter-xs"> v-if="balance.balance < 0 && !isSuperUser"
<q-btn color="primary"
size="sm" @click="showPayBalanceDialog"
color="positive" >
@click="approveExpense(entry.id)" Pay Balance
:loading="entry.approving" </q-btn>
> <q-btn
Approve v-if="balance.balance > 0 && !isSuperUser"
</q-btn> color="secondary"
<q-btn @click="showManualPaymentDialog"
size="sm" >
color="negative" Request Manual Payment
@click="rejectExpense(entry.id)" </q-btn>
:loading="entry.rejecting" </div>
> </div>
Reject <div v-else>
</q-btn> <q-spinner color="primary" size="md"></q-spinner>
</div> Loading balance...
</q-item-section> </div>
</q-item>
</q-list>
</q-card-section> </q-card-section>
</q-card> </q-card>
@ -319,6 +319,80 @@
</q-card-section> </q-card-section>
</q-card> </q-card>
<!-- Recent Transactions -->
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-sm">
<div class="col">
<h6 class="q-my-none">Recent Transactions</h6>
</div>
<div class="col-auto">
<q-btn flat round icon="refresh" @click="loadTransactions">
<q-tooltip>Refresh transactions</q-tooltip>
</q-btn>
</div>
</div>
<q-list v-if="transactions.length > 0" separator>
<q-item v-for="entry in transactions" :key="entry.id">
<q-item-section avatar>
<!-- Transaction status flag -->
<q-icon v-if="entry.flag === '*'" name="check_circle" color="positive" size="sm">
<q-tooltip>Cleared</q-tooltip>
</q-icon>
<q-icon v-else-if="entry.flag === '!'" name="pending" color="orange" size="sm">
<q-tooltip>Pending</q-tooltip>
</q-icon>
<q-icon v-else-if="entry.flag === '#'" name="flag" color="red" size="sm">
<q-tooltip>Flagged - needs review</q-tooltip>
</q-icon>
<q-icon v-else-if="entry.flag === 'x'" name="cancel" color="grey" size="sm">
<q-tooltip>Voided</q-tooltip>
</q-icon>
</q-item-section>
<q-item-section>
<q-item-label>
{% raw %}{{ entry.description }}{% endraw %}
<!-- Castle's perspective: Receivables are incoming (green), Payables are outgoing (red) -->
<q-badge v-if="isSuperUser && isReceivable(entry)" color="positive" class="q-ml-sm">
Receivable
</q-badge>
<q-badge v-else-if="isSuperUser && isPayable(entry)" color="negative" class="q-ml-sm">
Payable
</q-badge>
<!-- User's perspective: Receivables are outgoing (red), Payables are incoming (green) -->
<q-badge v-else-if="!isSuperUser && isReceivable(entry)" color="negative" class="q-ml-sm">
Payable
</q-badge>
<q-badge v-else-if="!isSuperUser && isPayable(entry)" color="positive" class="q-ml-sm">
Receivable
</q-badge>
</q-item-label>
<q-item-label caption>
{% raw %}{{ formatDate(entry.entry_date) }}{% endraw %}
</q-item-label>
<q-item-label caption v-if="entry.reference" class="text-grey">
Ref: {% raw %}{{ entry.reference }}{% endraw %}
</q-item-label>
<q-item-label caption v-if="entry.meta && Object.keys(entry.meta).length > 0" class="text-blue-grey-6">
<q-icon name="info" size="xs" class="q-mr-xs"></q-icon>
<span v-if="entry.meta.source">Source: {% raw %}{{ entry.meta.source }}{% endraw %}</span>
<span v-if="entry.meta.created_via" class="q-ml-sm">Via: {% raw %}{{ entry.meta.created_via }}{% endraw %}</span>
</q-item-label>
</q-item-section>
<q-item-section side>
<q-item-label>{% raw %}{{ formatSats(getTotalAmount(entry)) }} sats{% endraw %}</q-item-label>
<q-item-label caption v-if="getEntryFiatAmount(entry)">
{% raw %}{{ getEntryFiatAmount(entry) }}{% endraw %}
</q-item-label>
</q-item-section>
</q-item>
</q-list>
<div v-else class="text-center q-pa-md text-grey">
No transactions yet
</div>
</q-card-section>
</q-card>
<!-- Balance Assertions (Super User Only) --> <!-- Balance Assertions (Super User Only) -->
<q-card v-if="isSuperUser"> <q-card v-if="isSuperUser">
<q-card-section> <q-card-section>
@ -565,80 +639,6 @@
</div> </div>
</q-card-section> </q-card-section>
</q-card> </q-card>
<!-- Recent Transactions -->
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-sm">
<div class="col">
<h6 class="q-my-none">Recent Transactions</h6>
</div>
<div class="col-auto">
<q-btn flat round icon="refresh" @click="loadTransactions">
<q-tooltip>Refresh transactions</q-tooltip>
</q-btn>
</div>
</div>
<q-list v-if="transactions.length > 0" separator>
<q-item v-for="entry in transactions" :key="entry.id">
<q-item-section avatar>
<!-- Transaction status flag -->
<q-icon v-if="entry.flag === '*'" name="check_circle" color="positive" size="sm">
<q-tooltip>Cleared</q-tooltip>
</q-icon>
<q-icon v-else-if="entry.flag === '!'" name="pending" color="orange" size="sm">
<q-tooltip>Pending</q-tooltip>
</q-icon>
<q-icon v-else-if="entry.flag === '#'" name="flag" color="red" size="sm">
<q-tooltip>Flagged - needs review</q-tooltip>
</q-icon>
<q-icon v-else-if="entry.flag === 'x'" name="cancel" color="grey" size="sm">
<q-tooltip>Voided</q-tooltip>
</q-icon>
</q-item-section>
<q-item-section>
<q-item-label>
{% raw %}{{ entry.description }}{% endraw %}
<!-- Castle's perspective: Receivables are incoming (green), Payables are outgoing (red) -->
<q-badge v-if="isSuperUser && isReceivable(entry)" color="positive" class="q-ml-sm">
Receivable
</q-badge>
<q-badge v-else-if="isSuperUser && isPayable(entry)" color="negative" class="q-ml-sm">
Payable
</q-badge>
<!-- User's perspective: Receivables are outgoing (red), Payables are incoming (green) -->
<q-badge v-else-if="!isSuperUser && isReceivable(entry)" color="negative" class="q-ml-sm">
Payable
</q-badge>
<q-badge v-else-if="!isSuperUser && isPayable(entry)" color="positive" class="q-ml-sm">
Receivable
</q-badge>
</q-item-label>
<q-item-label caption>
{% raw %}{{ formatDate(entry.entry_date) }}{% endraw %}
</q-item-label>
<q-item-label caption v-if="entry.reference" class="text-grey">
Ref: {% raw %}{{ entry.reference }}{% endraw %}
</q-item-label>
<q-item-label caption v-if="entry.meta && Object.keys(entry.meta).length > 0" class="text-blue-grey-6">
<q-icon name="info" size="xs" class="q-mr-xs"></q-icon>
<span v-if="entry.meta.source">Source: {% raw %}{{ entry.meta.source }}{% endraw %}</span>
<span v-if="entry.meta.created_via" class="q-ml-sm">Via: {% raw %}{{ entry.meta.created_via }}{% endraw %}</span>
</q-item-label>
</q-item-section>
<q-item-section side>
<q-item-label>{% raw %}{{ formatSats(getTotalAmount(entry)) }} sats{% endraw %}</q-item-label>
<q-item-label caption v-if="getEntryFiatAmount(entry)">
{% raw %}{{ getEntryFiatAmount(entry) }}{% endraw %}
</q-item-label>
</q-item-section>
</q-item>
</q-list>
<div v-else class="text-center q-pa-md text-grey">
No transactions yet
</div>
</q-card-section>
</q-card>
</div> </div>
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md"> <div class="col-12 col-md-4 col-lg-5 q-gutter-y-md">