From 68eb28436282b63ae5b2463df7297ea61f747942 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Thu, 26 Jun 2025 20:24:06 +0700 Subject: [PATCH] feat(nextjs): migrate from raw html to nextjs --- bidv.html | 266 ---------------------------------------- index.html | 278 +----------------------------------------- src/app/bidv/page.jsx | 187 ++++++++++++++++++++++++++++ src/app/page.js | 123 ++++++------------- src/app/tcb/page.jsx | 163 +++++++++++++++++++++++++ tcb.html | 242 ------------------------------------ 6 files changed, 389 insertions(+), 870 deletions(-) delete mode 100644 bidv.html create mode 100644 src/app/bidv/page.jsx create mode 100644 src/app/tcb/page.jsx delete mode 100644 tcb.html diff --git a/bidv.html b/bidv.html deleted file mode 100644 index 134926c..0000000 --- a/bidv.html +++ /dev/null @@ -1,266 +0,0 @@ - - - - - - Exchange Rate Export - - - -
-

Exchange Rate Export (USD)

-
- - - - - - -
-
- - - - - - - - - - - - - - -
- - - \ No newline at end of file diff --git a/index.html b/index.html index bc6502b..a091bd4 100644 --- a/index.html +++ b/index.html @@ -1,279 +1,11 @@ - + - - - Exchange Rate Export - + Redirect + -
-

Exchange Rate Export (USD)

-
- - - - - - - - -
-
- - - - - - - - - - - - - - -
- diff --git a/src/app/bidv/page.jsx b/src/app/bidv/page.jsx new file mode 100644 index 0000000..411faa5 --- /dev/null +++ b/src/app/bidv/page.jsx @@ -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 ( + + + + + + + + + + + + + + {data.map((row, i) => ( + + + + + + + + + + ))} + +
DatenameVImuaTmmuaCkcurrencynameENban
{row.date}{row.nameVI}{row.muaTm}{row.muaCk}{row.currency}{row.nameEN}{row.ban}
+ ); + } + + 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("
Please select both start and end dates.
"); + return; + } + if (startDate > endDate) { + setStatus("
Start date must be before or equal to end date.
"); + return; + } + setStatus("
Fetching data, please wait...
"); + 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(`
Fetching: ${dateStr} (${i + 1}/${dates.length})
`); + 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 ? "" : "
No data found for the selected range.
"); + setLoading(false); + } + + return ( +
+

Exchange Rate Export (USD)

+
+ + setStartDate(e.target.value)} /> + + setEndDate(e.target.value)} /> + + +
+
+ {renderTable(results)} +
+ ); +} \ No newline at end of file diff --git a/src/app/page.js b/src/app/page.js index 03f4b46..7299d21 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -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 ( -
-
- Next.js logo -
    -
  1. - Get started by editing src/app/page.js. -
  2. -
  3. Save and see your changes instantly.
  4. -
+ const router = useRouter(); + const [bank, setBank] = useState("bidv"); + const [startDate, setStartDate] = useState(""); + const [endDate, setEndDate] = useState(""); -
- - Vercel logomark - Deploy now - - - Read our docs - -
-
- + 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 ( +
+

Exchange Rate Export (USD)

+
+ + setStartDate(e.target.value)} /> + + setEndDate(e.target.value)} /> + + + +
+
+ Select a bank and date range, then click Go to view and export exchange rates. +
); } diff --git a/src/app/tcb/page.jsx b/src/app/tcb/page.jsx new file mode 100644 index 0000000..55c296f --- /dev/null +++ b/src/app/tcb/page.jsx @@ -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 ( + + + + + + + + + + + + + + + {data.map((row, i) => ( + + + + + + + + + + + ))} + +
DatelabelaskRatebidRateCKbidRateTMaskRateTMsourceCurrencytargetCurrency
{row.date}{row.label}{row.askRate || ""}{row.bidRateCK || ""}{row.bidRateTM || ""}{row.askRateTM || ""}{row.sourceCurrency}{row.targetCurrency}
+ ); + } + + 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("
Please select both start and end dates.
"); + return; + } + if (startDate > endDate) { + setStatus("
Start date must be before or equal to end date.
"); + return; + } + setStatus("
Fetching data, please wait...
"); + 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(`
Fetching: ${dateStr} (${i + 1}/${dates.length})
`); + const usd = await fetchUSD(dateObj); + if (usd) { + newResults.push(usd); + setResults([...newResults]); + } + } + setStatus(newResults.length ? "" : "
No data found for the selected range.
"); + setLoading(false); + } + + return ( +
+

Exchange Rate Export (USD, Techcombank)

+
+ + setStartDate(e.target.value)} /> + + setEndDate(e.target.value)} /> + + +
+
+ {renderTable(results)} +
+ ); +} \ No newline at end of file diff --git a/tcb.html b/tcb.html deleted file mode 100644 index fdbe1e4..0000000 --- a/tcb.html +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - Exchange Rate Export - Techcombank - - - -
-

Exchange Rate Export (USD, Techcombank)

-
- - - - - - -
-
- - - - - - - - - - - - - - - -
- - - \ No newline at end of file