diff --git a/templates/index.html b/templates/index.html
index d0df754..4834125 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -325,112 +325,114 @@
if (loading) loading.style.display = 'inline';
- try {
+ try { // <--- THIS WAS MISSING
const response = await fetch('/api/summary');
const result = await response.json();
- // 1. Update Sync Time
- if (result.last_sync) {
- // Ensure format is ISO compliant for mobile browsers
- const dateStr = result.last_sync.replace(' ', 'T');
- const dateObj = new Date(dateStr + (dateStr.includes('Z') ? '' : 'Z'));
-
- document.getElementById('lastSyncTime').innerText = dateObj.toLocaleTimeString([], {
- hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false
- });
- }
- // --- FIX: Extract data from result ---
- const data = result.data;
-
- if (!data || data.length === 0) {
- tbody.innerHTML = '
| No data found. |
';
- return;
- }
+ // --- 1. DATE & TIMEZONE FIX ---
+ if (syncDisplay && result.last_sync) {
+ const dateStr = result.last_sync.trim().replace(' ', 'T');
+ const dateObj = new Date(dateStr + (dateStr.includes('Z') ? '' : 'Z'));
+
+ syncDisplay.innerText = dateObj.toLocaleString([], {
+ month: 'short', day: '2-digit',
+ hour: '2-digit', minute: '2-digit', second: '2-digit',
+ hour12: false
+ });
+
+ syncDisplay.classList.add('text-success');
+ setTimeout(() => syncDisplay.classList.remove('text-success'), 2000);
+ }
+ // --- 2. Extract data from result ---
+ const data = result.data;
let htmlContent = '';
const todayStr = new Date().toISOString().split('T')[0];
- data.forEach(item => {
- // --- A. Error Handling ---
- if (item.error || !item.last_close || item.last_close === 'N/A') {
+ if (data && data.length > 0) {
+ data.forEach(item => {
+ // --- A. Error Handling ---
+ if (item.error || !item.last_close || item.last_close === 'N/A') {
+ htmlContent += `
+
+ ${item.name || item.symbol} |
+
+ Needs Sync
+ Data not found or corrupted.
+ |
+
`;
+ return;
+ }
+
+ // --- B. Preparation & Math ---
+ let displayDate = item.last_date || "N/A";
+ let dateColor = '#dc3545';
+ if (item.last_date && item.last_date.includes('-')) {
+ const parts = item.last_date.split('-');
+ displayDate = `${parts[2]}/${parts[1]}`;
+ dateColor = (item.last_date === todayStr) ? '#28a745' : '#dc3545';
+ }
+
+ const current = parseFloat(item.last_close) || 0;
+ const low = parseFloat(item.low_52) || 0;
+ const high = parseFloat(item.high_52) || 0;
+
+ let rangePct = high > low ? Math.min(Math.max(((current - low) / (high - low)) * 100, 0), 100) : 0;
+ const rangeColor = rangePct > 80 ? 'text-danger' : (rangePct < 20 ? 'text-success' : 'text-muted');
+
+ const rsiVal = item.rsi !== null ? item.rsi : "N/A";
+ const bbRaw = parseFloat(item.bb_pct);
+ const bbDisplay = !isNaN(bbRaw) ? (bbRaw * 100).toFixed(0) + '%' : 'N/A';
+ const z60Val = item.z60 !== null ? item.z60 : "N/A";
+ const z120Val = item.z120 !== null ? item.z120 : "N/A";
+
+ const isFresh = item.last_date === todayStr;
+ const dateBadgeClass = isFresh ? 'badge bg-success-subtle text-success border border-success-subtle' : 'text-muted';
+
+ // --- C. Construct Row ---
htmlContent += `
- ${item.name || item.symbol} |
-
- Needs Sync
- Data not found or corrupted.
+ | ${item.name || item.symbol} |
+ ${displayDate} |
+ ${item.last_close} |
+
+ ${item.change_pct >= 0 ? '+' : ''}${item.change_pct}%
|
+
+
+ ${item.low_52}${item.high_52}
+
+
+ |
+ ${rsiVal} |
+ ${bbDisplay} |
+ ${z60Val} |
+ ${z120Val} |
+ ${typeof formatEma === 'function' ? formatEma(item.last_ema20) : item.last_ema20} |
+ ${typeof formatEma === 'function' ? formatEma(item.last_ema50) : item.last_ema50} |
+ ${typeof formatEma === 'function' ? formatEma(item.last_ema100) : item.last_ema100} |
+ ${typeof formatEma === 'function' ? formatEma(item.last_ema200) : item.last_ema200} |
+ ${typeof formatKD === 'function' ? formatKD(item.kd_values) : 'N/A'} |
`;
- return;
- }
-
- // --- B. Preparation & Math ---
- let displayDate = item.last_date || "N/A";
- let dateColor = '#dc3545';
- if (item.last_date && item.last_date.includes('-')) {
- const parts = item.last_date.split('-');
- displayDate = `${parts[2]}/${parts[1]}`;
- dateColor = (item.last_date === todayStr) ? '#28a745' : '#dc3545';
- }
-
- const current = parseFloat(item.last_close) || 0;
- const low = parseFloat(item.low_52) || 0;
- const high = parseFloat(item.high_52) || 0;
-
- // 52W Range calculation
- let rangePct = high > low ? Math.min(Math.max(((current - low) / (high - low)) * 100, 0), 100) : 0;
- const rangeColor = rangePct > 80 ? 'text-danger' : (rangePct < 20 ? 'text-success' : 'text-muted');
-
- // Indicators
- const rsiVal = item.rsi !== null ? item.rsi : "N/A";
- const bbRaw = parseFloat(item.bb_pct);
- const bbDisplay = !isNaN(bbRaw) ? (bbRaw * 100).toFixed(0) + '%' : 'N/A';
- const z60Val = item.z60 !== null ? item.z60 : "N/A";
- const z120Val = item.z120 !== null ? item.z120 : "N/A";
-
- const isFresh = item.last_date === todayStr;
- const dateBadgeClass = isFresh ? 'badge bg-success-subtle text-success border border-success-subtle' : 'text-muted';
-
- // --- C. Construct Row (14 Columns) ---
- htmlContent += `
-
- ${item.name || item.symbol} |
- ${displayDate} |
- ${item.last_close} |
-
- ${item.change_pct >= 0 ? '+' : ''}${item.change_pct}%
- |
-
-
- ${item.low_52}${item.high_52}
-
-
- |
- ${rsiVal} |
- ${bbDisplay} |
- ${z60Val} |
- ${z120Val} |
- ${typeof formatEma === 'function' ? formatEma(item.last_ema20) : item.last_ema20} |
- ${typeof formatEma === 'function' ? formatEma(item.last_ema50) : item.last_ema50} |
- ${typeof formatEma === 'function' ? formatEma(item.last_ema100) : item.last_ema100} |
- ${typeof formatEma === 'function' ? formatEma(item.last_ema200) : item.last_ema200} |
- ${typeof formatKD === 'function' ? formatKD(item.kd_values) : 'N/A'} |
-
`;
- });
+ });
+ } else {
+ htmlContent = '| No data available |
';
+ }
tbody.innerHTML = htmlContent;
- } catch (error) {
+ } catch (error) {
console.error("Fetch error:", error);
- if (tbody) tbody.innerHTML = '| Error loading data. |
';
+ if (tbody) tbody.innerHTML = '| API Error: Check Console |
';
} finally {
if (loading) loading.style.display = 'none';
}
}
// --- 4. Global Sync ---
async function runGlobalSync() {
+ console.log("Sync triggered...");
const syncBtn = document.getElementById('syncBtn');
const loading = document.getElementById('loading');
if (!syncBtn) return;
@@ -457,17 +459,24 @@
// This checks if the Flask server is responding every 30 seconds
async function checkStatus() {
const indicator = document.getElementById('statusIndicator');
+
+ // GUARD: If the element is missing from HTML, exit immediately
+ if (!indicator) return;
+
try {
- const response = await fetch('/api/summary'); // Or a dedicated /health endpoint
+ const response = await fetch('/api/summary');
if (response.ok) {
- indicator.innerHTML = '● Online';
- indicator.className = 'text-success';
+ // Using a span for the dot allows us to animate it separately if desired
+ indicator.innerHTML = '● Online';
+ indicator.className = 'text-success fw-bold';
} else {
throw new Error();
}
} catch (e) {
- indicator.innerHTML = '● Offline';
- indicator.className = 'text-danger';
+ if (indicator) {
+ indicator.innerHTML = '● Offline';
+ indicator.className = 'text-danger fw-bold';
+ }
}
}