05/31 Phase 7: fixed fetch fx rate

This commit is contained in:
2026-05-31 17:10:09 -04:00
parent 84f48a7b4d
commit d74b9e7543
4 changed files with 159 additions and 52 deletions
+44 -10
View File
@@ -6,6 +6,7 @@
.fx-card { background: #0f172a; border-color: #1e293b; color: #f1f5f9; cursor: pointer; transition: all .2s; }
.fx-card:hover { border-color: #3b82f6 !important; }
.period-btn.active { background: #3b82f6; color: #fff; border-color: #3b82f6; }
@keyframes spin { to { transform: rotate(360deg); } }
{% endblock %}
{% block topbar_actions %}
@@ -93,17 +94,21 @@
<div class="col-12 col-xl-4 d-flex flex-column gap-3">
<!-- USD/VND Widget -->
{% if fx %}
<div class="pcard fx-card" onclick="toggleFxChart()">
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="pcard fx-card" id="fxWidget">
<div class="d-flex justify-content-between align-items-start">
<div onclick="toggleFxChart()" style="cursor:pointer;flex:1;">
<div style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.07em;color:#475569;">USD → VND</div>
<div class="mono" style="font-size:24px;font-weight:600;margin-top:4px;">₫{{ "{:,.0f}".format(fx.rate) }}</div>
<div style="font-size:11px;color:#64748b;margin-top:2px;">
1 USD · {{ fx.date.strftime('%b %d, %Y') }}
{% if fx.is_stale %}<span title="Rate may be outdated">⚠️</span>{% endif %}
<div class="mono" id="fxRate" style="font-size:24px;font-weight:600;margin-top:4px;">₫{{ "{:,.0f}".format(fx.rate) }}</div>
<div style="font-size:11px;color:#64748b;margin-top:2px;" id="fxMeta">
1 USD · <span id="fxDate">{{ fx.date.strftime('%b %d, %Y') }}</span>
· <span id="fxSource" style="opacity:.6;">{{ fx.source }}</span>
{% if fx.is_stale %}<span id="fxStale" title="Rate may be outdated" style="color:#f59e0b;"> ⚠ stale</span>{% endif %}
</div>
</div>
<div style="font-size:32px;opacity:.15;font-family:serif;"></div>
<button id="fxRefreshBtn" onclick="refreshFxRate()" title="Refresh rate"
style="background:none;border:none;color:#475569;cursor:pointer;font-size:14px;padding:2px 4px;line-height:1;margin-top:2px;">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
<div id="fxChartWrap" style="display:none;margin-top:12px;">
<div style="height:70px;"><canvas id="fxChart"></canvas></div>
@@ -111,8 +116,15 @@
</div>
{% else %}
<div class="pcard" style="background:#1e293b;border-color:#334155;">
<div style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.07em;color:#475569;">USD → VND</div>
<div style="font-size:13px;color:#94a3b8;margin-top:6px;">Rate unavailable</div>
<div class="d-flex justify-content-between align-items-center">
<div>
<div style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.07em;color:#475569;">USD → VND</div>
<div style="font-size:13px;color:#94a3b8;margin-top:6px;">Rate unavailable</div>
</div>
<button onclick="refreshFxRate()" style="background:none;border:none;color:#475569;cursor:pointer;font-size:14px;">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
</div>
{% endif %}
@@ -285,6 +297,28 @@
})();
var fxChart = null;
function refreshFxRate() {
const btn = document.getElementById('fxRefreshBtn');
if (btn) { btn.innerHTML = '<i class="bi bi-arrow-clockwise" style="animation:spin .7s linear infinite;display:inline-block;"></i>'; }
fetch('/api/fx-refresh', { method:'POST', headers:{'Content-Type':'application/json','X-CSRFToken': csrfToken} })
.then(r => r.json())
.then(data => {
if (data.rate) {
const rateEl = document.getElementById('fxRate');
const dateEl = document.getElementById('fxDate');
const srcEl = document.getElementById('fxSource');
const staleEl = document.getElementById('fxStale');
if (rateEl) rateEl.textContent = '₫' + Math.round(data.rate).toLocaleString();
if (dateEl) dateEl.textContent = data.date;
if (srcEl) srcEl.textContent = data.source;
if (staleEl) staleEl.style.display = data.is_stale ? '' : 'none';
}
if (btn) btn.innerHTML = '<i class="bi bi-arrow-clockwise"></i>';
})
.catch(() => { if (btn) btn.innerHTML = '<i class="bi bi-arrow-clockwise"></i>'; });
}
function toggleFxChart(){
const wrap = document.getElementById('fxChartWrap');
wrap.style.display = wrap.style.display === 'none' ? 'block' : 'none';