add date on the last Sync display
This commit is contained in:
+99
-90
@@ -325,112 +325,114 @@
|
|||||||
|
|
||||||
if (loading) loading.style.display = 'inline';
|
if (loading) loading.style.display = 'inline';
|
||||||
|
|
||||||
try {
|
try { // <--- THIS WAS MISSING
|
||||||
const response = await fetch('/api/summary');
|
const response = await fetch('/api/summary');
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
// 1. Update Sync Time
|
// --- 1. DATE & TIMEZONE FIX ---
|
||||||
if (result.last_sync) {
|
if (syncDisplay && result.last_sync) {
|
||||||
// Ensure format is ISO compliant for mobile browsers
|
const dateStr = result.last_sync.trim().replace(' ', 'T');
|
||||||
const dateStr = result.last_sync.replace(' ', 'T');
|
const dateObj = new Date(dateStr + (dateStr.includes('Z') ? '' : 'Z'));
|
||||||
const dateObj = new Date(dateStr + (dateStr.includes('Z') ? '' : 'Z'));
|
|
||||||
|
syncDisplay.innerText = dateObj.toLocaleString([], {
|
||||||
document.getElementById('lastSyncTime').innerText = dateObj.toLocaleTimeString([], {
|
month: 'short', day: '2-digit',
|
||||||
hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false
|
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
||||||
});
|
hour12: false
|
||||||
}
|
});
|
||||||
// --- FIX: Extract data from result ---
|
|
||||||
const data = result.data;
|
syncDisplay.classList.add('text-success');
|
||||||
|
setTimeout(() => syncDisplay.classList.remove('text-success'), 2000);
|
||||||
if (!data || data.length === 0) {
|
}
|
||||||
tbody.innerHTML = '<tr><td colspan="14" class="text-center signal-neutral">No data found.</td></tr>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// --- 2. Extract data from result ---
|
||||||
|
const data = result.data;
|
||||||
let htmlContent = '';
|
let htmlContent = '';
|
||||||
const todayStr = new Date().toISOString().split('T')[0];
|
const todayStr = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
data.forEach(item => {
|
if (data && data.length > 0) {
|
||||||
// --- A. Error Handling ---
|
data.forEach(item => {
|
||||||
if (item.error || !item.last_close || item.last_close === 'N/A') {
|
// --- A. Error Handling ---
|
||||||
|
if (item.error || !item.last_close || item.last_close === 'N/A') {
|
||||||
|
htmlContent += `
|
||||||
|
<tr>
|
||||||
|
<td><div class="fw-bold text-muted">${item.name || item.symbol}</div></td>
|
||||||
|
<td colspan="13" class="text-center p-3">
|
||||||
|
<span class="badge bg-warning text-dark">Needs Sync</span>
|
||||||
|
<small class="text-muted ms-2">Data not found or corrupted.</small>
|
||||||
|
</td>
|
||||||
|
</tr>`;
|
||||||
|
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 += `
|
htmlContent += `
|
||||||
<tr>
|
<tr>
|
||||||
<td><div class="fw-bold text-muted">${item.name || item.symbol}</div></td>
|
<td><div class="fw-bold">${item.name || item.symbol}</div></td>
|
||||||
<td colspan="13" class="text-center p-3">
|
<td><span class="table-date ${dateBadgeClass}" style="color: ${dateColor} !important;">${displayDate}</span></td>
|
||||||
<span class="badge bg-warning text-dark">Needs Sync</span>
|
<td class="fw-bold">${item.last_close}</td>
|
||||||
<small class="text-muted ms-2">Data not found or corrupted.</small>
|
<td class="${item.change_pct >= 0 ? 'text-up' : 'text-down'}">
|
||||||
|
${item.change_pct >= 0 ? '+' : ''}${item.change_pct}%
|
||||||
</td>
|
</td>
|
||||||
|
<td class="${rangeColor} small">
|
||||||
|
<div class="d-flex justify-content-between mb-1" style="min-width: 100px; font-size: 0.7rem;">
|
||||||
|
<span>${item.low_52}</span><span>${item.high_52}</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress" style="height: 4px;">
|
||||||
|
<div class="progress-bar bg-primary" style="width: ${rangePct}%"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td ${getRSIColor(rsiVal)} class="text-center">${rsiVal}</td>
|
||||||
|
<td ${getBBColor(item.bb_pct)} class="text-center">${bbDisplay}</td>
|
||||||
|
<td ${getZColor(z60Val)} class="text-center">${z60Val}</td>
|
||||||
|
<td ${getZColor(z120Val)} class="text-center">${z120Val}</td>
|
||||||
|
<td>${typeof formatEma === 'function' ? formatEma(item.last_ema20) : item.last_ema20}</td>
|
||||||
|
<td>${typeof formatEma === 'function' ? formatEma(item.last_ema50) : item.last_ema50}</td>
|
||||||
|
<td>${typeof formatEma === 'function' ? formatEma(item.last_ema100) : item.last_ema100}</td>
|
||||||
|
<td>${typeof formatEma === 'function' ? formatEma(item.last_ema200) : item.last_ema200}</td>
|
||||||
|
<td>${typeof formatKD === 'function' ? formatKD(item.kd_values) : 'N/A'}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
return;
|
});
|
||||||
}
|
} else {
|
||||||
|
htmlContent = '<tr><td colspan="14" class="text-center">No data available</td></tr>';
|
||||||
// --- 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 += `
|
|
||||||
<tr>
|
|
||||||
<td><div class="fw-bold">${item.name || item.symbol}</div></td>
|
|
||||||
<td><span class="table-date ${dateBadgeClass}" style="color: ${dateColor} !important;">${displayDate}</span></td>
|
|
||||||
<td class="fw-bold">${item.last_close}</td>
|
|
||||||
<td class="${item.change_pct >= 0 ? 'text-up' : 'text-down'}">
|
|
||||||
${item.change_pct >= 0 ? '+' : ''}${item.change_pct}%
|
|
||||||
</td>
|
|
||||||
<td class="${rangeColor} small">
|
|
||||||
<div class="d-flex justify-content-between mb-1" style="min-width: 100px; font-size: 0.7rem;">
|
|
||||||
<span>${item.low_52}</span><span>${item.high_52}</span>
|
|
||||||
</div>
|
|
||||||
<div class="progress" style="height: 4px;">
|
|
||||||
<div class="progress-bar bg-primary" style="width: ${rangePct}%"></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td ${getRSIColor(rsiVal)} class="text-center">${rsiVal}</td>
|
|
||||||
<td ${getBBColor(item.bb_pct)} class="text-center">${bbDisplay}</td>
|
|
||||||
<td ${getZColor(z60Val)} class="text-center">${z60Val}</td>
|
|
||||||
<td ${getZColor(z120Val)} class="text-center">${z120Val}</td>
|
|
||||||
<td>${typeof formatEma === 'function' ? formatEma(item.last_ema20) : item.last_ema20}</td>
|
|
||||||
<td>${typeof formatEma === 'function' ? formatEma(item.last_ema50) : item.last_ema50}</td>
|
|
||||||
<td>${typeof formatEma === 'function' ? formatEma(item.last_ema100) : item.last_ema100}</td>
|
|
||||||
<td>${typeof formatEma === 'function' ? formatEma(item.last_ema200) : item.last_ema200}</td>
|
|
||||||
<td>${typeof formatKD === 'function' ? formatKD(item.kd_values) : 'N/A'}</td>
|
|
||||||
</tr>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
tbody.innerHTML = htmlContent;
|
tbody.innerHTML = htmlContent;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Fetch error:", error);
|
console.error("Fetch error:", error);
|
||||||
if (tbody) tbody.innerHTML = '<tr><td colspan="14" class="text-danger p-4 text-center">Error loading data.</td></tr>';
|
if (tbody) tbody.innerHTML = '<tr><td colspan="14" class="text-danger p-4 text-center">API Error: Check Console</td></tr>';
|
||||||
} finally {
|
} finally {
|
||||||
if (loading) loading.style.display = 'none';
|
if (loading) loading.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// --- 4. Global Sync ---
|
// --- 4. Global Sync ---
|
||||||
async function runGlobalSync() {
|
async function runGlobalSync() {
|
||||||
|
console.log("Sync triggered...");
|
||||||
const syncBtn = document.getElementById('syncBtn');
|
const syncBtn = document.getElementById('syncBtn');
|
||||||
const loading = document.getElementById('loading');
|
const loading = document.getElementById('loading');
|
||||||
if (!syncBtn) return;
|
if (!syncBtn) return;
|
||||||
@@ -457,17 +459,24 @@
|
|||||||
// This checks if the Flask server is responding every 30 seconds
|
// This checks if the Flask server is responding every 30 seconds
|
||||||
async function checkStatus() {
|
async function checkStatus() {
|
||||||
const indicator = document.getElementById('statusIndicator');
|
const indicator = document.getElementById('statusIndicator');
|
||||||
|
|
||||||
|
// GUARD: If the element is missing from HTML, exit immediately
|
||||||
|
if (!indicator) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/summary'); // Or a dedicated /health endpoint
|
const response = await fetch('/api/summary');
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
indicator.innerHTML = '● Online';
|
// Using a span for the dot allows us to animate it separately if desired
|
||||||
indicator.className = 'text-success';
|
indicator.innerHTML = '<span class="status-dot-pulse">●</span> Online';
|
||||||
|
indicator.className = 'text-success fw-bold';
|
||||||
} else {
|
} else {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
indicator.innerHTML = '● Offline';
|
if (indicator) {
|
||||||
indicator.className = 'text-danger';
|
indicator.innerHTML = '● Offline';
|
||||||
|
indicator.className = 'text-danger fw-bold';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user