diff --git a/package-lock.json b/package-lock.json index bb7699e..6aa5d16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "exchange-rate-export", "version": "0.1.0", "dependencies": { + "axios": "^1.10.0", + "date-fns": "^4.1.0", "next": "15.3.4", "react": "^19.0.0", "react-dom": "^19.0.0" @@ -2110,6 +2112,12 @@ "node": ">= 0.4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2136,6 +2144,17 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -2211,7 +2230,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2346,6 +2364,18 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2429,6 +2459,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -2490,6 +2530,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", @@ -2517,7 +2566,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -2622,7 +2670,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2632,7 +2679,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2670,7 +2716,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2683,7 +2728,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3290,6 +3334,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -3306,11 +3370,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3351,7 +3430,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3376,7 +3454,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -3464,7 +3541,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3543,7 +3619,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3556,7 +3631,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -3572,7 +3646,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -4486,7 +4559,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4516,6 +4588,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -5019,6 +5112,12 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 9789ef5..e611c47 100644 --- a/package.json +++ b/package.json @@ -9,15 +9,17 @@ "lint": "next lint" }, "dependencies": { + "axios": "^1.10.0", + "date-fns": "^4.1.0", + "next": "15.3.4", "react": "^19.0.0", - "react-dom": "^19.0.0", - "next": "15.3.4" + "react-dom": "^19.0.0" }, "devDependencies": { + "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4", - "tailwindcss": "^4", "eslint": "^9", "eslint-config-next": "15.3.4", - "@eslint/eslintrc": "^3" + "tailwindcss": "^4" } } diff --git a/src/app/api/exchange-rates/route.js b/src/app/api/exchange-rates/route.js new file mode 100644 index 0000000..244e747 --- /dev/null +++ b/src/app/api/exchange-rates/route.js @@ -0,0 +1,114 @@ +import { format } from 'date-fns'; +import axios from 'axios'; +import https from 'https'; +import crypto from 'crypto'; + +// Configure axios with legacy SSL support +const axiosInstance = axios.create({ + httpsAgent: new https.Agent({ + secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT, + }) +}); + +// Helper function to fetch BIDV exchange rates +async function fetchBIDVRates(dateObj) { + const dateStr = format(dateObj, 'dd/MM/yyyy'); + try { + // First API call to get namerecord + const timeRes = await axiosInstance.get(`https://bidv.com.vn/ServicesBIDV/ExchangeDetailSearchTimeServlet?date=${dateStr}`); + const timeData = timeRes.data; + if (timeData.status !== 1 || !timeData.data?.length) return null; + // Get the latest record + const latest = timeData.data.reduce((latest, current) => + current.time > latest.time ? current : latest, timeData.data[0]); + // Second API call to get exchange rates + const rateRes = await axiosInstance.get(`https://bidv.com.vn/ServicesBIDV/ExchangeDetailServlet?date=${dateStr}&time=${latest.namerecord}`); + const rateData = rateRes.data; + if (rateData.status !== 1 || !rateData.data) return null; + // Find USD data + const usd = rateData.data.find(item => item.currency === 'USD'); + if (!usd) return null; + return { + date: dateStr, + nameVI: usd.nameVI || '', + muaTm: usd.muaTm || '', + muaCk: usd.muaCk || '', + currency: usd.currency || 'USD', + nameEN: usd.nameEN || '', + ban: usd.ban || '' + }; + } catch (error) { + console.error('Error fetching BIDV rates:', error); + return null; + } +} + +// Helper function to fetch TCB exchange rates +async function fetchTCBRates(dateObj) { + const dateStr = format(dateObj, '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 axiosInstance.get(url); + const data = res.data; + if (!data.exchangeRate?.data) return null; + // Find USD data + const usd = data.exchangeRate.data.find(item => item.label === 'USD (50,100)'); + if (!usd) return null; + return { + date: dateStr, + label: usd.label || '', + askRate: usd.askRate || '', + bidRateCK: usd.bidRateCK || '', + bidRateTM: usd.bidRateTM || '', + askRateTM: usd.askRateTM || '', + sourceCurrency: usd.sourceCurrency || 'USD', + targetCurrency: usd.targetCurrency || 'VND' + }; + } catch (error) { + console.error('Error fetching TCB rates:', error); + return null; + } +} + +// Get date range between start and end dates +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; +} + +export async function POST(request) { + try { + const body = await request.json(); + const { startDate, endDate, bank } = body; + if (!startDate || !endDate || !bank) { + return new Response(JSON.stringify({ error: 'Missing required parameters' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); + } + const dates = getDateRange(new Date(startDate), new Date(endDate)); + const results = []; + for (const date of dates) { + let rate = null; + try { + if (bank === 'bidv') { + rate = await fetchBIDVRates(date); + } else if (bank === 'tcb') { + rate = await fetchTCBRates(date); + } + if (rate) { + results.push(rate); + } + } catch (error) { + console.error(`Error fetching rates for ${date}:`, error); + } + } + return new Response(JSON.stringify({ data: results }), { status: 200, headers: { 'Content-Type': 'application/json' } }); + } catch (error) { + console.error('Server error:', error); + return new Response(JSON.stringify({ error: 'Internal server error' }), { status: 500, headers: { 'Content-Type': 'application/json' } }); + } +} \ No newline at end of file diff --git a/src/app/components/ExchangeRateViewer.js b/src/app/components/ExchangeRateViewer.js new file mode 100644 index 0000000..94367f0 --- /dev/null +++ b/src/app/components/ExchangeRateViewer.js @@ -0,0 +1,212 @@ +"use client"; + +import React, { useState } from 'react'; +import { format } from 'date-fns'; + +function formatDisplayDate(dateStr) { + try { + let date; + if (dateStr.includes('/')) { + const [day, month, year] = dateStr.split('/'); + date = new Date(`${year}-${month}-${day}`); + } else { + date = new Date(dateStr); + } + return format(date, 'dd/MM/yyyy'); + } catch (e) { + return dateStr; + } +} + +export default function ExchangeRateViewer({ defaultStartDate, defaultEndDate }) { + const [startDate, setStartDate] = useState(defaultStartDate); + const [endDate, setEndDate] = useState(defaultEndDate); + const [selectedBank, setSelectedBank] = useState('bidv'); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(''); + const [results, setResults] = useState([]); + const [showTable, setShowTable] = useState(false); + + const handleFetch = async () => { + if (!startDate || !endDate) { + setError('Please select both start and end dates'); + return; + } + setError(''); + setIsLoading(true); + setResults([]); + setShowTable(false); + try { + const start = new Date(startDate); + const end = new Date(endDate); + if (start > end) { + setError('Start date cannot be after end date'); + setIsLoading(false); + return; + } + const response = await fetch('/api/exchange-rates', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + startDate: start.toISOString().split('T')[0], + endDate: end.toISOString().split('T')[0], + bank: selectedBank + }) + }); + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || 'Failed to fetch exchange rates'); + } + const data = await response.json(); + setResults(data.data || []); + setShowTable(true); + } catch (err) { + setError(`An error occurred while fetching exchange rates: ${err.message}`); + console.error(err); + } finally { + setIsLoading(false); + } + }; + + const handleExport = () => { + if (results.length === 0) return; + let csvContent = ''; + let headers = []; + if (selectedBank === 'bidv') { + headers = ['Date', 'Currency', 'Buy Cash', 'Buy Transfer', 'Sell', 'Name VI', 'Name EN']; + csvContent = headers.join(',') + '\n'; + csvContent += results.map(row => + `"${row.date}","${row.currency}","${row.muaTm}","${row.muaCk}","${row.ban}","${row.nameVI}","${row.nameEN}"` + ).join('\n'); + } else { + headers = ['Date', 'Label', 'Ask Rate', 'Bid Rate CK', 'Bid Rate TM', 'Ask Rate TM', 'From', 'To']; + csvContent = headers.join(',') + '\n'; + csvContent += results.map(row => + `"${row.date}","${row.label}","${row.askRate}","${row.bidRateCK}","${row.bidRateTM}","${row.askRateTM}","${row.sourceCurrency}","${row.targetCurrency}"` + ).join('\n'); + } + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `exchange_rates_${selectedBank}_${new Date().toISOString().split('T')[0]}.csv`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + + return ( +
+

