diff --git a/package-lock.json b/package-lock.json index b52b8b47..bdf24f21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "react-dom": "^18", "react-hook-form": "^7.52.1", "react-toastify": "^10.0.5", + "recharts": "^2.12.7", "sharp": "^0.33.4", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7", @@ -41,7 +42,6 @@ }, "devDependencies": { "@tanstack/eslint-plugin-query": "^5.50.0", - "@types/lodash": "^4.17.7", "@types/node": "^20.14.10", "@types/qs": "^6.9.15", "@types/react": "^18.3.3", @@ -81,6 +81,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/runtime": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", @@ -1922,6 +1933,60 @@ "react": "^18 || ^19" } }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", @@ -1937,12 +2002,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "node_modules/@types/lodash": { - "version": "4.17.7", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", - "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", - "dev": true - }, "node_modules/@types/node": { "version": "20.14.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz", @@ -2861,6 +2920,116 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -2945,6 +3114,11 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "node_modules/deep-equal": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", @@ -3079,6 +3253,15 @@ "node": ">=6.0.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -4028,6 +4211,11 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4040,6 +4228,14 @@ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, + "node_modules/fast-equals": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", + "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -4652,6 +4848,14 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -6236,7 +6440,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -6246,8 +6449,7 @@ "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/property-expr": { "version": "2.0.6", @@ -6395,6 +6597,20 @@ } } }, + "node_modules/react-smooth": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz", + "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -6429,6 +6645,21 @@ "react-dom": ">=18" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -6448,6 +6679,41 @@ "node": ">=8.10.0" } }, + "node_modules/recharts": { + "version": "2.12.7", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz", + "integrity": "sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^16.10.2", + "react-smooth": "^4.0.0", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", @@ -6469,6 +6735,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -7196,6 +7467,11 @@ "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, "node_modules/tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", @@ -7467,6 +7743,27 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vue-eslint-parser": { "version": "9.4.3", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", diff --git a/package.json b/package.json index 450b39fe..a892dda9 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "react-dom": "^18", "react-hook-form": "^7.52.1", "react-toastify": "^10.0.5", + "recharts": "^2.12.7", "sharp": "^0.33.4", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7", diff --git a/src/components/Card/UserProfileModal.tsx b/src/components/Card/UserProfileModal.tsx new file mode 100644 index 00000000..f4dd6f50 --- /dev/null +++ b/src/components/Card/UserProfileModal.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog_dim'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; + +interface UserProfileModalProps { + username: string; + profileImage: string; + children: React.ReactNode; +} + +function UserProfileModal({ username, profileImage, children }: UserProfileModalProps) { + return ( + + {children} + + + + + {username[0]} + + + {username} + + + + + ); +} + +export default UserProfileModal; diff --git a/src/components/epigram/Comment/CommentItem.tsx b/src/components/epigram/Comment/CommentItem.tsx index 3b1b4501..70617a82 100644 --- a/src/components/epigram/Comment/CommentItem.tsx +++ b/src/components/epigram/Comment/CommentItem.tsx @@ -3,9 +3,9 @@ import Image from 'next/image'; import { CommentType } from '@/schema/comment'; import { textSizeStyles, gapStyles, paddingStyles, contentWidthStyles } from '@/styles/CommentCardStyles'; import getCustomRelativeTime from '@/lib/dateUtils'; -import useDeleteCommentMutation from '@/hooks/useDeleteCommentHook'; -import { useToast } from '@/components/ui/use-toast'; import { Button } from '@/components/ui/button'; +import useDeleteCommentMutation from '@/hooks/useDeleteCommentHook'; +import UserProfileModal from '@/components/Card/UserProfileModal'; import DeleteAlertModal from '../DeleteAlertModal'; import CommentTextarea from './CommentTextarea'; @@ -18,9 +18,16 @@ interface CommentItemProps { } function CommentItem({ comment, status, onEditComment, isEditing, epigramId }: CommentItemProps) { - const deleteCommentMutation = useDeleteCommentMutation(); - const { toast } = useToast(); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const deleteCommentMutation = useDeleteCommentMutation({ + onSuccess: () => { + setIsDeleteModalOpen(false); + }, + }); + + const handleDeleteComment = async () => { + deleteCommentMutation.mutate({ commentId: comment.id, epigramId }); + }; const handleEditClick = () => { onEditComment(comment.id); @@ -31,32 +38,18 @@ function CommentItem({ comment, status, onEditComment, isEditing, epigramId }: C return onEditComment(0)} />; } - const handleDeleteComment = async () => { - try { - await deleteCommentMutation.mutateAsync(comment.id); - setIsDeleteModalOpen(false); - toast({ - title: '댓글이 삭제되었습니다.', - variant: 'destructive', - }); - } catch (error) { - toast({ - title: '댓글 삭제 실패했습니다.', - variant: 'destructive', - }); - } - }; - return ( - - - + + + + + - + @@ -69,14 +62,14 @@ function CommentItem({ comment, status, onEditComment, isEditing, epigramId }: C {status === 'edit' && ( 수정 setIsDeleteModalOpen(true)} type='button' > diff --git a/src/components/main/FAB.tsx b/src/components/main/FAB.tsx index 2d064dc6..43b177e3 100644 --- a/src/components/main/FAB.tsx +++ b/src/components/main/FAB.tsx @@ -10,10 +10,10 @@ function FAB() { - + ); } diff --git a/src/components/mypage/Chart.tsx b/src/components/mypage/Chart.tsx index 8d8a1da7..fa8c8a14 100644 --- a/src/components/mypage/Chart.tsx +++ b/src/components/mypage/Chart.tsx @@ -1,4 +1,5 @@ import { EmotionLog, EmotionTypeEN } from '@/types/emotion'; +import { PieChart, Pie, Cell, ResponsiveContainer } from 'recharts'; import Image from 'next/image'; import { iconPaths } from '../../user/utill/constants'; @@ -22,52 +23,51 @@ export default function Chart({ monthlyEmotionLogs }: ChartProps) { // 감정 종류 및 총 감정 수 계산 const TOTAL_COUNT = monthlyEmotionLogs.length; const EMOTIONS: EmotionTypeEN[] = ['MOVED', 'HAPPY', 'WORRIED', 'SAD', 'ANGRY']; - const RADIUS = 90; // 원의 반지름 - const CIRCUMFERENCE = 2 * Math.PI * RADIUS; // 가장 많이 나타나는 감정 찾기 const maxEmotion = EMOTIONS.reduce((max, emotion) => (emotionCounts[emotion] > emotionCounts[max] ? emotion : max), EMOTIONS[0]); - // 원형 차트의 각 감정에 대한 strokeDasharray와 strokeDashoffset 계산 - let offset = 0; + // 원형 차트 데이터 생성 및 정렬 (가장 많은 감정부터) + const chartData = EMOTIONS.map((emotion) => ({ + name: emotion, + value: emotionCounts[emotion] || 0, + })).sort((a, b) => b.value - a.value); + + // 감정 색상 설정 + const COLORS = { + MOVED: '#48BB98', + HAPPY: '#FBC85B', + WORRIED: '#C7D1E0', + SAD: '#E3E9F1', + ANGRY: '#EFF3F8', + }; return ( 감정 차트 - - - {EMOTIONS.map((emotion) => { - const count = emotionCounts[emotion] || 0; - const percentage = TOTAL_COUNT > 0 ? count / TOTAL_COUNT : 0; // 0으로 나누기 방지 - const strokeDasharray = `${CIRCUMFERENCE * percentage} ${CIRCUMFERENCE * (1 - percentage)}`; - - // 색상 설정 - let strokeColor; - switch (emotion) { - case 'HAPPY': - strokeColor = '#FBC85B'; - break; - case 'SAD': - strokeColor = '#E3E9F1'; - break; - case 'WORRIED': - strokeColor = '#C7D1E0'; - break; - case 'ANGRY': - strokeColor = '#EFF3F8'; - break; - default: - strokeColor = '#48BB98'; - } - - const circle = ; - - offset += CIRCUMFERENCE * percentage; // 다음 원을 위한 offset 업데이트 - return circle; - })} - + + + + {chartData.map((emotion, index) => ( + // TODO: index 값 Lint error. 임시로 주석 사용. 추후 수정 예정 + // eslint-disable-next-line react/no-array-index-key + + ))} + + + {/* 중앙에 가장 많이 나타나는 감정 출력 */} @@ -76,14 +76,13 @@ export default function Chart({ monthlyEmotionLogs }: ChartProps) { - {EMOTIONS.map((emotion) => { - const count = emotionCounts[emotion] || 0; - const percentage = TOTAL_COUNT > 0 ? Math.floor((count / TOTAL_COUNT) * 100) : 0; // 퍼센트 계산 및 소수점 버리기 + {chartData.map((emotion) => { + const percentage = TOTAL_COUNT > 0 ? Math.floor((emotion.value / TOTAL_COUNT) * 100) : 0; return ( - - - + + + {percentage}% ); diff --git a/src/components/ui/dialog_dim.tsx b/src/components/ui/dialog_dim.tsx new file mode 100644 index 00000000..dee1651c --- /dev/null +++ b/src/components/ui/dialog_dim.tsx @@ -0,0 +1,65 @@ +import * as React from 'react'; +import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { X } from 'lucide-react'; + +import cn from '@/lib/utils'; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +function DialogHeader({ className, ...props }: React.HTMLAttributes) { + return ; +} +DialogHeader.displayName = 'DialogHeader'; + +function DialogFooter({ className, ...props }: React.HTMLAttributes) { + return ; +} +DialogFooter.displayName = 'DialogFooter'; + +const DialogTitle = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription }; diff --git a/src/hooks/useDeleteCommentHook.ts b/src/hooks/useDeleteCommentHook.ts index 006019b5..224cf8a6 100644 --- a/src/hooks/useDeleteCommentHook.ts +++ b/src/hooks/useDeleteCommentHook.ts @@ -1,27 +1,36 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { deleteComment } from '@/apis/epigramComment'; import { toast } from '@/components/ui/use-toast'; +import queries from '@/apis/queries'; -const useDeleteCommentMutation = () => { - const queryClient = useQueryClient(); +interface DeleteCommentVariables { + commentId: number; + epigramId?: number; +} - return useMutation({ - mutationFn: (commentId: number) => deleteComment(commentId), - onSuccess: () => { - // 댓글 목록 쿼리 무효화 - queryClient.invalidateQueries({ queryKey: ['epigramComments'] }); +const useDeleteCommentMutation = (options?: { onSuccess?: (variables: DeleteCommentVariables) => void }) => { + const queryClient = useQueryClient(); - // 성공 메시지 표시 + return useMutation({ + mutationFn: ({ commentId }) => deleteComment(commentId), + onSuccess: (_, variables) => { + if (variables.epigramId) { + queryClient.invalidateQueries({ + queryKey: queries.epigramComment.getComments(variables.epigramId).queryKey, + }); + } toast({ - title: '댓글 삭제 성공', - description: '댓글이 성공적으로 삭제되었습니다.', + title: '댓글이 삭제되었습니다.', + variant: 'destructive', }); + + if (options?.onSuccess) { + options.onSuccess(variables); + } }, - onError: (error) => { - // 에러 메시지 표시 + onError: () => { toast({ - title: '댓글 삭제 실패', - description: `댓글 삭제 중 오류가 발생했습니다: ${error instanceof Error ? error.message : '알 수 없는 오류'}`, + title: '댓글 삭제 실패했습니다.', variant: 'destructive', }); }, diff --git a/src/hooks/useEpigramCommentsQueryHook.ts b/src/hooks/useEpigramCommentsQueryHook.ts index a8cbdba0..f1db908b 100644 --- a/src/hooks/useEpigramCommentsQueryHook.ts +++ b/src/hooks/useEpigramCommentsQueryHook.ts @@ -6,7 +6,7 @@ const useEpigramCommentsQuery = (epigramId: number) => useInfiniteQuery>({ ...queries.epigramComment.getComments(epigramId), initialPageParam: undefined, - getNextPageParam: (lastPage: CommentResponseType) => lastPage.nextCursor ?? undefined, + getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined, }); export default useEpigramCommentsQuery; diff --git a/src/pageLayout/About/AboutPageLayout.tsx b/src/pageLayout/About/AboutPageLayout.tsx index 4e2afc93..e5cf5806 100644 --- a/src/pageLayout/About/AboutPageLayout.tsx +++ b/src/pageLayout/About/AboutPageLayout.tsx @@ -8,7 +8,7 @@ function AboutLayout() { <> {}} /> - + @@ -75,12 +75,12 @@ function AboutLayout() { - + - + - + diff --git a/src/pageLayout/Epigram/EpigramFigure.tsx b/src/pageLayout/Epigram/EpigramFigure.tsx index dfef7eea..f000f9c7 100644 --- a/src/pageLayout/Epigram/EpigramFigure.tsx +++ b/src/pageLayout/Epigram/EpigramFigure.tsx @@ -22,14 +22,14 @@ function EpigramFigure({ epigram, currentUserId }: EpigramFigureProps) { {epigram.tags.map((tag) => ( - - #{tag.name} - + + #{tag.name} + ))} {isAuthor && } - + {epigram.content} -{epigram.author}- diff --git a/src/pageLayout/Epigrams/MainLayout.tsx b/src/pageLayout/Epigrams/MainLayout.tsx index 98283b91..75777faa 100644 --- a/src/pageLayout/Epigrams/MainLayout.tsx +++ b/src/pageLayout/Epigrams/MainLayout.tsx @@ -5,6 +5,7 @@ import TodayEmotion from '@/components/main/TodayEmotion'; import RecentEpigrams from '@/components/main/RecentEpigram'; import RecentComments from '@/components/main/RecentComment'; import FAB from '@/components/main/FAB'; +import AddEpigramFAB from '../Feed/AddEpigramFAB'; function MainLayout() { return ( @@ -26,6 +27,7 @@ function MainLayout() { + > ); diff --git a/src/pageLayout/Feed/AddEpigramFAB.tsx b/src/pageLayout/Feed/AddEpigramFAB.tsx index 6556eb72..2376e377 100644 --- a/src/pageLayout/Feed/AddEpigramFAB.tsx +++ b/src/pageLayout/Feed/AddEpigramFAB.tsx @@ -15,7 +15,7 @@ function AddEpigramFAB() { variant='default' size='lg' onClick={handleAddEpigramClick} - className='z-10 h-12 lg:h-16 px-3.5 lg:px-5 py-3 lg:py-4 bg-[#2c394d] text-white rounded-[100px] shadow-lg justify-center items-center gap-1 inline-flex fixed bottom-40 right-6 cursor-pointer' + className='z-10 bottom-[140px] md:bottom-[160px] right-6 h-12 lg:h-16 px-3.5 lg:px-5 py-3 lg:py-4 bg-[#2c394d] text-white rounded-[100px] shadow-lg justify-center items-center gap-1 inline-flex fixed cursor-pointer' role='button' aria-label='Add Epigram' tabIndex={0} diff --git a/src/pageLayout/MypageLayout/MyContent.tsx b/src/pageLayout/MypageLayout/MyContent.tsx index a41cc2ae..62713dbc 100644 --- a/src/pageLayout/MypageLayout/MyContent.tsx +++ b/src/pageLayout/MypageLayout/MyContent.tsx @@ -104,26 +104,19 @@ export default function MyContent({ user }: MyContentProps) { }; // 댓글 삭제 - const deleteCommentMutation = useDeleteCommentMutation(); - const handleDeleteComment = async (commentId: number) => { - try { - await deleteCommentMutation.mutateAsync(commentId); + const deleteCommentMutation = useDeleteCommentMutation({ + onSuccess: ({ commentId }) => { setComments((prev) => ({ totalCount: prev.totalCount - 1, nextCursor: prev.nextCursor, list: prev.list.filter((comment) => comment.id !== commentId), })); setCommentCount((prev) => prev - 1); - toast({ - title: '댓글이 삭제되었습니다.', - variant: 'destructive', - }); - } catch (error) { - toast({ - title: '댓글 삭제 실패했습니다.', - variant: 'destructive', - }); - } + }, + }); + + const handleDeleteComment = async (commentId: number) => { + deleteCommentMutation.mutate({ commentId }); }; // 댓글 수정 diff --git a/src/pages/mypage/index.tsx b/src/pages/mypage/index.tsx new file mode 100644 index 00000000..69b8c83e --- /dev/null +++ b/src/pages/mypage/index.tsx @@ -0,0 +1,5 @@ +import MyPageLayout from '@/pageLayout/MypageLayout/MyPageLayout'; + +export default function mypage() { + return ; +}
{percentage}%
- #{tag.name} -
+ {epigram.content} -{epigram.author}- diff --git a/src/pageLayout/Epigrams/MainLayout.tsx b/src/pageLayout/Epigrams/MainLayout.tsx index 98283b91..75777faa 100644 --- a/src/pageLayout/Epigrams/MainLayout.tsx +++ b/src/pageLayout/Epigrams/MainLayout.tsx @@ -5,6 +5,7 @@ import TodayEmotion from '@/components/main/TodayEmotion'; import RecentEpigrams from '@/components/main/RecentEpigram'; import RecentComments from '@/components/main/RecentComment'; import FAB from '@/components/main/FAB'; +import AddEpigramFAB from '../Feed/AddEpigramFAB'; function MainLayout() { return ( @@ -26,6 +27,7 @@ function MainLayout() {
{epigram.content}