mirror of
https://github.com/chrisnov-it/quantumbotx.git
synced 2026-07-28 11:17:44 +00:00
351292c26a
- Streamlined .env.example to include only essential Flask and MT5 configs for production environments - Added .vercel directory to .gitignore for clean Vercel deployments - Enabled internationalization by adding data-i18n attributes to strategy switcher UI elements - Updated MT5 setup guide and roadmap documentation for clarity and current focus This change simplifies configuration for server deployment while enhancing UI accessibility across languages.
970 lines
37 KiB
HTML
970 lines
37 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block head_extra %}
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/dashboard.css') }}" />
|
|
<style>
|
|
.ai-mentor-widget {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border-radius: 12px;
|
|
}
|
|
|
|
.stat-card {
|
|
transition: transform 0.2s ease;
|
|
}
|
|
|
|
.stat-card:hover {
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.activity-item {
|
|
border-left: 4px solid #3b82f6;
|
|
transition: background-color 0.2s ease;
|
|
}
|
|
|
|
.activity-item:hover {
|
|
background-color: #f8fafc;
|
|
}
|
|
|
|
.chart-container {
|
|
position: relative;
|
|
height: 300px;
|
|
}
|
|
|
|
.floating-ai-btn {
|
|
position: fixed;
|
|
bottom: 2rem;
|
|
right: 2rem;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border: none;
|
|
border-radius: 50%;
|
|
width: 60px;
|
|
height: 60px;
|
|
font-size: 1.5rem;
|
|
cursor: pointer;
|
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
|
transition: all 0.3s ease;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.floating-ai-btn:hover {
|
|
transform: scale(1.1);
|
|
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
|
|
}
|
|
|
|
.profit-positive { color: #10b981; }
|
|
.profit-negative { color: #ef4444; }
|
|
|
|
/* Holiday-specific styles */
|
|
.holiday-widget {
|
|
border-radius: 12px;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.holiday-widget:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.ramadan-widget {
|
|
background: linear-gradient(135deg, #006600 0%, #ffd700 100%);
|
|
color: white;
|
|
}
|
|
|
|
.christmas-widget {
|
|
background: linear-gradient(135deg, #c41e3a 0%, #228b22 100%);
|
|
color: white;
|
|
}
|
|
|
|
.new-year-widget {
|
|
background: linear-gradient(135deg, #ff6b35 0%, #f7931e 100%);
|
|
color: white;
|
|
}
|
|
|
|
.ramadan-countdown {
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
text-align: center;
|
|
}
|
|
|
|
.ramadan-feature-card {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 8px;
|
|
padding: 1rem;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- Holiday Widget Container -->
|
|
<div id="holiday-widgets" class="mb-6 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4 hidden">
|
|
<!-- Holiday widgets will be inserted here dynamically -->
|
|
</div>
|
|
|
|
<!-- Hero Section with AI Mentor -->
|
|
<div class="mb-6">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<h2 class="text-3xl font-bold text-gray-800" data-i18n="dashboard.title">Dashboard Trading</h2>
|
|
<p class="text-gray-600" data-i18n="dashboard.welcome_back">Selamat datang kembali! Berikut ringkasan trading Anda hari ini.</p>
|
|
</div>
|
|
<div class="ai-mentor-widget p-4 text-right">
|
|
<div class="text-sm opacity-90" data-i18n="dashboard.ai_mentor">🤖 AI Mentor</div>
|
|
<div class="text-xl font-bold" id="ai-mentor-status" data-i18n="dashboard.ai_mentor_ready">Siap Membantu</div>
|
|
<a href="/ai-mentor" class="text-sm underline opacity-90 hover:opacity-100" data-i18n="dashboard.view_analysis">Lihat Analisis →</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Enhanced STAT CARDS -->
|
|
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-5 mb-6">
|
|
<div class="stat-card bg-white rounded-lg shadow p-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-gray-500" data-i18n="dashboard.total_equity">Total Saldo (Equity)</h3>
|
|
<p id="total-equity" class="mt-1 text-2xl font-semibold text-gray-800">
|
|
<i class="fas fa-spinner fa-spin text-gray-400"></i>
|
|
</p>
|
|
</div>
|
|
<div class="text-green-500 text-2xl">
|
|
<i class="fas fa-wallet"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card bg-white rounded-lg shadow p-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-gray-500" data-i18n="dashboard.today_profit">Profit Hari Ini</h3>
|
|
<p id="todays-profit" class="mt-1 text-2xl font-semibold text-gray-800">
|
|
<i class="fas fa-spinner fa-spin text-gray-400"></i>
|
|
</p>
|
|
</div>
|
|
<div class="text-blue-500 text-2xl">
|
|
<i class="fas fa-chart-line"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card bg-white rounded-lg shadow p-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-gray-500" data-i18n="dashboard.active_bots">Bot Aktif</h3>
|
|
<p id="active-bots-count" class="mt-1 text-2xl font-semibold text-gray-800">
|
|
<i class="fas fa-spinner fa-spin text-gray-400"></i>
|
|
</p>
|
|
</div>
|
|
<div class="text-purple-500 text-2xl">
|
|
<i class="fas fa-robot"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card bg-white rounded-lg shadow p-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-gray-500" data-i18n="dashboard.total_bots">Total Bot</h3>
|
|
<p id="total-bots-count" class="mt-1 text-2xl font-semibold text-gray-800">
|
|
<i class="fas fa-spinner fa-spin text-gray-400"></i>
|
|
</p>
|
|
</div>
|
|
<div class="text-gray-500 text-2xl">
|
|
<i class="fas fa-cogs"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- New AI Mentor Quick Stats -->
|
|
<div class="stat-card bg-gradient-to-br from-purple-500 to-blue-600 text-white rounded-lg shadow p-6">
|
|
<div class="text-center">
|
|
<h3 class="text-sm font-medium opacity-90" data-i18n="dashboard.emotion_status">Status Emosi</h3>
|
|
<p id="emotion-status" class="mt-1 text-xl font-semibold" data-i18n="dashboard.emotion_calm">😌 Tenang</p>
|
|
<button onclick="quickEmotionCheck()" class="text-xs underline opacity-90 hover:opacity-100 mt-1" data-i18n="dashboard.update_status">
|
|
Update Status
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Three Column Layout -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
|
<!-- Charts Column (2/3 width) -->
|
|
<div class="lg:col-span-2 space-y-6">
|
|
<!-- Price Chart -->
|
|
<div class="bg-white rounded-lg shadow p-6">
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-700" data-i18n="dashboard.price_chart">📈 Grafik Harga EUR/USD</h3>
|
|
<div class="text-sm text-gray-500" id="last-updated">
|
|
<i class="fas fa-clock"></i> <span data-i18n="dashboard.loading">Memuat...</span>
|
|
</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<canvas id="priceChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- RSI Chart -->
|
|
<div class="bg-white rounded-lg shadow p-6">
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-700" data-i18n="dashboard.rsi_chart">📊 RSI EUR/USD (H1)</h3>
|
|
<div class="text-sm text-gray-500">
|
|
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800" data-i18n="dashboard.real_time">
|
|
Real-time
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<canvas id="rsiChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar Column (1/3 width) -->
|
|
<div class="space-y-6">
|
|
<!-- AI Mentor Today -->
|
|
<div class="bg-gradient-to-br from-indigo-500 to-purple-600 text-white rounded-lg shadow p-6">
|
|
<h3 class="text-lg font-semibold mb-3" data-i18n="dashboard.ai_mentor_today">🧠 AI Mentor Hari Ini</h3>
|
|
<div id="ai-mentor-summary" class="space-y-3">
|
|
<div class="bg-white bg-opacity-20 rounded-lg p-3">
|
|
<div class="text-sm opacity-90" data-i18n="dashboard.trading_analysis">Analisis Trading:</div>
|
|
<div class="font-semibold" id="trading-analysis" data-i18n="dashboard.loading">Memuat...</div>
|
|
</div>
|
|
<div class="bg-white bg-opacity-20 rounded-lg p-3">
|
|
<div class="text-sm opacity-90" data-i18n="dashboard.daily_tip">Tips Hari Ini:</div>
|
|
<div class="text-sm" id="daily-tip" data-i18n="dashboard.loading">Memuat...</div>
|
|
</div>
|
|
<a href="/ai-mentor/today-report" class="block w-full bg-white bg-opacity-20 hover:bg-opacity-30 rounded-lg p-2 text-center text-sm font-medium transition" data-i18n="dashboard.view_full_report">
|
|
📊 Lihat Laporan Lengkap
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Activities -->
|
|
<div class="bg-white rounded-lg shadow">
|
|
<div class="p-4 border-b">
|
|
<h3 class="text-lg font-semibold text-gray-800" data-i18n="dashboard.recent_activities">⚡ Aktivitas Terbaru</h3>
|
|
</div>
|
|
<div id="recent-activities" class="max-h-80 overflow-y-auto">
|
|
<div class="p-4 text-gray-500 text-center" data-i18n="dashboard.loading_activities">Memuat aktivitas...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- BOT AKTIF -->
|
|
<div class="bg-white rounded-lg shadow">
|
|
<div class="p-6 border-b">
|
|
<h3 class="text-lg font-semibold text-gray-800" data-i18n="dashboard.active_trading_bots">Active Trading Bots</h3>
|
|
</div>
|
|
<div id="active-bots-list" class="divide-y">
|
|
<p class="p-4 text-gray-500" data-i18n="dashboard.loading_bot_list">Memuat daftar bot...</p>
|
|
</div>
|
|
<div class="p-4 border-t">
|
|
<a href="/trading_bots" class="block w-full text-center text-blue-600 font-medium hover:text-blue-800 transition" data-i18n="dashboard.view_all_bots">View All Bots</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Floating AI Button -->
|
|
<button class="floating-ai-btn" onclick="window.location.href='/ai-mentor'" data-i18n-title="dashboard.chat_with_ai_mentor" title="Chat dengan AI Mentor">
|
|
🤖
|
|
</button>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<script src="{{ url_for('static', filename='js/dashboard.js') }}"></script>
|
|
<script>
|
|
// Enhanced Dashboard JavaScript with AI Mentor Integration
|
|
let priceChart, rsiChart;
|
|
|
|
// Initialize dashboard
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initializeCharts();
|
|
loadDashboardData();
|
|
loadAIMentorSummary();
|
|
loadRecentActivities();
|
|
checkHolidayStatus(); // Check for active holidays
|
|
|
|
// Auto-refresh every 30 seconds
|
|
setInterval(() => {
|
|
refreshDashboardData();
|
|
checkHolidayStatus(); // Refresh holiday status
|
|
}, 30000);
|
|
});
|
|
|
|
// Initialize Charts
|
|
function initializeCharts() {
|
|
// Price Chart
|
|
const priceCtx = document.getElementById('priceChart').getContext('2d');
|
|
priceChart = new Chart(priceCtx, {
|
|
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'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// RSI Chart
|
|
const rsiCtx = document.getElementById('rsiChart').getContext('2d');
|
|
rsiChart = new Chart(rsiCtx, {
|
|
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'
|
|
}
|
|
}
|
|
},
|
|
// Add horizontal lines for overbought/oversold
|
|
plugins: {
|
|
annotation: {
|
|
annotations: {
|
|
line1: {
|
|
type: 'line',
|
|
yMin: 70,
|
|
yMax: 70,
|
|
borderColor: '#ef4444',
|
|
borderWidth: 1,
|
|
borderDash: [5, 5]
|
|
},
|
|
line2: {
|
|
type: 'line',
|
|
yMin: 30,
|
|
yMax: 30,
|
|
borderColor: '#10b981',
|
|
borderWidth: 1,
|
|
borderDash: [5, 5]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Load Dashboard Data
|
|
function loadDashboardData() {
|
|
// Load account info
|
|
fetch('/api/account-info')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.getElementById('total-equity').textContent = `$${data.equity.toFixed(2)}`;
|
|
|
|
const profitElement = document.getElementById('todays-profit');
|
|
const profit = data.todays_profit;
|
|
profitElement.textContent = `$${profit.toFixed(2)}`;
|
|
profitElement.className = 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);
|
|
document.getElementById('total-equity').textContent = 'Error';
|
|
document.getElementById('todays-profit').textContent = 'Error';
|
|
});
|
|
|
|
// Load bot info
|
|
fetch('/api/bots/status')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.getElementById('active-bots-count').textContent = data.active_count;
|
|
document.getElementById('total-bots-count').textContent = data.total_count;
|
|
|
|
// Update active bots list
|
|
const botsList = document.getElementById('active-bots-list');
|
|
if (data.active_bots.length > 0) {
|
|
botsList.innerHTML = data.active_bots.map(bot => `
|
|
<div class="p-4 flex justify-between items-center">
|
|
<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'}">
|
|
$${bot.profit.toFixed(2)}
|
|
</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" data-i18n="dashboard.no_active_bots">Tidak ada bot yang aktif</p>';
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading bot info:', error);
|
|
document.getElementById('active-bots-count').textContent = 'Error';
|
|
document.getElementById('total-bots-count').textContent = 'Error';
|
|
});
|
|
|
|
// Load chart data
|
|
loadChartData();
|
|
}
|
|
|
|
// Load Chart Data
|
|
function loadChartData() {
|
|
fetch('/api/market-data/EURUSD')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// 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
|
|
document.getElementById('last-updated').innerHTML =
|
|
`<i class="fas fa-clock"></i> ${new Date().toLocaleTimeString('id-ID')}`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading chart data:', error);
|
|
document.getElementById('last-updated').innerHTML =
|
|
`<i class="fas fa-exclamation-triangle"></i> Error loading data`;
|
|
});
|
|
}
|
|
|
|
// Load AI Mentor Summary
|
|
function loadAIMentorSummary() {
|
|
fetch('/ai-mentor/api/dashboard-summary')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// Update emotion status
|
|
const emotionMap = {
|
|
'tenang': window.QuantumBotXI18n.t('dashboard.emotion_calm'),
|
|
'serakah': window.QuantumBotXI18n.t('dashboard.emotion_greedy'),
|
|
'takut': window.QuantumBotXI18n.t('dashboard.emotion_fear'),
|
|
'frustasi': window.QuantumBotXI18n.t('dashboard.emotion_frustrated'),
|
|
'netral': window.QuantumBotXI18n.t('dashboard.emotion_neutral')
|
|
};
|
|
document.getElementById('emotion-status').textContent =
|
|
emotionMap[data.today_emotions] || window.QuantumBotXI18n.t('dashboard.emotion_neutral');
|
|
|
|
// Update AI analysis
|
|
document.getElementById('trading-analysis').textContent =
|
|
data.trading_analysis || window.QuantumBotXI18n.t('msg.no_data');
|
|
|
|
// Update daily tip
|
|
document.getElementById('daily-tip').textContent =
|
|
data.daily_tip || window.QuantumBotXI18n.t('msg.no_data');
|
|
|
|
// Update AI mentor status
|
|
const statusElement = document.getElementById('ai-mentor-status');
|
|
if (data.today_has_data) {
|
|
statusElement.textContent = window.QuantumBotXI18n.t('dashboard.ai_mentor_active');
|
|
statusElement.className = 'text-xl font-bold text-green-100';
|
|
} else {
|
|
statusElement.textContent = window.QuantumBotXI18n.t('dashboard.ai_mentor_ready');
|
|
statusElement.className = 'text-xl font-bold';
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading AI mentor summary:', error);
|
|
document.getElementById('trading-analysis').textContent = window.QuantumBotXI18n.t('error.loading');
|
|
document.getElementById('daily-tip').textContent = window.QuantumBotXI18n.t('error.loading');
|
|
});
|
|
}
|
|
|
|
// Load Recent Activities
|
|
function loadRecentActivities() {
|
|
fetch('/api/recent-activities')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
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('');
|
|
|
|
document.getElementById('recent-activities').innerHTML =
|
|
activitiesHtml || '<div class="p-4 text-gray-500 text-center" data-i18n="msg.no_data">Belum ada aktivitas</div>';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading recent activities:', error);
|
|
document.getElementById('recent-activities').innerHTML =
|
|
'<div class="p-4 text-red-500 text-center" data-i18n="error.loading">Error loading activities</div>';
|
|
});
|
|
}
|
|
|
|
// Quick Emotion Check
|
|
function quickEmotionCheck() {
|
|
const emotions = ['tenang', 'serakah', 'takut', 'frustasi', 'netral'];
|
|
const emotionNames = [
|
|
window.QuantumBotXI18n.t('dashboard.emotion_calm'),
|
|
window.QuantumBotXI18n.t('dashboard.emotion_greedy'),
|
|
window.QuantumBotXI18n.t('dashboard.emotion_fear'),
|
|
window.QuantumBotXI18n.t('dashboard.emotion_frustrated'),
|
|
window.QuantumBotXI18n.t('dashboard.emotion_neutral')
|
|
];
|
|
|
|
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" data-i18n="dashboard.update_emotion_status">🎯 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" data-i18n="dashboard.cancel">
|
|
Batal
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(modal);
|
|
window.currentEmotionModal = modal;
|
|
}
|
|
|
|
// Update Emotion
|
|
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) {
|
|
// Update UI immediately
|
|
const emotionMap = {
|
|
'tenang': window.QuantumBotXI18n.t('dashboard.emotion_calm'),
|
|
'serakah': window.QuantumBotXI18n.t('dashboard.emotion_greedy'),
|
|
'takut': window.QuantumBotXI18n.t('dashboard.emotion_fear'),
|
|
'frustasi': window.QuantumBotXI18n.t('dashboard.emotion_frustrated'),
|
|
'netral': window.QuantumBotXI18n.t('dashboard.emotion_neutral')
|
|
};
|
|
document.getElementById('emotion-status').textContent = emotionMap[emotion];
|
|
|
|
// Show success message
|
|
showNotification(window.QuantumBotXI18n.t('dashboard.emotion_updated'), 'success');
|
|
|
|
// Refresh AI mentor summary
|
|
loadAIMentorSummary();
|
|
} else {
|
|
showNotification(window.QuantumBotXI18n.t('dashboard.emotion_update_failed'), 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error updating emotion:', error);
|
|
showNotification(window.QuantumBotXI18n.t('dashboard.error_updating_emotion'), 'error');
|
|
})
|
|
.finally(() => {
|
|
closeEmotionModal();
|
|
});
|
|
}
|
|
|
|
// Close Emotion Modal
|
|
function closeEmotionModal() {
|
|
if (window.currentEmotionModal) {
|
|
document.body.removeChild(window.currentEmotionModal);
|
|
window.currentEmotionModal = null;
|
|
}
|
|
}
|
|
|
|
// Show Notification
|
|
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);
|
|
|
|
// Auto remove after 3 seconds
|
|
setTimeout(() => {
|
|
notification.style.transform = 'translateX(100%)';
|
|
setTimeout(() => {
|
|
if (notification.parentNode) {
|
|
document.body.removeChild(notification);
|
|
}
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
|
|
// Refresh Dashboard Data (lighter version for auto-refresh)
|
|
function refreshDashboardData() {
|
|
// Only refresh essential data to avoid overwhelming the system
|
|
fetch('/api/account-info')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.getElementById('total-equity').textContent = `$${data.equity.toFixed(2)}`;
|
|
|
|
const profitElement = document.getElementById('todays-profit');
|
|
const profit = data.todays_profit;
|
|
profitElement.textContent = `$${profit.toFixed(2)}`;
|
|
profitElement.className = profit >= 0 ? 'mt-1 text-2xl font-semibold profit-positive' : 'mt-1 text-2xl font-semibold profit-negative';
|
|
}
|
|
})
|
|
.catch(error => console.error('Auto-refresh error:', error));
|
|
}
|
|
|
|
// Check for active holidays and update UI accordingly
|
|
async function checkHolidayStatus() {
|
|
try {
|
|
const response = await fetch('/api/holiday/status');
|
|
const data = await response.json();
|
|
|
|
const holidayWidgets = document.getElementById('holiday-widgets');
|
|
|
|
if (data.success && data.is_holiday) {
|
|
// Show holiday widgets container
|
|
if (holidayWidgets) {
|
|
holidayWidgets.classList.remove('hidden');
|
|
|
|
// Special handling for Ramadan
|
|
if (data.holiday_name === "Ramadan Trading Mode") {
|
|
updateRamadanWidgets(data);
|
|
}
|
|
// Special handling for Christmas
|
|
else if (data.holiday_name === "Christmas Trading Mode") {
|
|
updateChristmasWidgets(data);
|
|
}
|
|
// Special handling for New Year
|
|
else if (data.holiday_name === "New Year Trading Mode") {
|
|
updateNewYearWidgets(data);
|
|
}
|
|
}
|
|
} else {
|
|
// Hide holiday widgets if no active holiday
|
|
if (holidayWidgets) {
|
|
holidayWidgets.classList.add('hidden');
|
|
holidayWidgets.innerHTML = '';
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error checking holiday status:', error);
|
|
}
|
|
}
|
|
|
|
// Update Ramadan-specific widgets
|
|
function updateRamadanWidgets(holidayData) {
|
|
const holidayWidgets = document.getElementById('holiday-widgets');
|
|
if (!holidayWidgets) return;
|
|
|
|
let ramadanHtml = '';
|
|
|
|
// Iftar Countdown Widget
|
|
if (holidayData.ramadan_features && holidayData.ramadan_features.iftar_countdown) {
|
|
const countdown = holidayData.ramadan_features.iftar_countdown;
|
|
ramadanHtml += `
|
|
<div class="holiday-widget ramadan-widget p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold" data-i18n="holiday.countdown.iftar">⏱️ Hitung Mundur Iftar</h3>
|
|
<i class="fas fa-moon"></i>
|
|
</div>
|
|
<div class="ramadan-countdown">
|
|
${countdown.hours.toString().padStart(2, '0')}:${countdown.minutes.toString().padStart(2, '0')}
|
|
</div>
|
|
<div class="text-center text-sm opacity-90 mt-2">
|
|
Sampai ${countdown.next_prayer}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Patience Reminder Widget
|
|
if (holidayData.ramadan_features && holidayData.ramadan_features.patience_reminder) {
|
|
ramadanHtml += `
|
|
<div class="holiday-widget ramadan-widget p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold" data-i18n="holiday.patience_reminder">🧘♂️ Pengingat Kesabaran</h3>
|
|
<i class="fas fa-heart"></i>
|
|
</div>
|
|
<div class="text-sm italic text-center">
|
|
${holidayData.ramadan_features.patience_reminder}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Risk Adjustment Widget
|
|
if (holidayData.trading_adjustments && holidayData.trading_adjustments.risk_reduction) {
|
|
const riskReduction = (1 - holidayData.trading_adjustments.risk_reduction) * 100;
|
|
ramadanHtml += `
|
|
<div class="holiday-widget ramadan-widget p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold" data-i18n="holiday.risk_adjustment">🛡️ Penyesuaian Risiko</h3>
|
|
<i class="fas fa-shield-alt"></i>
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="text-2xl font-bold">${riskReduction.toFixed(0)}%</div>
|
|
<div class="text-sm opacity-90" data-i18n="holiday.risk_reduction">Pengurangan risiko otomatis</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Optimal Trading Hours Widget
|
|
if (holidayData.ramadan_features && holidayData.ramadan_features.optimal_trading_hours) {
|
|
ramadanHtml += `
|
|
<div class="holiday-widget ramadan-widget p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold" data-i18n="holiday.optimal_trading_hours">⏰ Waktu Trading Optimal</h3>
|
|
<i class="fas fa-clock"></i>
|
|
</div>
|
|
<div class="space-y-2">
|
|
${holidayData.ramadan_features.optimal_trading_hours.map(hour => `
|
|
<div class="ramadan-feature-card flex justify-between">
|
|
<span>${hour[0]}:00 - ${hour[1] || (hour[0] + 1)}:00 WIB</span>
|
|
<i class="fas fa-star text-yellow-300"></i>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
holidayWidgets.innerHTML = ramadanHtml;
|
|
}
|
|
|
|
// Update Christmas-specific widgets
|
|
function updateChristmasWidgets(holidayData) {
|
|
const holidayWidgets = document.getElementById('holiday-widgets');
|
|
if (!holidayWidgets) return;
|
|
|
|
let christmasHtml = '';
|
|
|
|
// Christmas Countdown Widget
|
|
const today = new Date();
|
|
const christmasDate = new Date(today.getFullYear(), 11, 25); // December 25
|
|
const timeDiff = christmasDate - today;
|
|
const daysDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
|
|
|
|
if (daysDiff >= 0) {
|
|
christmasHtml += `
|
|
<div class="holiday-widget christmas-widget p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold" data-i18n="holiday.countdown.christmas">🎄 Hitung Mundur Natal</h3>
|
|
<i class="fas fa-gift"></i>
|
|
</div>
|
|
<div class="ramadan-countdown">
|
|
${daysDiff}
|
|
</div>
|
|
<div class="text-center text-sm opacity-90 mt-2">
|
|
<span data-i18n="holiday.countdown.days_until">hari lagi sampai</span> Natal
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Risk Adjustment Widget
|
|
if (holidayData.trading_adjustments && holidayData.trading_adjustments.risk_reduction) {
|
|
const riskReduction = (1 - holidayData.trading_adjustments.risk_reduction) * 100;
|
|
christmasHtml += `
|
|
<div class="holiday-widget christmas-widget p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold" data-i18n="holiday.risk_adjustment">🛡️ Penyesuaian Risiko</h3>
|
|
<i class="fas fa-shield-alt"></i>
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="text-2xl font-bold">${riskReduction.toFixed(0)}%</div>
|
|
<div class="text-sm opacity-90" data-i18n="holiday.risk_reduction">Pengurangan risiko otomatis</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Holiday Greeting Widget
|
|
if (holidayData.greeting) {
|
|
christmasHtml += `
|
|
<div class="holiday-widget christmas-widget p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold" data-i18n="holiday.christmas_greeting">✨ Salam Natal</h3>
|
|
<i class="fas fa-star"></i>
|
|
</div>
|
|
<div class="text-sm text-center italic">
|
|
${holidayData.greeting}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Lot Size Adjustment Widget
|
|
if (holidayData.trading_adjustments && holidayData.trading_adjustments.lot_size_multiplier) {
|
|
const lotSizeReduction = (1 - holidayData.trading_adjustments.lot_size_multiplier) * 100;
|
|
christmasHtml += `
|
|
<div class="holiday-widget christmas-widget p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold" data-i18n="holiday.lot_adjustment">📏 Penyesuaian Lot</h3>
|
|
<i class="fas fa-ruler"></i>
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="text-2xl font-bold">${lotSizeReduction.toFixed(0)}%</div>
|
|
<div class="text-sm opacity-90" data-i18n="holiday.lot_reduction">Pengurangan ukuran lot</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
holidayWidgets.innerHTML = christmasHtml;
|
|
}
|
|
|
|
// Update New Year-specific widgets
|
|
function updateNewYearWidgets(holidayData) {
|
|
const holidayWidgets = document.getElementById('holiday-widgets');
|
|
if (!holidayWidgets) return;
|
|
|
|
let newYearHtml = '';
|
|
|
|
// New Year Countdown Widget
|
|
const today = new Date();
|
|
const newYearDate = new Date(today.getFullYear() + 1, 0, 1); // January 1 next year
|
|
const timeDiff = newYearDate - today;
|
|
const daysDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
|
|
|
|
newYearHtml += `
|
|
<div class="holiday-widget new-year-widget p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold" data-i18n="holiday.countdown.new_year">🎆 Hitung Mundur Tahun Baru</h3>
|
|
<i class="fas fa-champagne-glasses"></i>
|
|
</div>
|
|
<div class="ramadan-countdown">
|
|
${daysDiff}
|
|
</div>
|
|
<div class="text-center text-sm opacity-90 mt-2">
|
|
<span data-i18n="holiday.countdown.days_until">hari lagi sampai</span> Tahun Baru
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Risk Adjustment Widget
|
|
if (holidayData.trading_adjustments && holidayData.trading_adjustments.risk_reduction) {
|
|
const riskReduction = (1 - holidayData.trading_adjustments.risk_reduction) * 100;
|
|
newYearHtml += `
|
|
<div class="holiday-widget new-year-widget p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold" data-i18n="holiday.risk_adjustment">🛡️ Penyesuaian Risiko</h3>
|
|
<i class="fas fa-shield-alt"></i>
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="text-2xl font-bold">${riskReduction.toFixed(0)}%</div>
|
|
<div class="text-sm opacity-90" data-i18n="holiday.risk_reduction">Pengurangan risiko otomatis</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Goal Setting Widget
|
|
newYearHtml += `
|
|
<div class="holiday-widget new-year-widget p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold" data-i18n="holiday.new_year_resolution">🎯 Resolusi Trading</h3>
|
|
<i class="fas fa-bullseye"></i>
|
|
</div>
|
|
<div class="text-sm text-center" data-i18n="holiday.new_year_goal_setting">
|
|
Waktu yang tepat untuk menetapkan tujuan trading tahun ini
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
holidayWidgets.innerHTML = newYearHtml;
|
|
}
|
|
|
|
// Add floating AI button click handler
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Create floating AI button if it doesn't exist
|
|
if (!document.querySelector('.floating-ai-btn')) {
|
|
const floatingBtn = document.createElement('button');
|
|
floatingBtn.className = 'floating-ai-btn';
|
|
floatingBtn.innerHTML = '🤖';
|
|
floatingBtn.title = window.QuantumBotXI18n.t('dashboard.chat_with_ai_mentor');
|
|
floatingBtn.onclick = () => window.location.href = '/ai-mentor';
|
|
document.body.appendChild(floatingBtn);
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |