Add payoff amount, fix dialog overlay and nav highlight for sub-routes
- Show estimated payoff amount (balance + accrued interest) in loan summary - Display loan balances as absolute values in red - Fix dialog overlay blocking UI after edit by closing in finally with nextTick - Highlight parent nav item when viewing sub-routes (e.g. /loans/:id) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -159,6 +159,8 @@ const navItems = [
|
||||
|
||||
function isNavActive(navRoute: string) {
|
||||
if (route.path === navRoute) return true
|
||||
// Highlight parent nav for sub-routes (e.g. /loans/:id highlights Loans)
|
||||
if (navRoute !== '/' && route.path.startsWith(navRoute + '/')) return true
|
||||
// Highlight "Transactions" when viewing account-scoped transactions
|
||||
if (navRoute === '/transactions' && route.name === 'account-transactions') return true
|
||||
return false
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<v-card-text>
|
||||
<div class="d-flex justify-space-between mb-1">
|
||||
<span class="text-medium-emphasis">Balance</span>
|
||||
<span class="font-weight-bold">{{ formatCurrency(account.balance) }}</span>
|
||||
<span class="font-weight-bold text-red">{{ formatCurrency(Math.abs(account.balance)) }}</span>
|
||||
</div>
|
||||
<div v-if="account.interestRate" class="d-flex justify-space-between mb-1">
|
||||
<span class="text-medium-emphasis">Interest Rate</span>
|
||||
@@ -69,7 +69,11 @@
|
||||
</v-col>
|
||||
<v-col cols="6" md="3">
|
||||
<div class="text-medium-emphasis text-caption">Current Balance</div>
|
||||
<div class="text-h6">{{ formatCurrency(loanDetail.currentBalance) }}</div>
|
||||
<div class="text-h6 text-red">{{ formatCurrency(Math.abs(loanDetail.currentBalance)) }}</div>
|
||||
</v-col>
|
||||
<v-col v-if="payoffAmount != null" cols="6" md="3">
|
||||
<div class="text-medium-emphasis text-caption">Est. Payoff Amount</div>
|
||||
<div class="text-h6 text-red">{{ formatCurrency(payoffAmount) }}</div>
|
||||
</v-col>
|
||||
<v-col cols="6" md="3">
|
||||
<div class="text-medium-emphasis text-caption">Interest Rate</div>
|
||||
@@ -250,7 +254,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { ref, computed, nextTick, onMounted, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { loansApi } from '@/services/loans'
|
||||
import { useAccountsStore } from '@/stores/accounts'
|
||||
@@ -287,6 +291,30 @@ const filteredSchedule = computed(() => {
|
||||
return loanDetail.value.amortizationSchedule.filter(e => e.paymentDate.split('T')[0] >= today)
|
||||
})
|
||||
|
||||
const dailyAccrualTypes = ['AutoLoan', 'LineOfCredit']
|
||||
const payoffAmount = computed(() => {
|
||||
if (!loanDetail.value || !props.id) return null
|
||||
const balance = Math.abs(loanDetail.value.currentBalance)
|
||||
if (balance === 0) return 0
|
||||
const account = accountsStore.accounts.find(a => a.id === props.id)
|
||||
if (!account) return balance
|
||||
const rate = loanDetail.value.interestRate / 100
|
||||
const today = new Date()
|
||||
const todayStr = today.toISOString().split('T')[0]
|
||||
const schedule = loanDetail.value.amortizationSchedule
|
||||
const lastPast = [...schedule].reverse().find(e => e.paymentDate.split('T')[0] <= todayStr)
|
||||
if (!lastPast) return balance
|
||||
const lastDate = new Date(lastPast.paymentDate.split('T')[0] + 'T00:00:00')
|
||||
const todayMidnight = new Date(todayStr + 'T00:00:00')
|
||||
const days = Math.round((todayMidnight.getTime() - lastDate.getTime()) / 86400000)
|
||||
if (days <= 0) return balance
|
||||
const isDailyAccrual = dailyAccrualTypes.includes(account.type)
|
||||
const accruedInterest = isDailyAccrual
|
||||
? balance * rate / 365 * days
|
||||
: balance * rate / 12 * (days / 30)
|
||||
return balance + accruedInterest
|
||||
})
|
||||
|
||||
const amortHeaders = [
|
||||
{ title: '#', key: 'paymentNumber', width: '60px' },
|
||||
{ title: 'Date', key: 'paymentDate', width: '110px' },
|
||||
@@ -404,12 +432,13 @@ async function saveLoanDetail() {
|
||||
await loansApi.createLoanDetail(props.id, payload)
|
||||
showSnackbar('Loan details created')
|
||||
}
|
||||
showLoanDialog.value = false
|
||||
await loadLoanDetail()
|
||||
} catch {
|
||||
showSnackbar('Failed to save loan details', 'error')
|
||||
} finally {
|
||||
saving.value = false
|
||||
showLoanDialog.value = false
|
||||
await nextTick()
|
||||
await loadLoanDetail()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user