diff --git a/eslint.config.mjs b/eslint.config.mjs index c36bbce..f75b3f5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,6 +3,11 @@ import nextVitals from "eslint-config-next/core-web-vitals"; const eslintConfig = defineConfig([ ...nextVitals, + { + rules: { + "react-hooks/set-state-in-effect": "off", + }, + }, globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts"]), ]); diff --git a/package.json b/package.json index 28f7877..b1ba76b 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "react-datepicker": "^9.1.0", "react-dom": "^19.0.0", "recharts": "^3.8.1", - "xlsx": "^0.18.5" + "write-excel-file": "^4.1.1" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -29,8 +29,5 @@ }, "engines": { "node": ">=24" - }, - "pnpm": { - "ignoredBuiltDependencies": ["sharp", "unrs-resolver"] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad3e2d7..6b60402 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,16 +4,22 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + '@babel/core': 7.29.6 + form-data: 4.0.6 + js-yaml: 4.2.0 + postcss: 8.5.16 + importers: .: dependencies: '@vercel/analytics': specifier: ^2.0.1 - version: 2.0.1(next@16.2.6(@babel/core@7.29.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6) + version: 2.0.1(next@16.2.6(@babel/core@7.29.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6) '@vercel/speed-insights': specifier: ^2.0.0 - version: 2.0.0(next@16.2.6(@babel/core@7.29.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6) + version: 2.0.0(next@16.2.6(@babel/core@7.29.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6) axios: specifier: ^1.16.1 version: 1.16.1 @@ -22,7 +28,7 @@ importers: version: 4.3.0 next: specifier: 16.2.6 - version: 16.2.6(@babel/core@7.29.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + version: 16.2.6(@babel/core@7.29.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: specifier: ^19.0.0 version: 19.2.6 @@ -35,9 +41,9 @@ importers: recharts: specifier: ^3.8.1 version: 3.8.1(react-dom@19.2.6(react@19.2.6))(react-is@16.13.1)(react@19.2.6)(redux@5.0.1) - xlsx: - specifier: ^0.18.5 - version: 0.18.5 + write-excel-file: + specifier: ^4.1.1 + version: 4.1.1 devDependencies: '@tailwindcss/postcss': specifier: ^4 @@ -66,14 +72,18 @@ packages: resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} engines: {node: '>=6.9.0'} - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + '@babel/core@7.29.6': + resolution: {integrity: sha512-QdxmAo/ikZqqRGA8s43ww8lcql6naWRvEz0FFrl6MIlc7Gi6TroXnSdWa5U/kq6fzcpqpHesicQxFZIieZbyIA==} engines: {node: '>=6.9.0'} '@babel/generator@7.29.1': resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.28.6': resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} @@ -90,16 +100,24 @@ packages: resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.6 '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} @@ -113,6 +131,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -125,6 +148,10 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} @@ -863,10 +890,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - adler-32@1.3.1: - resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==} - engines: {node: '>=0.8'} - agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -989,10 +1012,6 @@ packages: caniuse-lite@1.0.30001793: resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} - cfb@1.2.2: - resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==} - engines: {node: '>=0.8'} - chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1004,10 +1023,6 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - codepage@1.15.0: - resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==} - engines: {node: '>=0.8'} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1025,11 +1040,6 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - crc-32@1.2.2: - resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} - engines: {node: '>=0.8'} - hasBin: true - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1347,6 +1357,9 @@ packages: picomatch: optional: true + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -1379,14 +1392,10 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - form-data@4.0.5: - resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} - frac@1.1.2: - resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} - engines: {node: '>=0.8'} - function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -1474,6 +1483,10 @@ packages: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + hermes-estree@0.25.1: resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} @@ -1634,8 +1647,8 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} hasBin: true jsesc@3.1.0: @@ -1923,12 +1936,8 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss@8.4.31: - resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} - engines: {node: ^10 || ^12 || >=14} - - postcss@8.5.14: - resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} + postcss@8.5.16: + resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -2097,10 +2106,6 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - ssf@0.11.2: - resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==} - engines: {node: '>=0.8'} - stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} @@ -2270,22 +2275,13 @@ packages: engines: {node: '>= 8'} hasBin: true - wmf@1.0.2: - resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==} - engines: {node: '>=0.8'} - word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - word@0.3.0: - resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==} - engines: {node: '>=0.8'} - - xlsx@0.18.5: - resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} - engines: {node: '>=0.8'} - hasBin: true + write-excel-file@4.1.1: + resolution: {integrity: sha512-MUnCnNtQrcZek832ZcU24uU0rSphFmKPD1DvIjXOlygVb93CV7Tme6H3jUTkxsMmjB2W7HIzERzjqTi5kui71A==} + engines: {node: '>=18'} yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -2315,12 +2311,12 @@ snapshots: '@babel/compat-data@7.29.3': {} - '@babel/core@7.29.0': + '@babel/core@7.29.6': dependencies: '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 + '@babel/generator': 7.29.7 '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.6) '@babel/helpers': 7.29.2 '@babel/parser': 7.29.3 '@babel/template': 7.28.6 @@ -2343,6 +2339,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + '@babel/helper-compilation-targets@7.28.6': dependencies: '@babel/compat-data': 7.29.3 @@ -2360,9 +2364,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-module-imports': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 '@babel/traverse': 7.29.0 @@ -2371,8 +2375,12 @@ snapshots: '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@7.29.7': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-option@7.27.1': {} '@babel/helpers@7.29.2': @@ -2384,6 +2392,10 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 @@ -2407,6 +2419,11 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@emnapi/core@1.10.0': dependencies: '@emnapi/wasi-threads': 1.2.1 @@ -2454,7 +2471,7 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.2.0 minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -2765,7 +2782,7 @@ snapshots: '@alloc/quick-lru': 5.2.0 '@tailwindcss/node': 4.3.0 '@tailwindcss/oxide': 4.3.0 - postcss: 8.5.14 + postcss: 8.5.16 tailwindcss: 4.3.0 '@tybys/wasm-util@0.10.2': @@ -2966,14 +2983,14 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true - '@vercel/analytics@2.0.1(next@16.2.6(@babel/core@7.29.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)': + '@vercel/analytics@2.0.1(next@16.2.6(@babel/core@7.29.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)': optionalDependencies: - next: 16.2.6(@babel/core@7.29.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + next: 16.2.6(@babel/core@7.29.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 - '@vercel/speed-insights@2.0.0(next@16.2.6(@babel/core@7.29.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)': + '@vercel/speed-insights@2.0.0(next@16.2.6(@babel/core@7.29.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)': optionalDependencies: - next: 16.2.6(@babel/core@7.29.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + next: 16.2.6(@babel/core@7.29.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 acorn-jsx@5.3.2(acorn@8.16.0): @@ -2982,8 +2999,6 @@ snapshots: acorn@8.16.0: {} - adler-32@1.3.1: {} - agent-base@6.0.2: dependencies: debug: 4.4.3 @@ -3087,7 +3102,7 @@ snapshots: axios@1.16.1: dependencies: follow-redirects: 1.16.0 - form-data: 4.0.5 + form-data: 4.0.6 https-proxy-agent: 5.0.1 proxy-from-env: 2.1.0 transitivePeerDependencies: @@ -3144,11 +3159,6 @@ snapshots: caniuse-lite@1.0.30001793: {} - cfb@1.2.2: - dependencies: - adler-32: 1.3.1 - crc-32: 1.2.2 - chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -3158,8 +3168,6 @@ snapshots: clsx@2.1.1: {} - codepage@1.15.0: {} - color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -3174,8 +3182,6 @@ snapshots: convert-source-map@2.0.0: {} - crc-32@1.2.2: {} - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -3502,7 +3508,7 @@ snapshots: eslint-plugin-react-hooks@7.1.1(eslint@9.39.4(jiti@2.7.0)): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/parser': 7.29.3 eslint: 9.39.4(jiti@2.7.0) hermes-parser: 0.25.1 @@ -3627,6 +3633,8 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + fflate@0.8.3: {} + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -3653,16 +3661,14 @@ snapshots: dependencies: is-callable: 1.2.7 - form-data@4.0.5: + form-data@4.0.6: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.3 + hasown: 2.0.4 mime-types: 2.1.35 - frac@1.1.2: {} - function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -3751,6 +3757,10 @@ snapshots: dependencies: function-bind: 1.1.2 + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + hermes-estree@0.25.1: {} hermes-parser@0.25.1: @@ -3916,7 +3926,7 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@4.1.1: + js-yaml@4.2.0: dependencies: argparse: 2.0.1 @@ -4056,16 +4066,16 @@ snapshots: natural-compare@1.4.0: {} - next@16.2.6(@babel/core@7.29.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + next@16.2.6(@babel/core@7.29.6)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: '@next/env': 16.2.6 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.10.31 caniuse-lite: 1.0.30001793 - postcss: 8.4.31 + postcss: 8.5.16 react: 19.2.6 react-dom: 19.2.6(react@19.2.6) - styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.6) + styled-jsx: 5.1.6(@babel/core@7.29.6)(react@19.2.6) optionalDependencies: '@next/swc-darwin-arm64': 16.2.6 '@next/swc-darwin-x64': 16.2.6 @@ -4172,13 +4182,7 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss@8.4.31: - dependencies: - nanoid: 3.3.12 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - postcss@8.5.14: + postcss@8.5.16: dependencies: nanoid: 3.3.12 picocolors: 1.1.1 @@ -4405,10 +4409,6 @@ snapshots: source-map-js@1.2.1: {} - ssf@0.11.2: - dependencies: - frac: 1.1.2 - stable-hash@0.0.5: {} stop-iteration-iterator@1.1.0: @@ -4470,12 +4470,12 @@ snapshots: strip-json-comments@3.1.1: {} - styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.6): + styled-jsx@5.1.6(@babel/core@7.29.6)(react@19.2.6): dependencies: client-only: 0.0.1 react: 19.2.6 optionalDependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 supports-color@7.2.0: dependencies: @@ -4673,21 +4673,11 @@ snapshots: dependencies: isexe: 2.0.0 - wmf@1.0.2: {} - word-wrap@1.2.5: {} - word@0.3.0: {} - - xlsx@0.18.5: + write-excel-file@4.1.1: dependencies: - adler-32: 1.3.1 - cfb: 1.2.2 - codepage: 1.15.0 - crc-32: 1.2.2 - ssf: 0.11.2 - wmf: 1.0.2 - word: 0.3.0 + fflate: 0.8.3 yallist@3.1.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index dd1b4fd..55d091c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,9 @@ +overrides: + '@babel/core': 7.29.6 + form-data: 4.0.6 + js-yaml: 4.2.0 + postcss: 8.5.16 + allowBuilds: sharp: true unrs-resolver: true diff --git a/src/app/components/exchange-rate-export-files.js b/src/app/components/exchange-rate-export-files.js new file mode 100644 index 0000000..52e714f --- /dev/null +++ b/src/app/components/exchange-rate-export-files.js @@ -0,0 +1,20 @@ +export function rowsToCsv(rows) { + return `${rows.map((row) => row.map(formatCsvCell).join(",")).join("\r\n")}\r\n`; +} + +export function downloadTextFile(text, filename, type) { + const blob = new Blob([text], { type }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); +} + +function formatCsvCell(value) { + const text = value == null ? "" : String(value); + return /[",\r\n]/.test(text) ? `"${text.replaceAll("\"", "\"\"")}"` : text; +} diff --git a/src/app/components/exchange-rate-viewer.js b/src/app/components/exchange-rate-viewer.js index 3b0d8cc..baa27d7 100644 --- a/src/app/components/exchange-rate-viewer.js +++ b/src/app/components/exchange-rate-viewer.js @@ -4,13 +4,14 @@ import { useState, useEffect, useCallback } from "react"; import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; import { format, subDays, differenceInCalendarDays } from "date-fns"; -import * as XLSX from "xlsx"; +import writeExcelFile from "write-excel-file/browser"; import { LoadingSpinner, ErrorMessage, EmptyState } from "./exchange-rate-status"; import { RateTable, ComparisonTable } from "./exchange-rate-table"; import { RateTrendChart } from "./exchange-rate-chart"; import { SunIcon, MoonIcon, SystemIcon, FetchIcon, ExcelIcon, CsvIcon } from "./exchange-rate-icons"; import { loadFromUrl, loadSavedSettings, saveSettings, applyTheme } from "./exchange-rate-storage"; import { CURRENCY_OPTIONS, DATE_PRESETS, getActivePreset, THEME_CYCLE, THEME_LABELS } from "./exchange-rate-constants"; +import { downloadTextFile, rowsToCsv } from "./exchange-rate-export-files"; export default function ExchangeRateViewer() { const [startDate, setStartDate] = useState(() => subDays(new Date(), 7)); @@ -135,27 +136,14 @@ export default function ExchangeRateViewer() { const baseFilename = `exchange_rates_${compareMode ? "compare" : selectedBank}_${selectedCurrency}_${format(new Date(), "yyyy-MM-dd")}`; - const handleExportExcel = () => { + const handleExportExcel = async () => { if (results.length === 0) return; - const ws = XLSX.utils.aoa_to_sheet(getExportRows()); - const wb = XLSX.utils.book_new(); - XLSX.utils.book_append_sheet(wb, ws, "Exchange Rates"); - XLSX.writeFile(wb, `${baseFilename}.xlsx`); + await writeExcelFile(getExportRows(), { sheet: "Exchange Rates" }).toFile(`${baseFilename}.xlsx`); }; const handleExportCsv = () => { if (results.length === 0) return; - const ws = XLSX.utils.aoa_to_sheet(getExportRows()); - const csv = XLSX.utils.sheet_to_csv(ws); - const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); - const url = URL.createObjectURL(blob); - const link = document.createElement("a"); - link.href = url; - link.download = `${baseFilename}.csv`; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(url); + downloadTextFile(rowsToCsv(getExportRows()), `${baseFilename}.csv`, "text/csv;charset=utf-8;"); }; const activePreset = getActivePreset(startDate, endDate);