mirror of
https://github.com/tiennm99/exchange-rate-export.git
synced 2026-10-11 03:13:21 +00:00
feat: date picker
This commit is contained in:
1 parent
c3f2d7e60e
commit
1ec1dde0f5
4 files changed
+109
-32
No files matched your search
Generated
+84
@@ -12,6 +12,7 @@
|
|||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"next": "15.3.4",
|
"next": "15.3.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
"react-datepicker": "^8.4.0",
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -236,6 +237,59 @@
|
|||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@floating-ui/core": {
|
||||||
|
"version": "1.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.1.tgz",
|
||||||
|
"integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/utils": "^0.2.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/dom": {
|
||||||
|
"version": "1.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.1.tgz",
|
||||||
|
"integrity": "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/core": "^1.7.1",
|
||||||
|
"@floating-ui/utils": "^0.2.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/react": {
|
||||||
|
"version": "0.27.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.12.tgz",
|
||||||
|
"integrity": "sha512-kKlWNrpIQxF1B/a2MZvE0/uyKby4960yjO91W7nVyNKmmfNi62xU9HCjL1M1eWzx/LFj/VPSwJVbwQk9Pq/68A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/react-dom": "^2.1.3",
|
||||||
|
"@floating-ui/utils": "^0.2.9",
|
||||||
|
"tabbable": "^6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=17.0.0",
|
||||||
|
"react-dom": ">=17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/react-dom": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/dom": "^1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/utils": {
|
||||||
|
"version": "0.2.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
|
||||||
|
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.1",
|
"version": "0.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||||
@@ -2319,6 +2373,15 @@
|
|||||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/clsx": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color": {
|
"node_modules/color": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||||
@@ -5158,6 +5221,21 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-datepicker": {
|
||||||
|
"version": "8.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-8.4.0.tgz",
|
||||||
|
"integrity": "sha512-6nPDnj8vektWCIOy9ArS3avus9Ndsyz5XgFCJ7nBxXASSpBdSL6lG9jzNNmViPOAOPh6T5oJyGaXuMirBLECag==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/react": "^0.27.3",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"date-fns": "^4.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "19.1.0",
|
"version": "19.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
@@ -5794,6 +5872,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tabbable": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "4.1.11",
|
"version": "4.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"next": "15.3.4",
|
"next": "15.3.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
"react-datepicker": "^8.4.0",
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,26 +1,17 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { format } from 'date-fns';
|
import DatePicker from 'react-datepicker';
|
||||||
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
function formatDisplayDate(dateStr) {
|
import { parse, isDate } from 'date-fns';
|
||||||
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 }) {
|
export default function ExchangeRateViewer({ defaultStartDate, defaultEndDate }) {
|
||||||
const [startDate, setStartDate] = useState(defaultStartDate);
|
const [startDate, setStartDate] = useState(
|
||||||
const [endDate, setEndDate] = useState(defaultEndDate);
|
defaultStartDate ? (isDate(defaultStartDate) ? defaultStartDate : parse(defaultStartDate, 'yyyy-MM-dd', new Date())) : null
|
||||||
|
);
|
||||||
|
const [endDate, setEndDate] = useState(
|
||||||
|
defaultEndDate ? (isDate(defaultEndDate) ? defaultEndDate : parse(defaultEndDate, 'yyyy-MM-dd', new Date())) : null
|
||||||
|
);
|
||||||
const [selectedBank, setSelectedBank] = useState('bidv');
|
const [selectedBank, setSelectedBank] = useState('bidv');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
@@ -37,8 +28,8 @@ export default function ExchangeRateViewer({ defaultStartDate, defaultEndDate })
|
|||||||
setResults([]);
|
setResults([]);
|
||||||
setShowTable(false);
|
setShowTable(false);
|
||||||
try {
|
try {
|
||||||
const start = new Date(startDate);
|
const start = startDate;
|
||||||
const end = new Date(endDate);
|
const end = endDate;
|
||||||
if (start > end) {
|
if (start > end) {
|
||||||
setError('Start date cannot be after end date');
|
setError('Start date cannot be after end date');
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@@ -110,25 +101,27 @@ export default function ExchangeRateViewer({ defaultStartDate, defaultEndDate })
|
|||||||
</div>
|
</div>
|
||||||
<div className="form-group flex flex-col">
|
<div className="form-group flex flex-col">
|
||||||
<label htmlFor="start-date" className="mb-1">Start Date:</label>
|
<label htmlFor="start-date" className="mb-1">Start Date:</label>
|
||||||
<input
|
<DatePicker
|
||||||
type="date"
|
|
||||||
id="start-date"
|
id="start-date"
|
||||||
value={startDate}
|
selected={startDate}
|
||||||
max={endDate}
|
onChange={date => setStartDate(date)}
|
||||||
onChange={e => setStartDate(e.target.value)}
|
maxDate={endDate || new Date()}
|
||||||
|
dateFormat="yyyy-MM-dd"
|
||||||
className="border rounded p-2"
|
className="border rounded p-2"
|
||||||
|
placeholderText="Select start date"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group flex flex-col">
|
<div className="form-group flex flex-col">
|
||||||
<label htmlFor="end-date" className="mb-1">End Date:</label>
|
<label htmlFor="end-date" className="mb-1">End Date:</label>
|
||||||
<input
|
<DatePicker
|
||||||
type="date"
|
|
||||||
id="end-date"
|
id="end-date"
|
||||||
value={endDate}
|
selected={endDate}
|
||||||
min={startDate}
|
onChange={date => setEndDate(date)}
|
||||||
max={new Date().toISOString().split('T')[0]}
|
minDate={startDate}
|
||||||
onChange={e => setEndDate(e.target.value)}
|
maxDate={new Date()}
|
||||||
|
dateFormat="yyyy-MM-dd"
|
||||||
className="border rounded p-2"
|
className="border rounded p-2"
|
||||||
|
placeholderText="Select end date"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="button-group flex gap-2 items-end">
|
<div className="button-group flex gap-2 items-end">
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import Image from "next/image";
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import ExchangeRateViewer from "./components/ExchangeRateViewer";
|
import ExchangeRateViewer from "./components/ExchangeRateViewer";
|
||||||
import { format, subDays } from "date-fns";
|
import { format, subDays } from "date-fns";
|
||||||
|
|||||||
Reference in new issue
Block a user