feat(accounts): merge real estate accounts with linked mortgages in UI (#248)

Combine real estate and linked loan accounts into a unified card on the
accounts list, showing equity as the primary balance with a dual-line
sparkline (solid for market value, dashed for mortgage owed). Hide linked
loans from the loan group and display mortgage bank info in the subtitle.

On the detail page, render LoanDetailsCard for real estate accounts with
linked loans and add header actions for editing loan details and updating
owed amounts via dialogs.
This commit is contained in:
Víctor Falcón 2026-03-26 20:54:12 +01:00
parent 8b71115afc
commit 6e976354ba
6 changed files with 628 additions and 23 deletions

View File

@ -27,13 +27,23 @@ class AccountController extends Controller
$accounts = Account::query()
->where('user_id', $user->id)
->with('bank:id,name,logo')
->with(['bank:id,name,logo', 'realEstateDetail:account_id,linked_loan_account_id'])
->orderByRaw("FIELD(type, 'checking', 'savings', 'investment', 'retirement', 'real_estate', 'loan', 'credit_card', 'others')")
->orderBy('name')
->get(['id', 'name', 'name_iv', 'encrypted', 'bank_id', 'type', 'currency_code', 'banking_connection_id']);
$accountsData = $accounts->map(function (Account $account) {
$data = $account->only(['id', 'name', 'name_iv', 'encrypted', 'bank_id', 'type', 'currency_code', 'banking_connection_id', 'bank']);
if ($account->type === AccountType::RealEstate && $account->realEstateDetail?->linked_loan_account_id) {
$data['linked_loan_account_id'] = $account->realEstateDetail->linked_loan_account_id;
}
return $data;
});
return Inertia::render('Accounts/Index', [
'accounts' => $accounts,
'accounts' => $accountsData,
'accountMetrics' => Inertia::defer(fn () => $this->accountMetricsService->getAccountMetrics($user->currency_code, $accounts)),
]);
}
@ -72,6 +82,37 @@ class AccountController extends Controller
->where('balance_date', '<=', now()->toDateString())
->orderByDesc('balance_date')
->value('balance') ?? 0;
// Include linked loan account at top level for header actions
$data['linked_loan_account'] = $linkedLoan->only(['id', 'name', 'name_iv', 'encrypted', 'type', 'currency_code', 'bank', 'banking_connection_id']);
// Load loan amortization details for the linked loan
$linkedLoan->load('loanDetail');
$loanDetail = $linkedLoan->loanDetail;
if ($loanDetail) {
$remainingMonths = $this->loanAmortizationService->calculateRemainingMonths($loanDetail, now());
$lastLoanBalance = AccountBalance::query()
->where('account_id', $linkedLoan->id)
->orderBy('balance_date', 'desc')
->value('balance');
$monthlyPayment = $this->loanAmortizationService->calculateMonthlyPayment(
$lastLoanBalance ?? $loanDetail->original_amount,
(float) $loanDetail->annual_interest_rate,
$lastLoanBalance ? $remainingMonths : $loanDetail->loan_term_months,
);
$data['loan_detail'] = [
...$loanDetail->only([
'id', 'annual_interest_rate', 'loan_term_months',
'start_date', 'original_amount',
]),
'monthly_payment' => $monthlyPayment,
'remaining_months' => $remainingMonths,
];
}
}
$data['real_estate_detail']['current_market_value'] = AccountBalance::query()

View File

