diff --git a/package-lock.json b/package-lock.json index 366ce2a..afef438 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "0.1.0", "dependencies": { "@ant-design/icons": "^5.3.7", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-icons": "^1.3.1", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -3133,6 +3135,40 @@ "integrity": "sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==", "dev": true }, + "node_modules/@floating-ui/core": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", + "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", + "dependencies": { + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.12", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz", + "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", + "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -4560,14 +4596,59 @@ "node_modules/@radix-ui/primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", - "dev": true + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", + "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", + "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", - "dev": true, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -4582,7 +4663,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "dev": true, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -4629,6 +4709,20 @@ } } }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-dismissable-layer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz", @@ -4656,6 +4750,48 @@ } } }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.2.tgz", + "integrity": "sha512-GVZMR+eqK8/Kes0a36Qrv+i20bAPXSn8rCBTHx30w+3ECnR5o3xixAlqcVaYvLeyKUsm0aqyhWfmUcqufM8nYA==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-menu": "2.1.2", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz", @@ -4675,7 +4811,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", - "dev": true, "dependencies": { "@radix-ui/react-compose-refs": "1.1.0", "@radix-ui/react-primitive": "2.0.0", @@ -4696,11 +4831,18 @@ } } }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.1.tgz", + "integrity": "sha512-QvYompk0X+8Yjlo/Fv4McrzxohDdM5GgLHyQcPpcsPvlOSXCGFjdbuyGL5dzRbg0GpknAjQJJZzdiRK7iWVuFQ==", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x || ^19.x" + } + }, "node_modules/@radix-ui/react-id": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", - "dev": true, "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, @@ -4714,6 +4856,200 @@ } } }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.2.tgz", + "integrity": "sha512-lZ0R4qR2Al6fZ4yCCZzu/ReTFrylHFxIqy7OezIpWF4bL0o9biKo0pFIvkaew3TyZ9Fy5gYVrR5zCGZBVbO1zg==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", + "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", + "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/react-remove-scroll": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", + "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", + "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-portal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", @@ -4766,7 +5102,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", - "dev": true, "dependencies": { "@radix-ui/react-slot": "1.1.0" }, @@ -4785,11 +5120,40 @@ } } }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", + "integrity": "sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "dev": true, "dependencies": { "@radix-ui/react-compose-refs": "1.1.0" }, @@ -4807,7 +5171,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", - "dev": true, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -4822,7 +5185,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", - "dev": true, "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, @@ -4840,7 +5202,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", - "dev": true, "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, @@ -4858,7 +5219,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", - "dev": true, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -4869,6 +5229,45 @@ } } }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", + "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", + "dependencies": { + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", + "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", + "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==" + }, "node_modules/@rc-component/async-validator": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz", @@ -12698,7 +13097,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", - "dev": true, "dependencies": { "tslib": "^2.0.0" }, @@ -16093,8 +16491,7 @@ "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "dev": true + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, "node_modules/detect-package-manager": { "version": "2.0.1", @@ -18360,7 +18757,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "dev": true, "engines": { "node": ">=6" } @@ -19262,7 +19658,6 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, "dependencies": { "loose-envify": "^1.0.0" } @@ -27417,7 +27812,6 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", - "dev": true, "dependencies": { "react-style-singleton": "^2.2.1", "tslib": "^2.0.0" @@ -27541,7 +27935,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", - "dev": true, "dependencies": { "get-nonce": "^1.0.0", "invariant": "^2.2.4", @@ -30574,7 +30967,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", - "dev": true, "dependencies": { "tslib": "^2.0.0" }, @@ -30595,7 +30987,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", - "dev": true, "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" diff --git a/package.json b/package.json index 0af0259..8119945 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,8 @@ "private": true, "dependencies": { "@ant-design/icons": "^5.3.7", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-icons": "^1.3.1", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/src/api/get-admission-detail-type.query.ts b/src/api/get-admission-detail-type.query.ts index d4d9715..453b66f 100644 --- a/src/api/get-admission-detail-type.query.ts +++ b/src/api/get-admission-detail-type.query.ts @@ -12,7 +12,7 @@ export const getAllDetailType = async () => { export const getDetailType = async ({ type }: TypeStatusProps) => { try { - const response = await server_axiosInstance.get(`/api/admissions/detail/${type}`); + const response = await server_axiosInstance.get(`/api/admissions/details/${type}`); return response.data; } catch (error: any) { throw new Error('get all detail type fail', error); diff --git a/src/hooks/use-chat-section.hooks.ts b/src/hooks/use-chat-section.hooks.ts index 7cacc4f..df5794e 100644 --- a/src/hooks/use-chat-section.hooks.ts +++ b/src/hooks/use-chat-section.hooks.ts @@ -4,7 +4,8 @@ import useTypeStore, { TypeCategoryState } from '../store/type-category-store'; const useChatSection = () => { const { setSelectedType, setSelectedCategory } = useTypeStore(); const [selectedTypeButton, setSelectedTypeButton] = React.useState(undefined); - const [selectedCategoryButton, setSelectedCategoryButton] = React.useState(undefined); + const [selectedCategoryButton, setSelectedCategoryButton] = + React.useState('ADMISSION_GUIDELINE'); const handleTypeButtonClick = (selectedType: TypeCategoryState['type']) => { setSelectedType(selectedType); setSelectedTypeButton(selectedType); diff --git a/src/hooks/use-detail-type-prompt.hooks.ts b/src/hooks/use-detail-type-prompt.hooks.ts new file mode 100644 index 0000000..6c991a7 --- /dev/null +++ b/src/hooks/use-detail-type-prompt.hooks.ts @@ -0,0 +1,35 @@ +import { useUserDetailTypeStore } from '../store/user-detail-type-store'; + +interface DetailTypeItem { + middleName: string; + lastNames: string[]; +} + +export const useDetailTypePrompt = () => { + const { detailTypeData } = useUserDetailTypeStore(); + + const itemMap = new Map>(); + + detailTypeData.forEach((item) => { + const [middle, lastWithBracket] = item.name.split('('); + const middleTrimmed = middle.trim(); + const lastTrimmed = lastWithBracket ? lastWithBracket.replace(/\)$/, '').trim() : ''; + + if (!itemMap.has(middleTrimmed)) { + itemMap.set(middleTrimmed, new Set()); + } + + if (lastTrimmed) { + itemMap.get(middleTrimmed)?.add(lastTrimmed); + } + }); + + const itemList: DetailTypeItem[] = Array.from(itemMap.entries()).map(([middleName, lastNamesSet]) => ({ + middleName, + lastNames: Array.from(lastNamesSet), + })); + + return { + itemList, + }; +}; diff --git a/src/hooks/use-preset-button.hooks.ts b/src/hooks/use-preset-button.hooks.ts index b563dff..d43e4b7 100644 --- a/src/hooks/use-preset-button.hooks.ts +++ b/src/hooks/use-preset-button.hooks.ts @@ -46,9 +46,24 @@ const usePresetButton = () => { } }; + const handleDetailTypeButtonClick = async (detailTypeName: string) => { + try { + addMessage({ content: detailTypeName, role: 'user' }); + addMessage({ content: 'loading', role: 'system' }); + setLoading(true); + + const response = await fetchResponse(`${detailTypeName}에 대해 설명해줘`); + updateStateWithResponse(response); + } catch (error) { + setLoading(false); + updateLastMessage('답변 생성에 실패했습니다. 새로고침해주세요'); + } + }; + return { handleReferenceButtonClick, handleButtonClick, + handleDetailTypeButtonClick, }; }; diff --git a/src/store/type-category-store.ts b/src/store/type-category-store.ts index ee1ba1d..71edbde 100644 --- a/src/store/type-category-store.ts +++ b/src/store/type-category-store.ts @@ -2,14 +2,14 @@ import { create } from 'zustand'; export interface TypeCategoryState { type: undefined | 'SUSI' | 'PYEONIP' | 'JEONGSI'; - category: undefined | 'ADMISSION_GUIDELINE' | 'PASSING_RESULT' | 'PAST_QUESTIONS' | 'INTERVIEW_PRACTICAL_TEST'; + category: 'ADMISSION_GUIDELINE' | 'PASSING_RESULT' | 'PAST_QUESTIONS' | 'INTERVIEW_PRACTICAL_TEST'; setSelectedType: (button: TypeCategoryState['type']) => void; setSelectedCategory: (button: TypeCategoryState['category']) => void; } const useTypeStore = create()((set) => ({ type: undefined, - category: undefined, + category: 'ADMISSION_GUIDELINE', setSelectedType: (button) => set({ type: button }), setSelectedCategory: (button) => set({ category: button }), })); diff --git a/src/store/user-detail-type-store.ts b/src/store/user-detail-type-store.ts new file mode 100644 index 0000000..bad85ab --- /dev/null +++ b/src/store/user-detail-type-store.ts @@ -0,0 +1,34 @@ +import { create } from 'zustand'; +export interface DetailTypeItem { + middleName: string; + lastNames: string[]; +} + +//TSK-53 admission-detail-type-store.ts와 일부 겹침 나중에 병합 필요성 +export interface UserDetailTypeState { + type: 'SUSI' | 'JEONGSI' | 'PYEONIP'; + id: number; + name: string; +} + +interface UserDetailTypeStoreState { + detailTypeData: UserDetailTypeState[]; + updateDetailTypeData: (data: UserDetailTypeState[]) => void; + loading: boolean; + selectedName: string; + setSelectedName: (name: string) => void; + setLoading: (load: boolean) => void; + itemsArray: DetailTypeItem[]; + setItemArray: (dataArray: DetailTypeItem[]) => void; +} + +export const useUserDetailTypeStore = create((set) => ({ + detailTypeData: [], + loading: false, + itemsArray: [], + selectedName: '', + setSelectedName: (name) => set({ selectedName: name }), + updateDetailTypeData: (data) => set({ detailTypeData: data }), + setLoading: (load) => set({ loading: load }), + setItemArray: (data) => set({ itemsArray: data }), +})); diff --git a/src/ui/components/atom/chat-card/chat-card.tsx b/src/ui/components/atom/chat-card/chat-card.tsx index 46e407a..ed9a5aa 100644 --- a/src/ui/components/atom/chat-card/chat-card.tsx +++ b/src/ui/components/atom/chat-card/chat-card.tsx @@ -7,8 +7,9 @@ import Loader from '../loader/loader'; interface ChatCardProps { content: string; role: 'user' | 'system'; + children?: React.ReactNode; } -const ChatCard = ({ content, role }: ChatCardProps) => { +const ChatCard = ({ content, role, children }: ChatCardProps) => { return (
{role === 'user' ? null : } @@ -26,6 +27,7 @@ const ChatCard = ({ content, role }: ChatCardProps) => { >
{content === 'loading' ? : {content}} + {children}
diff --git a/src/ui/components/atom/dropdown/dropdown.stories.tsx b/src/ui/components/atom/dropdown/dropdown.stories.tsx new file mode 100644 index 0000000..41640c3 --- /dev/null +++ b/src/ui/components/atom/dropdown/dropdown.stories.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { Meta, StoryFn } from '@storybook/react'; +import Dropdown from './dropdown'; + +export default { + title: 'Components/Dropdown', + component: Dropdown, + argTypes: { + type: { + control: { type: 'select' }, + options: ['SUSI', 'JEONGSI', 'PYEONIP'], + }, + }, +} as Meta; + +const Template: StoryFn = (args) => ; + +export const SUSI = Template.bind({}); +SUSI.args = { + type: 'SUSI', + items: [ + { + middleName: '학생부교과', + lastNames: ['학교장추천', '교과면접', '기회균형', '특성화고교', '만학도', '특성화고등졸재직자', '특수교육대상자'], + }, + { + middleName: '학생부종합', + lastNames: ['명지인재면접', '명지인재서류', '크리스천리더', '사회적배려대상자', '농어촌학생'], + }, + { + middleName: '실기/실적', + lastNames: ['실기우수자', '특기자-문학/체육'], + }, + ], +}; + +export const JEONGSI = Template.bind({}); +JEONGSI.args = { + type: 'JEONGSI', + items: [ + { + middleName: '수능', + lastNames: ['일반-가/나/다', '실기-가/나/다', '농어촌학생', '특성화고교'], + }, + { + middleName: '실기/실적', + lastNames: ['실기우수자'], + }, + { + middleName: '학생부교과', + lastNames: ['만학도', '특성화고등졸재직자'], + }, + ], +}; + +export const PYEONIP = Template.bind({}); +PYEONIP.args = { + type: 'PYEONIP', + items: [ + { middleName: '일반', lastNames: [] }, + { middleName: '학사', lastNames: [] }, + { middleName: '농어촌학생', lastNames: [] }, + { middleName: '특성화고교', lastNames: [] }, + { middleName: '재외국민', lastNames: [] }, + { middleName: '특성화고등졸재직자', lastNames: [] }, + ], +}; diff --git a/src/ui/components/atom/dropdown/dropdown.tsx b/src/ui/components/atom/dropdown/dropdown.tsx new file mode 100644 index 0000000..24337ba --- /dev/null +++ b/src/ui/components/atom/dropdown/dropdown.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; +import { ChevronRightIcon } from '@radix-ui/react-icons'; +import { useDetailTypePrompt } from '../../../../hooks/use-detail-type-prompt.hooks'; +import { useUserDetailTypeStore } from '../../../../store/user-detail-type-store'; +import usePresetButton from '../../../../hooks/use-preset-button.hooks'; + +interface DropdownProps { + type: 'SUSI' | 'JEONGSI' | 'PYEONIP'; + items: { middleName: string; lastNames: string[] }[]; +} + +const Dropdown: React.FC = ({ type }) => { + const { itemList: items } = useDetailTypePrompt(); + const { setSelectedName } = useUserDetailTypeStore(); + const { handleDetailTypeButtonClick } = usePresetButton(); + + const handleNameClick = (name: string) => { + setSelectedName(name); + handleDetailTypeButtonClick(name); + }; + + return ( + + + + + + + {type === 'PYEONIP' + ? // 편입의 경우, 한 번에 전체 목록 표시 + items.map((item, index) => ( + handleNameClick(item.middleName)} + > + {item.middleName} + + )) + : // 수시, 정시의 경우, 중간 이름을 상위 메뉴로, 마지막 이름을 하위 메뉴로 표시 + items.map((item, index) => ( + + + {item.middleName} + + + + + {item.lastNames.map((lastName, subIndex) => ( + handleNameClick(lastName)} + > + {lastName} + + ))} + + + + ))} + + + + ); +}; + +export default Dropdown; diff --git a/src/ui/components/molecule/chat-section/chat-section.tsx b/src/ui/components/molecule/chat-section/chat-section.tsx index 3d375ad..ef7ff99 100644 --- a/src/ui/components/molecule/chat-section/chat-section.tsx +++ b/src/ui/components/molecule/chat-section/chat-section.tsx @@ -2,13 +2,14 @@ import * as React from 'react'; import ChatCard from '../../atom/chat-card/chat-card'; import useTypeStore from '../../../../store/type-category-store'; import useChatStore from '../../../../store/chat-store'; -import useChatSection from '../../../../hooks/use-chat-section.hooks'; import { useTypeDisabledStore } from '../../../../store/type-disabled-store'; import { getTypeStatus } from '../../../../api/admin/question-type-status/get-type-status'; import useMessage from 'antd/es/message/useMessage'; import { TypePresetButtons } from '../../user-domain/type-preset-buttons'; -import { CategoryPresetButtons } from '../../user-domain/category-preset-buttons'; import { QuestionPresetButtons } from '../../user-domain/question-preset-buttons'; +import Dropdown from '../../atom/dropdown/dropdown'; +import { getDetailType } from '../../../../api/get-admission-detail-type.query'; +import { useUserDetailTypeStore } from '../../../../store/user-detail-type-store'; const ChatSection: React.FC = () => { const { type, category } = useTypeStore(); @@ -16,6 +17,7 @@ const ChatSection: React.FC = () => { const { activeSusi, activeJeongsi, activePyeonip, setSusiDisabled, setJeongsiDisabled, setPyeonipDisabled } = useTypeDisabledStore(); const [messageApi, contextHolder] = useMessage(); + const { updateDetailTypeData, itemsArray, selectedName } = useUserDetailTypeStore(); const messageEndRef = React.useRef(null); @@ -93,6 +95,21 @@ const ChatSection: React.FC = () => { updateCategoryStatus(); }, []); + React.useEffect(() => { + const fetchDetailType = async () => { + try { + if (type !== undefined) { + const response = await getDetailType({ type }); + updateDetailTypeData(response); + } + } catch (error) { + console.error('Fetching Failed', error); + } + }; + + fetchDetailType(); + }, [type]); + React.useEffect(() => { showCategoryStatus(); }, [activeSusi, activeJeongsi, activePyeonip]); @@ -108,50 +125,21 @@ const ChatSection: React.FC = () => { role="system" /> - {type && } {type && ( <> + - + content={`어떤 세부 학과가 궁금하신가요? 아래에서 세부 전형을 선택해주세요!`} + > +
{selectedName ? <> : }
)} - {category && ( - - )} - {type && category && ( - - )} + {messages.map((msg, index) => ( ))} - {category && } + {selectedName && }
); diff --git a/src/ui/pages/maru-egg.tsx b/src/ui/pages/maru-egg.tsx index 1a3596d..5871d92 100644 --- a/src/ui/pages/maru-egg.tsx +++ b/src/ui/pages/maru-egg.tsx @@ -5,9 +5,11 @@ import useTypeStore from '../../store/type-category-store'; import ChatForm from '../components/molecule/chat-form/chat-form'; import ChatSection from '../components/molecule/chat-section/chat-section'; import Onboarding from '../components/molecule/onboarding/onboarding'; +import { useUserDetailTypeStore } from '../../store/user-detail-type-store'; const MaruEgg: React.FC = () => { - const { type, category } = useTypeStore(); + const { type } = useTypeStore(); + const { selectedName } = useUserDetailTypeStore(); const [showOnboarding, setShowOnboarding] = useState(false); useEffect(() => { @@ -30,7 +32,7 @@ const MaruEgg: React.FC = () => { {showOnboarding && }
- {type !== undefined && category !== undefined && ( + {selectedName !== '' && (