mirror of
https://github.com/chrisnov-it/quantumbotx.git
synced 2026-07-28 11:17:44 +00:00
518 lines
18 KiB
JavaScript
518 lines
18 KiB
JavaScript
// --- ENHANCED DASHBOARD WITH AI MENTOR INTEGRATION ---
|
|
|
|
// Formatter untuk nilai USD
|
|
const formatter = new Intl.NumberFormat('en-US', {
|
|
style: 'currency',
|
|
currency: 'USD'
|
|
});
|
|
|
|
// Global chart variables
|
|
let priceChart, rsiChart;
|
|
|
|
// Initialize dashboard when DOM is ready
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Check if Chart.js is loaded before initializing charts
|
|
if (typeof Chart !== 'undefined') {
|
|
initializeCharts();
|
|
}
|
|
|
|
// Load all dashboard data
|
|
loadDashboardData();
|
|
loadAIMentorSummary();
|
|
loadRecentActivities();
|
|
|
|
// Legacy compatibility - call existing functions if charts exist
|
|
const priceChartEl = document.getElementById('priceChart');
|
|
const rsiChartEl = document.getElementById('rsiChart');
|
|
|
|
if (priceChartEl && typeof Chart !== 'undefined') {
|
|
updatePriceChart();
|
|
}
|
|
if (rsiChartEl && typeof Chart !== 'undefined') {
|
|
updateRsiChart();
|
|
}
|
|
|
|
// Auto-refresh every 30 seconds
|
|
setInterval(() => {
|
|
refreshDashboardData();
|
|
}, 30000);
|
|
|
|
// Legacy intervals for compatibility
|
|
setInterval(updateDashboardStats, 10000);
|
|
setInterval(fetchAllBots, 5000);
|
|
});
|
|
|
|
// Enhanced chart initialization
|
|
function initializeCharts() {
|
|
if (typeof Chart === 'undefined') {
|
|
console.warn('Chart.js not loaded, skipping chart initialization');
|
|
return;
|
|
}
|
|
|
|
// Initialize Price Chart
|
|
const priceCtx = document.getElementById('priceChart');
|
|
if (priceCtx && !priceChart) {
|
|
priceChart = new Chart(priceCtx.getContext('2d'), {
|
|
type: 'line',
|
|
data: {
|
|
labels: [],
|
|
datasets: [{
|
|
label: 'EUR/USD',
|
|
data: [],
|
|
borderColor: '#3b82f6',
|
|
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
borderWidth: 2,
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: { legend: { display: false } },
|
|
scales: {
|
|
y: { beginAtZero: false, grid: { color: '#f3f4f6' } },
|
|
x: { grid: { color: '#f3f4f6' } }
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Initialize RSI Chart
|
|
const rsiCtx = document.getElementById('rsiChart');
|
|
if (rsiCtx && !rsiChart) {
|
|
rsiChart = new Chart(rsiCtx.getContext('2d'), {
|
|
type: 'line',
|
|
data: {
|
|
labels: [],
|
|
datasets: [{
|
|
label: 'RSI',
|
|
data: [],
|
|
borderColor: '#8b5cf6',
|
|
backgroundColor: 'rgba(139, 92, 246, 0.1)',
|
|
borderWidth: 2,
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: { legend: { display: false } },
|
|
scales: {
|
|
y: {
|
|
min: 0,
|
|
max: 100,
|
|
grid: { color: '#f3f4f6' },
|
|
ticks: {
|
|
callback: function(value) {
|
|
if (value === 70) return '70 (Overbought)';
|
|
if (value === 30) return '30 (Oversold)';
|
|
return value;
|
|
}
|
|
}
|
|
},
|
|
x: { grid: { color: '#f3f4f6' } }
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Enhanced dashboard data loading
|
|
function loadDashboardData() {
|
|
// Load account info
|
|
loadAccountInfo();
|
|
// Load bot status
|
|
loadBotStatus();
|
|
// Load chart data
|
|
loadChartData();
|
|
}
|
|
|
|
function loadAccountInfo() {
|
|
fetch('/api/account-info')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
const equityEl = document.getElementById('total-equity');
|
|
const profitEl = document.getElementById('todays-profit');
|
|
|
|
if (equityEl) equityEl.textContent = formatter.format(data.equity);
|
|
if (profitEl) {
|
|
profitEl.textContent = formatter.format(data.todays_profit);
|
|
profitEl.className = data.todays_profit >= 0 ?
|
|
'mt-1 text-2xl font-semibold profit-positive' :
|
|
'mt-1 text-2xl font-semibold profit-negative';
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading account info:', error);
|
|
const equityEl = document.getElementById('total-equity');
|
|
const profitEl = document.getElementById('todays-profit');
|
|
if (equityEl) equityEl.textContent = 'Error';
|
|
if (profitEl) profitEl.textContent = 'Error';
|
|
});
|
|
}
|
|
|
|
function loadBotStatus() {
|
|
fetch('/api/bots/status')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
const activeCountEl = document.getElementById('active-bots-count');
|
|
const totalCountEl = document.getElementById('total-bots-count');
|
|
|
|
if (activeCountEl) activeCountEl.textContent = data.active_count;
|
|
if (totalCountEl) totalCountEl.textContent = data.total_count;
|
|
|
|
// Update active bots list
|
|
updateActiveBotsList(data.active_bots);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading bot status:', error);
|
|
const activeCountEl = document.getElementById('active-bots-count');
|
|
const totalCountEl = document.getElementById('total-bots-count');
|
|
if (activeCountEl) activeCountEl.textContent = 'Error';
|
|
if (totalCountEl) totalCountEl.textContent = 'Error';
|
|
});
|
|
}
|
|
|
|
function updateActiveBotsList(activeBots) {
|
|
const botsList = document.getElementById('active-bots-list');
|
|
if (!botsList) return;
|
|
|
|
if (activeBots.length > 0) {
|
|
botsList.innerHTML = activeBots.map(bot => `
|
|
<div class="p-4 flex justify-between items-center hover:bg-gray-50 transition">
|
|
<div>
|
|
<h4 class="font-medium text-gray-800">${bot.name}</h4>
|
|
<p class="text-sm text-gray-600">${bot.symbol} • ${bot.strategy}</p>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="text-sm font-medium ${bot.profit >= 0 ? 'profit-positive' : 'profit-negative'}">
|
|
${formatter.format(bot.profit)}
|
|
</div>
|
|
<div class="text-xs text-gray-500">${bot.trades} trades</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
} else {
|
|
botsList.innerHTML = '<p class="p-4 text-gray-500">Tidak ada bot yang aktif</p>';
|
|
}
|
|
}
|
|
|
|
function loadChartData() {
|
|
fetch('/api/market-data/EURUSD')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success && priceChart && rsiChart) {
|
|
// Update price chart
|
|
priceChart.data.labels = data.timestamps;
|
|
priceChart.data.datasets[0].data = data.prices;
|
|
priceChart.update();
|
|
|
|
// Update RSI chart
|
|
rsiChart.data.labels = data.timestamps;
|
|
rsiChart.data.datasets[0].data = data.rsi;
|
|
rsiChart.update();
|
|
|
|
// Update timestamp
|
|
const timestampEl = document.getElementById('last-updated');
|
|
if (timestampEl) {
|
|
timestampEl.innerHTML = `<i class="fas fa-clock"></i> ${new Date().toLocaleTimeString('id-ID')}`;
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading chart data:', error);
|
|
const timestampEl = document.getElementById('last-updated');
|
|
if (timestampEl) {
|
|
timestampEl.innerHTML = `<i class="fas fa-exclamation-triangle"></i> Error loading data`;
|
|
}
|
|
});
|
|
}
|
|
|
|
// AI Mentor Integration
|
|
function loadAIMentorSummary() {
|
|
fetch('/ai-mentor/api/dashboard-summary')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// Update emotion status
|
|
const emotionMap = {
|
|
'tenang': '😌 Tenang',
|
|
'serakah': '🤑 Serakah',
|
|
'takut': '😰 Takut',
|
|
'frustasi': '😤 Frustasi',
|
|
'netral': '😐 Netral'
|
|
};
|
|
|
|
const emotionEl = document.getElementById('emotion-status');
|
|
if (emotionEl) {
|
|
emotionEl.textContent = emotionMap[data.today_emotions] || '😐 Netral';
|
|
}
|
|
|
|
// Update AI analysis
|
|
const analysisEl = document.getElementById('trading-analysis');
|
|
if (analysisEl) {
|
|
analysisEl.textContent = data.trading_analysis || 'Belum ada data hari ini';
|
|
}
|
|
|
|
// Update daily tip
|
|
const tipEl = document.getElementById('daily-tip');
|
|
if (tipEl) {
|
|
tipEl.textContent = data.daily_tip || 'Mulai trading untuk mendapat tips personal!';
|
|
}
|
|
|
|
// Update AI mentor status
|
|
const statusEl = document.getElementById('ai-mentor-status');
|
|
if (statusEl) {
|
|
if (data.today_has_data) {
|
|
statusEl.textContent = 'Aktif Menganalisis';
|
|
statusEl.className = 'text-xl font-bold text-green-100';
|
|
} else {
|
|
statusEl.textContent = 'Siap Membantu';
|
|
statusEl.className = 'text-xl font-bold';
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading AI mentor summary:', error);
|
|
});
|
|
}
|
|
|
|
function loadRecentActivities() {
|
|
fetch('/api/recent-activities')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
const activitiesEl = document.getElementById('recent-activities');
|
|
if (activitiesEl) {
|
|
const activitiesHtml = data.activities.map(activity => `
|
|
<div class="activity-item p-4 hover:bg-gray-50 transition-colors">
|
|
<div class="flex items-start space-x-3">
|
|
<div class="text-lg">${activity.icon}</div>
|
|
<div class="flex-1">
|
|
<p class="text-sm font-medium text-gray-900">${activity.title}</p>
|
|
<p class="text-xs text-gray-500">${activity.description}</p>
|
|
<p class="text-xs text-gray-400 mt-1">${activity.time}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
activitiesEl.innerHTML = activitiesHtml || '<div class="p-4 text-gray-500 text-center">Belum ada aktivitas</div>';
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading recent activities:', error);
|
|
});
|
|
}
|
|
|
|
// Legacy functions for compatibility
|
|
function updateDashboardStats() {
|
|
loadAccountInfo();
|
|
}
|
|
|
|
function fetchAllBots() {
|
|
loadBotStatus();
|
|
}
|
|
|
|
// Updated chart functions for legacy compatibility
|
|
function updatePriceChart(symbol = 'EURUSD') {
|
|
if (typeof Chart === 'undefined') return;
|
|
|
|
fetch(`/api/chart/data?symbol=${symbol}`)
|
|
.then(response => response.json())
|
|
.then(chartData => {
|
|
const ctx = document.getElementById('priceChart');
|
|
if (!ctx) return;
|
|
|
|
const config = {
|
|
type: 'line',
|
|
data: {
|
|
labels: chartData.labels,
|
|
datasets: [{
|
|
label: symbol,
|
|
data: chartData.data,
|
|
borderColor: '#3B82F6',
|
|
backgroundColor: 'rgba(59, 130, 246, 0.05)',
|
|
borderWidth: 2,
|
|
fill: true,
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: { legend: { display: false } },
|
|
scales: {
|
|
y: { beginAtZero: false },
|
|
x: { grid: { display: false } }
|
|
}
|
|
}
|
|
};
|
|
|
|
if (priceChart) {
|
|
priceChart.data.labels = chartData.labels;
|
|
priceChart.data.datasets[0].data = chartData.data;
|
|
priceChart.update();
|
|
} else {
|
|
priceChart = new Chart(ctx, config);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('[Chart] Gagal update grafik:', error);
|
|
});
|
|
}
|
|
|
|
function updateRsiChart(symbol = 'EURUSD') {
|
|
if (typeof Chart === 'undefined') return;
|
|
|
|
fetch(`/api/rsi_data?symbol=${symbol}&timeframe=H1`)
|
|
.then(response => response.json())
|
|
.then(rsiData => {
|
|
const ctx = document.getElementById('rsiChart');
|
|
if (!ctx) return;
|
|
|
|
const config = {
|
|
type: 'line',
|
|
data: {
|
|
labels: rsiData.timestamps,
|
|
datasets: [{
|
|
label: 'RSI',
|
|
data: rsiData.rsi_values,
|
|
borderColor: 'rgba(75, 192, 192, 1)',
|
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
|
borderWidth: 2,
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: { legend: { display: false } },
|
|
scales: {
|
|
y: {
|
|
min: 0,
|
|
max: 100,
|
|
ticks: { stepSize: 10 },
|
|
grid: { color: 'rgba(0,0,0,0.05)' }
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
if (rsiChart) {
|
|
rsiChart.data.labels = rsiData.timestamps;
|
|
rsiChart.data.datasets[0].data = rsiData.rsi_values;
|
|
rsiChart.update();
|
|
} else {
|
|
rsiChart = new Chart(ctx, config);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('[RSI Chart] Gagal update grafik RSI:', error);
|
|
});
|
|
}
|
|
|
|
// Lighter refresh for auto-refresh
|
|
function refreshDashboardData() {
|
|
loadAccountInfo();
|
|
}
|
|
|
|
// Quick emotion check functions (from template)
|
|
function quickEmotionCheck() {
|
|
const emotions = ['tenang', 'serakah', 'takut', 'frustasi', 'netral'];
|
|
const emotionNames = ['😌 Tenang', '🤑 Serakah', '😰 Takut', '😤 Frustasi', '😐 Netral'];
|
|
|
|
const modal = document.createElement('div');
|
|
modal.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center';
|
|
modal.innerHTML = `
|
|
<div class="bg-white rounded-lg p-6 max-w-sm w-full mx-4">
|
|
<h3 class="text-lg font-semibold mb-4">🎯 Update Status Emosi</h3>
|
|
<div class="space-y-2">
|
|
${emotions.map((emotion, index) => `
|
|
<button onclick="updateEmotion('${emotion}')"
|
|
class="w-full text-left p-3 rounded-lg border hover:bg-gray-50 transition-colors">
|
|
${emotionNames[index]}
|
|
</button>
|
|
`).join('')}
|
|
</div>
|
|
<button onclick="closeEmotionModal()"
|
|
class="mt-4 w-full bg-gray-200 text-gray-800 py-2 rounded-lg hover:bg-gray-300 transition-colors">
|
|
Batal
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(modal);
|
|
window.currentEmotionModal = modal;
|
|
}
|
|
|
|
function updateEmotion(emotion) {
|
|
fetch('/ai-mentor/update-emotions', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ emotions: emotion })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
const emotionMap = {
|
|
'tenang': '😌 Tenang',
|
|
'serakah': '🤑 Serakah',
|
|
'takut': '😰 Takut',
|
|
'frustasi': '😤 Frustasi',
|
|
'netral': '😐 Netral'
|
|
};
|
|
|
|
const emotionEl = document.getElementById('emotion-status');
|
|
if (emotionEl) {
|
|
emotionEl.textContent = emotionMap[emotion];
|
|
}
|
|
|
|
showNotification('Status emosi berhasil diupdate!', 'success');
|
|
loadAIMentorSummary();
|
|
} else {
|
|
showNotification('Gagal update status emosi', 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error updating emotion:', error);
|
|
showNotification('Error: Gagal update status emosi', 'error');
|
|
})
|
|
.finally(() => {
|
|
closeEmotionModal();
|
|
});
|
|
}
|
|
|
|
function closeEmotionModal() {
|
|
if (window.currentEmotionModal) {
|
|
document.body.removeChild(window.currentEmotionModal);
|
|
window.currentEmotionModal = null;
|
|
}
|
|
}
|
|
|
|
function showNotification(message, type = 'info') {
|
|
const notification = document.createElement('div');
|
|
notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg transition-all duration-300 ${
|
|
type === 'success' ? 'bg-green-500 text-white' :
|
|
type === 'error' ? 'bg-red-500 text-white' :
|
|
'bg-blue-500 text-white'
|
|
}`;
|
|
notification.textContent = message;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
setTimeout(() => {
|
|
notification.style.transform = 'translateX(100%)';
|
|
setTimeout(() => {
|
|
if (notification.parentNode) {
|
|
document.body.removeChild(notification);
|
|
}
|
|
}, 300);
|
|
}, 3000);
|
|
}
|