Compare commits

...

2 Commits

Author SHA1 Message Date
Carlos Escalante
45166f9d20 Add privacy mode toggle to blur sensitive financial amounts
All checks were successful
Deploy to VPS / deploy (push) Successful in 14s
Eye/EyeOff icon next to theme toggle. Persists in localStorage.
Applies CSS blur to all elements marked with data-sensitive attribute
across Dashboard, Budget, Pensions, Salarios, and Transactions pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:43:50 -06:00
Carlos Escalante
aedf3aa3b0 Fix plus sign showing before negative rendimientos values
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:24:58 -06:00
15 changed files with 101 additions and 49 deletions

View File

@@ -1,6 +1,7 @@
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { AuthProvider, useAuth } from './AuthContext'; import { AuthProvider, useAuth } from './AuthContext';
import { ThemeProvider } from './ThemeContext'; import { ThemeProvider } from './ThemeContext';
import { PrivacyProvider } from './PrivacyContext';
import Layout from './components/Layout'; import Layout from './components/Layout';
import Login from './pages/Login'; import Login from './pages/Login';
import Dashboard from './pages/Dashboard'; import Dashboard from './pages/Dashboard';
@@ -47,9 +48,11 @@ export default function App() {
return ( return (
<BrowserRouter> <BrowserRouter>
<ThemeProvider> <ThemeProvider>
<AuthProvider> <PrivacyProvider>
<AppRoutes /> <AuthProvider>
</AuthProvider> <AppRoutes />
</AuthProvider>
</PrivacyProvider>
</ThemeProvider> </ThemeProvider>
</BrowserRouter> </BrowserRouter>
); );

View File

@@ -0,0 +1,27 @@
import { createContext, useContext, useEffect, useState } from 'react';
const PrivacyContext = createContext<{
privacyMode: boolean;
togglePrivacy: () => void;
}>({ privacyMode: false, togglePrivacy: () => {} });
export function PrivacyProvider({ children }: { children: React.ReactNode }) {
const [privacyMode, setPrivacyMode] = useState<boolean>(() => {
return localStorage.getItem('privacyMode') === 'true';
});
useEffect(() => {
document.documentElement.classList.toggle('privacy', privacyMode);
localStorage.setItem('privacyMode', String(privacyMode));
}, [privacyMode]);
const togglePrivacy = () => setPrivacyMode((p) => !p);
return (
<PrivacyContext.Provider value={{ privacyMode, togglePrivacy }}>
{children}
</PrivacyContext.Provider>
);
}
export const usePrivacy = () => useContext(PrivacyContext);

View File

@@ -58,7 +58,7 @@ export default function DashboardSection({
{settings.label} {settings.label}
</span> </span>
{total != null && totalCurrency && ( {total != null && totalCurrency && (
<span className="text-sm font-bold font-mono text-foreground"> <span data-sensitive className="text-sm font-bold font-mono text-foreground">
{formatAmount(total, totalCurrency)} {formatAmount(total, totalCurrency)}
</span> </span>
)} )}

View File

@@ -10,10 +10,13 @@ import {
Menu, Menu,
Sun, Sun,
Moon, Moon,
Eye,
EyeOff,
} from 'lucide-react'; } from 'lucide-react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useAuth } from '../AuthContext'; import { useAuth } from '../AuthContext';
import { useTheme } from '../ThemeContext'; import { useTheme } from '../ThemeContext';
import { usePrivacy } from '../PrivacyContext';
import { subscribeToPush } from '../pushNotifications'; import { subscribeToPush } from '../pushNotifications';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { import {
@@ -37,6 +40,7 @@ const navItems = [
export default function Layout() { export default function Layout() {
const { logout } = useAuth(); const { logout } = useAuth();
const { theme, toggleTheme } = useTheme(); const { theme, toggleTheme } = useTheme();
const { privacyMode, togglePrivacy } = usePrivacy();
const navigate = useNavigate(); const navigate = useNavigate();
const [mobileOpen, setMobileOpen] = useState(false); const [mobileOpen, setMobileOpen] = useState(false);
@@ -86,6 +90,9 @@ export default function Layout() {
</nav> </nav>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Button variant="ghost" size="icon" onClick={togglePrivacy} title="Toggle privacy mode" aria-label="Toggle privacy mode">
{privacyMode ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</Button>
<Button variant="ghost" size="icon" onClick={toggleTheme} title="Toggle theme" aria-label="Toggle theme"> <Button variant="ghost" size="icon" onClick={toggleTheme} title="Toggle theme" aria-label="Toggle theme">
{theme === 'dark' ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />} {theme === 'dark' ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
</Button> </Button>

View File

@@ -128,6 +128,7 @@ export default function TransactionList({
</p> </p>
</div> </div>
<span <span
data-sensitive
className={cn( className={cn(
'font-mono text-sm font-medium shrink-0', 'font-mono text-sm font-medium shrink-0',
tx.transaction_type === 'DEVOLUCION' && 'text-primary' tx.transaction_type === 'DEVOLUCION' && 'text-primary'

View File

@@ -62,7 +62,7 @@ export default function MonthlyDetail({ detail, loading }: MonthlyDetailProps) {
{detail.income_items.map((item) => ( {detail.income_items.map((item) => (
<div key={item.id} className="flex items-center justify-between text-sm"> <div key={item.id} className="flex items-center justify-between text-sm">
<span className="truncate mr-2">{item.name}</span> <span className="truncate mr-2">{item.name}</span>
<span className="font-mono text-primary whitespace-nowrap"> <span data-sensitive className="font-mono text-primary whitespace-nowrap">
{formatAmount(item.amount, 'CRC')} {formatAmount(item.amount, 'CRC')}
</span> </span>
</div> </div>
@@ -70,7 +70,7 @@ export default function MonthlyDetail({ detail, loading }: MonthlyDetailProps) {
<Separator /> <Separator />
<div className="flex items-center justify-between font-semibold text-sm"> <div className="flex items-center justify-between font-semibold text-sm">
<span>Total</span> <span>Total</span>
<span className="font-mono text-primary"> <span data-sensitive className="font-mono text-primary">
{formatAmount(detail.total_projected_income, 'CRC')} {formatAmount(detail.total_projected_income, 'CRC')}
</span> </span>
</div> </div>
@@ -96,7 +96,7 @@ export default function MonthlyDetail({ detail, loading }: MonthlyDetailProps) {
</Badge> </Badge>
)} )}
</div> </div>
<div className="text-right whitespace-nowrap"> <div data-sensitive className="text-right whitespace-nowrap">
<span className="font-mono">{formatAmount(item.amount, 'CRC')}</span> <span className="font-mono">{formatAmount(item.amount, 'CRC')}</span>
{item.used_actual && item.projected_amount != null && ( {item.used_actual && item.projected_amount != null && (
<span className="block text-[10px] text-muted-foreground font-mono line-through"> <span className="block text-[10px] text-muted-foreground font-mono line-through">
@@ -112,7 +112,7 @@ export default function MonthlyDetail({ detail, loading }: MonthlyDetailProps) {
<Separator /> <Separator />
<div className="flex items-center justify-between font-semibold text-sm"> <div className="flex items-center justify-between font-semibold text-sm">
<span>Total Fijos</span> <span>Total Fijos</span>
<span className="font-mono"> <span data-sensitive className="font-mono">
{formatAmount(detail.total_projected_expenses, 'CRC')} {formatAmount(detail.total_projected_expenses, 'CRC')}
</span> </span>
</div> </div>
@@ -139,7 +139,7 @@ export default function MonthlyDetail({ detail, loading }: MonthlyDetailProps) {
<span>{meta.label}</span> <span>{meta.label}</span>
<span className="text-xs text-muted-foreground">({src.count})</span> <span className="text-xs text-muted-foreground">({src.count})</span>
</div> </div>
<span className="font-mono whitespace-nowrap"> <span data-sensitive className="font-mono whitespace-nowrap">
{formatAmount(src.net, 'CRC')} {formatAmount(src.net, 'CRC')}
</span> </span>
</div> </div>
@@ -153,7 +153,7 @@ export default function MonthlyDetail({ detail, loading }: MonthlyDetailProps) {
<Info className="w-3 h-3 text-muted-foreground" /> <Info className="w-3 h-3 text-muted-foreground" />
<span className="text-muted-foreground">No cubierto por fijos</span> <span className="text-muted-foreground">No cubierto por fijos</span>
</div> </div>
<span className="font-mono">{formatAmount(detail.uncovered_actual, 'CRC')}</span> <span data-sensitive className="font-mono">{formatAmount(detail.uncovered_actual, 'CRC')}</span>
</div> </div>
</> </>
)} )}
@@ -176,13 +176,13 @@ export default function MonthlyDetail({ detail, loading }: MonthlyDetailProps) {
{detail.savings_items.map((item) => ( {detail.savings_items.map((item) => (
<div key={item.id} className="flex items-center justify-between text-sm"> <div key={item.id} className="flex items-center justify-between text-sm">
<span>{item.name}</span> <span>{item.name}</span>
<span className="font-mono">{formatAmount(item.amount, 'CRC')}</span> <span data-sensitive className="font-mono">{formatAmount(item.amount, 'CRC')}</span>
</div> </div>
))} ))}
<Separator /> <Separator />
<div className="flex items-center justify-between font-semibold text-sm"> <div className="flex items-center justify-between font-semibold text-sm">
<span>Total Ahorro</span> <span>Total Ahorro</span>
<span className="font-mono"> <span data-sensitive className="font-mono">
{formatAmount(detail.total_projected_savings, 'CRC')} {formatAmount(detail.total_projected_savings, 'CRC')}
</span> </span>
</div> </div>
@@ -198,19 +198,19 @@ export default function MonthlyDetail({ detail, loading }: MonthlyDetailProps) {
<CardContent className="pt-6 space-y-3"> <CardContent className="pt-6 space-y-3">
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-sm">
<span>Total Ingresos</span> <span>Total Ingresos</span>
<span className="font-mono font-medium text-primary"> <span data-sensitive className="font-mono font-medium text-primary">
+{formatAmount(detail.total_projected_income, 'CRC')} +{formatAmount(detail.total_projected_income, 'CRC')}
</span> </span>
</div> </div>
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-sm">
<span>Gran Total Egresos</span> <span>Gran Total Egresos</span>
<span className="font-mono font-medium"> <span data-sensitive className="font-mono font-medium">
-{formatAmount(detail.gran_total_egresos, 'CRC')} -{formatAmount(detail.gran_total_egresos, 'CRC')}
</span> </span>
</div> </div>
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-sm">
<span>Ahorro</span> <span>Ahorro</span>
<span className="font-mono font-medium"> <span data-sensitive className="font-mono font-medium">
-{formatAmount(detail.total_projected_savings, 'CRC')} -{formatAmount(detail.total_projected_savings, 'CRC')}
</span> </span>
</div> </div>
@@ -218,6 +218,7 @@ export default function MonthlyDetail({ detail, loading }: MonthlyDetailProps) {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="font-semibold">Balance Neto</span> <span className="font-semibold">Balance Neto</span>
<span <span
data-sensitive
className={cn( className={cn(
'font-mono font-bold text-lg', 'font-mono font-bold text-lg',
detail.net_balance >= 0 ? 'text-primary' : 'text-destructive', detail.net_balance >= 0 ? 'text-primary' : 'text-destructive',

View File

@@ -106,7 +106,7 @@ export default function RecurringItemsManager({
<DataTableColumnHeader column={column} title="Monto" className="justify-end" /> <DataTableColumnHeader column={column} title="Monto" className="justify-end" />
), ),
cell: ({ row }) => ( cell: ({ row }) => (
<span className="font-mono text-sm"> <span data-sensitive className="font-mono text-sm">
{formatAmount(row.original.amount, row.original.currency)} {formatAmount(row.original.amount, row.original.currency)}
</span> </span>
), ),

View File

@@ -120,19 +120,19 @@ export default function YearlyOverview({
<span className="ml-1.5 inline-block w-1.5 h-1.5 rounded-full bg-primary" /> <span className="ml-1.5 inline-block w-1.5 h-1.5 rounded-full bg-primary" />
)} )}
</TableCell> </TableCell>
<TableCell className="text-right font-mono text-sm text-primary"> <TableCell data-sensitive className="text-right font-mono text-sm text-primary">
{formatAmount(m.projected_income, 'CRC')} {formatAmount(m.projected_income, 'CRC')}
</TableCell> </TableCell>
<TableCell className="text-right font-mono text-sm"> <TableCell data-sensitive className="text-right font-mono text-sm">
{formatAmount(m.projected_fixed_expenses, 'CRC')} {formatAmount(m.projected_fixed_expenses, 'CRC')}
</TableCell> </TableCell>
<TableCell className="text-right font-mono text-sm text-muted-foreground"> <TableCell data-sensitive className="text-right font-mono text-sm text-muted-foreground">
{formatAmount(m.uncovered_actual, 'CRC')} {formatAmount(m.uncovered_actual, 'CRC')}
</TableCell> </TableCell>
<TableCell className="text-right font-mono text-sm font-medium"> <TableCell data-sensitive className="text-right font-mono text-sm font-medium">
{formatAmount(m.gran_total_egresos, 'CRC')} {formatAmount(m.gran_total_egresos, 'CRC')}
</TableCell> </TableCell>
<TableCell className="text-right font-mono text-sm text-muted-foreground"> <TableCell data-sensitive className="text-right font-mono text-sm text-muted-foreground">
{formatAmount(m.projected_savings, 'CRC')} {formatAmount(m.projected_savings, 'CRC')}
</TableCell> </TableCell>
<TableCell <TableCell
@@ -145,13 +145,14 @@ export default function YearlyOverview({
> >
{isBeforeFreshStart {isBeforeFreshStart
? '—' ? '—'
: <> : <span data-sensitive>
{m.carryover_balance >= 0 ? '+' : ''} {m.carryover_balance >= 0 ? '+' : ''}
{formatAmount(m.carryover_balance, 'CRC')} {formatAmount(m.carryover_balance, 'CRC')}
</> </span>
} }
</TableCell> </TableCell>
<TableCell <TableCell
data-sensitive
className={cn( className={cn(
'text-right font-mono text-sm font-semibold', 'text-right font-mono text-sm font-semibold',
m.net_balance >= 0 ? 'text-primary' : 'text-destructive', m.net_balance >= 0 ? 'text-primary' : 'text-destructive',
@@ -193,8 +194,10 @@ export default function YearlyOverview({
{m.balance_overridden && ( {m.balance_overridden && (
<Pencil className="w-3 h-3 text-amber-500 shrink-0" /> <Pencil className="w-3 h-3 text-amber-500 shrink-0" />
)} )}
{m.cumulative_balance >= 0 ? '+' : ''} <span data-sensitive>
{formatAmount(m.cumulative_balance, 'CRC')} {m.cumulative_balance >= 0 ? '+' : ''}
{formatAmount(m.cumulative_balance, 'CRC')}
</span>
</span> </span>
)} )}
</TableCell> </TableCell>

View File

@@ -88,6 +88,7 @@ export function getTransactionColumns({
const tx = row.original; const tx = row.original;
return ( return (
<span <span
data-sensitive
className={cn( className={cn(
'font-mono font-medium', 'font-mono font-medium',
tx.transaction_type !== 'COMPRA' && 'text-primary', tx.transaction_type !== 'COMPRA' && 'text-primary',

View File

@@ -129,3 +129,10 @@
@apply font-sans; @apply font-sans;
} }
} }
/* Privacy mode: blur sensitive financial data */
.privacy [data-sensitive] {
filter: blur(8px);
user-select: none;
transition: filter 0.2s ease;
}

View File

@@ -151,7 +151,7 @@ export default function Budget() {
<Card> <Card>
<CardContent className="pt-4 pb-3 px-4"> <CardContent className="pt-4 pb-3 px-4">
<p className="text-xs text-muted-foreground">Ingresos Anuales</p> <p className="text-xs text-muted-foreground">Ingresos Anuales</p>
<p className="text-lg font-bold font-mono text-primary"> <p data-sensitive className="text-lg font-bold font-mono text-primary">
{formatAmount(projection.annual_income, 'CRC')} {formatAmount(projection.annual_income, 'CRC')}
</p> </p>
</CardContent> </CardContent>
@@ -159,7 +159,7 @@ export default function Budget() {
<Card> <Card>
<CardContent className="pt-4 pb-3 px-4"> <CardContent className="pt-4 pb-3 px-4">
<p className="text-xs text-muted-foreground">Egresos Anuales</p> <p className="text-xs text-muted-foreground">Egresos Anuales</p>
<p className="text-lg font-bold font-mono"> <p data-sensitive className="text-lg font-bold font-mono">
{formatAmount(projection.annual_expenses, 'CRC')} {formatAmount(projection.annual_expenses, 'CRC')}
</p> </p>
</CardContent> </CardContent>
@@ -167,7 +167,7 @@ export default function Budget() {
<Card> <Card>
<CardContent className="pt-4 pb-3 px-4"> <CardContent className="pt-4 pb-3 px-4">
<p className="text-xs text-muted-foreground">Ahorro Anual</p> <p className="text-xs text-muted-foreground">Ahorro Anual</p>
<p className="text-lg font-bold font-mono"> <p data-sensitive className="text-lg font-bold font-mono">
{formatAmount(projection.annual_savings, 'CRC')} {formatAmount(projection.annual_savings, 'CRC')}
</p> </p>
</CardContent> </CardContent>
@@ -176,6 +176,7 @@ export default function Budget() {
<CardContent className="pt-4 pb-3 px-4"> <CardContent className="pt-4 pb-3 px-4">
<p className="text-xs text-muted-foreground">Balance Neto Anual</p> <p className="text-xs text-muted-foreground">Balance Neto Anual</p>
<p <p
data-sensitive
className={cn( className={cn(
'text-lg font-bold font-mono', 'text-lg font-bold font-mono',
projection.annual_net >= 0 ? 'text-primary' : 'text-destructive', projection.annual_net >= 0 ? 'text-primary' : 'text-destructive',

View File

@@ -94,7 +94,7 @@ function AccountRow({
</div> </div>
) : ( ) : (
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className={cn('text-lg font-bold font-mono tracking-tight', isLiability && 'text-destructive')}> <span data-sensitive className={cn('text-lg font-bold font-mono tracking-tight', isLiability && 'text-destructive')}>
{formatAmount(account.balance, account.currency)} {formatAmount(account.balance, account.currency)}
</span> </span>
<button <button
@@ -106,7 +106,7 @@ function AccountRow({
<Pencil className="w-3.5 h-3.5" /> <Pencil className="w-3.5 h-3.5" />
</button> </button>
{isLiability && account.next_payment != null && ( {isLiability && account.next_payment != null && (
<span className="text-xs font-mono text-destructive/60 ml-2"> <span data-sensitive className="text-xs font-mono text-destructive/60 ml-2">
Next: {formatAmount(account.next_payment, account.currency)} Next: {formatAmount(account.next_payment, account.currency)}
</span> </span>
)} )}
@@ -215,10 +215,10 @@ export default function Dashboard() {
<Card> <Card>
<CardContent className="px-4 py-3"> <CardContent className="px-4 py-3">
<div className="flex items-center justify-between text-sm font-mono text-muted-foreground"> <div className="flex items-center justify-between text-sm font-mono text-muted-foreground">
<span>Net <span className="text-foreground font-bold">{netWorthBreakdown.net < 0 ? '-' : ''}{formatAmount(netWorthBreakdown.net, 'CRC')}</span></span> <span>Net <span data-sensitive className="text-foreground font-bold">{netWorthBreakdown.net < 0 ? '-' : ''}{formatAmount(netWorthBreakdown.net, 'CRC')}</span></span>
<div className="flex gap-4"> <div className="flex gap-4">
<span>Assets <span className="text-foreground">{formatAmount(netWorthBreakdown.assets, 'CRC')}</span></span> <span>Assets <span data-sensitive className="text-foreground">{formatAmount(netWorthBreakdown.assets, 'CRC')}</span></span>
<span>Liabilities <span className="text-foreground">{formatAmount(netWorthBreakdown.liabilities, 'CRC')}</span></span> <span>Liabilities <span data-sensitive className="text-foreground">{formatAmount(netWorthBreakdown.liabilities, 'CRC')}</span></span>
</div> </div>
</div> </div>
</CardContent> </CardContent>
@@ -262,8 +262,8 @@ export default function Dashboard() {
<CardContent className="p-4"> <CardContent className="p-4">
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">USD/CRC Exchange Rate</span> <span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">USD/CRC Exchange Rate</span>
<div className="flex items-baseline gap-3 mt-1"> <div className="flex items-baseline gap-3 mt-1">
<span className="text-lg font-bold font-mono">Buy: {exchangeRate.buy_rate.toFixed(2)}</span> <span data-sensitive className="text-lg font-bold font-mono">Buy: {exchangeRate.buy_rate.toFixed(2)}</span>
<span className="text-lg font-bold font-mono text-muted-foreground">Sell: {exchangeRate.sell_rate.toFixed(2)}</span> <span data-sensitive className="text-lg font-bold font-mono text-muted-foreground">Sell: {exchangeRate.sell_rate.toFixed(2)}</span>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
@@ -306,7 +306,7 @@ export default function Dashboard() {
</p> </p>
</div> </div>
</div> </div>
<span className={cn( <span data-sensitive className={cn(
'font-mono text-sm font-medium shrink-0 ml-4', 'font-mono text-sm font-medium shrink-0 ml-4',
tx.transaction_type !== 'COMPRA' && 'text-primary' tx.transaction_type !== 'COMPRA' && 'text-primary'
)}> )}>

View File

@@ -229,7 +229,7 @@ function ChartTooltipContent({
/> />
<span className="text-muted-foreground">{entry.name}</span> <span className="text-muted-foreground">{entry.name}</span>
</span> </span>
<span className="font-mono font-medium text-foreground">{formatCRC(entry.value)}</span> <span data-sensitive className="font-mono font-medium text-foreground">{formatCRC(entry.value)}</span>
</div> </div>
))} ))}
</div> </div>
@@ -440,7 +440,7 @@ export default function Pensions() {
<p className="text-xs text-muted-foreground uppercase tracking-wider font-medium"> <p className="text-xs text-muted-foreground uppercase tracking-wider font-medium">
Balance actual Balance actual
</p> </p>
<p className="text-xl font-bold font-mono mt-0.5 leading-tight"> <p data-sensitive className="text-xl font-bold font-mono mt-0.5 leading-tight">
{formatCRC(fund.startBalance)} {formatCRC(fund.startBalance)}
</p> </p>
{snap && ( {snap && (
@@ -456,19 +456,19 @@ export default function Pensions() {
<div className="space-y-1.5 text-xs"> <div className="space-y-1.5 text-xs">
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-muted-foreground">Aportes</span> <span className="text-muted-foreground">Aportes</span>
<span className="font-mono font-medium"> <span data-sensitive className="font-mono font-medium">
{formatCRC(snap.aportes)} {formatCRC(snap.aportes)}
</span> </span>
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-muted-foreground">Rendimientos</span> <span className="text-muted-foreground">Rendimientos</span>
<span className="font-mono font-medium text-emerald-500"> <span data-sensitive className="font-mono font-medium text-emerald-500">
{formatCRC(snap.rendimientos)} {formatCRC(snap.rendimientos)}
</span> </span>
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-muted-foreground">Comisión</span> <span className="text-muted-foreground">Comisión</span>
<span className="font-mono font-medium text-destructive"> <span data-sensitive className="font-mono font-medium text-destructive">
{formatCRC(snap.comision)} {formatCRC(snap.comision)}
</span> </span>
</div> </div>
@@ -477,7 +477,7 @@ export default function Pensions() {
<div className="space-y-1.5 text-xs"> <div className="space-y-1.5 text-xs">
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-muted-foreground">Aporte mensual</span> <span className="text-muted-foreground">Aporte mensual</span>
<span className="font-mono font-medium"> <span data-sensitive className="font-mono font-medium">
{formatCRC(fund.monthlyContribution)} {formatCRC(fund.monthlyContribution)}
</span> </span>
</div> </div>
@@ -606,8 +606,8 @@ export default function Pensions() {
{fund.isDividend ? `Dividendos ${fund.annualRate}%` : `${fund.annualRate}% anual`} {fund.isDividend ? `Dividendos ${fund.annualRate}%` : `${fund.annualRate}% anual`}
</p> </p>
)} )}
<p className="text-lg font-bold font-mono" style={{ color: fund.color }}> <p data-sensitive className="text-lg font-bold font-mono" style={{ color: fund.color }}>
+{formatCRC(earned)} {earned >= 0 ? '+' : ''}{formatCRC(earned)}
</p> </p>
<p className="text-xs text-muted-foreground">en rendimientos</p> <p className="text-xs text-muted-foreground">en rendimientos</p>
</CardContent> </CardContent>
@@ -686,6 +686,7 @@ export default function Pensions() {
Valor en {years} {years === 1 ? 'año' : 'años'} Valor en {years} {years === 1 ? 'año' : 'años'}
</p> </p>
<p <p
data-sensitive
className="text-lg font-bold font-mono leading-tight" className="text-lg font-bold font-mono leading-tight"
style={{ color: fund.color }} style={{ color: fund.color }}
> >
@@ -855,7 +856,7 @@ export default function Pensions() {
{' — '} {' — '}
{new Date(snap.period_end).toLocaleDateString('es-CR', { month: 'short', year: '2-digit' })} {new Date(snap.period_end).toLocaleDateString('es-CR', { month: 'short', year: '2-digit' })}
</span> </span>
<span className="font-mono font-medium">{formatCRC(snap.saldo_final)}</span> <span data-sensitive className="font-mono font-medium">{formatCRC(snap.saldo_final)}</span>
</div> </div>
))} ))}
</div> </div>

View File

@@ -64,7 +64,7 @@ export default function Salarios() {
accessorKey: 'amount', accessorKey: 'amount',
header: ({ column }) => <DataTableColumnHeader column={column} title="Monto" />, header: ({ column }) => <DataTableColumnHeader column={column} title="Monto" />,
cell: ({ row }) => ( cell: ({ row }) => (
<span className="font-mono font-bold text-primary"> <span data-sensitive className="font-mono font-bold text-primary">
+{formatAmount(row.original.amount, row.original.currency)} +{formatAmount(row.original.amount, row.original.currency)}
</span> </span>
), ),
@@ -126,7 +126,7 @@ export default function Salarios() {
<Banknote className="w-4 h-4" /> <Banknote className="w-4 h-4" />
<span className="text-xs font-medium uppercase tracking-wider">Total acumulado</span> <span className="text-xs font-medium uppercase tracking-wider">Total acumulado</span>
</div> </div>
<span className="text-2xl font-bold font-mono text-primary"> <span data-sensitive className="text-2xl font-bold font-mono text-primary">
{formatAmount(summary.total_amount, 'CRC')} {formatAmount(summary.total_amount, 'CRC')}
</span> </span>
</CardContent> </CardContent>

View File

@@ -72,10 +72,10 @@ export default function Transactions() {
<p className="text-sm text-muted-foreground mt-1"> <p className="text-sm text-muted-foreground mt-1">
{transactions.length} transactions {transactions.length} transactions
{totalCRC !== 0 && ( {totalCRC !== 0 && (
<> &middot; <span className="font-mono text-foreground">{formatAmount(totalCRC, 'CRC')}</span></> <> &middot; <span data-sensitive className="font-mono text-foreground">{formatAmount(totalCRC, 'CRC')}</span></>
)} )}
{totalUSD !== 0 && ( {totalUSD !== 0 && (
<> &middot; <span className="font-mono text-foreground">{formatAmount(totalUSD, 'USD')}</span></> <> &middot; <span data-sensitive className="font-mono text-foreground">{formatAmount(totalUSD, 'USD')}</span></>
)} )}
</p> </p>
</div> </div>