diff --git a/package-lock.json b/package-lock.json index 77a29f8c..af615c0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,14 +13,21 @@ "@tanstack/react-query": "^5.17.9", "@tanstack/react-query-devtools": "^5.17.9", "axios": "^1.6.5", + "date-fns": "^2.0.0-alpha.27", + "date-fns-tz": "^2.0.0", "react": "^18.2.0", "react-chartjs-2": "^5.2.0", + "react-datepicker": "^4.25.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.12", "react-loading-skeleton": "^3.3.1", + "react-paginate": "^8.2.0", "react-router-dom": "^6.21.1", "recoil": "^0.7.7", - "recoil-persist": "^5.1.0" + "recoil-persist": "^5.1.0", + "semantic-ui-css": "^2.5.0", + "semantic-ui-react": "^2.1.5", + "xlsx": "^0.18.5" }, "devDependencies": { "@swc/core": "^1.3.102", @@ -28,6 +35,7 @@ "@tanstack/eslint-plugin-query": "^5.17.7", "@types/jest": "^29.5.11", "@types/react": "^18.2.43", + "@types/react-datepicker": "^4.19.5", "@types/react-dom": "^18.2.17", "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^6.14.0", @@ -1305,6 +1313,31 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fluentui/react-component-event-listener": { + "version": "0.63.1", + "resolved": "https://registry.npmjs.org/@fluentui/react-component-event-listener/-/react-component-event-listener-0.63.1.tgz", + "integrity": "sha512-gSMdOh6tI3IJKZFqxfQwbTpskpME0CvxdxGM2tdglmf6ZPVDi0L4+KKIm+2dN8nzb8Ya1A8ZT+Ddq0KmZtwVQg==", + "dependencies": { + "@babel/runtime": "^7.10.4" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" + } + }, + "node_modules/@fluentui/react-component-ref": { + "version": "0.63.1", + "resolved": "https://registry.npmjs.org/@fluentui/react-component-ref/-/react-component-ref-0.63.1.tgz", + "integrity": "sha512-8MkXX4+R3i80msdbD4rFpEB4WWq2UDvGwG386g3ckIWbekdvN9z2kWAd9OXhRGqB7QeOsoAGWocp6gAMCivRlw==", + "dependencies": { + "@babel/runtime": "^7.10.4", + "react-is": "^16.6.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -2060,6 +2093,15 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@remix-run/router": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.1.tgz", @@ -2237,6 +2279,19 @@ "win32" ] }, + "node_modules/@semantic-ui-react/event-stack": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@semantic-ui-react/event-stack/-/event-stack-3.1.3.tgz", + "integrity": "sha512-FdTmJyWvJaYinHrKRsMLDrz4tTMGdFfds299Qory53hBugiDvGC0tEJf+cHsi5igDwWb/CLOgOiChInHwq8URQ==", + "dependencies": { + "exenv": "^1.2.2", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -2795,6 +2850,18 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-datepicker": { + "version": "4.19.5", + "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.19.5.tgz", + "integrity": "sha512-tKpuj19p9T4sBQm3Bw13CPuhalo4CFOe/LcSUGJ5z6DmHoiBX3uq33iMKePeSEq7OxyU8O1rh5emAm92nyXZLg==", + "dev": true, + "dependencies": { + "@popperjs/core": "^2.9.2", + "@types/react": "*", + "date-fns": "^2.0.1", + "react-popper": "^2.2.5" + } + }, "node_modules/@types/react-dom": { "version": "18.2.18", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", @@ -3066,6 +3133,14 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3417,6 +3492,18 @@ } ] }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3475,6 +3562,11 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -3489,6 +3581,14 @@ "node": ">=12" } }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -3499,6 +3599,14 @@ "node": ">= 0.12.0" } }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", @@ -3560,6 +3668,17 @@ "node": ">=10" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -3626,6 +3745,29 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/date-fns-tz": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-2.0.0.tgz", + "integrity": "sha512-OAtcLdB9vxSXTWHdT8b398ARImVwQMyjfYGkKD2zaGpHseG2UPHbHjXELReErZFxWdSLph3c2zOaaTyHfOhERQ==", + "peerDependencies": { + "date-fns": ">=2.0.0" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -4045,6 +4187,11 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" + }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -4231,6 +4378,14 @@ "node": ">= 6" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5632,6 +5787,11 @@ "@types/yargs-parser": "*" } }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5702,6 +5862,11 @@ "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", "dev": true }, + "node_modules/keyboard-key": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keyboard-key/-/keyboard-key-1.1.0.tgz", + "integrity": "sha512-qkBzPTi3rlAKvX7k0/ub44sqOfXeLc/jcnGGmj5c7BJpU8eDrEVPyhCvNYAaoubbsLm9uGWwQJO1ytQK1a9/dQ==" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -5762,6 +5927,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5949,6 +6124,14 @@ "node": ">=8" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -6285,6 +6468,16 @@ "node": ">= 6" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "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", @@ -6355,6 +6548,23 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-datepicker": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.25.0.tgz", + "integrity": "sha512-zB7CSi44SJ0sqo8hUQ3BF1saE/knn7u25qEMTO1CQGofY1VAKahO8k9drZtp0cfW1DMfoYLR3uSY1/uMvbEzbg==", + "dependencies": { + "@popperjs/core": "^2.11.8", + "classnames": "^2.2.6", + "date-fns": "^2.30.0", + "prop-types": "^15.7.2", + "react-onclickoutside": "^6.13.0", + "react-popper": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18", + "react-dom": "^16.9.0 || ^17 || ^18" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -6378,6 +6588,11 @@ "react": ">=16.13.1" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6391,6 +6606,44 @@ "react": ">=16.8.0" } }, + "node_modules/react-onclickoutside": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz", + "integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==", + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md" + }, + "peerDependencies": { + "react": "^15.5.x || ^16.x || ^17.x || ^18.x", + "react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x" + } + }, + "node_modules/react-paginate": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/react-paginate/-/react-paginate-8.2.0.tgz", + "integrity": "sha512-sJCz1PW+9PNIjUSn919nlcRVuleN2YPoFBOvL+6TPgrH/3lwphqiSOgdrLafLdyLDxsgK+oSgviqacF4hxsDIw==", + "dependencies": { + "prop-types": "^15" + }, + "peerDependencies": { + "react": "^16 || ^17 || ^18" + } + }, + "node_modules/react-popper": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", + "dependencies": { + "react-fast-compare": "^3.0.1", + "warning": "^4.0.2" + }, + "peerDependencies": { + "@popperjs/core": "^2.0.0", + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/react-router": { "version": "6.21.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.1.tgz", @@ -6601,6 +6854,38 @@ "loose-envify": "^1.1.0" } }, + "node_modules/semantic-ui-css": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/semantic-ui-css/-/semantic-ui-css-2.5.0.tgz", + "integrity": "sha512-jIWn3WXXE2uSaWCcB+gVJVRG3masIKtTMNEP2X8Aw909H2rHpXGneYOxzO3hT8TpyvB5/dEEo9mBFCitGwoj1A==", + "dependencies": { + "jquery": "x.*" + } + }, + "node_modules/semantic-ui-react": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-2.1.5.tgz", + "integrity": "sha512-nIqmmUNpFHfovEb+RI2w3E2/maZQutd8UIWyRjf1SLse+XF51hI559xbz/sLN3O6RpLjr/echLOOXwKCirPy3Q==", + "dependencies": { + "@babel/runtime": "^7.10.5", + "@fluentui/react-component-event-listener": "~0.63.0", + "@fluentui/react-component-ref": "~0.63.0", + "@popperjs/core": "^2.6.0", + "@semantic-ui-react/event-stack": "^3.1.3", + "clsx": "^1.1.1", + "keyboard-key": "^1.1.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "prop-types": "^15.7.2", + "react-is": "^16.8.6 || ^17.0.0 || ^18.0.0", + "react-popper": "^2.3.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -6616,6 +6901,11 @@ "node": ">=10" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6700,6 +6990,17 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -7133,6 +7434,14 @@ "makeerror": "1.0.12" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7148,6 +7457,22 @@ "node": ">= 8" } }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -7184,6 +7509,26 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 8c7218e7..4b76cf3f 100644 --- a/package.json +++ b/package.json @@ -16,14 +16,21 @@ "@tanstack/react-query": "^5.17.9", "@tanstack/react-query-devtools": "^5.17.9", "axios": "^1.6.5", + "date-fns": "^2.0.0-alpha.27", + "date-fns-tz": "^2.0.0", "react": "^18.2.0", "react-chartjs-2": "^5.2.0", + "react-datepicker": "^4.25.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.12", "react-loading-skeleton": "^3.3.1", + "react-paginate": "^8.2.0", "react-router-dom": "^6.21.1", "recoil": "^0.7.7", - "recoil-persist": "^5.1.0" + "recoil-persist": "^5.1.0", + "semantic-ui-css": "^2.5.0", + "semantic-ui-react": "^2.1.5", + "xlsx": "^0.18.5" }, "devDependencies": { "@swc/core": "^1.3.102", @@ -31,6 +38,7 @@ "@tanstack/eslint-plugin-query": "^5.17.7", "@types/jest": "^29.5.11", "@types/react": "^18.2.43", + "@types/react-datepicker": "^4.19.5", "@types/react-dom": "^18.2.17", "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^6.14.0", diff --git a/src/api/index.ts b/src/api/index.ts index cecb4d8f..cc34ffdd 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -3,3 +3,4 @@ export { default as postLogin } from './lib/postLogin'; export { default as postRefreshToken } from './lib/postRefreshToken'; export { default as getTotalReport } from './lib/getTotalReport'; export { default as getYearReport } from './lib/getYearReport'; +export { default as getCouponList } from './lib/getCouponList'; diff --git a/src/api/lib/getCouponList.ts b/src/api/lib/getCouponList.ts new file mode 100644 index 00000000..0cb1ee03 --- /dev/null +++ b/src/api/lib/getCouponList.ts @@ -0,0 +1,22 @@ +import { CouponListResponse } from '@/types/couponList'; +import { instance } from '..'; + +// 쿠폰 정보 가져오는 api +const getCouponList = async ( + accommodationId: number, + date?: string, + status?: string, + title?: string +): Promise => { + const params = { + date, + status, + title + }; + const response = await instance.get(`/v1/coupons/${accommodationId}`, { + params + }); + return response.data; +}; + +export default getCouponList; diff --git a/src/assets/icons/calendar-number-outline.svg b/src/assets/icons/calendar-number-outline.svg new file mode 100644 index 00000000..334f04ba --- /dev/null +++ b/src/assets/icons/calendar-number-outline.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/icons/information-circle-outline.svg b/src/assets/icons/information-circle-outline.svg new file mode 100644 index 00000000..8592c7eb --- /dev/null +++ b/src/assets/icons/information-circle-outline.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/icons/receipt-sharp.svg b/src/assets/icons/receipt-sharp.svg new file mode 100644 index 00000000..5aa90f5e --- /dev/null +++ b/src/assets/icons/receipt-sharp.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/icons/settlements-admin.svg b/src/assets/icons/settlements-admin.svg new file mode 100644 index 00000000..170495c1 --- /dev/null +++ b/src/assets/icons/settlements-admin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/settlements-data-frame.svg b/src/assets/icons/settlements-data-frame.svg new file mode 100644 index 00000000..aa53f413 --- /dev/null +++ b/src/assets/icons/settlements-data-frame.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/settlements-logo.svg b/src/assets/icons/settlements-logo.svg new file mode 100644 index 00000000..006232fa --- /dev/null +++ b/src/assets/icons/settlements-logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/icons/sync-outline.svg b/src/assets/icons/sync-outline.svg new file mode 100644 index 00000000..4af17bbf --- /dev/null +++ b/src/assets/icons/sync-outline.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/CouponList/CouponHeader/index.tsx b/src/components/CouponList/CouponHeader/index.tsx index 114c6e78..4d632a24 100644 --- a/src/components/CouponList/CouponHeader/index.tsx +++ b/src/components/CouponList/CouponHeader/index.tsx @@ -1,5 +1,6 @@ import styled from '@emotion/styled'; import { useNavigate } from 'react-router-dom'; + import theme from '@styles/theme'; const CouponHeader = () => { diff --git a/src/components/CouponList/CouponItem/CouponExpired/index.tsx b/src/components/CouponList/CouponItem/CouponExpired/index.tsx index 21895112..b9deeb60 100644 --- a/src/components/CouponList/CouponItem/CouponExpired/index.tsx +++ b/src/components/CouponList/CouponItem/CouponExpired/index.tsx @@ -1,49 +1,98 @@ import styled from '@emotion/styled'; +import { useRef, useState } from 'react'; import theme from '@styles/theme'; +import rightIcon from '@assets/icons/ic-couponlist-right.svg'; +import deleteIcon from '@assets/icons/ic-couponlist-delete.svg'; +import { useOutsideClick } from '@hooks/index'; +import { CouponListProps } from '@/types/couponList'; + +const CouponExpired = ({ couponInfo }: CouponListProps) => { + const [isShowRoomList, setIsShowRoomList] = useState(false); + const roomListRef = useRef(null); + + const handleRoomList = () => { + setIsShowRoomList(!isShowRoomList); + }; + + useOutsideClick(roomListRef, () => setIsShowRoomList(false)); -const CouponExpired = () => { return ( - 2024 신년행사 + {couponInfo.title} 기간만료 - 모든 고객 10% 할인 + {couponInfo.coupon_concat_title} 다운로드 - 50 + {couponInfo.download_count} 사용완료 - 50 + {couponInfo.use_count}
가격 - 99,999,999원 이상 + + {couponInfo.minimum_reservation_price}원 이상 + 일정 - 2박 이상, 일~목 + {couponInfo.coupon_room_type} 객실 - 전체 + {couponInfo.register_room_numbers.length === 0 ? ( + 전체 + ) : ( + <> + +
일부 객실
+ 오른쪽 화살표 +
+ {isShowRoomList && ( + + + 쿠폰 적용 객실 + 리스트 닫기 아이콘 + + +
    + {couponInfo.register_room_numbers.map((room, index) => ( +
  • {room}
  • + ))} +
+
+
+ )} + + )}
노출기간 - 2024.01.31 ~ 2024.02.10 + + {couponInfo.exposure_start_date} ~ {couponInfo.exposure_end_date} + 등록일 - 2024.12.01 + {couponInfo.created_date} 삭제 @@ -225,3 +274,98 @@ const Delete = styled.div` font-size: 11px; cursor: pointer; `; + +const ContentRoom = styled.div` + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + + div { + margin-right: 3px; + padding: 2px 0px; + border-bottom: 1px solid #757676; + + color: #757676; + font-size: 11px; + font-style: normal; + font-weight: 600; + } +`; + +const RoomList = styled.div` + position: absolute; + top: 0; + right: 0; + z-index: 1; + + width: 188px; + height: 204px; + + margin-top: 150px; + border-radius: 18px; + text-align: center; + + background: #415574; + + &::before { + content: ''; + position: absolute; + top: -10px; + left: 50%; + transform: translateX(-50%); + + width: 0; + height: 0; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + border-bottom: 10px solid #415574; + } +`; + +const RoomListTitleWrap = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + margin: 10px; + padding: 8px; + border-bottom: 1px solid #cdcfd0; + + img { + cursor: pointer; + } +`; + +const RoomListTitle = styled.div` + margin-left: 35px; + + font-size: 15px; + font-weight: 700; + font-style: normal; + color: ${theme.colors.white}; +`; + +const RoomListItem = styled.div` + max-height: 125px; + + display: flex; + flex-direction: column; + align-items: center; + overflow-y: auto; // 세로 스크롤만 허용 + + color: ${theme.colors.white}; + font-size: 14px; + font-weight: 400; + line-height: 36px; + + li { + max-width: 130px; + + overflow: hidden; + overflow-y: scroll; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +`; diff --git a/src/components/CouponList/CouponItem/CouponExpose/index.tsx b/src/components/CouponList/CouponItem/CouponExpose/index.tsx index 69ec3df8..bfcb5222 100644 --- a/src/components/CouponList/CouponItem/CouponExpose/index.tsx +++ b/src/components/CouponList/CouponItem/CouponExpose/index.tsx @@ -6,12 +6,12 @@ import toggleOnIcon from '@assets/icons/ic-couponlist-toggleOn.svg'; import toggleOffIcon from '@assets/icons/ic-couponlist-toggleOff.svg'; import rightIcon from '@assets/icons/ic-couponlist-right.svg'; import deleteIcon from '@assets/icons/ic-couponlist-delete.svg'; -import { ToggleStyleProps } from '@/types/couponList'; +import { CouponListProps, ToggleStyleProps } from '@/types/couponList'; import { useOutsideClick } from '@hooks/index'; -const CouponExpose = () => { +const CouponExpose = ({ couponInfo }: CouponListProps) => { const [isToggle, setIsToggle] = useState(true); - const [isRoomList, setIsRoomList] = useState(false); + const [isShowRoomList, setIsShowRoomList] = useState(false); const roomListRef = useRef(null); const handleToggle = () => { @@ -19,16 +19,16 @@ const CouponExpose = () => { }; const handleRoomList = () => { - setIsRoomList(!isRoomList); + setIsShowRoomList(!isShowRoomList); }; - useOutsideClick(roomListRef, () => setIsRoomList(false)); + useOutsideClick(roomListRef, () => setIsShowRoomList(false)); return ( - 2024 신년행사 + {couponInfo.title} { )} - 모든 고객 10% 할인 + {couponInfo.coupon_concat_title} 다운로드 - 50 + {couponInfo.download_count} 사용완료 - 50 + {couponInfo.use_count} - +
가격 - 99,999,999원 이상 + + {couponInfo.minimum_reservation_price}원 이상 + 일정 - 2박 이상, 일~목 + {couponInfo.coupon_room_type} 객실 - -
일부 객실
- 오른쪽 화살표 -
- {isRoomList && ( - - - 쿠폰 적용 객실 + {couponInfo.register_room_numbers.length === 0 ? ( + 전체 + ) : ( + <> + +
일부 객실
리스트 닫기 아이콘 -
- -
    -
  • 스탠다드 더블
  • -
  • 스탠다드 트윈
  • -
  • 프리미엄 스위트 더블 디럭스
  • -
  • 프리미엄 스위트 더블 디럭스
  • -
  • 프리미엄 스위트 더블 디럭스
  • -
-
-
+ + {isShowRoomList && ( + + + 쿠폰 적용 객실 + 리스트 닫기 아이콘 + + +
    + {couponInfo.register_room_numbers.map((room, index) => ( +
  • {room}
  • + ))} +
+
+
+ )} + )} - -
- +
노출기간 - 2024.01.31 ~ 2024.02.10 + + {couponInfo.exposure_start_date} ~ {couponInfo.exposure_end_date} + 등록일 - 2024.12.01 + {couponInfo.created_date}
@@ -242,9 +248,6 @@ const CountNumber = styled.div` font-weight: 700; `; -// HACK: ContentContainer 로직 추가하기 -const ContentContainer = styled.div``; - const ContentWrap = styled.div` margin: 8px; @@ -266,11 +269,9 @@ const ContentRoom = styled.div` justify-content: center; cursor: pointer; - img { - margin-bottom: 3px; - } div { margin-right: 3px; + padding: 2px 0px; border-bottom: 1px solid #757676; color: #757676; diff --git a/src/components/CouponList/CouponItem/CouponStop/index.tsx b/src/components/CouponList/CouponItem/CouponStop/index.tsx index 59a20a03..2d310c06 100644 --- a/src/components/CouponList/CouponItem/CouponStop/index.tsx +++ b/src/components/CouponList/CouponItem/CouponStop/index.tsx @@ -1,22 +1,34 @@ -import { useState } from 'react'; +import { useRef, useState } from 'react'; import styled from '@emotion/styled'; import theme from '@styles/theme'; import toggleOnIcon from '@assets/icons/ic-couponlist-toggleOn.svg'; import toggleOffIcon from '@assets/icons/ic-couponlist-toggleOff.svg'; -import { ToggleStyleProps } from '@/types/couponList'; +import rightIcon from '@assets/icons/ic-couponlist-right.svg'; +import deleteIcon from '@assets/icons/ic-couponlist-delete.svg'; +import { CouponListProps, ToggleStyleProps } from '@/types/couponList'; +import { useOutsideClick } from '@hooks/index'; -const CouponStop = () => { +const CouponStop = ({ couponInfo }: CouponListProps) => { const [isToggle, setIsToggle] = useState(false); + const [isShowRoomList, setIsShowRoomList] = useState(false); + const roomListRef = useRef(null); + + useOutsideClick(roomListRef, () => setIsShowRoomList(false)); + + const handleRoomList = () => { + setIsShowRoomList(!isShowRoomList); + }; const handleToggle = () => { setIsToggle(!isToggle); }; + return ( - 2024 신년행사 + {couponInfo.title} { )} - 모든 고객 10% 할인 + {couponInfo.coupon_concat_title} 다운로드 - 50 + {couponInfo.download_count} 사용완료 - 50 + {couponInfo.use_count} - +
가격 - 99,999,999원 이상 + + {couponInfo.minimum_reservation_price}원 이상 + 일정 - 2박 이상, 일~목 + {couponInfo.coupon_room_type} 객실 - 전체 + {couponInfo.register_room_numbers.length === 0 ? ( + 전체 + ) : ( + <> + +
일부 객실
+ 오른쪽 화살표 +
+ {isShowRoomList && ( + + + 쿠폰 적용 객실 + 리스트 닫기 아이콘 + + +
    + {couponInfo.register_room_numbers.map((room, index) => ( +
  • {room}
  • + ))} +
+
+
+ )} + + )}
- +
노출기간 - 2024.01.31 ~ 2024.02.10 + + {couponInfo.exposure_start_date} ~ {couponInfo.exposure_end_date} + 등록일 - 2024.12.01 + {couponInfo.created_date}
@@ -83,6 +130,8 @@ const CouponStop = () => { export default CouponStop; const CouponContainer = styled.div` + position: relative; + width: 290px; height: 203px; @@ -199,9 +248,6 @@ const CountNumber = styled.div` font-weight: 700; `; -// HACK: ContentContainer 로직 추가하기 -const ContentContainer = styled.div``; - const ContentWrap = styled.div` margin: 8px; @@ -267,3 +313,98 @@ const RegisterDateValue = styled.div` font-style: normal; font-weight: 400; `; + +const ContentRoom = styled.div` + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + + div { + margin-right: 3px; + padding: 2px 0px; + border-bottom: 1px solid #757676; + + color: #757676; + font-size: 11px; + font-style: normal; + font-weight: 600; + } +`; + +const RoomList = styled.div` + position: absolute; + top: 0; + right: 0; + z-index: 1; + + width: 188px; + height: 204px; + + margin-top: 150px; + border-radius: 18px; + text-align: center; + + background: #415574; + + &::before { + content: ''; + position: absolute; + top: -10px; + left: 50%; + transform: translateX(-50%); + + width: 0; + height: 0; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + border-bottom: 10px solid #415574; + } +`; + +const RoomListTitleWrap = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + margin: 10px; + padding: 8px; + border-bottom: 1px solid #cdcfd0; + + img { + cursor: pointer; + } +`; + +const RoomListTitle = styled.div` + margin-left: 35px; + + font-size: 15px; + font-weight: 700; + font-style: normal; + color: ${theme.colors.white}; +`; + +const RoomListItem = styled.div` + max-height: 125px; + + display: flex; + flex-direction: column; + align-items: center; + overflow-y: auto; // 세로 스크롤만 허용 + + color: ${theme.colors.white}; + font-size: 14px; + font-weight: 400; + line-height: 36px; + + li { + max-width: 130px; + + overflow: hidden; + overflow-y: scroll; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +`; diff --git a/src/components/CouponList/CouponItem/CouponWait/index.tsx b/src/components/CouponList/CouponItem/CouponWait/index.tsx index 6deb2ed7..bae274eb 100644 --- a/src/components/CouponList/CouponItem/CouponWait/index.tsx +++ b/src/components/CouponList/CouponItem/CouponWait/index.tsx @@ -1,50 +1,99 @@ import styled from '@emotion/styled'; +import { useRef, useState } from 'react'; import theme from '@styles/theme'; import centerIcon from '@assets/icons/ic-couponlist-center.svg'; +import rightIcon from '@assets/icons/ic-couponlist-right.svg'; +import deleteIcon from '@assets/icons/ic-couponlist-delete.svg'; +import { useOutsideClick } from '@hooks/index'; +import { CouponListProps } from '@/types/couponList'; + +const CouponWait = ({ couponInfo }: CouponListProps) => { + const [isShowRoomList, setIsShowRoomList] = useState(false); + const roomListRef = useRef(null); + + const handleRoomList = () => { + setIsShowRoomList(!isShowRoomList); + }; + + useOutsideClick(roomListRef, () => setIsShowRoomList(false)); -const CouponWait = () => { return ( - 2024 신년행사 + {couponInfo.title} 노출대기 - 모든 고객 10% 할인 + {couponInfo.coupon_concat_title} 다운로드 - 50 + {couponInfo.download_count} 사용완료 - 50 + {couponInfo.use_count}
가격 - 99,999,999원 이상 + + {couponInfo.minimum_reservation_price}원 이상 + 일정 - 2박 이상, 일~목 + {couponInfo.coupon_room_type} 객실 - 전체 + {couponInfo.register_room_numbers.length === 0 ? ( + 전체 + ) : ( + <> + +
일부 객실
+ 오른쪽 화살표 +
+ {isShowRoomList && ( + + + 쿠폰 적용 객실 + 리스트 닫기 아이콘 + + +
    + {couponInfo.register_room_numbers.map((room, index) => ( +
  • {room}
  • + ))} +
+
+
+ )} + + )}
노출기간 - 2024.01.31 ~ 2024.02.10 + + {couponInfo.exposure_start_date} ~ {couponInfo.exposure_end_date} + 등록일 - 2024.12.01 + {couponInfo.created_date} @@ -240,3 +289,98 @@ const CouponModifiedWrap = styled.div` cursor: pointer; } `; + +const ContentRoom = styled.div` + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + + div { + margin-right: 3px; + padding: 2px 0px; + border-bottom: 1px solid #757676; + + color: #757676; + font-size: 11px; + font-style: normal; + font-weight: 600; + }s +`; + +const RoomList = styled.div` + position: absolute; + top: 0; + right: 0; + z-index: 1; + + width: 188px; + height: 204px; + + margin-top: 150px; + border-radius: 18px; + text-align: center; + + background: #415574; + + &::before { + content: ''; + position: absolute; + top: -10px; + left: 50%; + transform: translateX(-50%); + + width: 0; + height: 0; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + border-bottom: 10px solid #415574; + } +`; + +const RoomListTitleWrap = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + margin: 10px; + padding: 8px; + border-bottom: 1px solid #cdcfd0; + + img { + cursor: pointer; + } +`; + +const RoomListTitle = styled.div` + margin-left: 35px; + + font-size: 15px; + font-weight: 700; + font-style: normal; + color: ${theme.colors.white}; +`; + +const RoomListItem = styled.div` + max-height: 125px; + + display: flex; + flex-direction: column; + align-items: center; + overflow-y: auto; // 세로 스크롤만 허용 + + color: ${theme.colors.white}; + font-size: 14px; + font-weight: 400; + line-height: 36px; + + li { + max-width: 130px; + + overflow: hidden; + overflow-y: scroll; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +`; diff --git a/src/components/CouponList/CouponMain/index.tsx b/src/components/CouponList/CouponMain/index.tsx index 56796cd3..f5f71575 100644 --- a/src/components/CouponList/CouponMain/index.tsx +++ b/src/components/CouponList/CouponMain/index.tsx @@ -1,4 +1,5 @@ import styled from '@emotion/styled'; +import { useRecoilValue } from 'recoil'; import { CouponExpired, @@ -6,39 +7,38 @@ import { CouponStop, CouponWait } from '../CouponItem'; -// import { CouponLitResponse } from '@/types/couponList'; +import couponListState from '@recoil/atoms/couponListState'; const CouponMain = () => { - // HACK: 쿠폰 데이터 상태저장 (추가 예정) - // const [coupons, setCoupons] = useState([]); + const coupons = useRecoilValue(couponListState); + console.log('recoil로 관리되는 쿠폰 리스트 ', coupons); return ( - // HACK: 받아온 쿠폰 데이터 종류에 따라 컴포넌트 분리 (추가 예정) - {/* {coupons.map((coupon, index) => { + {coupons?.content.map((coupon, index) => { switch (coupon.coupon_status) { - case '노출 중': + case '노출 ON': return ( ); - case '노출 중지': + case '노출 OFF': return ( ); - case '노출 대기': + case '노출 대기중': return ( ); - case '만료': + case '노출 기간 만료': return ( { /> ); } - })} */} - - - - - + })} ); }; diff --git a/src/components/CouponList/CouponNav/index.tsx b/src/components/CouponList/CouponNav/index.tsx index e5520fd4..890345a5 100644 --- a/src/components/CouponList/CouponNav/index.tsx +++ b/src/components/CouponList/CouponNav/index.tsx @@ -1,9 +1,12 @@ import styled from '@emotion/styled'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; import theme from '@styles/theme'; import searchIcon from '@assets/icons/ic-couponlist-search.svg'; import centerIcon from '@assets/icons/ic-couponlist-period-center.svg'; +import { couponListState, headerAccommodationState } from '@recoil/index'; +import { getCouponList } from 'src/api'; import { CategoryTabStyleProps, ResisterDateStyleProps @@ -12,6 +15,10 @@ import { const CouponNav = () => { const [resisterDateClick, setResisterDateClick] = useState('1년'); const [categoryTab, setCategoryTab] = useState('전체'); + const [searchText, setSearchText] = useState(''); + const headerAccommodation = useRecoilValue(headerAccommodationState); + const setGlobalCoupons = useSetRecoilState(couponListState); + const coupons = useRecoilValue(couponListState); const handleDateClick = (period: string) => { setResisterDateClick(period); @@ -21,32 +28,78 @@ const CouponNav = () => { setCategoryTab(tab); }; + const handleSearchChange = (e: React.ChangeEvent) => { + setSearchText(e.target.value); + }; + + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + setSearchText(''); + fetchCoupons(); + }; + + // recoil 숙소 ID 가져오기 + const fetchCoupons = async () => { + try { + const couponData = await getCouponList( + headerAccommodation.accommodationId, + resisterDateClick !== '1년' ? resisterDateClick : undefined, + categoryTab !== '전체' ? categoryTab : undefined, + searchText + ); + setGlobalCoupons(couponData); + + console.log( + '검색어, 등록일, 카테고리:', + searchText, + resisterDateClick, + categoryTab + ); + } catch (error) { + console.log('쿠폰 조회 api 에러 ', error); + } + }; + + useEffect(() => { + fetchCoupons(); + }, [headerAccommodation.accommodationId, categoryTab, resisterDateClick]); + return ( handleCategoryTab('전체')}> 전체 - 8 + + {coupons?.category.all} + handleCategoryTab('노출 ON')}> 노출 ON - 2 + + {coupons?.category.exposure_on} + handleCategoryTab('노출 OFF')}> 노출 OFF - 2 + + {coupons?.category.exposure_off} + handleCategoryTab('만료')}> 만료 - 4 + + {coupons?.category.expiration} + - + { - 전체 - 20개 + {categoryTab} + {coupons?.content.length}개 모든 쿠폰은 다운로드 후 14일까지 사용 가능하며, 등록 후 1년이 경과한 쿠폰은 조회되지 않습니다. diff --git a/src/components/Settlements/SettlementsLeft/SettlementsSetting/Settlemented/SettlementsPagination/index.tsx b/src/components/Settlements/SettlementsLeft/SettlementsSetting/Settlemented/SettlementsPagination/index.tsx new file mode 100644 index 00000000..fc00fa31 --- /dev/null +++ b/src/components/Settlements/SettlementsLeft/SettlementsSetting/Settlemented/SettlementsPagination/index.tsx @@ -0,0 +1,99 @@ +import React, { useEffect } from 'react'; +import ReactPaginate from 'react-paginate'; +import styled from '@emotion/styled'; + +import { SettlementsPaginationProps } from '@/types/settlements'; + +const SettlementsPagination: React.FC = ({ + currentPage, + totalPages, + onPageChange, + totalItems +}) => { + useEffect(() => { + onPageChange(currentPage); + }, [currentPage, onPageChange]); + + return ( + + {totalItems > 0 && ( + '} + breakLabel={'...'} + breakClassName={'hidden'} + pageCount={totalPages} + marginPagesDisplayed={2} + pageRangeDisplayed={5} + onPageChange={(selectedItem) => onPageChange(selectedItem.selected + 1)} + containerClassName={'pagination'} + activeClassName={'active'} + pageClassName={'pagination-li'} + pageLinkClassName={'pagination-link'} + previousClassName={'pagination-previous'} + nextClassName={'pagination-next'} + /> + )} + + ); +}; + +export default SettlementsPagination; + +const PaginationContainer = styled.div` + margin-top: 20px; + margin-bottom: 20px; + + display: flex; + justify-content: center; + align-items: center; + + .pagination { + display: flex; + align-items: center; + } + + .pagination-li { + margin: 5px; + padding: 9.4px 0px; + + border: 1px solid #FFFFFF; + border-radius: 4px; + cursor: pointer; + + @media (max-width: 900px) { + font-size: 12px; + } + } + + .pagination-link { + padding: 8px 12px; + + color: #FFFFFF; + text-decoration: none; + } + + .pagination-li.active { + padding: 9.4px 0px; + border: 3px solid #FFFFFF; + border-radius: 4px; + + font-weight: bold; + } + + .pagination-previous, + .pagination-next { + padding: 8px 12px; + color: #FFFFFF !important; + text-decoration: none; + + &:hover { + cursor: pointer; + } + } + + .pagination-previous a, + .pagination-next a { + color: white !important; + } +`; \ No newline at end of file diff --git a/src/components/Settlements/SettlementsLeft/SettlementsSetting/Settlemented/SettlementsTable/index.tsx b/src/components/Settlements/SettlementsLeft/SettlementsSetting/Settlemented/SettlementsTable/index.tsx new file mode 100644 index 00000000..e644686e --- /dev/null +++ b/src/components/Settlements/SettlementsLeft/SettlementsSetting/Settlemented/SettlementsTable/index.tsx @@ -0,0 +1,225 @@ +import styled from '@emotion/styled'; + +import { SettlementItem, SettlementsTableProps } from '@/types/settlements'; +import settlementsFrame from '@assets/icons/settlements-data-frame.svg'; + +const SettlementsTable = ({ data, pageStartNumber }: SettlementsTableProps) => { + + const keys: (keyof SettlementItem)[] = [ + 'NO', + '쿠폰번호', + '관리 쿠폰명', + '사용 건수', + '쿠폰 할인 금액', + '쿠폰 취소 금액', + '정산 금액', + '쿠폰 적용일', + '정산 완료일' + ]; + + if (!data || data.length === 0) { + return ( + +
+ {keys.map((key, index) => ( + {key} + ))} +
+ + + 데이터가 없습니다. + + +
+ ); + } + + return ( + +
+ {keys.map((key, index) => ( + {key} + ))} +
+ + + {data.map((row, index) => ( + + {keys.map((key) => ( + + {key} + {key === 'NO' ? pageStartNumber - index : row[key]} + {key === 'NO' ? pageStartNumber - index : row[key]} + + ))} + + ))} + + +
+ ); +}; + +export default SettlementsTable; + +const Container = styled.div` + margin-top: 30px; + + display: flex; + flex-direction: column; + + + @media (max-width: 900px) { + overflow-x: hidden; + } +`; + +const Header = styled.div` + padding: 20px 0; + + display: flex; + justify-content: space-between; + + border: 1px solid #000; + border-radius: 10px; + background-color: rgba(255, 255, 255, 0.05); + + @media (max-width: 900px) { + display: none; + } + +`; + +const KeyElement = styled.div` + width: 10%; + text-align: center; + + font-size: 14px; + font-weight: bold; + color: white; +`; + + +const FrameContainer = styled.div` + width: 100%; + + box-sizing: border-box; + + text-align: center; +`; + +const Frame = styled.div<{ hasData?: boolean }>` + width: 99%; + height: 500px; + + margin-left: auto; + margin-right: auto; + + position: relative; + + background: url(${settlementsFrame}); + + @media (max-width: 900px) { + background: none; + background-color: white; + height: 100%; + } +`; + +const NoDataText = styled.div` + font-size: 15px; + + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + @media (max-width: 900px) { + margin: 10px 0px; + + position: static; + transform: none; + + background-color: #1A2849; + color: white; + } +`; + +const DataElement = styled.div` + width: 10%; + height: 48px; + + padding-top: 18px; + + justify-content: center; + + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + @media (max-width: 900px) { + padding: 5px 0px; + + width: 100%; + height: 100%; + + display: flex; + + white-space: none; + } + + @media (max-width: 500px) { + font-size: 12px; + } +`; + +const MobileDataElement = styled.span` + margin-left: auto; + + display: flex; + + font-weight: 700; + + @media (min-width: 900px) { + display: none; + } +`; + +const MobileData = styled.span` + padding-left: 10%; + + width: 55%; + + display: flex; + + @media (min-width: 900px) { + display: none; + } +`; + +const WebData = styled.span` +@media (max-width: 900px) { + display: none; +} +`; + +const Row = styled.div<{ isLast: boolean }>` + display: flex; + justify-content: space-between; + + border-bottom: ${(props) => (props.isLast ? 'none' : '1px solid #ccc')}; + + &:last-child { + border-bottom: none; + } + + @media (max-width: 900px) { + padding: 20px 0px; + + display: flex; + flex-direction: column; + flex-wrap: wrap; + text-overflow: ellipsis; + } +`; + diff --git a/src/components/Settlements/SettlementsLeft/SettlementsSetting/Settlemented/index.tsx b/src/components/Settlements/SettlementsLeft/SettlementsSetting/Settlemented/index.tsx new file mode 100644 index 00000000..16bceb7f --- /dev/null +++ b/src/components/Settlements/SettlementsLeft/SettlementsSetting/Settlemented/index.tsx @@ -0,0 +1,281 @@ +import styled from '@emotion/styled'; +import 'semantic-ui-css/semantic.min.css'; +import { Dropdown, DropdownProps } from 'semantic-ui-react'; +import { useState, useEffect } from 'react'; +import { useRecoilValue, useRecoilState, useSetRecoilState } from 'recoil'; +import * as XLSX from 'xlsx'; + +import SettlementsTable from './SettlementsTable'; +import SettlementsPagination from './SettlementsPagination'; +import { settlementsDateState, settlementDataState, fakeData } from '@recoil/atoms/settlemented'; + +const Settlemented = () => { + + const { startDate, endDate } = useRecoilValue(settlementsDateState); + const setSettlementData = useSetRecoilState(settlementDataState); + + const [sortedData, setSortedData] = useRecoilState(settlementDataState); + const [sortOrder, setSortOrder] = useState('couponDateDesc'); + + const sortOptions = [ + { key: 'amountDesc', text: '정산금액 많은 순', value: 'amountDesc' }, + { key: 'dateDesc', text: '정산 완료일 최근 순', value: 'dateDesc' }, + { key: 'couponDateDesc', text: '쿠폰 사용일 최근 순', value: 'couponDateDesc' }, + { key: 'usageCountDesc', text: '사용건 많은 순', value: 'usageCountDesc' }, + ]; + + const handleSortChange = (_e: React.SyntheticEvent, data: DropdownProps) => { + setSortOrder(data.value as string); + }; + + const defaultOption = sortOptions.find(option => option.value === 'couponDateDesc'); + + const sortedAndNumberedData = fakeData.map((data, index) => ({ + ...data, + NO: index + 1, + })).sort((a, b) => new Date(a['쿠폰 적용일']).getTime() - new Date(b['쿠폰 적용일']).getTime()); + + const itemsPerPage = 10; + const totalItems = sortedData.length; + const totalPages = Math.ceil(totalItems / itemsPerPage); + + const [currentPage, setCurrentPage] = useState(1); + + const calculatePageStartNumber = (currentPage: number) => { + return totalItems - (currentPage - 1) * itemsPerPage; + }; + + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + + const startIndex = (currentPage - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + const currentData = sortedData.slice(startIndex, endIndex); + + const sortData = (sortType: string, data: any[]) => { + return [...data].sort((a, b) => { + switch (sortType) { + case 'amountDesc': + return parseFloat(b['정산 금액']) - parseFloat(a['정산 금액']); + case 'dateDesc': + return new Date(b['정산 완료일']).getTime() - new Date(a['정산 완료일']).getTime(); + case 'couponDateDesc': + return new Date(b['쿠폰 적용일']).getTime() - new Date(a['쿠폰 적용일']).getTime(); + case 'usageCountDesc': + return parseInt(b['사용 건수']) - parseInt(a['사용 건수']); + default: + return 0; + } + }); + }; + + useEffect(() => { + const sorted = sortData(sortOrder, sortedAndNumberedData); + setSettlementData(sorted); + }, []); + + useEffect(() => { + if (startDate && endDate) { + const filteredData = sortedAndNumberedData.filter((data) => { + const couponDate = new Date(data['쿠폰 적용일']); + return couponDate >= startDate && couponDate <= endDate; + }); + + const sorted = sortData(sortOrder, filteredData); + setSettlementData(sorted); + setCurrentPage(1); + } else { + const sorted = sortData(sortOrder, sortedAndNumberedData); + setSettlementData(sorted); + } + }, [startDate, endDate, sortOrder]); + + useEffect(() => { + const sorted = sortData(sortOrder, sortedData); + setSortedData(sorted); + }, [sortOrder]); + + const handleDownloadExcel = () => { + const workBook = XLSX.utils.book_new(); + const workSheet = XLSX.utils.json_to_sheet(sortedData); + + XLSX.utils.book_append_sheet(workBook, workSheet, "Sheet1"); + + XLSX.writeFile(workBook, "download.xlsx"); + }; + + + return ( + + + + 전체 내역 {sortedData.length}개 + + + + + + + + + + + + + + ); +} + +export default Settlemented; + +const Container = styled.nav` + margin-right: 43px; + margin-left: 43px; + + @media (max-width: 900px) { + margin-left: 20px; + margin-right: 20px; + } +`; + +const SettlementedHeader = styled.div` + width: 100%; + + margin-top: 20px; + + display: flex; + justify-content: space-between; + + @media (max-width: 498px) { + flex-direction: column; + align-items: flex-start; + } +`; + +const TotalData = styled.div` + margin-top: auto; + margin-bottom: auto; + + font-size: 14px; + font-weight: bold; + color: white; + + @media (max-width: 498px) { + order: 2; + margin-top: 10px; + } +`; + +const OptionContainer = styled.div` + display: flex; + align-items: center; + + @media (max-width: 498px) { + order: 1; + margin-bottom: 10px; + justify-content: space-between; + } +`; + +const StyledDropdown = styled(Dropdown)` + @media (max-width: 900px) { + border: none; + } + + &.ui.dropdown { + min-width: 160px; + backdrop-filter: blur(50px); + border-radius: 14px; + font-color: white !important; + background-color: rgba(255, 255, 255, 0.1); + + @media (max-width: 900px) { + } + + @media (max-width: 498px) { + max-width: 160px; + } + + .text { + color: white; + font-size: 11px; + max-height: 30px; + } + .icon { + color: white; + } + .menu .item.selected { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; + } + .menu { + font-size: 11px; + background-color: rgba(255, 255, 255, 0.1) !important; + border: 1px solid rgba(255, 255, 255, 0.2) !important; + border-radius: 0px 0px 5px 5px; + margin-bottom: 2px; + + .item { + white-space: nowrap; + border-bottom: none !important; + border-top: none !important; + } + } + } + + &.ui.selection.dropdown { + min-height: 20px; + } + + &.ui.selection.active.dropdown { + color: white; + border: 1px solid rgba(255, 255, 255, 0.2) !important; + box-shadow: 0 2px 3px 0 rgba(34, 36, 38, 0.15) !important; + } + + &.ui.selection.visible.dropdown>.text:not(.default) { + font-weight: 400; + color: white; +} +`; + + + +const ExcelDownload = styled.div` + margin-left: 10px; + + width: 100%; + + white-space: nowrap; + + button { + font-size: 12px; + font-weight: bold; + color: white; + + border: none; + text-decoration: underline; + cursor: pointer; + background: none; + text-underline-offset : 8px; + } +`; + +const DataLow = styled.div` + width: 100%; +`; diff --git a/src/components/Settlements/SettlementsLeft/SettlementsSetting/SettlementsHeader/SettlementsPopup/index.tsx b/src/components/Settlements/SettlementsLeft/SettlementsSetting/SettlementsHeader/SettlementsPopup/index.tsx new file mode 100644 index 00000000..f0ef669e --- /dev/null +++ b/src/components/Settlements/SettlementsLeft/SettlementsSetting/SettlementsHeader/SettlementsPopup/index.tsx @@ -0,0 +1,111 @@ +import styled from '@emotion/styled'; + +import { SettlementsPopupProps } from '@/types/settlements'; + +const SettlementsPopup: React.FC = ({ isOpen, onClose }) => { + return ( + + + 정산내역 안내 + X + + + +

쿠폰 번호는 쿠폰을 등록하면 자동으로 발행되는 관리목적의 번호입니다.

+

관리 쿠폰명은 사장님께서 직접 설정한 쿠폰명입니다.

+

사용 건수는 쿠폰 적용일에 고객들이 쿠폰을 사용한 건 수입니다.

+

구폰 적용일은 사용자가 쿠폰을 적용하여 결제(예약)를 한 날입니다.

+

정산 완료일은 정산이 완료된 날로 매월 10일 (영업일 기준)입니다.

+
+ +

[정산 금액 관련]

+

쿠폰 할인 금액은 쿠폰 적용일에 사용된 모든 쿠폰 금액의 합계입니다.

+

쿠폰 취소 금액은 환불/취소를 통해 쿠폰 사용이 취소된 금액(- 표기)입니다.

+

정산 금액은 “쿠폰 할인 금액 + 쿠폰 취소 금액” 입니다.

+
+
+
+ ); +}; + +const PopupContainer = styled.div<{ isOpen: boolean }>` + display: ${({ isOpen }) => (isOpen ? 'block' : 'none')}; + position: fixed; + top: 50%; + left: 50%; + + padding: 20px; + border-radius: 6px; + + transform: translate(-50%, -50%); + background-color: white; + box-shadow: 0px 2px 64px rgba(0, 0, 0, 0.1); + + z-index: 999; +`; + +const PopupHeader = styled.div` + display: flex; + flex-direction: row; + + text-align: center; + align-items: center; + justify-content: space-between; +`; + +const PopupTitle = styled.h2` + font-size: 20px; + font-weight: 900; +`; + +const PopupCloseButton = styled.button` + background: none; + border: none; + + font-size: 20px; + font-weight: bold; + cursor: pointer; +`; + +const PopupContentHeader = styled.div` + margin-top: 30px; + + p { + margin-bottom: 10px; + + font-size: 14px; + font-weight: normal; + } + + strong { + font-weight: 900; + } +`; + +const PopupContentMiddle = styled.div` + margin-top: 20px; + + p { + margin-bottom: 10px; + + font-size: 14px; + font-weight: normal; + } + + strong { + font-weight: 900; + } +`; + + +const PopupContent = styled.div` + margin-top: 10px; + + line-height: 1.5; + + p { + margin-bottom: 10px; + } +`; + +export default SettlementsPopup; diff --git a/src/components/Settlements/SettlementsLeft/SettlementsSetting/SettlementsHeader/index.tsx b/src/components/Settlements/SettlementsLeft/SettlementsSetting/SettlementsHeader/index.tsx new file mode 100644 index 00000000..186d8849 --- /dev/null +++ b/src/components/Settlements/SettlementsLeft/SettlementsSetting/SettlementsHeader/index.tsx @@ -0,0 +1,94 @@ +import styled from '@emotion/styled'; +import { useState } from 'react'; + +import settlementsAdminIcon from '@assets/icons/settlements-admin.svg'; +import informationIcon from '@assets/icons/information-circle-outline.svg'; +import SettlementsPopup from './SettlementsPopup'; + +const SettlementsHeader = () => { + const [isPopupOpen, setIsPopupOpen] = useState(false) + + const handlePopupToggle = () => { + setIsPopupOpen(!isPopupOpen); + }; + + return ( + +
+ + + 정산내역 + +
+ + + 쿠폰 프로모션에 적용한 정산 내역을 확인할 수 있습니다. + + + + + + +
+ ) +} + +export default SettlementsHeader; + +const Container = styled.nav` + margin-left: 43px; + margin-top: 96px; + + @media (max-width: 900px) { + display: none; + } +`; + +const Header = styled.nav` + margin-bottom: 30px; + + display: flex; + + align-items: center; +`; + +const SettlementsAdminIcon = styled.img` + width: 44px; + height: 34.57px; + + margin-right: 10px; +`; + +const HeaderText = styled.div` + font-size: 32px; + font-weight: bold; + color: white; +`; + +const Middle = styled.div` + display: flex; + align-items: center; +`; + +const MiddleText = styled.div` + margin-right: 5px; + + font-size: 18px; + color: white; +`; + +const InformationContainer = styled.div` + display: flex; + align-items: center; +`; + +const InformationIcon = styled.img` + margin-bottom: 5px; + + cursor: pointer; +`; \ No newline at end of file diff --git a/src/components/Settlements/SettlementsLeft/SettlementsSetting/index.tsx b/src/components/Settlements/SettlementsLeft/SettlementsSetting/index.tsx new file mode 100644 index 00000000..aef8c1df --- /dev/null +++ b/src/components/Settlements/SettlementsLeft/SettlementsSetting/index.tsx @@ -0,0 +1,190 @@ +import styled from '@emotion/styled'; +import { useState } from 'react'; +import DatePicker from 'react-datepicker'; +import 'react-datepicker/dist/react-datepicker.css'; +import { ko } from 'date-fns/locale'; +import { useSetRecoilState } from 'recoil'; + +import CalendarIcon from '@assets/icons/calendar-number-outline.svg'; +import Settlemented from './Settlemented'; +import SettlementsHeader from './SettlementsHeader'; +import { settlementsDateState } from '@recoil/atoms/settlemented'; + +const SettlementsSetting = () => { + const [startDate, setStartDate] = useState(null); + const [endDate, setEndDate] = useState(null); + + const setSettlementsDate = useSetRecoilState(settlementsDateState); + + const handleStartDateChange = (date: Date | null) => { + if (date) { + date.setDate(1); + date.setHours(0, 0, 0, 0); + } + setStartDate(date); + }; + + const handleEndDateChange = (date: Date | null) => { + if (date) { + date.setMonth(date.getMonth() + 1); + date.setDate(0); + date.setHours(23, 59, 59, 999); + } + setEndDate(date); + }; + + const handleButtonClick = () => { + if (startDate && endDate) { + setSettlementsDate({ startDate, endDate }); + } + }; + + return ( + + + + + + 기간 설정 + + + + + 조회하기 + + + +
+
+ +
+ ) +} + +export default SettlementsSetting; + +const Container = styled.div` + width: 100%; + height: 100%; +`; + +const BreakLine = styled.div` + margin: 0 40px; + + hr { + @media (max-width: 900px) { + display: none; + } + } +`; + +const CalendarContainer = styled.nav` + display: flex; + justify-content: flex-end; + align-items: center; + + @media (max-width: 900px) { + margin: 10px 20px; + + display: flex; + flex-direction: column; + justify-content: center; + } +`; + +const CalendarInnerContainer = styled.div` + margin-top: 10px; + + display: flex; + + @media (max-width: 900px) { + width: 100%; + } +`; + +const Calendar = styled.img` + width: 24px; + height: 24px; + + margin-right: 10px; +`; + +const StyledDatePicker = styled(DatePicker)` + margin-right: 10px; + padding: 5px; + + width: 100px; + + border: none; + border-radius: 8px; + + font-size: 14px; + + @media (max-width: 900px) { + } +`; + +const CalendarText = styled.div` + font-size: 15px; + font-weight: bold; + color: white; + + margin-right: 10px; + margin-top: auto; + margin-bottom: auto; +`; + +const StyledButton = styled.button` + padding: 7px 14px; + + background-color: #3182F6; + color: white; + + font-weight: 600; + font-size: 12px; + + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 8px; + cursor: pointer; + + &:hover { + opacity: 1; + } + + @media (max-width: 900px) { + margin-left: auto; + } +`; + +const StyledDatePickerContainer = styled.div` + margin-top: 15px; + margin-right: 43px; + + display: flex; + align-items: center; + + @media (max-width: 900px) { + margin-left: auto; + margin-right: 0px; + } + + @media (max-width: 500px) { + width: 100%; + } +`; \ No newline at end of file diff --git a/src/components/Settlements/SettlementsLeft/index.tsx b/src/components/Settlements/SettlementsLeft/index.tsx new file mode 100644 index 00000000..e278041b --- /dev/null +++ b/src/components/Settlements/SettlementsLeft/index.tsx @@ -0,0 +1,26 @@ +import styled from '@emotion/styled'; + +import SettlementsSetting from './SettlementsSetting'; + +const SettlementsLeft = () => { + return ( + + + + ) +} + +export default SettlementsLeft; + +const Container = styled.div` + width: 80%; + min-width: 1110px; + + background: linear-gradient(45deg, rgba(17, 31, 63, 1), rgba(26, 40, 73, 0.75)); + + @media (max-width: 900px) { + width: 100%; + min-width: 0; + background: #1A2849; + } +`; \ No newline at end of file diff --git a/src/components/Settlements/SettlementsRight/SettlementsBefore/index.tsx b/src/components/Settlements/SettlementsRight/SettlementsBefore/index.tsx new file mode 100644 index 00000000..ee076392 --- /dev/null +++ b/src/components/Settlements/SettlementsRight/SettlementsBefore/index.tsx @@ -0,0 +1,276 @@ +import styled from '@emotion/styled'; + +import SyncIcon from '@assets/icons/sync-outline.svg'; +import receiptIcon from '@assets/icons/receipt-sharp.svg'; + +const SettlementsBefore = () => { + + const currentDate = new Date(); + const lastMonth = new Date(currentDate); + lastMonth.setMonth(currentDate.getMonth() - 1); + lastMonth.setDate(1); + + const formatDate = (date: Date) => { + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + return `${year}.${month < 10 ? '0' : ''}${month}.${day < 10 ? '0' : ''}${day}`; + }; + + const isBeforeDueDate = currentDate.getDate() < 10; + + return ( + + + + + {`${currentDate.getMonth() + 1}월`} + + 정산 금액 + + + + + + {isBeforeDueDate + ? `매월 1일 00시 00분 업데이트` + : `매월 11일 00시 00분 업데이트` + } + + + + + + + 쿠폰 적용 금액 + + + {`${lastMonth.getFullYear()}.${lastMonth.getMonth() < 9 ? '0' : ''}${lastMonth.getMonth() + 1}.01 ~ ${formatDate(new Date(currentDate.getFullYear(), currentDate.getMonth(), 0))}`} + + + + + + + {isBeforeDueDate + ? `정산 예정 금액` + : `정산 완료 금액` + } + + + + {isBeforeDueDate + ? `${currentDate.getMonth() + 1}월 10일에 정산할 금액` + : `${currentDate.getMonth() + 1}월 10일 정산 완료 금액` + } + + + 50,000원 + + + + + + + + + + ) +} + +export default SettlementsBefore; + +const Container = styled.div` + margin: 0px 15px; + + display: flex; + flex-direction: column; + + @media (max-width: 900px) { + margin-left: 15px; + margin-right: 0px; + } +`; + +const InnerContainer = styled.div` + width: 100%; +`; + +const ExpectedContainer = styled.div` + margin-top: 30px; + + @media (max-width: 900px) { + margin: 0px; + } +`; + +const SettlementedMonth = styled.p` + font-size: 22px; + font-weight: bold; + color: white; + + & > span { + font-size: 18px; + } + + @media (max-width: 478px) { + font-size: 17px; + + & > span { + font-size: 14px; + } + } +`; + +const ExpectedText = styled.span` + margin-left: 5px; +`; + +const CommonContainer = styled.div` + margin-top: 10px; + + display: flex; + flex-direction: row; + + align-items: center; +`; + +const UpdatedText = styled.div` + margin-top: 1px; + + font-size: 11px; + font-weight: normal; + color: #CDCFD0; + + @media (max-width: 478px) { + font-size: 9px; + } +`; + +const UpdatedContainer = styled.div` + width: 100%; + + margin-top: 25px; + + border: 1.5px solid white; + border-radius: 8px; +`; + +const UpdatedInnerContainer = styled.div` + margin-top: 20px; + + background-color: white; + + border: 1px solid white; + border-radius: 5px; +`; + +const UpdatedWrapper = styled.div` + margin: 15px; +`; + +const WrapperTop = styled.div` + display: flex; + flex-direction: column; +`; + +const DueDateText = styled.div` + font-size: 14px; + font-weight: bold; + color: black; + + @media (max-width: 478px) { + font-size: 11px; + } +`; + +const DueDateInnerContainer = styled.div` + margin-top: 10px; + + display: flex; + flex-direction: row; + + justify-content: space-between; + align-items: center; + + @media (max-width: 530px) { + flex-direction: column; + } +`; + +const DueDateDay = styled.div` + font-size: 12px; + font-weight: regular; + color: black; + + @media (max-width: 530px) { + width: 100%; + } + + @media (max-width: 478px) { + font-size: 10px; + } +`; + +const DueDateMoney = styled.div` + margin-left: auto; + + align-items: flex-end + + font-size: 16px; + font-weight: bold; + + @media (max-width: 530px) { + margin-top: 8px; + + width: 100%; + } + + @media (max-width: 478px) { + font-size: 10px; + } +`; + +const MoneyContainer = styled.div` + margin: 15px; +`; + +const MoneyText = styled.div` + margin-bottom: 15px; + + font-size: 14px; + font-weight: bold; + color: white; + + @media (max-width: 478px) { + font-size: 12px; + } +`; + +const MoneyDay = styled.div` + font-size: 12px; + color: white; + + @media (max-width: 478px) { + font-size: 9px; + } +`; + +const Icon = styled.img` + margin-right: 3px; +`; + +const ReceiptIcon = styled.img` + max-width: 200px; + + margin-top: 40px; + margin-left: auto; + + @media (max-width: 900px) { + display: none; + } +`; \ No newline at end of file diff --git a/src/components/Settlements/SettlementsRight/SettlementsExpected/index.tsx b/src/components/Settlements/SettlementsRight/SettlementsExpected/index.tsx new file mode 100644 index 00000000..dc616126 --- /dev/null +++ b/src/components/Settlements/SettlementsRight/SettlementsExpected/index.tsx @@ -0,0 +1,232 @@ +import styled from '@emotion/styled'; + +import SyncIcon from '@assets/icons/sync-outline.svg'; + +const SettlementsExpected = () => { + const currentDate = new Date(); + + const nextMonth = new Date(currentDate); + nextMonth.setMonth(currentDate.getMonth() + 1); + + const formatDate = (date: Date) => { + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + return `${year}.${month < 10 ? '0' : ''}${month}.${day < 10 ? '0' : ''}${day}`; + }; + + const firstDayOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1); + + return ( + + + + + + {`${nextMonth.getMonth() + 1}월`} + + 정산 예정 금액 + + + + + + + 매월 1일 00시 00분 업데이트 + + + + + + + 쿠폰 적용 기간 + + + {`${formatDate(firstDayOfMonth)} ~ ${formatDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0))}`} + + + + + + + 정산예정일 + + + {`${currentDate.getFullYear()}년 ${currentDate.getMonth() + 2}월 10일`} + + + + + {`${currentDate.getMonth() + 1}월 1일부터 현재까지 금액`} + + + + + 매일 00시 00분에 업데이트 + + + + 50000원 + + + + + + + + ); +}; + +export default SettlementsExpected; + +const Container = styled.div` + display: flex; + margin: 150px 15px 30px 15px; + align-items: center; + + @media (max-width: 900px) { + margin: 0px; + } +`; + +const InnerContainer = styled.div` + width: 100%; +`; + +const ExpectedContainer = styled.div` +`; + +const TextContainer = styled.div` + display: flex; +`; + +const ExpectedMonth = styled.p` + font-size: 22px; + font-weight: bold; + color: white; + + & > span { + font-size: 18px; + } + + @media (max-width: 478px) { + font-size: 17px; + + & > span { + font-size: 14px; + } + } +`; + +const ExpectedText = styled.span` + margin-left: 5px; +`; + +const CommonContainer = styled.div` + margin-top: 10px; + + display: flex; + flex-direction: row; + + align-items: center; +`; + +const UpdatedText = styled.div` + margin-top: 1px; + + font-size: 11px; + font-weight: normal; + color: #CDCFD0; + + @media (max-width: 478px) { + font-size: 9px; + } +`; + +const UpdatedContainer = styled.div` + width: 100%; + + margin-top: 25px; + + border: 1.5px solid white; + border-radius: 8px; +`; + +const UpdatedInnerContainer = styled.div` + margin-top: 20px; + + background-color: white; + + border: 1px solid white; + border-radius: 5px; +`; + +const UpdatedWrapper = styled.div` + margin: 15px; +`; + +const WrapperTop = styled.div` + display: flex; + flex-direction: column; +`; + +const DueDateText = styled.div` + font-size: 14px; + font-weight: bold; + color: black; + + @media (max-width: 478px) { + font-size: 11px; + } +`; + +const DueDateDay = styled.div` + margin-left: auto; + margin-top: 10px; + + align-items: flex-end + + font-size: 16px; + font-weight: bold; + color: #3182F6; + + @media (max-width: 478px) { + font-size: 10px; + } +`; + +const WrapperBottom = styled.div` + margin-top: 20px; + + display: flex; + flex-direction: column; +`; + +const CouponContainer = styled.div` + margin: 15px; +`; + +const CouponText = styled.div` + margin-bottom: 15px; + + font-size: 14px; + font-weight: bold; + color: white; + + @media (max-width: 478px) { + font-size: 12px; + } +`; + +const CouponDay = styled.div` + font-size: 12px; + color: white; + + @media (max-width: 478px) { + font-size: 10px; + } +`; + +const Icon = styled.img` + margin-right: 3px; +`; diff --git a/src/components/Settlements/SettlementsRight/index.tsx b/src/components/Settlements/SettlementsRight/index.tsx new file mode 100644 index 00000000..f2e3fd62 --- /dev/null +++ b/src/components/Settlements/SettlementsRight/index.tsx @@ -0,0 +1,116 @@ +import styled from '@emotion/styled'; + +import SettlementsBefore from "./SettlementsBefore" +import SettlementsExpected from "./SettlementsExpected" +import settlementsLogo from '@assets/icons/settlements-logo.svg'; + +const SettlementsRight = () => { + return ( + + + + + +
+ + + +
+ +
+ ) +} + +export default SettlementsRight; + +const Container = styled.div` + position: relative; + + width: 20%; + min-width: 290px; + + background-color: #1A2849; + border-top-right-radius: 1.25rem; + border-bottom-right-radius: 1.25rem; + + @media (max-width: 900px) { + width: 100%; + min-width: 0; + border-radius: 0; + } +`; + +const InnerContainer = styled.div` + height: 100%; + + flex-direction: column; + align-items: center; + justify-content: center; + + position: relative; + z-index: 10; + + background-color: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.1); + backdrop-filter: blur(20px); + box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.2); + + hr { + margin: 0px 15px; + + border: 1px solid rgba(217, 217, 217, 0.2); + + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + + @media (max-width: 900px) { + display: none; + } + } + + @media (max-width: 900px) { + margin: 20px; + display: flex; + flex-direction: row; + align-items: center; + background-color: transparent; + border: none; + backdrop-filter: none; + box-shadow: none; + } +`; + +const StyledSettlementsExpected = styled.div` + @media (max-width: 900px) { + width: 50%; + height: 300px; + } +`; +const StyledSettlementsBefore = styled.div` + position: relative; + z-index: 1; + + @media (max-width: 900px) { + width: 50%; + height: 300px; + } +`; + +const Logo = styled.div` + position: absolute; + + width: 165px; + height: 227px; + + top: 65%; + right: 0; + + background: url(${settlementsLogo}); + + @media (max-width: 900px) { + position: static; + width: 100%; + max-width: 165px; + height: auto; + margin-top: 20px; + } +`; diff --git a/src/components/Settlements/index.tsx b/src/components/Settlements/index.tsx new file mode 100644 index 00000000..5fb96e1e --- /dev/null +++ b/src/components/Settlements/index.tsx @@ -0,0 +1,2 @@ +export { default as SettlementsLeft } from './SettlementsLeft'; +export { default as SettlementsRight } from './SettlementsRight'; diff --git a/src/pages/Settlements/index.tsx b/src/pages/Settlements/index.tsx new file mode 100644 index 00000000..355811b1 --- /dev/null +++ b/src/pages/Settlements/index.tsx @@ -0,0 +1,24 @@ +import styled from '@emotion/styled'; + +import { SettlementsRight, SettlementsLeft } from '@components/Settlements'; + +const Settlements = () => { + return ( + + + + + ); +}; + +export default Settlements; + +const Container = styled.div` + width: 100%; + display: flex; + border-radius: 1.25rem; + + @media (max-width: 900px) { + flex-direction: column-reverse; + } +`; diff --git a/src/recoil/atoms/couponListState.ts b/src/recoil/atoms/couponListState.ts new file mode 100644 index 00000000..4fa77ba4 --- /dev/null +++ b/src/recoil/atoms/couponListState.ts @@ -0,0 +1,10 @@ +import { atom } from 'recoil'; + +import { CouponListResponse } from '@/types/couponList'; + +const couponListState = atom({ + key: 'couponListState', + default: null +}); + +export default couponListState; diff --git a/src/recoil/atoms/settlemented.ts b/src/recoil/atoms/settlemented.ts new file mode 100644 index 00000000..9fc130f4 --- /dev/null +++ b/src/recoil/atoms/settlemented.ts @@ -0,0 +1,49 @@ +import { atom } from 'recoil'; +import { SettlementItem } from '@/types/settlements'; + +export const fakeData = [ + { '쿠폰 적용일': "2023.11.21", '쿠폰번호': 30, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '100회', '쿠폰 할인 금액': '10원', '쿠폰 취소 금액':'0원', '정산 금액': '1000원', '정산 완료일': '2023.11.10'}, + { '쿠폰 적용일': "2023.11.20", '쿠폰번호': 29, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '99회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '999원', '정산 완료일': '2023.11.10'}, + { '쿠폰 적용일': "2023.11.19", '쿠폰번호': 28, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '98회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '998', '정산 완료일': '2023.11.10'}, + { '쿠폰 적용일': "2023.11.18", '쿠폰번호': 27, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '102회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '10000원', '정산 완료일': '2023.11.10'}, + { '쿠폰 적용일': "2023.11.17", '쿠폰번호': 26, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '103회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '13000원', '정산 완료일': '2023.11.10'}, + { '쿠폰 적용일': "2023.11.16", '쿠폰번호': 25, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '104회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '14000원', '정산 완료일': '2023.11.10'}, + { '쿠폰 적용일': "2023.11.15", '쿠폰번호': 24, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '105회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '16000원', '정산 완료일': '2023.11.10'}, + { '쿠폰 적용일': "2023.11.22", '쿠폰번호': 23, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '106회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '176000원', '정산 완료일': '2023.11.10'}, + { '쿠폰 적용일': "2023.11.23", '쿠폰번호': 23, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '107회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '12700원', '정산 완료일': '2023.11.10'}, + { '쿠폰 적용일': "2023.11.24", '쿠폰번호': 22, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '1회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '9934원', '정산 완료일': '2023.11.10'}, + { '쿠폰 적용일': "2023.11.25", '쿠폰번호': 22, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '9회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '9993원', '정산 완료일': '2023.11.10'}, + { '쿠폰 적용일': "2023.11.26", '쿠폰번호': 21, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '2회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '9959원', '정산 완료일': '2023.11.10'}, + { '쿠폰 적용일': "2023.11.20", '쿠폰번호': 22, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '3회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '912원', '정산 완료일': '2023.11.10'}, + { '쿠폰 적용일': "2023.11.27", '쿠폰번호': 20, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '4회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '999123원', '정산 완료일': '2023.11.10'}, + { '쿠폰 적용일': "2023.11.28", '쿠폰번호': 19, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '5회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '999312원', '정산 완료일': '2023.11.10'}, + { '쿠폰 적용일': "2023.11.29", '쿠폰번호': 18, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '45회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '100원', '정산 완료일': '2023.12.10'}, + { '쿠폰 적용일': "2023.12.01", '쿠폰번호': 17, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '44회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '200원', '정산 완료일': '2023.12.10'}, + { '쿠폰 적용일': "2023.12.02", '쿠폰번호': 16, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '43회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '300원', '정산 완료일': '2023.12.10'}, + { '쿠폰 적용일': "2023.10.04", '쿠폰번호': 15, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '47회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '500원', '정산 완료일': '2023.11.02'}, + { '쿠폰 적용일': "2023.10.05", '쿠폰번호': 14, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '66회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '504원', '정산 완료일': '2023.11.02'}, + { '쿠폰 적용일': "2023.10.10", '쿠폰번호': 13, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '55회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '600원', '정산 완료일': '2023.11.02'}, + { '쿠폰 적용일': "2023.10.30", '쿠폰번호': 12, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '54회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '700원', '정산 완료일': '2023.11.02'}, + { '쿠폰 적용일': "2023.10.29", '쿠폰번호': 11, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '66회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '800원', '정산 완료일': '2023.11.02'}, + { '쿠폰 적용일': "2023.10.28", '쿠폰번호': 10, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '97회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '900원', '정산 완료일': '2023.11.02'}, + { '쿠폰 적용일': "2023.10.27", '쿠폰번호': 1, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '98회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '2200원', '정산 완료일': '2023.11.02'}, + { '쿠폰 적용일': "2023.10.26", '쿠폰번호': 2, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '65회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '3000원', '정산 완료일': '2023.11.02'}, + { '쿠폰 적용일': "2023.10.25", '쿠폰번호': 3, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '78회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '4000원', '정산 완료일': '2023.11.02'}, + { '쿠폰 적용일': "2023.10.24", '쿠폰번호': 4, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '88회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '5000원', '정산 완료일': '2023.11.02'}, + { '쿠폰 적용일': "2023.10.23", '쿠폰번호': 5, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '65회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '6000원', '정산 완료일': '2023.11.02'}, + { '쿠폰 적용일': "2023.10.22", '쿠폰번호': 6, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '26회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '6500원', '정산 완료일': '2023.11.02'}, + { '쿠폰 적용일': "2023.10.21", '쿠폰번호': 7, '관리 쿠폰명': "가을 선착순 쿠폰", '사용 건수': '46회', '쿠폰 할인 금액': '50원', '쿠폰 취소 금액':'0원', '정산 금액': '7000원', '정산 완료일': '2023.11.02'}, + ]; + + export const settlementDataState = atom({ + key: 'settlementData', + default: fakeData.map((data, index) => ({ + ...data, + NO: index + 1, + })).sort((a, b) => new Date(a['쿠폰 적용일']).getTime() - new Date(b['쿠폰 적용일']).getTime()) + }); + + export const settlementsDateState = atom({ + key: 'settlementsDateState', + default: { startDate: new Date(), endDate: new Date() }, + }); \ No newline at end of file diff --git a/src/recoil/index.ts b/src/recoil/index.ts index c12015f8..eca2caa8 100644 --- a/src/recoil/index.ts +++ b/src/recoil/index.ts @@ -1 +1,2 @@ export { default as headerAccommodationState } from './atoms/headerAccommodationState'; +export { default as couponListState } from './atoms/couponListState'; diff --git a/src/routes/MainRouter/index.tsx b/src/routes/MainRouter/index.tsx index 8c6f8357..b47c91f3 100644 --- a/src/routes/MainRouter/index.tsx +++ b/src/routes/MainRouter/index.tsx @@ -7,6 +7,7 @@ import Dashboard from '@pages/Dashboard'; import Report from '@pages/Report'; import CouponList from '@pages/CouponList'; import Register from '@pages/Register'; +import Settlements from '@pages/Settlements'; const MainRouter = () => { return ( @@ -34,19 +35,7 @@ const MainRouter = () => { /> - 🧃 정산관리 페이지 입주 예정 🧃 - - } + element={} /> void; + totalItems: number; +}; + +export interface SettlementsPopupProps { + isOpen: boolean; + onClose: () => void; +}; \ No newline at end of file