@ -9,25 +9,88 @@ import { AccountWithMetrics } from '@/hooks/use-dashboard-data';
import { formatAccountType, supportsInvestedAmount } from '@/types/account';
import { __ } from '@/utils/i18n';
import { Link } from '@inertiajs/react';
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { Line, LineChart, ResponsiveContainer, Tooltip } from 'recharts';
import { Button } from '../ui/button';
import { UpdateBalanceDialog } from './update-balance-dialog';
interface LinkedLoanMetrics {
currentBalance: number;
previousBalance: number;
diff: number;
history: Array<{
date: string;
value: number;
}>;
loanAccount?: {
name: string;
bank: { name: string; logo: string | null } | null;
};
}
interface AccountListCardProps {
account: AccountWithMetrics;
loading?: boolean;
onBalanceUpdated?: () => void;
linkedLoanMetrics?: LinkedLoanMetrics;
}
export function AccountListCard({
account,
loading,
onBalanceUpdated,
linkedLoanMetrics,
}: AccountListCardProps) {
const { accountMainLineColor, accountGainLineColor } = useChartColors();
const { accountMainLineColor, accountGainLineColor, mortgageLineColor } =
useChartColors();
const [updateBalanceOpen, setUpdateBalanceOpen] = useState(false);
const hasMortgage = !!linkedLoanMetrics;
// Compute equity data when this is a real estate account with a linked mortgage
const equityData = useMemo(() => {
if (!hasMortgage) return null;
const marketValue = account.currentBalance;
const mortgageOwed = linkedLoanMetrics.currentBalance;
const equity = marketValue - mortgageOwed;
const prevMarketValue = account.previousBalance;
const prevMortgageOwed = linkedLoanMetrics.previousBalance;
const prevEquity = prevMarketValue - prevMortgageOwed;
const equityDiff = equity - prevEquity;
// Build dual-line sparkline: market value (solid) + mortgage owed (dashed)
const marketMap = new Map(
account.history.map((h) => [h.date, h.value]),
);
const mortgageMap = new Map(
linkedLoanMetrics.history.map((h) => [h.date, h.value]),
);
const allDates = [
...new Set([...marketMap.keys(), ...mortgageMap.keys()]),
].sort();
let lastMarket = 0;
let lastMortgage = 0;
const dualHistory = allDates.map((date) => {
const mv = marketMap.get(date) ?? lastMarket;
const mo = mortgageMap.get(date) ?? lastMortgage;
lastMarket = mv;
lastMortgage = mo;
return { date, value: mv, mortgageOwed: mo };
});
return {
equity,
prevEquity,
equityDiff,
mortgageOwed,
marketValue,
dualHistory,
};
}, [hasMortgage, account, linkedLoanMetrics]);
if (loading) {
return (
<Card className="w-full">
@ -53,9 +116,19 @@ export function AccountListCard({
);
}
const isPositive = account.diff >= 0;
const displayBalance = equityData
? equityData.equity
: account.currentBalance;
const displayDiff = equityData ? equityData.equityDiff : account.diff;
const displayPreviousBalance = equityData
? equityData.prevEquity
: account.previousBalance;
const isPositive = displayDiff >= 0;
const isConnected = !!account.banking_connection_id;
// Choose sparkline data: dual-line history for merged, account history for normal
const sparklineData = equityData ? equityData.dualHistory : account.history;
return (
<Card className="w-full py-0">
<CardContent className="p-4">
@ -84,10 +157,38 @@ export function AccountListCard({
</h3>
</Link>
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<span>
{account.bank?.name ||
formatAccountType(account.type)}
</span>
{hasMortgage &&
linkedLoanMetrics.loanAccount ? (
<span className="flex items-center gap-1">
{__('Mortgage at')}{' '}
{linkedLoanMetrics.loanAccount
.bank && (
<BankLogo
src={
linkedLoanMetrics
.loanAccount.bank
.logo
}
name={
linkedLoanMetrics
.loanAccount.bank
.name
}
className="size-3.5 shrink-0"
fallback="letter"
/>
)}
{linkedLoanMetrics.loanAccount.bank
?.name ??
linkedLoanMetrics.loanAccount
.name}
</span>
) : (
<span>
{account.bank?.name ||
formatAccountType(account.type)}
</span>
)}
</div>
</div>
</div>
@ -95,7 +196,7 @@ export function AccountListCard({
{isConnected ? (
<div className="-mr-2 px-2 py-1">
<AmountDisplay
amountInCents={account.currentBalance}
amountInCents={displayBalance}
currencyCode={account.currency_code}
size="2xl"
weight="bold"
@ -108,7 +209,7 @@ export function AccountListCard({
className="-mr-2 cursor-pointer rounded-md px-2 py-1 transition-colors hover:bg-muted"
>
<AmountDisplay
amountInCents={account.currentBalance}
amountInCents={displayBalance}
currencyCode={account.currency_code}
size="2xl"
weight="bold"
@ -117,23 +218,24 @@ export function AccountListCard({
)}
<AmountTrendIndicator
isPositive={isPositive}
trend={Math.abs(account.diff)}
trend={Math.abs(displayDiff)}
label={__('vs last month')}
className="text-sm"
previousAmount={account.previousBalance}
currentAmount={account.currentBalance}
previousAmount={displayPreviousBalance}
currentAmount={displayBalance}
tooltipSide="bottom"
currencyCode={account.currency_code}
/>
</div>
</div>
<div className="h-[100px] w-full">
<ResponsiveContainer
width="100%"
height="100%"
initialDimension={{ width: 1, height: 1 }}
>
<LineChart data={account.history}>
<LineChart data={sparklineData}>
<Tooltip
content={({ active, payload }) => {
if (!active || !payload?.length)
@ -142,7 +244,68 @@ export function AccountListCard({
date: string;
value: number;
investedAmount?: number | null;
mortgageOwed?: number;
};
if (equityData) {
const equity =
data.value -
(data.mortgageOwed ?? 0);
return (
<div className="rounded-lg border border-border/50 bg-background px-3 py-2 text-sm shadow-xl">
<p className="mb-1 text-muted-foreground">
{data.date}
</p>
<div className="grid grid-cols-[auto_1fr] gap-x-2 gap-y-0.5">
<span className="text-muted-foreground">
{__('Market Value')}
</span>
<span className="text-right font-mono font-medium text-foreground tabular-nums">
<AmountDisplay
amountInCents={
data.value
}
currencyCode={
account.currency_code
}
/>
</span>
<span className="text-muted-foreground">
{__(
'Mortgage Owed',
)}
</span>
<span className="text-right font-mono text-muted-foreground tabular-nums">
<AmountDisplay
amountInCents={
data.mortgageOwed ??
0
}
currencyCode={
account.currency_code
}
/>
</span>
<span className="text-muted-foreground">
{__('Equity')}
</span>
<span
className={`text-right font-mono whitespace-nowrap tabular-nums ${equity >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}`}
>
<AmountDisplay
amountInCents={
equity
}
currencyCode={
account.currency_code
}
/>
</span>
</div>
</div>
);
}
const invested = supportsInvestedAmount(
account,
)
@ -234,17 +397,29 @@ export function AccountListCard({
strokeWidth={2}
dot={false}
/>
{supportsInvestedAmount(account) && (
{equityData && (
<Line
type="monotone"
dataKey="investedAmount"
stroke={accountGainLineColor}
dataKey="mortgageOwed"
stroke={mortgageLineColor}
strokeWidth={1.5}
strokeDasharray="4 3"
dot={false}
connectNulls
/>
)}
{!equityData &&
supportsInvestedAmount(account) && (
<Line
type="monotone"
dataKey="investedAmount"
stroke={accountGainLineColor}
strokeWidth={1.5}
strokeDasharray="4 3"
dot={false}
connectNulls
/>
)}
</LineChart>
</ResponsiveContainer>
</div>

View File

@ -0,0 +1,199 @@
import { update as updateLoanDetail } from '@/actions/App/Http/Controllers/LoanDetailController';
import InputError from '@/components/input-error';
import { AmountInput } from '@/components/ui/amount-input';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import type { CurrencyCode, LoanDetail } from '@/types/account';
import { __ } from '@/utils/i18n';
import { router } from '@inertiajs/react';
import { useEffect, useState } from 'react';
interface EditLoanDetailDialogProps {
loanAccountId: string;
currencyCode: CurrencyCode;
detail: LoanDetail | null;
open: boolean;
onOpenChange: (open: boolean) => void;
}
export function EditLoanDetailDialog({
loanAccountId,
currencyCode,
detail,
open,
onOpenChange,
}: EditLoanDetailDialogProps) {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errors, setErrors] = useState<Record<string, string>>({});
const [formData, setFormData] = useState({
annual_interest_rate: detail?.annual_interest_rate ?? '',
loan_term_months: detail?.loan_term_months
? String(detail.loan_term_months)
: '',
start_date: detail?.start_date?.slice(0, 10) ?? '',
original_amount: detail?.original_amount ?? 0,
});
useEffect(() => {
if (open) {
setFormData({
annual_interest_rate: detail?.annual_interest_rate ?? '',
loan_term_months: detail?.loan_term_months
? String(detail.loan_term_months)
: '',
start_date: detail?.start_date?.slice(0, 10) ?? '',
original_amount: detail?.original_amount ?? 0,
});
setErrors({});
}
}, [open, detail]);
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setIsSubmitting(true);
setErrors({});
router.patch(
updateLoanDetail.url(loanAccountId),
{
annual_interest_rate: formData.annual_interest_rate,
loan_term_months: Number(formData.loan_term_months),
start_date: formData.start_date,
original_amount: formData.original_amount,
},
{
preserveScroll: true,
onSuccess: () => onOpenChange(false),
onError: (errors) => setErrors(errors),
onFinish: () => setIsSubmitting(false),
},
);
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent hasKeyboard className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>
{detail
? __('Edit Loan Details')
: __('Add Loan Details')}
</DialogTitle>
<DialogDescription>
{__(
'Set interest rate, term, and amount to track amortization.',
)}
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid gap-4 sm:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="dialog_annual_interest_rate">
{__('Annual Interest Rate (%)')}
</Label>
<Input
id="dialog_annual_interest_rate"
type="number"
value={formData.annual_interest_rate}
onChange={(e) =>
setFormData((prev) => ({
...prev,
annual_interest_rate: e.target.value,
}))
}
placeholder="3.500"
min="0"
max="99.999"
step="0.001"
/>
<InputError message={errors.annual_interest_rate} />
</div>
<div className="space-y-2">
<Label htmlFor="dialog_loan_term_months">
{__('Loan Term (months)')}
</Label>
<Input
id="dialog_loan_term_months"
type="number"
value={formData.loan_term_months}
onChange={(e) =>
setFormData((prev) => ({
...prev,
loan_term_months: e.target.value,
}))
}
placeholder="360"
min="1"
max="600"
/>
<InputError message={errors.loan_term_months} />
</div>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="dialog_loan_start_date">
{__('Start Date')}
</Label>
<Input
id="dialog_loan_start_date"
type="date"
value={formData.start_date}
onChange={(e) =>
setFormData((prev) => ({
...prev,
start_date: e.target.value,
}))
}
/>
<InputError message={errors.start_date} />
</div>
<div className="space-y-2">
<Label htmlFor="dialog_original_amount">
{__('Original Amount')}
</Label>
<AmountInput
id="dialog_original_amount"
value={formData.original_amount}
onChange={(value) =>
setFormData((prev) => ({
...prev,
original_amount: value,
}))
}
currencyCode={currencyCode}
/>
<InputError message={errors.original_amount} />
</div>
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
disabled={isSubmitting}
>
{__('Cancel')}
</Button>
<Button type="submit" disabled={isSubmitting}>
{isSubmitting ? __('Saving...') : __('Save')}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}

View File

@ -50,6 +50,20 @@ interface Props {
export default function AccountsIndex({ accounts, accountMetrics }: Props) {
const isLoading = !accountMetrics;
// Identify loan account IDs that are linked to a real estate account
const linkedLoanAccountIds = useMemo(() => {
const ids = new Set<string>();
accounts.forEach((account) => {
if (
account.type === 'real_estate' &&
account.linked_loan_account_id
) {
ids.add(account.linked_loan_account_id);
}
});
return ids;
}, [accounts]);
const accountsWithMetrics: AccountWithMetrics[] = useMemo(() => {
return accounts.map((account) => {
const metrics = accountMetrics?.[account.id];
@ -78,6 +92,12 @@ export default function AccountsIndex({ accounts, accountMetrics }: Props) {
accountsWithMetrics.forEach((account) => {
const type = account.type as AccountType;
// Hide loan accounts that are linked to a real estate account
if (type === 'loan' && linkedLoanAccountIds.has(account.id)) {
return;
}
if (groups[type]) {
groups[type].push(account);
} else {
@ -86,7 +106,39 @@ export default function AccountsIndex({ accounts, accountMetrics }: Props) {
});
return groups;
}, [accountsWithMetrics]);
}, [accountsWithMetrics, linkedLoanAccountIds]);
// Build a map of linked loan metrics keyed by real estate account ID
const linkedLoanMetricsMap = useMemo(() => {
if (!accountMetrics) return {};
const map: Record<
string,
AccountMetrics & {
loanAccount?: {
name: string;
bank: { name: string; logo: string | null } | null;
};
}
> = {};
accounts.forEach((account) => {
if (
account.type === 'real_estate' &&
account.linked_loan_account_id &&
accountMetrics[account.linked_loan_account_id]
) {
const loanAccount = accounts.find(
(a) => a.id === account.linked_loan_account_id,
);
map[account.id] = {
...accountMetrics[account.linked_loan_account_id],
loanAccount: loanAccount
? { name: loanAccount.name, bank: loanAccount.bank }
: undefined,
};
}
});
return map;
}, [accounts, accountMetrics]);
const handleBalanceUpdated = useCallback(() => {
router.reload({ only: ['accountMetrics'] });
@ -120,6 +172,9 @@ export default function AccountsIndex({ accounts, accountMetrics }: Props) {
account={account}
loading={isLoading}
onBalanceUpdated={handleBalanceUpdated}
linkedLoanMetrics={
linkedLoanMetricsMap[account.id]
}
/>
));
})}

View File

@ -9,6 +9,7 @@ import {
import { BalancesModal } from '@/components/accounts/balances-modal';
import { DeleteAccountDialog } from '@/components/accounts/delete-account-dialog';
import { EditAccountDialog } from '@/components/accounts/edit-account-dialog';
import { EditLoanDetailDialog } from '@/components/accounts/edit-loan-detail-dialog';
import { ImportBalancesDrawer } from '@/components/accounts/import-balances-drawer';
import { UpdateBalanceDialog } from '@/components/accounts/update-balance-dialog';
import { BankLogo } from '@/components/bank-logo';
@ -70,6 +71,7 @@ interface AccountWithDetails extends Account {
real_estate_detail?: RealEstateDetail;
available_loan_accounts?: Account[];
loan_detail?: LoanDetail;
linked_loan_account?: Account;
}
interface Props {
@ -92,11 +94,13 @@ export default function AccountShow({
const [editOpen, setEditOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
const [updateBalanceOpen, setUpdateBalanceOpen] = useState(false);
const [updateLoanBalanceOpen, setUpdateLoanBalanceOpen] = useState(false);
const [importBalancesOpen, setImportBalancesOpen] = useState(false);
const [balancesOpen, setBalancesOpen] = useState(false);
const [chartRefreshKey, setChartRefreshKey] = useState(0);
const [editingDetails, setEditingDetails] = useState(false);
const [editingLoanDetails, setEditingLoanDetails] = useState(false);
const [editLoanDialogOpen, setEditLoanDialogOpen] = useState(false);
const [chartComputedData, setChartComputedData] =
useState<ChartComputedData | null>(null);
@ -113,6 +117,8 @@ export default function AccountShow({
const isRealEstate = account.type === 'real_estate';
const realEstateDetail = account.real_estate_detail;
const loanDetail = account.loan_detail;
const linkedLoanAccount = account.linked_loan_account;
const hasLinkedLoan = isRealEstate && !!linkedLoanAccount;
const breadcrumbs: BreadcrumbItem[] = [
{
@ -168,13 +174,96 @@ export default function AccountShow({
/>
</div>
{isConnected ? (
{isConnected && !hasLinkedLoan ? (
<Button
variant="outline"
onClick={() => setEditOpen(true)}
>
{__('Edit account')}
</Button>
) : isConnected && hasLinkedLoan ? (
<ButtonGroup>
<Button
variant="outline"
onClick={() => setEditOpen(true)}
>
{__('Edit account')}
</Button>
<Button
variant="outline"
onClick={() => setEditLoanDialogOpen(true)}
>
{__('Edit loan details')}
</Button>
</ButtonGroup>
) : !isConnected && hasLinkedLoan ? (
<ButtonGroup>
<ButtonGroup>
<Button
variant="outline"
onClick={() => setUpdateBalanceOpen(true)}
>
{__('Update market value')}
</Button>
<Button
variant="outline"
onClick={() =>
setUpdateLoanBalanceOpen(true)
}
>
{__('Update owed amount')}
</Button>
</ButtonGroup>
<ButtonGroup>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="icon"
aria-label={__('More options')}
>
<ChevronDown className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() =>
setBalancesOpen(true)
}
>
{__('See market values')}
</DropdownMenuItem>
<DropdownMenuItem
onClick={() =>
setImportBalancesOpen(true)
}
>
{__('Import market values')}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => setEditOpen(true)}
>
{__('Edit account')}
</DropdownMenuItem>
<DropdownMenuItem
onClick={() =>
setEditLoanDialogOpen(true)
}
>
{__('Edit loan details')}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => setDeleteOpen(true)}
variant="destructive"
>
{__('Delete')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
</ButtonGroup>
) : (
<ButtonGroup>
<ButtonGroup>
@ -265,6 +354,17 @@ export default function AccountShow({
/>
)}
{isRealEstate && hasLinkedLoan && (
<LoanDetailsCard
detail={loanDetail ?? null}
account={account}
loanAccountId={linkedLoanAccount.id}
isEditing={editingLoanDetails}
onEditToggle={setEditingLoanDetails}
onEditDialogOpen={() => setEditLoanDialogOpen(true)}
/>
)}
{isLoan && (
<LoanDetailsCard
detail={loanDetail ?? null}
@ -327,6 +427,27 @@ export default function AccountShow({
accountId={account.id}
onSuccess={handleBalanceUpdated}
/>
{hasLinkedLoan && (
<>
<EditLoanDetailDialog
loanAccountId={linkedLoanAccount.id}
currencyCode={
linkedLoanAccount.currency_code ??
account.currency_code
}
detail={loanDetail ?? null}
open={editLoanDialogOpen}
onOpenChange={setEditLoanDialogOpen}
/>
<UpdateBalanceDialog
account={linkedLoanAccount}
open={updateLoanBalanceOpen}
onOpenChange={setUpdateLoanBalanceOpen}
onSuccess={handleBalanceUpdated}
/>
</>
)}
</AppSidebarLayout>
);
}
@ -986,14 +1107,19 @@ function PropertyDetailsCard({
function LoanDetailsCard({
detail,
account,
loanAccountId,
isEditing,
onEditToggle,
onEditDialogOpen,
}: {
detail: LoanDetail | null;
account: AccountWithDetails;
loanAccountId?: string;
isEditing: boolean;
onEditToggle: (editing: boolean) => void;
onEditDialogOpen?: () => void;
}) {
const targetAccountId = loanAccountId ?? account.id;
const [isSubmitting, setIsSubmitting] = useState(false);
const [errors, setErrors] = useState<Record<string, string>>({});
const [formData, setFormData] = useState({
@ -1011,7 +1137,7 @@ function LoanDetailsCard({
setErrors({});
router.patch(
updateLoanDetail.url(account.id),
updateLoanDetail.url(targetAccountId),
{
annual_interest_rate: formData.annual_interest_rate,
loan_term_months: Number(formData.loan_term_months),
@ -1161,7 +1287,11 @@ function LoanDetailsCard({
<Button
variant="ghost"
size="sm"
onClick={() => onEditToggle(true)}
onClick={() =>
onEditDialogOpen
? onEditDialogOpen()
: onEditToggle(true)
}
>
<Pencil className="mr-1 h-3.5 w-3.5" />
{__('Add')}
@ -1185,7 +1315,11 @@ function LoanDetailsCard({
<Button
variant="ghost"
size="sm"
onClick={() => onEditToggle(true)}
onClick={() =>
onEditDialogOpen
? onEditDialogOpen()
: onEditToggle(true)
}
>
<Pencil className="mr-1 h-3.5 w-3.5" />
{__('Edit')}

View File

@ -59,6 +59,7 @@ export interface Account {
banking_connection_id: UUID | null;
external_account_id: string | null;
linked_at: string | null;
linked_loan_account_id?: UUID | null;
}
export interface AccountBalance {