Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/accounts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ export default function AccountsPage() {
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
<button
className="btn-ghost"
onClick={() => openTransactionModal('transfer', undefined)}
onClick={() => openTransactionModal('transfer', card.id)}
title="Pay credit card bill"
style={{ fontSize: '0.75rem', color: 'var(--brand-primary)', fontWeight: 600 }}
>
Expand Down
1 change: 1 addition & 0 deletions src/app/api/transfers/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export async function POST(req: Request) {
date: body.date,
destinationAccountId: body.toAccountId,
notes: body.notes,
tags: body.tags,
});

return NextResponse.json({ transfer: result }, { status: 201 });
Expand Down
12 changes: 6 additions & 6 deletions src/app/loans/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -445,9 +445,9 @@ export default function LoansPage() {
</div>
<div>
<div style={{ fontSize: '0.72rem', fontWeight: 600, color: 'var(--text-muted)', letterSpacing: '0.04em' }}>
{isEmi ? 'REMAINING BALANCE' : 'OUTSTANDING PRINCIPAL'}
{isEmi ? 'REMAINING BALANCE' : 'OUTSTANDING BALANCE'}
</div>
<MoneyDisplay amount={loan.outstanding_principal} size="lg" weight="bold" colored />
<MoneyDisplay amount={loan.remainingBalance ?? loan.outstanding_principal} size="lg" weight="bold" colored />
</div>
<div>
<div style={{ fontSize: '0.72rem', fontWeight: 600, color: 'var(--text-muted)', letterSpacing: '0.04em' }}>
Expand All @@ -469,8 +469,8 @@ export default function LoansPage() {
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.75rem', marginBottom: '0.25rem' }}>
<span>
Principal Repaid: β‚Ή{Math.round(loan.paidPrincipal / 100).toLocaleString('en-IN')} of β‚Ή
{Math.round(loan.principal / 100).toLocaleString('en-IN')}
Repaid: β‚Ή{Math.round((loan.totalPaid ?? loan.paidPrincipal) / 100).toLocaleString('en-IN')} of β‚Ή
{Math.round((loan.totalPayable ?? loan.principal) / 100).toLocaleString('en-IN')}
</span>
<span style={{ fontWeight: 600, color: 'var(--color-income)' }}>{loan.progressPercent}% Completed</span>
</div>
Expand Down Expand Up @@ -517,8 +517,8 @@ export default function LoansPage() {
<td style={{ padding: '0.4rem 0.75rem', textAlign: 'right', color: 'var(--color-expense)' }}>
β‚Ή{Math.round(row.interestComponent / 100).toLocaleString('en-IN')}
</td>
<td style={{ padding: '0.4rem 0.75rem', textAlign: 'right' }}>
β‚Ή{Math.round(row.remainingPrincipal / 100).toLocaleString('en-IN')}
<td style={{ padding: '0.4rem 0.75rem', textAlign: 'right', fontWeight: 600 }}>
β‚Ή{Math.round((row.remainingBalance ?? row.remainingPrincipal) / 100).toLocaleString('en-IN')}
</td>
</tr>
))}
Expand Down
10 changes: 6 additions & 4 deletions src/app/reports/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ function ExpenseDonutChart({
letterSpacing="0.04em"
style={{ textTransform: 'uppercase' }}
>
{activeSlice ? (activeSlice.name.length > 14 ? activeSlice.name.substring(0, 12) + '…' : activeSlice.name) : 'TOTAL SPENT'}
{activeSlice ? (activeSlice.name.length > 14 ? activeSlice.name.substring(0, 12) + '…' : activeSlice.name) : (analyticsLabel === 'Tag' ? 'TOTAL TAGGED' : 'TOTAL SPENT')}
</text>

<text
Expand All @@ -378,7 +378,7 @@ function ExpenseDonutChart({
fontSize="11"
fontWeight="600"
>
{activeSlice ? `${activeSlice.percentage}% of total` : `${slices.length} ${analyticsLabel.toLowerCase()}s`}
{activeSlice ? `${activeSlice.percentage}% of ${analyticsLabel === 'Tag' ? 'tagged' : 'total'}` : `${slices.length} ${analyticsLabel.toLowerCase()}s`}
</text>
</svg>
</div>
Expand Down Expand Up @@ -493,7 +493,9 @@ export default function ReportsPage() {
const savingsRate = totalMonthIncome > 0 ? Math.round(((totalMonthIncome - totalMonthSpend) / totalMonthIncome) * 100) : 0;

// Active analytics data based on view mode
const totalTaggedSpend = useMemo(() => spendingByTag.reduce((acc: number, t: any) => acc + (t.total || 0), 0), [spendingByTag]);
const activeAnalytics = analyticsView === 'category' ? spendingByCategory : spendingByTag;
const activeAnalyticsTotal = analyticsView === 'category' ? totalMonthSpend : totalTaggedSpend;
const analyticsLabel = analyticsView === 'category' ? 'Category' : 'Tag';

return (
Expand Down Expand Up @@ -1101,7 +1103,7 @@ export default function ReportsPage() {
>
<ExpenseDonutChart
items={activeAnalytics}
totalSpend={totalMonthSpend}
totalSpend={activeAnalyticsTotal}
analyticsLabel={analyticsLabel}
hoveredId={hoveredAnalyticsId}
onHover={setHoveredAnalyticsId}
Expand Down Expand Up @@ -1156,7 +1158,7 @@ export default function ReportsPage() {
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '1.25rem', padding: '0.5rem 0' }}>
<ExpenseDonutChart
items={activeAnalytics}
totalSpend={totalMonthSpend}
totalSpend={activeAnalyticsTotal}
analyticsLabel={analyticsLabel}
hoveredId={hoveredAnalyticsId}
onHover={setHoveredAnalyticsId}
Expand Down
29 changes: 26 additions & 3 deletions src/components/TransactionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -233,13 +233,28 @@ export const TransactionModal = () => {
setAccounts(d.accounts);
if (!editingTransaction) {
if (preselectedAccountId) {
setAccountId(preselectedAccountId);
const preselectedAcc = d.accounts.find((a: any) => a.id === preselectedAccountId);
if (preselectedAcc && preselectedAcc.type === 'credit_card' && transactionModalType === 'transfer') {
const defaultBank = d.accounts.find((a: any) => a.id !== preselectedAcc.id && a.type !== 'credit_card') || d.accounts.find((a: any) => a.id !== preselectedAcc.id);
if (defaultBank) setAccountId(defaultBank.id);
setDestinationAccountId(preselectedAcc.id);
setSelectedTags(prev => prev.some(t => t.name.toLowerCase() === 'cc-repayment') ? prev : [...prev, { id: 'cc-repayment', name: 'cc-repayment', color: '#3B82F6' }]);
} else {
setAccountId(preselectedAccountId);
const otherAcc = d.accounts.find((a: any) => a.id !== preselectedAccountId);
if (otherAcc) setDestinationAccountId(otherAcc.id);
}
} else {
const defaultAcc = d.accounts.find((a: any) => Boolean(a.is_default)) || d.accounts[0];
if (defaultAcc) {
setAccountId(defaultAcc.id);
const otherAcc = d.accounts.find((a: any) => a.id !== defaultAcc.id);
if (otherAcc) setDestinationAccountId(otherAcc.id);
if (otherAcc) {
setDestinationAccountId(otherAcc.id);
if (otherAcc.type === 'credit_card' && transactionModalType === 'transfer') {
setSelectedTags(prev => prev.some(t => t.name.toLowerCase() === 'cc-repayment') ? prev : [...prev, { id: 'cc-repayment', name: 'cc-repayment', color: '#3B82F6' }]);
}
}
}
}
}
Expand Down Expand Up @@ -517,6 +532,7 @@ export const TransactionModal = () => {
amount: minorAmount,
date,
notes,
tags: selectedTags.map(t => t.name),
}),
});
const data = await res.json();
Expand Down Expand Up @@ -1006,7 +1022,14 @@ export const TransactionModal = () => {
<select
className="form-select"
value={destinationAccountId}
onChange={e => setDestinationAccountId(e.target.value)}
onChange={e => {
const newDestId = e.target.value;
setDestinationAccountId(newDestId);
const destAcc = accounts.find(a => a.id === newDestId);
if (destAcc && destAcc.type === 'credit_card') {
setSelectedTags(prev => prev.some(t => t.name.toLowerCase() === 'cc-repayment') ? prev : [...prev, { id: 'cc-repayment', name: 'cc-repayment', color: '#3B82F6' }]);
}
}}
>
{accounts.map(acc => {
const bal = acc.isLiability ? (acc.totalDebt ?? acc.current_balance) : acc.current_balance;
Expand Down
18 changes: 18 additions & 0 deletions src/lib/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,24 @@ export function initSchema(db: DatabaseSync) {
try {
db.exec(`UPDATE users SET owner_email = email WHERE owner_email IS NULL OR owner_email = ''`);
db.exec(`CREATE INDEX IF NOT EXISTS idx_users_owner_email ON users(owner_email)`);

// Ensure cc-repayment tag exists for users who have credit cards
db.exec(`
INSERT OR IGNORE INTO tags (id, user_id, name, color)
SELECT DISTINCT 'tag_' || a.user_id || '_ccrepayment', a.user_id, 'cc-repayment', '#3B82F6'
FROM accounts a
WHERE a.type = 'credit_card';
`);

// Backfill cc-repayment tag link on credit card transfers
db.exec(`
INSERT OR IGNORE INTO transaction_tags (transaction_id, tag_id)
SELECT t.id, tg.id
FROM transactions t
JOIN accounts a ON a.id = COALESCE(t.destination_account_id, t.transfer_peer_account_id)
JOIN tags tg ON tg.user_id = t.user_id AND LOWER(tg.name) = 'cc-repayment'
WHERE t.type = 'transfer' AND a.type = 'credit_card';
`);
} catch {
// Ignore
}
Expand Down
Loading
Loading