mirror of
https://github.com/tiennm99/exchange-rate-export.git
synced 2026-05-14 04:58:22 +00:00
242 lines
8.6 KiB
HTML
242 lines
8.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Exchange Rate Export - Techcombank</title>
|
|
<style>
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
margin: 40px;
|
|
background: #f7f7f7;
|
|
}
|
|
.container {
|
|
background: #fff;
|
|
padding: 24px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
|
|
max-width: 700px;
|
|
margin: auto;
|
|
}
|
|
h1 {
|
|
text-align: center;
|
|
}
|
|
label {
|
|
margin-right: 8px;
|
|
}
|
|
input[type="date"] {
|
|
margin-right: 16px;
|
|
padding: 4px 8px;
|
|
}
|
|
button {
|
|
padding: 8px 16px;
|
|
margin-right: 8px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
background: #007bff;
|
|
color: #fff;
|
|
cursor: pointer;
|
|
font-size: 1rem;
|
|
}
|
|
button:disabled {
|
|
background: #aaa;
|
|
cursor: not-allowed;
|
|
}
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-top: 24px;
|
|
}
|
|
th, td {
|
|
border: 1px solid #ddd;
|
|
padding: 8px;
|
|
text-align: center;
|
|
}
|
|
th {
|
|
background: #007bff;
|
|
color: #fff;
|
|
}
|
|
.loading {
|
|
text-align: center;
|
|
margin-top: 20px;
|
|
color: #007bff;
|
|
}
|
|
.error {
|
|
color: red;
|
|
text-align: center;
|
|
margin-top: 20px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>Exchange Rate Export (USD, Techcombank)</h1>
|
|
<div style="margin-bottom: 20px;">
|
|
<label for="start-date">Start Date:</label>
|
|
<input type="date" id="start-date">
|
|
<label for="end-date">End Date:</label>
|
|
<input type="date" id="end-date">
|
|
<button id="fetch-btn">Fetch</button>
|
|
<button id="export-btn" disabled>Export CSV</button>
|
|
</div>
|
|
<div id="status"></div>
|
|
<table id="result-table" style="display:none;">
|
|
<thead>
|
|
<tr>
|
|
<th>Date</th>
|
|
<th>label</th>
|
|
<th>askRate</th>
|
|
<th>bidRateCK</th>
|
|
<th>bidRateTM</th>
|
|
<th>askRateTM</th>
|
|
<th>sourceCurrency</th>
|
|
<th>targetCurrency</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
<script>
|
|
const fetchBtn = document.getElementById('fetch-btn');
|
|
const exportBtn = document.getElementById('export-btn');
|
|
const startDateInput = document.getElementById('start-date');
|
|
const endDateInput = document.getElementById('end-date');
|
|
const statusDiv = document.getElementById('status');
|
|
const table = document.getElementById('result-table');
|
|
const tbody = table.querySelector('tbody');
|
|
let results = [];
|
|
|
|
function formatDate(date) {
|
|
// Format as dd/MM/yyyy
|
|
const d = new Date(date);
|
|
const day = String(d.getDate()).padStart(2, '0');
|
|
const month = String(d.getMonth() + 1).padStart(2, '0');
|
|
const year = d.getFullYear();
|
|
return `${day}/${month}/${year}`;
|
|
}
|
|
|
|
function getDateRange(start, end) {
|
|
const dates = [];
|
|
let current = new Date(start);
|
|
const last = new Date(end);
|
|
while (current <= last) {
|
|
dates.push(new Date(current));
|
|
current.setDate(current.getDate() + 1);
|
|
}
|
|
return dates;
|
|
}
|
|
|
|
async function fetchUSD(dateObj) {
|
|
// Techcombank API expects yyyy-mm-dd in URL
|
|
const yyyy = dateObj.getFullYear();
|
|
const mm = String(dateObj.getMonth() + 1).padStart(2, '0');
|
|
const dd = String(dateObj.getDate()).padStart(2, '0');
|
|
const dateStr = `${yyyy}-${mm}-${dd}`;
|
|
const url = `https://techcombank.com/content/techcombank/web/vn/vi/cong-cu-tien-ich/ty-gia/_jcr_content.exchange-rates.${dateStr}.integration.json`;
|
|
try {
|
|
const res = await fetch(url);
|
|
if (!res.ok) throw new Error('API error');
|
|
const data = await res.json();
|
|
if (!data.exchangeRate || !data.exchangeRate.data) return null;
|
|
// Find USD (50,100)
|
|
const usd = data.exchangeRate.data.find(item => item.label === 'USD (50,100)');
|
|
if (!usd) return null;
|
|
return {
|
|
date: formatDate(dateObj),
|
|
label: usd.label,
|
|
askRate: usd.askRate,
|
|
bidRateCK: usd.bidRateCK,
|
|
bidRateTM: usd.bidRateTM,
|
|
askRateTM: usd.askRateTM,
|
|
sourceCurrency: usd.sourceCurrency,
|
|
targetCurrency: usd.targetCurrency
|
|
};
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function renderTable(data) {
|
|
tbody.innerHTML = '';
|
|
for (const row of data) {
|
|
const tr = document.createElement('tr');
|
|
tr.innerHTML = `
|
|
<td>${row.date}</td>
|
|
<td>${row.label}</td>
|
|
<td>${row.askRate || ''}</td>
|
|
<td>${row.bidRateCK || ''}</td>
|
|
<td>${row.bidRateTM || ''}</td>
|
|
<td>${row.askRateTM || ''}</td>
|
|
<td>${row.sourceCurrency}</td>
|
|
<td>${row.targetCurrency}</td>
|
|
`;
|
|
tbody.appendChild(tr);
|
|
}
|
|
table.style.display = data.length ? '' : 'none';
|
|
}
|
|
|
|
function exportCSV(data) {
|
|
const header = ['Date', 'label', 'askRate', 'bidRateCK', 'bidRateTM', 'askRateTM', 'sourceCurrency', 'targetCurrency'];
|
|
const rows = [header];
|
|
for (const row of data) {
|
|
rows.push([
|
|
row.date,
|
|
row.label,
|
|
row.askRate,
|
|
row.bidRateCK,
|
|
row.bidRateTM,
|
|
row.askRateTM,
|
|
row.sourceCurrency,
|
|
row.targetCurrency
|
|
]);
|
|
}
|
|
const csvContent = rows.map(r => r.map(cell => '"' + String(cell || '').replace(/"/g, '""') + '"').join(',')).join('\r\n');
|
|
const blob = new Blob([csvContent], { type: 'text/csv' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'usd_exchange_rates_tcb.csv';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
fetchBtn.onclick = async () => {
|
|
const start = startDateInput.value;
|
|
const end = endDateInput.value;
|
|
if (!start || !end) {
|
|
statusDiv.innerHTML = '<div class="error">Please select both start and end dates.</div>';
|
|
return;
|
|
}
|
|
if (start > end) {
|
|
statusDiv.innerHTML = '<div class="error">Start date must be before or equal to end date.</div>';
|
|
return;
|
|
}
|
|
statusDiv.innerHTML = '<div class="loading">Fetching data, please wait...</div>';
|
|
fetchBtn.disabled = true;
|
|
exportBtn.disabled = true;
|
|
results = [];
|
|
renderTable([]);
|
|
const dates = getDateRange(start, end);
|
|
for (let i = 0; i < dates.length; i++) {
|
|
const dateObj = dates[i];
|
|
const dateStr = formatDate(dateObj);
|
|
statusDiv.innerHTML = `<div class=\"loading\">Fetching: ${dateStr} (${i+1}/${dates.length})</div>`;
|
|
const usd = await fetchUSD(dateObj);
|
|
if (usd) {
|
|
results.push(usd);
|
|
renderTable(results);
|
|
}
|
|
}
|
|
statusDiv.innerHTML = results.length ? '' : '<div class="error">No data found for the selected range.</div>';
|
|
fetchBtn.disabled = false;
|
|
exportBtn.disabled = results.length === 0;
|
|
};
|
|
|
|
exportBtn.onclick = () => {
|
|
if (results.length) exportCSV(results);
|
|
};
|
|
</script>
|
|
</body>
|
|
</html> |