/home/devscfvi/web.devsquantum.com/js/app.js
// ============================================
// MAIN APPLICATION STATE & NAVIGATION
// ============================================
// Global State
const state = {
view: 'calculator',
tradeMode: getTradeMode(), // 'crypto' | 'stocks'
user: {
budget: 0,
favoriteCoins: ['BTC', 'ETH', 'SOL', 'SUI', 'XRP', 'ADA', 'LINK']
},
trades: [],
stats: null,
analytics: null,
goals: [],
journalEntries: [],
mistakes: [],
budgetTransactions: [],
loading: false
};
// State Management
function setState(updates) {
Object.assign(state, updates);
render();
}
function getState(key) {
return key ? state[key] : state;
}
// Navigation
function navigateTo(view) {
setState({ view });
updateNavigation(view);
// Load data for specific views
switch(view) {
case 'history':
loadTrades();
break;
case 'analytics':
loadAnalytics();
break;
case 'goals':
loadGoals();
break;
case 'journal':
loadJournal();
break;
case 'mistakes':
loadMistakes();
break;
case 'calendar':
loadCalendar();
break;
case 'import':
render();
break;
case 'more':
// More menu doesn't need data loading
break;
}
}
function updateNavigation(activeView) {
// Update bottom nav + sidebar buttons
document.querySelectorAll('.nav-btn, .sidebar-btn').forEach(btn => {
const view = btn.getAttribute('data-view');
btn.classList.toggle('active', view === activeView);
});
// Update mode toggle buttons
const mode = getTradeMode();
const cryptoBtn = document.getElementById('mode-crypto-btn');
const stocksBtn = document.getElementById('mode-stocks-btn');
if (cryptoBtn) cryptoBtn.classList.toggle('active', mode === 'crypto');
if (stocksBtn) stocksBtn.classList.toggle('active', mode === 'stocks');
// Update dynamic sidebar label for coins/watchlist
const coinsLabel = document.getElementById('sidebar-coins-label');
if (coinsLabel) coinsLabel.textContent = getTerms().assetListFull;
// Update persistent desktop header
updatePageHeader(activeView);
}
function updatePageHeader(view) {
const terms = getTerms();
const viewTitles = {
'calculator': 'Calculator',
'history': 'Trade History',
'analytics': 'Analytics',
'goals': 'Goals',
'journal': 'Journal',
'stats': 'Statistics',
'calendar': 'P&L Calendar',
'mistakes': 'Mistakes',
'budget': 'Budget',
'coins': terms.assetListFull,
'import': 'Import Trades',
'more': 'More'
};
const titleEl = document.getElementById('page-header-title');
const modeEl = document.getElementById('page-header-mode');
if (titleEl) titleEl.textContent = viewTitles[view] || view;
if (modeEl) {
const mode = getTradeMode();
modeEl.textContent = `${terms.modeIcon} ${terms.modeLabel}`;
modeEl.className = `text-xs px-2.5 py-1 rounded-full font-medium ${
mode === 'stocks'
? 'bg-blue-500/20 text-blue-300'
: 'bg-violet-500/20 text-violet-300'
}`;
}
}
// Switch trading mode and reload current view
function switchMode(mode) {
setTradeMode(mode);
state.tradeMode = mode;
// Reload mode-specific lists
loadFavoriteCoins();
// Reload data for current view
const view = state.view;
switch (view) {
case 'history': loadTrades(); break;
case 'analytics': loadAnalytics(); break;
case 'stats': loadStats().then(() => render()); break;
case 'calculator': render(); break;
default: render(); break;
}
updateNavigation(state.view);
showToast(`Switched to ${getTerms().modeLabel}`, 'info');
}
// Data Loading Functions
async function loadTrades() {
setState({ loading: true });
const mode = getTradeMode();
const result = await apiGet('getTrades', { mode });
if (result.success) {
setState({ trades: result.trades, loading: false });
} else {
showToast('Failed to load trades', 'error');
setState({ loading: false });
}
}
async function loadStats() {
const mode = getTradeMode();
const result = await apiGet('getStats', { mode });
if (result.success) {
setState({ stats: result.stats });
}
}
async function loadBudget() {
const result = await apiGet('getBudget');
if (result.success && result.budget) {
state.user.budget = parseFloat(result.budget);
}
// Also load transactions
const transResult = await apiGet('getBudgetTransactions');
console.log('Budget transactions API response:', transResult); // DEBUG
if (transResult.success) {
state.budgetTransactions = transResult.transactions || [];
console.log('Loaded transactions:', state.budgetTransactions); // DEBUG
} else {
console.error('Failed to load transactions:', transResult); // DEBUG
}
}
async function loadFavoriteCoins() {
const mode = getTradeMode();
if (mode === 'stocks') {
state.user.favoriteCoins = getStocksWatchlist();
} else {
const result = await apiGet('getFavoriteCoins');
if (result.success) {
state.user.favoriteCoins = result.coins;
}
}
}
async function loadAnalytics() {
setState({ loading: true });
const mode = getTradeMode();
// Load both analytics and trades data (needed for full analytics view)
const [analyticsResult, tradesResult, statsResult] = await Promise.all([
apiGet('getAdvancedAnalytics', { mode }),
apiGet('getTrades', { mode }),
apiGet('getStats', { mode })
]);
if (analyticsResult.success) {
setState({
analytics: analyticsResult.analytics,
trades: tradesResult.success ? tradesResult.trades : state.trades,
stats: statsResult.success ? statsResult.stats : state.stats,
loading: false
});
} else {
showToast('Failed to load analytics', 'error');
setState({ loading: false });
}
}
async function loadGoals() {
setState({ loading: true });
// Load both goals and stats for progress tracking
const [goalsResult, statsResult] = await Promise.all([
apiGet('getGoals'),
apiGet('getStats')
]);
if (goalsResult.success) {
setState({
goals: goalsResult.goals,
stats: statsResult.success ? statsResult.stats : state.stats,
loading: false
});
} else {
showToast('Failed to load goals', 'error');
setState({ loading: false });
}
}
async function loadJournal() {
setState({ loading: true });
const result = await apiGet('getJournalEntries');
if (result.success) {
setState({ journalEntries: result.entries, loading: false });
} else {
showToast('Failed to load journal', 'error');
setState({ loading: false });
}
}
async function loadCalendar() {
setState({ loading: true });
// Calendar uses trades data — must pass current mode (stocks vs crypto)
const mode = getTradeMode();
const result = await apiGet('getTrades', { mode });
if (result.success) {
setState({
trades: result.trades,
calendarDate: state.calendarDate || new Date(),
loading: false
});
} else {
showToast('Failed to load calendar', 'error');
setState({ loading: false });
}
}
async function loadMistakes() {
setState({ loading: true });
const result = await apiGet('getMistakes');
if (result.success) {
setState({
mistakes: {
mistakes: result.mistakes || [],
mistakeTypes: result.mistakeTypes || []
},
loading: false
});
} else {
showToast('Failed to load mistakes', 'error');
setState({ loading: false });
}
}
// Main Render Function
function render() {
const app = document.getElementById('app');
if (state.loading) {
showLoading();
return;
}
switch(state.view) {
case 'calculator':
app.innerHTML = renderCalculator();
break;
case 'history':
app.innerHTML = renderHistory();
break;
case 'analytics':
app.innerHTML = renderAnalytics();
setTimeout(() => initAnalyticsCharts(), 100);
break;
case 'goals':
app.innerHTML = renderGoals();
break;
case 'journal':
app.innerHTML = renderJournal();
break;
case 'calendar':
app.innerHTML = renderCalendar();
setTimeout(() => initCalendarCharts(), 100);
break;
case 'mistakes':
app.innerHTML = renderMistakes();
setTimeout(() => initMistakesCharts(), 100);
break;
case 'import':
app.innerHTML = renderImport();
break;
case 'more':
app.innerHTML = renderMore();
break;
case 'stats':
app.innerHTML = renderStats();
setTimeout(() => initStatsChart(), 100);
break;
case 'budget':
loadBudget().then(() => {
app.innerHTML = renderBudget();
});
break;
case 'coins':
app.innerHTML = renderCoins();
break;
case 'mistakes':
app.innerHTML = renderMistakes();
break;
default:
app.innerHTML = renderCalculator();
}
}
// More Menu
function renderMore() {
const terms = getTerms();
const mode = getTradeMode();
return `
<div class="min-h-screen p-4">
<div class="max-w-2xl mx-auto">
<div class="mb-6 pt-4">
<h1 class="text-2xl font-bold text-white mb-2">More</h1>
<p class="text-violet-300">Settings and additional features</p>
</div>
<!-- Mode Toggle (visible on mobile only) -->
<div class="md:hidden glass rounded-2xl p-4 border border-violet-500/20 mb-4">
<p class="text-sm font-semibold text-violet-300 mb-3">Trading Mode</p>
<div class="grid grid-cols-2 gap-2">
<button onclick="switchMode('crypto')"
class="mode-btn py-3 rounded-xl text-sm font-semibold ${mode === 'crypto' ? 'active' : ''}">
🪙 Crypto
</button>
<button onclick="switchMode('stocks')"
class="mode-btn py-3 rounded-xl text-sm font-semibold ${mode === 'stocks' ? 'active-stocks active' : ''}">
📈 Stocks
</button>
</div>
<p class="text-xs text-slate-500 mt-2 text-center">Currently: ${terms.modeIcon} ${terms.modeLabel}</p>
</div>
<div class="space-y-3">
<!-- Budget -->
<button onclick="navigateTo('budget')"
class="w-full glass rounded-2xl p-5 border border-violet-500/20 flex items-center justify-between active:scale-95 transition">
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-xl bg-yellow-500/20 flex items-center justify-center">
<svg class="w-6 h-6 text-yellow-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<div class="text-left">
<h3 class="font-semibold text-white">Budget Management</h3>
<p class="text-sm text-slate-400">Manage your trading capital</p>
</div>
</div>
<svg class="w-6 h-6 text-violet-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</button>
<!-- Statistics -->
<button onclick="navigateTo('stats')"
class="w-full glass rounded-2xl p-5 border border-violet-500/20 flex items-center justify-between active:scale-95 transition">
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-xl bg-blue-500/20 flex items-center justify-center">
<svg class="w-6 h-6 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
</svg>
</div>
<div class="text-left">
<h3 class="font-semibold text-white">Statistics</h3>
<p class="text-sm text-slate-400">View basic performance stats</p>
</div>
</div>
<svg class="w-6 h-6 text-violet-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</button>
<!-- Mistakes Library -->
<button onclick="navigateTo('mistakes')"
class="w-full glass rounded-2xl p-5 border border-violet-500/20 flex items-center justify-between active:scale-95 transition">
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-xl bg-red-500/20 flex items-center justify-center">
<svg class="w-6 h-6 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
</svg>
</div>
<div class="text-left">
<h3 class="font-semibold text-white">Mistakes Library</h3>
<p class="text-sm text-slate-400">Learn from your errors</p>
</div>
</div>
<svg class="w-6 h-6 text-violet-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</button>
<!-- P&L Calendar -->
<button onclick="navigateTo('calendar')"
class="w-full glass rounded-2xl p-5 border border-violet-500/20 flex items-center justify-between active:scale-95 transition">
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-xl bg-green-500/20 flex items-center justify-center">
<svg class="w-6 h-6 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</div>
<div class="text-left">
<h3 class="font-semibold text-white">P&L Calendar</h3>
<p class="text-sm text-slate-400">Visual monthly performance</p>
</div>
</div>
<svg class="w-6 h-6 text-violet-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</button>
<!-- Favorite Coins / Watchlist -->
<button onclick="navigateTo('coins')"
class="w-full glass rounded-2xl p-5 border border-violet-500/20 flex items-center justify-between active:scale-95 transition">
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-xl bg-violet-500/20 flex items-center justify-center">
<svg class="w-6 h-6 text-violet-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"></path>
</svg>
</div>
<div class="text-left">
<h3 class="font-semibold text-white">${terms.assetListFull}</h3>
<p class="text-sm text-slate-400">Manage your ${terms.assetList.toLowerCase()}</p>
</div>
</div>
<svg class="w-6 h-6 text-violet-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</button>
<!-- Export Data -->
<button onclick="exportAllData()"
class="w-full glass rounded-2xl p-5 border border-violet-500/20 flex items-center justify-between active:scale-95 transition">
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-xl bg-green-500/20 flex items-center justify-center">
<svg class="w-6 h-6 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
</div>
<div class="text-left">
<h3 class="font-semibold text-white">Export Data</h3>
<p class="text-sm text-slate-400">Download your trading data as CSV</p>
</div>
</div>
<svg class="w-6 h-6 text-violet-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</button>
<!-- Import Trades -->
<button onclick="navigateTo('import')"
class="w-full glass rounded-2xl p-5 border border-violet-500/20 flex items-center justify-between active:scale-95 transition">
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-xl bg-teal-500/20 flex items-center justify-center">
<svg class="w-6 h-6 text-teal-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path>
</svg>
</div>
<div class="text-left">
<h3 class="font-semibold text-white">Import Trades</h3>
<p class="text-sm text-slate-400">Import from CSV, Binance or Coinbase</p>
</div>
</div>
<svg class="w-6 h-6 text-violet-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</button>
</div>
<!-- App Info -->
<div class="mt-8 text-center text-slate-500 text-sm">
<p>Trading Journal v2.0</p>
<p class="mt-1">Track • Analyze • Improve</p>
</div>
</div>
</div>
`;
}
// Export all data
async function exportAllData() {
if (!state.trades || state.trades.length === 0) {
showToast('No trades to export', 'info');
return;
}
const csv = [
['Date', 'Coin', 'Category', 'Order Type', 'Position', 'Entry', 'SL%', 'TP%', 'SL Price', 'TP Price',
'Max Loss', 'Max Profit', 'Fees', 'R:R', 'Status', 'Emotion', 'Discipline', 'Mistake', 'Comment'],
...state.trades.map(t => [
t.created_at,
t.coin,
t.trade_category || '',
t.order_type || 'market',
t.position_size,
t.entry_price,
t.stop_loss_percent,
t.take_profit_percent,
t.sl_price,
t.tp_price,
t.loss_amount,
t.profit_amount,
t.fees || 0,
t.reward_risk_ratio,
t.status || 'pending',
t.emotion || '',
t.discipline_score || '',
t.mistake_type || '',
(t.comment || '').replace(/,/g, ';').replace(/\n/g, ' ')
])
];
exportToCSV(csv, 'trading-journal-complete');
}
// Initialize App
async function initApp() {
showLoading();
// Initialize mode from localStorage
state.tradeMode = getTradeMode();
// Load initial data
await Promise.all([
loadBudget(),
loadFavoriteCoins(),
loadStats()
]);
// Set initial view
setState({ view: 'calculator', loading: false });
updateNavigation('calculator');
}
// Stats View
function renderStats() {
const s = state.stats || {};
const budget = state.user.budget || 0;
const currentBalance = budget + parseFloat(s.total_pnl || 0);
const budgetChange = budget > 0 ? ((currentBalance - budget) / budget * 100).toFixed(2) : 0;
return `
<div class="min-h-screen p-4">
<div class="max-w-2xl mx-auto">
<div class="flex items-center justify-between mb-6 pt-4">
<h1 class="text-2xl font-bold text-white">Statistics</h1>
<button onclick="navigateTo('more')"
class="bg-violet-600 hover:bg-violet-700 text-white px-4 py-3 rounded-xl shadow-lg active:scale-95 transition font-medium">
Back
</button>
</div>
${!s.total_trades ? `
<div class="glass rounded-3xl p-12 text-center border border-violet-500/20">
<p class="text-lg text-slate-400">No trades yet</p>
</div>
` : `
<div class="space-y-4">
${budget > 0 ? `
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<h3 class="text-lg font-semibold text-white mb-4">Portfolio</h3>
<div class="grid grid-cols-2 gap-4">
<div>
<p class="text-sm text-slate-400 mb-1">Starting Budget</p>
<p class="text-2xl font-bold text-white">${formatCurrency(budget)}</p>
</div>
<div>
<p class="text-sm text-slate-400 mb-1">Current Balance</p>
<p class="text-2xl font-bold ${currentBalance >= budget ? 'text-green-400' : 'text-red-400'}">
${formatCurrency(currentBalance)}
</p>
<p class="text-sm ${parseFloat(budgetChange) >= 0 ? 'text-green-400' : 'text-red-400'}">
${formatPercent(budgetChange)}
</p>
</div>
</div>
</div>
` : ''}
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<h3 class="text-lg font-semibold text-white mb-4">Portfolio Growth</h3>
<div style="height: 250px;">
<canvas id="pnlChart"></canvas>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="glass rounded-2xl p-5 border border-violet-500/20">
<p class="text-sm text-violet-300 mb-1">Total Trades</p>
<p class="text-3xl font-bold text-white">${s.total_trades || 0}</p>
</div>
<div class="glass rounded-2xl p-5 border border-yellow-500/20">
<p class="text-sm text-yellow-300 mb-1">Pending</p>
<p class="text-3xl font-bold text-white">${s.pending_trades || 0}</p>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="glass rounded-2xl p-5 border border-green-500/20">
<p class="text-sm text-green-300 mb-1">Wins</p>
<p class="text-3xl font-bold text-green-400">${s.wins || 0}</p>
<p class="text-xs text-slate-400 mt-1">${s.win_rate || 0}% win rate</p>
</div>
<div class="glass rounded-2xl p-5 border border-red-500/20">
<p class="text-sm text-red-300 mb-1">Losses</p>
<p class="text-3xl font-bold text-red-400">${s.losses || 0}</p>
<p class="text-xs text-slate-400 mt-1">${(100 - (s.win_rate || 0)).toFixed(2)}% loss rate</p>
</div>
</div>
<div class="glass rounded-2xl p-5 border border-violet-500/20">
<p class="text-sm text-violet-300 mb-1">Total P&L</p>
<p class="text-4xl font-bold ${parseFloat(s.total_pnl || 0) >= 0 ? 'text-green-400' : 'text-red-400'}">
${parseFloat(s.total_pnl || 0) >= 0 ? '+' : ''}${formatCurrency(s.total_pnl || 0)}
</p>
</div>
<div class="glass rounded-2xl p-5 border border-blue-500/20">
<p class="text-sm text-blue-300 mb-3">Average Risk/Reward</p>
<p class="text-3xl font-bold text-white">1:${parseFloat(s.avg_rr || 0).toFixed(2)}</p>
</div>
</div>
`}
</div>
</div>
`;
}
function initStatsChart() {
const canvas = document.getElementById('pnlChart');
if (!canvas || !state.trades) return;
const ctx = canvas.getContext('2d');
if (window.pnlChart && typeof window.pnlChart.destroy === 'function') {
window.pnlChart.destroy();
}
const trades = state.trades.filter(t => t.status === 'win' || t.status === 'loss');
if (trades.length === 0) return;
let runningTotal = parseFloat(state.user.budget) || 0;
const chartData = [runningTotal];
const labels = ['Start'];
const sortedTrades = [...trades].sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
sortedTrades.forEach((trade, i) => {
if (trade.status === 'win') {
runningTotal += parseFloat(trade.profit_amount);
} else {
runningTotal -= parseFloat(trade.loss_amount);
}
chartData.push(runningTotal);
labels.push(`#${i + 1}`);
});
window.pnlChart = new Chart(ctx, {
type: 'line',
data: {
labels,
datasets: [{
label: 'Portfolio Value',
data: chartData,
borderColor: '#8b5cf6',
backgroundColor: 'rgba(139, 92, 246, 0.1)',
tension: 0.4,
fill: true,
pointBackgroundColor: '#8b5cf6',
pointBorderColor: '#fff',
pointBorderWidth: 2,
pointRadius: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: 12,
displayColors: false,
callbacks: {
label: (context) => '$' + context.parsed.y.toFixed(2)
}
}
},
scales: {
y: {
beginAtZero: false,
grid: { color: 'rgba(139, 92, 246, 0.1)' },
ticks: {
color: '#a78bfa',
callback: (value) => '$' + value.toFixed(0)
}
},
x: {
grid: { display: false },
ticks: { color: '#a78bfa' }
}
}
}
});
}
// Budget View
function renderBudget() {
const transactions = state.budgetTransactions || [];
const hasTransactions = transactions.length > 0;
console.log('Rendering budget with transactions:', transactions); // DEBUG
console.log('Has transactions:', hasTransactions); // DEBUG
return `
<div class="min-h-screen p-4">
<div class="max-w-lg mx-auto">
<div class="flex items-center justify-between mb-6 pt-4">
<h1 class="text-2xl font-bold text-white">Budget Settings</h1>
<button onclick="navigateTo('more')"
class="bg-violet-600 hover:bg-violet-700 text-white px-4 py-3 rounded-xl shadow-lg active:scale-95 transition font-medium">
Back
</button>
</div>
<div class="space-y-4">
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<label class="block text-sm font-medium text-violet-200 mb-2">Total Budget ($)</label>
<input type="number"
id="budget-input"
inputmode="decimal"
value="${state.user.budget || 0}"
oninput="state.user.budget = this.value;"
class="w-full px-4 py-3.5 bg-slate-900/50 border border-violet-500/30 rounded-xl text-white text-lg placeholder-slate-500 focus:border-violet-400 focus:ring-2 focus:ring-violet-400/20 mb-4"
placeholder="10000">
<button onclick="saveBudget()"
class="w-full gradient-bg text-white py-4 rounded-2xl font-bold text-lg shadow-lg active:scale-95 transition">
Save Budget
</button>
</div>
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<h3 class="text-lg font-semibold text-white mb-4">Quick Actions</h3>
<div class="grid grid-cols-2 gap-4">
<button onclick="addFunds()"
class="bg-green-600 hover:bg-green-700 text-white py-4 rounded-xl font-semibold shadow-lg active:scale-95 transition flex items-center justify-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg>
Add Funds
</button>
<button onclick="withdrawFunds()"
class="bg-red-600 hover:bg-red-700 text-white py-4 rounded-xl font-semibold shadow-lg active:scale-95 transition flex items-center justify-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"></path>
</svg>
Withdraw
</button>
</div>
</div>
${state.user.budget > 0 ? `
<div class="glass rounded-2xl p-5 border border-blue-500/20 text-center">
<p class="text-sm text-blue-300 mb-2">Current Budget</p>
<p class="text-4xl font-bold text-white">${formatCurrency(state.user.budget)}</p>
</div>
` : ''}
<!-- Transaction History -->
${hasTransactions ? `
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<h3 class="text-lg font-semibold text-white mb-4">Transaction History</h3>
<div class="space-y-3 max-h-96 overflow-y-auto">
${transactions.map(t => `
<div class="bg-slate-900/50 rounded-xl p-4 border border-slate-700">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full ${t.transaction_type === 'deposit' ? 'bg-green-500/20' : 'bg-red-500/20'} flex items-center justify-center">
${t.transaction_type === 'deposit' ?
'<svg class="w-5 h-5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>' :
'<svg class="w-5 h-5 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"></path></svg>'
}
</div>
<div>
<p class="font-semibold text-white">${t.description || (t.transaction_type === 'deposit' ? 'Deposit' : 'Withdrawal')}</p>
<p class="text-xs text-slate-400">${formatDateTime(t.created_at)}</p>
</div>
</div>
<div class="text-right">
<p class="text-lg font-bold ${t.transaction_type === 'deposit' ? 'text-green-400' : 'text-red-400'}">
${t.transaction_type === 'deposit' ? '+' : '-'}${formatCurrency(t.amount)}
</p>
</div>
</div>
</div>
`).join('')}
</div>
</div>
` : `
<div class="glass rounded-2xl p-6 border border-slate-700 text-center">
<p class="text-slate-400">No transactions yet</p>
<p class="text-sm text-slate-500 mt-1">Use Add Funds or Withdraw to track your capital changes</p>
</div>
`}
</div>
</div>
</div>
`;
}
async function saveBudget() {
const budget = state.user.budget;
const result = await apiPost('saveBudget', { budget });
if (result.success) {
showToast('Budget saved!', 'success');
} else {
showToast('Failed to save budget', 'error');
}
}
async function addFunds() {
const amount = promptInput('Enter amount to add:');
if (!amount || isNaN(amount) || parseFloat(amount) <= 0) return;
const description = promptInput('Description (optional):', 'Deposit');
// Record transaction
const result = await apiPost('addBudgetTransaction', {
type: 'deposit',
amount: parseFloat(amount),
description: description || 'Deposit'
});
if (result.success) {
// Update local state
state.user.budget = (parseFloat(state.user.budget) || 0) + parseFloat(amount);
showToast(`Added ${formatCurrency(amount)}!`, 'success');
navigateTo('budget'); // Reload page to show transaction
} else {
showToast('Failed to add funds', 'error');
}
}
async function withdrawFunds() {
const amount = promptInput('Enter amount to withdraw:');
if (!amount || isNaN(amount) || parseFloat(amount) <= 0) return;
const newBudget = (parseFloat(state.user.budget) || 0) - parseFloat(amount);
if (newBudget < 0) {
showToast('Insufficient balance', 'error');
return;
}
const description = promptInput('Description (optional):', 'Withdrawal');
// Record transaction
const result = await apiPost('addBudgetTransaction', {
type: 'withdrawal',
amount: parseFloat(amount),
description: description || 'Withdrawal'
});
if (result.success) {
// Update local state
state.user.budget = newBudget;
showToast(`Withdrew ${formatCurrency(amount)}!`, 'success');
navigateTo('budget'); // Reload page to show transaction
} else {
showToast('Failed to withdraw', 'error');
}
}
// Coins / Watchlist View (mode-aware)
function renderCoins() {
const terms = getTerms();
return `
<div class="min-h-screen p-4">
<div class="max-w-lg mx-auto">
<div class="flex items-center justify-between mb-6 pt-4">
<div>
<h1 class="text-2xl font-bold text-white">${terms.assetListFull}</h1>
<p class="text-sm text-violet-300">Manage your ${terms.assetList.toLowerCase()}</p>
</div>
<button onclick="navigateTo('more')"
class="bg-violet-600 hover:bg-violet-700 text-white px-4 py-3 rounded-xl shadow-lg active:scale-95 transition font-medium">
Back
</button>
</div>
<div class="space-y-4">
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-white">Your ${terms.assets}</h3>
<button onclick="addCustomCoin()"
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg active:scale-95 transition flex items-center gap-2 text-sm font-semibold">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg>
Add ${terms.asset}
</button>
</div>
<div class="space-y-2">
${state.user.favoriteCoins.map(coin => `
<div class="flex items-center justify-between bg-slate-900/50 rounded-xl p-4 border border-violet-500/20">
<span class="text-lg font-semibold text-white">${coin}</span>
<button onclick="removeCoin('${coin}')"
class="text-red-400 hover:text-red-300 p-2 active:scale-95 transition">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
</svg>
</button>
</div>
`).join('')}
</div>
</div>
</div>
</div>
</div>
`;
}
function addCustomCoin() {
const terms = getTerms();
const coin = promptInput(`Enter ${terms.asset.toLowerCase()} symbol (e.g., ${terms.placeholder}):`);
if (!coin) return;
const coinUpper = coin.trim().toUpperCase();
if (state.user.favoriteCoins.includes(coinUpper)) {
showToast(`${terms.asset} already in ${terms.assetList.toLowerCase()}`, 'error');
return;
}
state.user.favoriteCoins.push(coinUpper);
saveFavoriteCoins();
}
function removeCoin(coin) {
const terms = getTerms();
if (!confirmAction(`Remove ${coin} from ${terms.assetList.toLowerCase()}?`)) return;
state.user.favoriteCoins = state.user.favoriteCoins.filter(c => c !== coin);
saveFavoriteCoins();
}
async function saveFavoriteCoins() {
const terms = getTerms();
const mode = getTradeMode();
if (mode === 'stocks') {
saveStocksWatchlist(state.user.favoriteCoins);
showToast(`${terms.assetList} updated!`, 'success');
render();
} else {
const result = await apiPost('saveFavoriteCoins', { coins: state.user.favoriteCoins });
if (result.success) {
showToast(`${terms.assets} updated!`, 'success');
render();
} else {
showToast(`Failed to save ${terms.assets.toLowerCase()}`, 'error');
}
}
}
// Make functions global
window.navigateTo = navigateTo;
window.setState = setState;
window.getState = getState;
window.initApp = initApp;
window.exportAllData = exportAllData;
window.renderStats = renderStats;
window.initStatsChart = initStatsChart;
window.renderBudget = renderBudget;
window.saveBudget = saveBudget;
window.addFunds = addFunds;
window.withdrawFunds = withdrawFunds;
window.renderCoins = renderCoins;
window.addCustomCoin = addCustomCoin;
window.removeCoin = removeCoin;
window.switchMode = switchMode;
window.loadFavoriteCoins = loadFavoriteCoins;
window.saveFavoriteCoins = saveFavoriteCoins;