191 lines
10 KiB
HTML
191 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Money Trace · Dashboard</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
|
|
<link rel="stylesheet" href="styles.css" />
|
|
<link rel="stylesheet" href="dashboard.css" />
|
|
</head>
|
|
<body>
|
|
<div id="loading">reconstructing money flow…</div>
|
|
|
|
<div id="app" style="display:none; flex-direction:column; height:100vh;">
|
|
<header class="bar">
|
|
<div class="brand">
|
|
<div class="mark">money<b>·</b>trace</div>
|
|
<div class="sub">725 txns · 12 banks · 10 months</div>
|
|
</div>
|
|
<div class="stats">
|
|
<a href="money-trace.html" style="text-decoration:none; align-self:center; margin-right:6px;">
|
|
<div class="seg"><button style="color:var(--ink-mute)">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;"><path d="M3 5h6c4 0 4 7 9 7h3M3 12h4c5 0 4 7 11 7h3M3 19h7"/></svg>
|
|
Flow view
|
|
</button></div>
|
|
</a>
|
|
</div>
|
|
</header>
|
|
|
|
<nav class="tabs" id="tabs">
|
|
<button class="tab on" data-tab="overview">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></svg>
|
|
Overview
|
|
</button>
|
|
<button class="tab" data-tab="spending">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18"/><path d="M7 14l3-4 3 3 5-7"/></svg>
|
|
Where it goes
|
|
</button>
|
|
<button class="tab" data-tab="income">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
|
|
Where it comes from
|
|
</button>
|
|
<button class="tab" data-tab="people">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="8" r="3"/><path d="M3 20c0-3.3 2.7-6 6-6s6 2.7 6 6"/><path d="M16 5.5a3 3 0 0 1 0 5.5M21 20c0-2.5-1.5-4.6-3.7-5.5"/></svg>
|
|
People
|
|
</button>
|
|
<button class="tab" data-tab="cards">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20"/></svg>
|
|
Cards & credit
|
|
</button>
|
|
<button class="tab" data-tab="cycles">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3-6.7"/><path d="M21 3v5h-5"/></svg>
|
|
The cycles
|
|
</button>
|
|
<button class="tab" data-tab="balances">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18"/><path d="M6 14l4-5 3 3 5-7"/></svg>
|
|
Balances
|
|
</button>
|
|
<button class="tab" data-tab="rhythm">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="17" rx="2"/><path d="M3 9h18M8 2v4M16 2v4"/></svg>
|
|
Rhythm
|
|
</button>
|
|
<button class="tab" data-tab="platforms">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="6" cy="6" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="12" cy="18" r="3"/><path d="M6 9v3a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V9M12 14v1"/></svg>
|
|
Platforms
|
|
</button>
|
|
<button class="tab" data-tab="banks">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 21h18M4 10h16M5 10V7l7-4 7 4v3M8 10v8M12 10v8M16 10v8"/></svg>
|
|
Banks
|
|
</button>
|
|
<button class="tab" data-tab="ledger">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16v16H4z"/><path d="M4 9h16M4 14h16M9 4v16"/></svg>
|
|
Ledger
|
|
</button>
|
|
<button class="tab" data-tab="quality">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12l2 2 4-4"/><path d="M12 3l8 4v5c0 4.4-3.1 8.3-8 9-4.9-.7-8-4.6-8-9V7z"/></svg>
|
|
Data quality
|
|
</button>
|
|
</nav>
|
|
|
|
<div class="view dash-body" style="flex:1;">
|
|
<div class="panel-wrap">
|
|
<div class="panel on" id="panel-overview">
|
|
<div class="panel-head"><h2>The big picture</h2><p id="ov-intro">Across these statements, real money came in and went out — but the ledger logged far more movement than that. Here's the real story versus the noise.</p></div>
|
|
<div id="host-overview"></div>
|
|
</div>
|
|
<div class="panel" id="panel-spending">
|
|
<div class="panel-head"><h2>What am I spending my money on, at the end of the day?</h2><p>Every purchase and fee, grouped by what it actually was. Merchant names aren't in the raw statements, so categories are inferred from transaction descriptions.</p></div>
|
|
<div id="host-spending"></div>
|
|
</div>
|
|
<div class="panel" id="panel-income">
|
|
<div class="panel-head"><h2>Where does the money come from?</h2><p>Real income only — salary, business deposits, and transfers in from other people. Internal moves are excluded.</p></div>
|
|
<div id="host-income"></div>
|
|
</div>
|
|
<div class="panel" id="panel-people">
|
|
<div class="panel-head"><h2>Who do you exchange money with?</h2><p>Every named payer and person, with money in versus money out and your net position with each. Counterparties are resolved from transaction descriptions.</p></div>
|
|
<div id="host-people"></div>
|
|
</div>
|
|
<div class="panel" id="panel-cards">
|
|
<div class="panel-head"><h2>What is credit costing you?</h2><p>Fees, interest, and taxes charged on your cards and credit lines — plus what you paid back to your own cards.</p></div>
|
|
<div id="host-cards"></div>
|
|
</div>
|
|
<div class="panel" id="panel-cycles">
|
|
<div class="panel-head"><h2>Money that just moves in circles</h2><p>The bulk of your statement volume is internal: paying your own cards, sweeping credit lines, and shuffling cash between accounts. None of it is real income or spending.</p></div>
|
|
<div id="host-cycles"></div>
|
|
</div>
|
|
<div class="panel" id="panel-balances">
|
|
<div class="panel-head"><h2>How much did you actually have?</h2><p>Running account balances over time, wherever the statements reported them. This turns the flow of money into the level of money you held.</p></div>
|
|
<div id="host-balances"></div>
|
|
</div>
|
|
<div class="panel" id="panel-rhythm">
|
|
<div class="panel-head"><h2>When do you spend?</h2><p>The cadence of your spending — by day of the month and by day of the week. Each cell and bar is real purchase activity.</p></div>
|
|
<div id="host-rhythm"></div>
|
|
</div>
|
|
<div class="panel" id="panel-platforms">
|
|
<div class="panel-head"><h2>Which payment rails carry your money?</h2><p>The processors and wallets your money flows through — MercadoPago, MACH, Servipag, PayU and more — detected from transaction descriptions and platform tags.</p></div>
|
|
<div id="host-platforms"></div>
|
|
</div>
|
|
<div class="panel" id="panel-banks">
|
|
<div class="panel-head"><h2>12 banks, side by side</h2><p>How activity splits across the banks you reconstructed — and which ones hold real money flow versus pure internal cycling.</p></div>
|
|
<div id="host-banks"></div>
|
|
</div>
|
|
<div class="panel" id="panel-ledger">
|
|
<div class="panel-head"><h2>The full ledger</h2><p>All 725 reconstructed transactions. Search, filter by type or bank, and sort any column.</p></div>
|
|
<div id="host-ledger"></div>
|
|
</div>
|
|
<div class="panel" id="panel-quality">
|
|
<div class="panel-head"><h2>How complete is this picture?</h2><p>What was reconstructed cleanly versus inferred or missing — statement coverage, balance availability, and categorization confidence.</p></div>
|
|
<div id="host-quality"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="tip"></div>
|
|
<div id="tweaks-root"></div>
|
|
|
|
<script src="engine.js"></script>
|
|
<script src="charts.js"></script>
|
|
<script>
|
|
/* point the renderers at the host divs inside each panel */
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
document.querySelectorAll('.panel').forEach(p => {
|
|
const tab = p.id.replace('panel-', '');
|
|
const host = document.getElementById('host-' + tab);
|
|
if (host) p._host = host;
|
|
});
|
|
});
|
|
</script>
|
|
<script src="dashboard.js"></script>
|
|
<script src="dashboard2.js"></script>
|
|
|
|
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
|
|
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
|
|
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
|
<script type="text/babel" src="tweaks-panel.jsx"></script>
|
|
<script type="text/babel">
|
|
const TWEAK_DEFAULTS = { palette: ['#2ee6a6', '#ff5f73', '#b98cff', '#ffc24b'], bg: '#090b0f' };
|
|
function applyTweaks(t) {
|
|
const r = document.documentElement.style;
|
|
r.setProperty('--c-income', t.palette[0]); r.setProperty('--c-spend', t.palette[1]);
|
|
r.setProperty('--c-person', t.palette[2]); r.setProperty('--c-fee', t.palette[3]);
|
|
r.setProperty('--bg', t.bg);
|
|
if (window.MTapply) window.MTapply();
|
|
}
|
|
function TweakApp() {
|
|
const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
|
|
React.useEffect(() => { applyTweaks(t); }, [t]);
|
|
return (
|
|
<TweaksPanel title="Tweaks">
|
|
<TweakSection label="Palette" hint="income · spending · people · fees" />
|
|
<TweakColor label="Colors" value={t.palette} onChange={v => setTweak('palette', v)}
|
|
options={[
|
|
['#2ee6a6', '#ff5f73', '#b98cff', '#ffc24b'],
|
|
['#34d399', '#fb7185', '#a78bfa', '#fbbf24'],
|
|
['#00e5a0', '#ff4d6d', '#8c9eff', '#ffd45a'],
|
|
['#7CFFB2', '#FF8FA3', '#C9A6FF', '#FFE08A']
|
|
]} />
|
|
<TweakSection label="Background" />
|
|
<TweakColor label="Canvas" value={t.bg} onChange={v => setTweak('bg', v)}
|
|
options={['#090b0f', '#0c0a0e', '#0a0d0c', '#0d0d0d']} />
|
|
</TweaksPanel>
|
|
);
|
|
}
|
|
ReactDOM.createRoot(document.getElementById('tweaks-root')).render(<TweakApp />);
|
|
</script>
|
|
</body>
|
|
</html>
|