Exchange Rate Export

+
+
+ + +
+
+ + setStartDate(e.target.value)} + className="border rounded p-2" + /> +
+
+ + setEndDate(e.target.value)} + className="border rounded p-2" + /> +
+
+ + +
+
+ {error &&
{error}
} + {isLoading &&
Loading data, please wait...
} + {showTable && ( +
+ {selectedBank === 'bidv' ? ( + + + + + + + + + + + + + + {results.map((result, idx) => ( + + + + + + + + + + ))} + +
DateCurrencyBuy CashBuy TransferSellName (VI)Name (EN)
{result.date}{result.currency}{result.muaTm}{result.muaCk}{result.ban}{result.nameVI}{result.nameEN}
+ ) : ( + + + + + + + + + + + + + + + {results.map((result, idx) => ( + + + + + + + + + + + ))} + +
DateLabelAsk RateBid Rate CKBid Rate TMAsk Rate TMFromTo
{result.date}{result.label}{result.askRate}{result.bidRateCK}{result.bidRateTM}{result.askRateTM}{result.sourceCurrency}{result.targetCurrency}
+ )} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/app/page.js b/src/app/page.js index d625a20..2db7935 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -1,103 +1,30 @@ +"use client"; import Image from "next/image"; +import React, { useEffect, useState } from "react"; +import ExchangeRateViewer from "./components/ExchangeRateViewer"; +import { format, subDays } from "date-fns"; 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 [defaultStartDate, setDefaultStartDate] = useState(""); + const [defaultEndDate, setDefaultEndDate] = useState(""); -
- - Vercel logomark - Deploy now - - - Read our docs - -
+ useEffect(() => { + const today = new Date(); + const weekAgo = subDays(today, 7); + setDefaultStartDate(format(weekAgo, "yyyy-MM-dd")); + setDefaultEndDate(format(today, "yyyy-MM-dd")); + }, []); + + return ( +
+
+ {defaultStartDate && defaultEndDate && ( + + )}
-
); }