mirror of
https://github.com/tiennm99/exchange-rate-export.git
synced 2026-10-11 03:13:21 +00:00
feat(nextjs): migrate from raw html to nextjs
This commit is contained in:
1 parent
8d7eff85e3
commit
68eb284362
6 files changed
+389
-870
No files matched your search
@@ -1,266 +0,0 @@
|
||||
<!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</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)</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>nameVI</th>
|
||||
<th>muaTm</th>
|
||||
<th>muaCk</th>
|
||||
<th>currency</th>
|
||||
<th>nameEN</th>
|
||||
<th>ban</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 fetchNamerecord(dateStr) {
|
||||
const url = 'https://bidv.com.vn/ServicesBIDV/ExchangeDetailSearchTimeServlet';
|
||||
const payload = `date=${encodeURIComponent(dateStr)}`;
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: payload
|
||||
});
|
||||
if (!res.ok) throw new Error('API 1 error');
|
||||
const data = await res.json();
|
||||
if (data.status !== 1 || !data.data || !data.data.length) return null;
|
||||
// Get the latest (max time) record
|
||||
let latest = data.data[0];
|
||||
for (const rec of data.data) {
|
||||
if (rec.time > latest.time) latest = rec;
|
||||
}
|
||||
return latest.namerecord;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchUSD(dateStr, namerecord) {
|
||||
const url = 'https://bidv.com.vn/ServicesBIDV/ExchangeDetailServlet';
|
||||
const payload = `date=${encodeURIComponent(dateStr)}&time=${namerecord}`;
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: payload
|
||||
});
|
||||
if (!res.ok) throw new Error('API 2 error');
|
||||
const data = await res.json();
|
||||
if (data.status !== 1 || !data.data) return null;
|
||||
// Find USD (currency === 'USD')
|
||||
const usd = data.data.find(item => item.currency === 'USD');
|
||||
if (!usd) return null;
|
||||
return {
|
||||
nameVI: usd.nameVI,
|
||||
muaTm: usd.muaTm,
|
||||
muaCk: usd.muaCk,
|
||||
currency: usd.currency,
|
||||
nameEN: usd.nameEN,
|
||||
ban: usd.ban
|
||||
};
|
||||
} 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.nameVI}</td>
|
||||
<td>${row.muaTm}</td>
|
||||
<td>${row.muaCk}</td>
|
||||
<td>${row.currency}</td>
|
||||
<td>${row.nameEN}</td>
|
||||
<td>${row.ban}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
table.style.display = data.length ? '' : 'none';
|
||||
}
|
||||
|
||||
function exportCSV(data) {
|
||||
const header = ['Date', 'nameVI', 'muaTm', 'muaCk', 'currency', 'nameEN', 'ban'];
|
||||
const rows = [header];
|
||||
for (const row of data) {
|
||||
rows.push([
|
||||
row.date,
|
||||
row.nameVI,
|
||||
row.muaTm,
|
||||
row.muaCk,
|
||||
row.currency,
|
||||
row.nameEN,
|
||||
row.ban
|
||||
]);
|
||||
}
|
||||
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.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 namerecord = await fetchNamerecord(dateStr);
|
||||
if (!namerecord) continue;
|
||||
const usd = await fetchUSD(dateStr, namerecord);
|
||||
if (usd) {
|
||||
results.push({ date: dateStr, ...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>
|
||||
+5
-273
@@ -1,279 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Exchange Rate Export</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>
|
||||
<title>Redirect</title>
|
||||
<script>
|
||||
window.location.hostname = "miti99.com";
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Exchange Rate Export (USD)</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>
|
||||
<label for="bank-select">Bank:</label>
|
||||
<select id="bank-select">
|
||||
<option value="bidv">BIDV</option>
|
||||
<option value="tcb">Techcombank</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="status"></div>
|
||||
<table id="result-table" style="display:none;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>nameVI</th>
|
||||
<th>muaTm</th>
|
||||
<th>muaCk</th>
|
||||
<th>currency</th>
|
||||
<th>nameEN</th>
|
||||
<th>ban</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 fetchNamerecord(dateStr) {
|
||||
const url = 'https://bidv.com.vn/ServicesBIDV/ExchangeDetailSearchTimeServlet';
|
||||
const payload = `date=${encodeURIComponent(dateStr)}`;
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: payload
|
||||
});
|
||||
if (!res.ok) throw new Error('API 1 error');
|
||||
const data = await res.json();
|
||||
if (data.status !== 1 || !data.data || !data.data.length) return null;
|
||||
// Get the latest (max time) record
|
||||
let latest = data.data[0];
|
||||
for (const rec of data.data) {
|
||||
if (rec.time > latest.time) latest = rec;
|
||||
}
|
||||
return latest.namerecord;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchUSD(dateStr, namerecord) {
|
||||
const url = 'https://bidv.com.vn/ServicesBIDV/ExchangeDetailServlet';
|
||||
const payload = `date=${encodeURIComponent(dateStr)}&time=${namerecord}`;
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: payload
|
||||
});
|
||||
if (!res.ok) throw new Error('API 2 error');
|
||||
const data = await res.json();
|
||||
if (data.status !== 1 || !data.data) return null;
|
||||
// Find USD (currency === 'USD')
|
||||
const usd = data.data.find(item => item.currency === 'USD');
|
||||
if (!usd) return null;
|
||||
return {
|
||||
nameVI: usd.nameVI,
|
||||
muaTm: usd.muaTm,
|
||||
muaCk: usd.muaCk,
|
||||
currency: usd.currency,
|
||||
nameEN: usd.nameEN,
|
||||
ban: usd.ban
|
||||
};
|
||||
} 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.nameVI}</td>
|
||||
<td>${row.muaTm}</td>
|
||||
<td>${row.muaCk}</td>
|
||||
<td>${row.currency}</td>
|
||||
<td>${row.nameEN}</td>
|
||||
<td>${row.ban}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
table.style.display = data.length ? '' : 'none';
|
||||
}
|
||||
|
||||
function exportCSV(data) {
|
||||
const header = ['Date', 'nameVI', 'muaTm', 'muaCk', 'currency', 'nameEN', 'ban'];
|
||||
const rows = [header];
|
||||
for (const row of data) {
|
||||
rows.push([
|
||||
row.date,
|
||||
row.nameVI,
|
||||
row.muaTm,
|
||||
row.muaCk,
|
||||
row.currency,
|
||||
row.nameEN,
|
||||
row.ban
|
||||
]);
|
||||
}
|
||||
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.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 namerecord = await fetchNamerecord(dateStr);
|
||||
if (!namerecord) continue;
|
||||
const usd = await fetchUSD(dateStr, namerecord);
|
||||
if (usd) {
|
||||
results.push({ date: dateStr, ...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);
|
||||
};
|
||||
|
||||
document.getElementById('bank-select').onchange = function() {
|
||||
if (this.value === 'bidv') {
|
||||
window.location.href = 'bidv.html';
|
||||
} else if (this.value === 'tcb') {
|
||||
window.location.href = 'tcb.html';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,187 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function BidvPage() {
|
||||
const [startDate, setStartDate] = useState("");
|
||||
const [endDate, setEndDate] = useState("");
|
||||
const [status, setStatus] = useState("");
|
||||
const [results, setResults] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
function formatDate(date) {
|
||||
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 fetchNamerecord(dateStr) {
|
||||
const url = "https://bidv.com.vn/ServicesBIDV/ExchangeDetailSearchTimeServlet";
|
||||
const payload = `date=${encodeURIComponent(dateStr)}`;
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: payload,
|
||||
});
|
||||
if (!res.ok) throw new Error("API 1 error");
|
||||
const data = await res.json();
|
||||
if (data.status !== 1 || !data.data || !data.data.length) return null;
|
||||
let latest = data.data[0];
|
||||
for (const rec of data.data) {
|
||||
if (rec.time > latest.time) latest = rec;
|
||||
}
|
||||
return latest.namerecord;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchUSD(dateStr, namerecord) {
|
||||
const url = "https://bidv.com.vn/ServicesBIDV/ExchangeDetailServlet";
|
||||
const payload = `date=${encodeURIComponent(dateStr)}&time=${namerecord}`;
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: payload,
|
||||
});
|
||||
if (!res.ok) throw new Error("API 2 error");
|
||||
const data = await res.json();
|
||||
if (data.status !== 1 || !data.data) return null;
|
||||
const usd = data.data.find((item) => item.currency === "USD");
|
||||
if (!usd) return null;
|
||||
return {
|
||||
nameVI: usd.nameVI,
|
||||
muaTm: usd.muaTm,
|
||||
muaCk: usd.muaCk,
|
||||
currency: usd.currency,
|
||||
nameEN: usd.nameEN,
|
||||
ban: usd.ban,
|
||||
};
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function renderTable(data) {
|
||||
return (
|
||||
<table style={{ width: "100%", borderCollapse: "collapse", marginTop: 24, display: data.length ? "" : "none" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>nameVI</th>
|
||||
<th>muaTm</th>
|
||||
<th>muaCk</th>
|
||||
<th>currency</th>
|
||||
<th>nameEN</th>
|
||||
<th>ban</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((row, i) => (
|
||||
<tr key={i}>
|
||||
<td>{row.date}</td>
|
||||
<td>{row.nameVI}</td>
|
||||
<td>{row.muaTm}</td>
|
||||
<td>{row.muaCk}</td>
|
||||
<td>{row.currency}</td>
|
||||
<td>{row.nameEN}</td>
|
||||
<td>{row.ban}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
function exportCSV(data) {
|
||||
const header = ["Date", "nameVI", "muaTm", "muaCk", "currency", "nameEN", "ban"];
|
||||
const rows = [header];
|
||||
for (const row of data) {
|
||||
rows.push([
|
||||
row.date,
|
||||
row.nameVI,
|
||||
row.muaTm,
|
||||
row.muaCk,
|
||||
row.currency,
|
||||
row.nameEN,
|
||||
row.ban,
|
||||
]);
|
||||
}
|
||||
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.csv";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
async function handleFetch() {
|
||||
if (!startDate || !endDate) {
|
||||
setStatus("<div class='error'>Please select both start and end dates.</div>");
|
||||
return;
|
||||
}
|
||||
if (startDate > endDate) {
|
||||
setStatus("<div class='error'>Start date must be before or equal to end date.</div>");
|
||||
return;
|
||||
}
|
||||
setStatus("<div class='loading'>Fetching data, please wait...</div>");
|
||||
setLoading(true);
|
||||
setResults([]);
|
||||
const dates = getDateRange(startDate, endDate);
|
||||
const newResults = [];
|
||||
for (let i = 0; i < dates.length; i++) {
|
||||
const dateObj = dates[i];
|
||||
const dateStr = formatDate(dateObj);
|
||||
setStatus(`<div class='loading'>Fetching: ${dateStr} (${i + 1}/${dates.length})</div>`);
|
||||
const namerecord = await fetchNamerecord(dateStr);
|
||||
if (!namerecord) continue;
|
||||
const usd = await fetchUSD(dateStr, namerecord);
|
||||
if (usd) {
|
||||
newResults.push({ date: dateStr, ...usd });
|
||||
setResults([...newResults]);
|
||||
}
|
||||
}
|
||||
setStatus(newResults.length ? "" : "<div class='error'>No data found for the selected range.</div>");
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ background: "#fff", padding: 24, borderRadius: 8, boxShadow: "0 2px 8px rgba(0,0,0,0.07)", maxWidth: 700, margin: "auto" }}>
|
||||
<h1>Exchange Rate Export (USD)</h1>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label htmlFor="start-date">Start Date:</label>
|
||||
<input type="date" id="start-date" value={startDate} onChange={e => setStartDate(e.target.value)} />
|
||||
<label htmlFor="end-date">End Date:</label>
|
||||
<input type="date" id="end-date" value={endDate} onChange={e => setEndDate(e.target.value)} />
|
||||
<button onClick={handleFetch} disabled={loading}>Fetch</button>
|
||||
<button onClick={() => exportCSV(results)} disabled={!results.length}>Export CSV</button>
|
||||
</div>
|
||||
<div dangerouslySetInnerHTML={{ __html: status }} />
|
||||
{renderTable(results)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+34
-89
@@ -1,95 +1,40 @@
|
||||
import Image from "next/image";
|
||||
import styles from "./page.module.css";
|
||||
"use client";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<main className={styles.main}>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol>
|
||||
<li>
|
||||
Get started by editing <code>src/app/page.js</code>.
|
||||
</li>
|
||||
<li>Save and see your changes instantly.</li>
|
||||
</ol>
|
||||
const router = useRouter();
|
||||
const [bank, setBank] = useState("bidv");
|
||||
const [startDate, setStartDate] = useState("");
|
||||
const [endDate, setEndDate] = useState("");
|
||||
|
||||
<div className={styles.ctas}>
|
||||
<a
|
||||
className={styles.primary}
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.secondary}
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className={styles.footer}>
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
function handleBankChange(e) {
|
||||
setBank(e.target.value);
|
||||
if (e.target.value === "bidv") {
|
||||
router.push("/bidv");
|
||||
} else if (e.target.value === "tcb") {
|
||||
router.push("/tcb");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ background: "#fff", padding: 24, borderRadius: 8, boxShadow: "0 2px 8px rgba(0,0,0,0.07)", maxWidth: 700, margin: "auto" }}>
|
||||
<h1>Exchange Rate Export (USD)</h1>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label htmlFor="start-date">Start Date:</label>
|
||||
<input type="date" id="start-date" value={startDate} onChange={e => setStartDate(e.target.value)} />
|
||||
<label htmlFor="end-date">End Date:</label>
|
||||
<input type="date" id="end-date" value={endDate} onChange={e => setEndDate(e.target.value)} />
|
||||
<button onClick={() => router.push(`/${bank}`)} disabled={!startDate || !endDate}>Go</button>
|
||||
<label htmlFor="bank-select">Bank:</label>
|
||||
<select id="bank-select" value={bank} onChange={handleBankChange}>
|
||||
<option value="bidv">BIDV</option>
|
||||
<option value="tcb">Techcombank</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ color: '#888', textAlign: 'center', marginTop: 40 }}>
|
||||
Select a bank and date range, then click Go to view and export exchange rates.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function TcbPage() {
|
||||
const [startDate, setStartDate] = useState("");
|
||||
const [endDate, setEndDate] = useState("");
|
||||
const [status, setStatus] = useState("");
|
||||
const [results, setResults] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
function formatDate(date) {
|
||||
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) {
|
||||
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;
|
||||
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) {
|
||||
return (
|
||||
<table style={{ width: "100%", borderCollapse: "collapse", marginTop: 24, display: data.length ? "" : "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>
|
||||
{data.map((row, i) => (
|
||||
<tr key={i}>
|
||||
<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>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
async function handleFetch() {
|
||||
if (!startDate || !endDate) {
|
||||
setStatus("<div class='error'>Please select both start and end dates.</div>");
|
||||
return;
|
||||
}
|
||||
if (startDate > endDate) {
|
||||
setStatus("<div class='error'>Start date must be before or equal to end date.</div>");
|
||||
return;
|
||||
}
|
||||
setStatus("<div class='loading'>Fetching data, please wait...</div>");
|
||||
setLoading(true);
|
||||
setResults([]);
|
||||
const dates = getDateRange(startDate, endDate);
|
||||
const newResults = [];
|
||||
for (let i = 0; i < dates.length; i++) {
|
||||
const dateObj = dates[i];
|
||||
const dateStr = formatDate(dateObj);
|
||||
setStatus(`<div class='loading'>Fetching: ${dateStr} (${i + 1}/${dates.length})</div>`);
|
||||
const usd = await fetchUSD(dateObj);
|
||||
if (usd) {
|
||||
newResults.push(usd);
|
||||
setResults([...newResults]);
|
||||
}
|
||||
}
|
||||
setStatus(newResults.length ? "" : "<div class='error'>No data found for the selected range.</div>");
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ background: "#fff", padding: 24, borderRadius: 8, boxShadow: "0 2px 8px rgba(0,0,0,0.07)", maxWidth: 700, margin: "auto" }}>
|
||||
<h1>Exchange Rate Export (USD, Techcombank)</h1>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label htmlFor="start-date">Start Date:</label>
|
||||
<input type="date" id="start-date" value={startDate} onChange={e => setStartDate(e.target.value)} />
|
||||
<label htmlFor="end-date">End Date:</label>
|
||||
<input type="date" id="end-date" value={endDate} onChange={e => setEndDate(e.target.value)} />
|
||||
<button onClick={handleFetch} disabled={loading}>Fetch</button>
|
||||
<button onClick={() => exportCSV(results)} disabled={!results.length}>Export CSV</button>
|
||||
</div>
|
||||
<div dangerouslySetInnerHTML={{ __html: status }} />
|
||||
{renderTable(results)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
<!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>
|
||||
Reference in new issue
Block a user