mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-05-19 11:28:49 +02:00
Compare commits
2 Commits
cab4d86b5c
...
45166f9d20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45166f9d20 | ||
|
|
aedf3aa3b0 |
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
27
frontend/src/PrivacyContext.tsx
Normal file
27
frontend/src/PrivacyContext.tsx
Normal 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);
|
||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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'
|
||||||
)}>
|
)}>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 && (
|
||||||
<> · <span className="font-mono text-foreground">{formatAmount(totalCRC, 'CRC')}</span></>
|
<> · <span data-sensitive className="font-mono text-foreground">{formatAmount(totalCRC, 'CRC')}</span></>
|
||||||
)}
|
)}
|
||||||
{totalUSD !== 0 && (
|
{totalUSD !== 0 && (
|
||||||
<> · <span className="font-mono text-foreground">{formatAmount(totalUSD, 'USD')}</span></>
|
<> · <span data-sensitive className="font-mono text-foreground">{formatAmount(totalUSD, 'USD')}</span></>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user