diff --git a/.env.example b/.env.example index b8ca0df..13c01cd 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,2 @@ VITE_API_BASE_URL= +VITE_RECAPTCHA_SITE_KEY diff --git a/package-lock.json b/package-lock.json index 82e1a1d..eb4dcd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,12 +13,14 @@ "@tiptap/starter-kit": "^2.23.0", "axios": "^1.10.0", "froala-editor": "^4.5.2", + "lucide-react": "^0.553.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-froala-wysiwyg": "^4.5.2", "react-google-recaptcha": "^3.1.0", "react-icons": "^5.5.0", "react-router-dom": "^7.6.0", + "recharts": "^3.4.1", "tailwindcss": "^4.1.6" }, "devDependencies": { @@ -871,13 +873,12 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", - "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.1", + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { @@ -885,11 +886,10 @@ } }, "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", - "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -1071,6 +1071,31 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.10.1.tgz", + "integrity": "sha512-/U17EXQ9Do9Yx4DlNGU6eVNfZvFJfYpUtRRdLf19PbPjdWBxNlxGZXywQZ1p1Nz8nMkWplTI7iD/23m07nolDA==", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^10.2.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@remirror/core-constants": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", @@ -1344,6 +1369,16 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==" + }, "node_modules/@tailwindcss/node": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", @@ -2036,6 +2071,60 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==" + }, + "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.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==" + }, + "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/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2075,7 +2164,7 @@ "version": "19.1.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -2453,17 +2542,15 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", - "license": "MIT", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -2535,7 +2622,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -2601,6 +2687,14 @@ "node": ">=18" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2625,7 +2719,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -2681,9 +2774,119 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "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/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -2702,6 +2905,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-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2713,7 +2921,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -2731,7 +2938,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -2777,7 +2983,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -2786,7 +2991,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -2795,7 +2999,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -2807,7 +3010,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -2818,6 +3020,15 @@ "node": ">= 0.4" } }, + "node_modules/es-toolkit": { + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.41.0.tgz", + "integrity": "sha512-bDd3oRmbVgqZCJS6WmeQieOrzpl3URcWBUVDXxOELlUW2FuW+0glPOz1n0KnRie+PdyvUZcXz2sOn00c6pPRIA==", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/esbuild": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", @@ -3058,6 +3269,11 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3203,10 +3419,9 @@ } }, "node_modules/form-data": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", - "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", - "license": "MIT", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -3242,7 +3457,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3261,7 +3475,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -3285,7 +3498,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -3324,7 +3536,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3359,7 +3570,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3371,7 +3581,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" }, @@ -3386,7 +3595,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -3413,6 +3621,15 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -3440,6 +3657,14 @@ "node": ">=0.8.19" } }, + "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/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3496,11 +3721,10 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -3861,6 +4085,14 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.553.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.553.0.tgz", + "integrity": "sha512-BRgX5zrWmNy/lkVAe0dXBgd7XQdZ3HTf+Hwe3c9WK6dqgnj9h+hxV+MDncM88xDWlCq27+TKvHGE70ViODNILw==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -3891,7 +4123,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -3929,7 +4160,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -3938,7 +4168,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -4519,6 +4748,28 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -4567,6 +4818,53 @@ "react-dom": ">=18" } }, + "node_modules/recharts": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.4.1.tgz", + "integrity": "sha512-35kYg6JoOgwq8sE4rhYkVWwa6aAIgOtT+Ob0gitnShjwUwZmhrmy7Jco/5kJNF4PnLXgt9Hwq+geEMS+WrjU1g==", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "1.x.x || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4781,6 +5079,11 @@ "node": ">=18" } }, + "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/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", @@ -4963,11 +5266,31 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "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/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "license": "MIT", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/package.json b/package.json index 3962d48..47c1bcc 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,14 @@ "@tiptap/starter-kit": "^2.23.0", "axios": "^1.10.0", "froala-editor": "^4.5.2", + "lucide-react": "^0.553.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-froala-wysiwyg": "^4.5.2", "react-google-recaptcha": "^3.1.0", "react-icons": "^5.5.0", "react-router-dom": "^7.6.0", + "recharts": "^3.4.1", "tailwindcss": "^4.1.6" }, "devDependencies": { diff --git a/src/App.tsx b/src/App.tsx index 3917b36..1e5ef39 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -20,6 +20,11 @@ import ListLowongan from "./Pages/ListLowongan"; import ListProduct from "./Pages/ListProduct"; import AddProductHumic from "./Pages/AddProductHumic"; import EditProductHumic from "./Pages/EditProductHumic"; +import ListFeedback from "./Pages/ListFeedback"; +import AddFeedback from "./Pages/AddFeedback"; +import ListFaq from "./Pages/ListFaq"; +import AddFaq from "./Pages/AddFAQ"; +import Dashboard from "./Pages/Dashboard"; function App() { return ( @@ -51,6 +56,11 @@ function App() { } /> } /> } /> + } /> + } /> + } /> + } /> + } /> diff --git a/src/Layout/Footer.tsx b/src/Layout/Footer.tsx index 0725600..8c52c5e 100644 --- a/src/Layout/Footer.tsx +++ b/src/Layout/Footer.tsx @@ -52,9 +52,15 @@ const Footer = () => {

Tentang

Life at Humic

-
+

Lainnya

-

Humic Website

+

+ window.open("https://humic.telkomuniversity.ac.id", "_blank") + } + > + Humic Website +

Our Activity

Life at Humic

{

navigate("/dashboard")} + > + + + + + +

Dashboard

+
+
{

Pelamar

+
navigate("/feedback-list")} + > + + + + + + + +

Feedback

+
+
navigate("/faq-list")} + > + + {/* Balon chat */} + + {/* Tanda tanya */} + + + + +

FAQ

+
diff --git a/src/Pages/AddFAQ.tsx b/src/Pages/AddFAQ.tsx new file mode 100644 index 0000000..eed2b98 --- /dev/null +++ b/src/Pages/AddFAQ.tsx @@ -0,0 +1,114 @@ +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import SidebarAdmin from "../Layout/SidebarAdmin"; +import NavbarAdmin from "../Layout/NavbarAdmin"; +import "froala-editor/js/plugins.pkgd.min.js"; +import "froala-editor/css/froala_editor.pkgd.min.css"; +import "froala-editor/css/froala_style.min.css"; +import FroalaEditor from "react-froala-wysiwyg"; +import axios from "axios"; + +const AddFaq = () => { + const [questions, setQuestions] = useState(""); + const [answers, setAnswers] = useState(""); + const navigate = useNavigate(); + + const handleSubmit = async () => { + const token = document.cookie + .split("; ") + .find((row) => row.startsWith("token=")) + ?.split("=")[1]; + + if (!token) { + alert("Token tidak ditemukan."); + return; + } + + if (!questions || !answers) { + alert("Semua field wajib diisi."); + return; + } + + try { + const response = await axios.post( + `${import.meta.env.VITE_API_BASE_URL}/faq-api/add`, + { + pertanyaan: questions, + jawaban: answers, + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + + if (response.data.status) { + alert("Faq berhasil ditambahkan!"); + navigate("/faq-list"); + } else { + alert(response.data.message || "Gagal menambahkan faq."); + } + } catch (error: any) { + if (error.response) { + console.log("Error response data:", error.response.data); + } + console.error("Error adding faq:", error); + alert("Terjadi kesalahan saat menambahkan faq."); + } + }; + + return ( +
+ +
+ +
+
+

+ Tambah Frequently Asked Questions +

+ +
+ +
+ {/* Questions */} +
+ + setQuestions(e.target.value)} + placeholder="Tulis pertanyaan kamu..." + /> +
+ + {/* Answers */} +
+ + setAnswers(model)} + config={{ + placeholderText: "Tulis jawaban kamu...", + charCounterCount: true, + toolbarSticky: true, + heightMin: 200, + }} + /> +
+
+
+
+
+ ); +}; + +export default AddFaq; diff --git a/src/Pages/AddFeedback.tsx b/src/Pages/AddFeedback.tsx new file mode 100644 index 0000000..ea9b5aa --- /dev/null +++ b/src/Pages/AddFeedback.tsx @@ -0,0 +1,178 @@ +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import SidebarAdmin from "../Layout/SidebarAdmin"; +import NavbarAdmin from "../Layout/NavbarAdmin"; +import "froala-editor/js/plugins.pkgd.min.js"; +import "froala-editor/css/froala_editor.pkgd.min.css"; +import "froala-editor/css/froala_style.min.css"; +import FroalaEditor from "react-froala-wysiwyg"; +import axios from "axios"; + +const AddFeedback = () => { + const [posisi, setPosisi] = useState(""); + const [universitas, setUniversitas] = useState(""); + const [nama, setNama] = useState(""); + const [image, setImage] = useState(null); + const [batch, setBatch] = useState(""); + const [feedback, setFeedbacks] = useState(""); + const [year, setYear] = useState(""); + const navigate = useNavigate(); + + const handleSubmit = async () => { + const token = document.cookie + .split("; ") + .find((row) => row.startsWith("token=")) + ?.split("=")[1]; + + if (!token) { + alert("Token tidak ditemukan."); + return; + } + + if (!nama || !universitas || !posisi || !batch || !year || !feedback) { + alert("Semua field wajib diisi."); + return; + } + + try { + const formData = new FormData(); + formData.append("nama", nama); + formData.append("universitas", universitas); + formData.append("posisi", posisi); + formData.append("batch", batch); + formData.append("tahun", year); + formData.append("pesan", feedback); + if (image) formData.append("image", image); + + const response = await axios.post( + `${import.meta.env.VITE_API_BASE_URL}/feedback-api/add`, + formData, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "multipart/form-data", + }, + } + ); + + if (response.data.status) { + alert("Feedback berhasil ditambahkan!"); + navigate("/feedback-list"); + } else { + alert(response.data.message || "Gagal menambahkan feedback."); + } + } catch (error: any) { + console.error("Error adding feedback:", error); + alert("Terjadi kesalahan saat menambahkan feedback."); + } + }; + + return ( +
+ +
+ +
+
+

Tambah Feedback

+ +
+ +
+
+
+ + setNama(e.target.value)} + placeholder="John Doe" + /> +
+ +
+ + setUniversitas(e.target.value)} + placeholder="Telkom University Bandung" + /> +
+ +
+ + setPosisi(e.target.value)} + placeholder="UI/UX Designer" + /> +
+ +
+ + setBatch(e.target.value)} + placeholder="5" + /> +
+ +
+ + setYear(e.target.value)} + placeholder="2025" + /> +
+ +
+ + + setImage(e.target.files ? e.target.files[0] : null) + } + className="border border-gray-300 rounded-lg p-3" + /> +
+
+ + {/* Isi Konten */} +
+ + setFeedbacks(model)} + config={{ + placeholderText: "Tulis feedback kamu...", + charCounterCount: true, + toolbarSticky: true, + heightMin: 200, + }} + /> +
+
+
+
+
+ ); +}; + +export default AddFeedback; diff --git a/src/Pages/Dashboard.tsx b/src/Pages/Dashboard.tsx new file mode 100644 index 0000000..162ace4 --- /dev/null +++ b/src/Pages/Dashboard.tsx @@ -0,0 +1,372 @@ +import React, { useEffect, useState } from "react"; +import { PieChart, Pie, Cell, ResponsiveContainer } from "recharts"; +import { Search, ChevronDown, Users, UserCheck, UserX, Clock } from "lucide-react"; +import NavbarAdmin from "../Layout/NavbarAdmin"; +import SidebarAdmin from "../Layout/SidebarAdmin"; + +type ChartDataItem = { + name: string; + value: number; +}; + +type StatCard = { + title: string; + value: number; + subtitle: string; + icon: React.ReactNode; + iconColor: string; +}; + +type DashboardApiResponse = { + totalPendaftar: number; + totalDiterima: number; + totalDitolak: number; + totalDiproses: number; + universitas: ChartDataItem[]; + negara: ChartDataItem[]; +}; + +const UNIVERSITAS_COLORS = ["#001a66", "#334d99", "#6699cc", "#99b3ff"]; +const PENERIMAAN_COLORS = ["#0088ff", "#00cc00", "#ff3333"]; +const NEGARA_COLORS = ["#cc3333", "#dd6666", "#ee9999", "#f0b3b3", "#f5cccc"]; + +const Dashboard: React.FC = () => { + const [loading, setLoading] = useState(true); + const [errorMessage, setErrorMessage] = useState(""); + const [statCards, setStatCards] = useState([]); + const [universitasData, setUniversitasData] = useState([]); + const [penerimaanData, setPenerimaanData] = useState([]); + const [negaraData, setNegaraData] = useState([]); + + useEffect(() => { + const fetchDashboardData = async () => { + try { + const response = await fetch( + `${import.meta.env.VITE_API_BASE_URL}/lowongan-magang-api/get` + ); + const result = await response.json(); + + if (!response.ok) { + console.error("Gagal mengambil data dashboard:", result.message); + setErrorMessage(result.message || "Gagal mengambil data dashboard."); + setLoading(false); + return; + } + + const data: DashboardApiResponse = result.data; + + const statCardsFromApi: StatCard[] = [ + { + title: "Total Pendaftar", + value: data.totalPendaftar, + subtitle: "+200 dari batch sebelumnya", // sesuaikan bila BE sudah support + icon: , + iconColor: "#0088ff", + }, + { + title: "Total Diterima", + value: data.totalDiterima, + subtitle: `${( + (data.totalDiterima / (data.totalPendaftar || 1)) * + 100 + ).toFixed(1)}% acceptance rate`, + icon: , + iconColor: "#00cc00", + }, + { + title: "Total Ditolak", + value: data.totalDitolak, + subtitle: `${( + (data.totalDitolak / (data.totalPendaftar || 1)) * + 100 + ).toFixed(1)}% rejection rate`, + icon: , + iconColor: "#ff3333", + }, + { + title: "Sedang Diproses", + value: data.totalDiproses, + subtitle: `${( + (data.totalDiproses / (data.totalPendaftar || 1)) * + 100 + ).toFixed(1)}% pending review`, + icon: , + iconColor: "#ffaa00", + }, + ]; + + const penerimaanFromApi: ChartDataItem[] = [ + { name: "Total Pendaftar", value: data.totalPendaftar }, + { name: "Total Diterima", value: data.totalDiterima }, + { name: "Total Ditolak", value: data.totalDitolak }, + ]; + + setStatCards(statCardsFromApi); + setUniversitasData(data.universitas || []); + setPenerimaanData(penerimaanFromApi); + setNegaraData(data.negara || []); + setLoading(false); + } catch (error) { + console.error("Terjadi kesalahan saat mengambil data dashboard:", error); + setErrorMessage("Terjadi kesalahan saat mengambil data dashboard."); + setLoading(false); + } + }; + + fetchDashboardData(); + }, []); + + return ( +
+ + +
+ + +
+ {/* Header Section */} +
+

Dashboard

+ +
+ {/* Batch Filter */} + + + {/* Search Box */} +
+ + +
+
+
+ + {/* Loading / Error State */} + {loading && ( +

Memuat data dashboard...

+ )} + + {!loading && errorMessage && ( +

{errorMessage}

+ )} + + {!loading && !errorMessage && ( + <> + {/* Stat Cards */} +
+ {statCards.map((card, index) => ( +
+
+
+ {card.icon} +
+

+ {card.title} +

+
+
+

+ {card.value} +

+
+

+ {card.subtitle} +

+
+ ))} +
+ + {/* Charts Section */} +
+ {/* Universitas Chart */} +
+

+ Universitas +

+ +
+ + + + {universitasData.map((_, index) => ( + + ))} + + + +
+ + {/* Legend */} +
+ {universitasData.map((item, index) => ( +
+
+
+ {item.name} +
+ + {item.value}% + +
+ ))} +
+
+ + {/* Right Column - Penerimaan & Negara */} +
+ {/* Penerimaan Chart */} +
+

+ Penerimaan +

+ +
+ + + + {penerimaanData.map((_, index) => ( + + ))} + + + +
+ + {/* Legend */} +
+ {penerimaanData.map((item, index) => ( +
+
+ {item.name} +
+ ))} +
+
+ + {/* Negara Chart */} +
+

+ Negara +

+ +
+ + + + {negaraData.map((_, index) => ( + + ))} + + + +
+ + {/* Legend */} +
+ {negaraData.map((item, index) => ( +
+
+ {item.name} +
+ ))} +
+
+
+
+ + )} +
+
+
+ ); +}; + +export default Dashboard; diff --git a/src/Pages/DetailsProduct.tsx b/src/Pages/DetailsProduct.tsx index e5803d4..d5eff44 100644 --- a/src/Pages/DetailsProduct.tsx +++ b/src/Pages/DetailsProduct.tsx @@ -23,8 +23,10 @@ const DetailsProduct = () => { fetch(`${import.meta.env.VITE_API_BASE_URL}/hasil-research-api/get/${id}`) .then((res) => res.json()) .then((data) => { - if (data && data.data && data.data.length > 0) { - setProduct(data.data[0]); + if (data && data.data) { + setProduct(data.data); + } else { + setProduct(null); } setLoading(false); }) @@ -43,6 +45,8 @@ const DetailsProduct = () => {
Data tidak ditemukan
); } + console.log(`${import.meta.env.VITE_API_BASE_URL}${product.image_path}`); + return (
diff --git a/src/Pages/EditPartnerships.tsx b/src/Pages/EditPartnerships.tsx index 6288ca8..60638b0 100644 --- a/src/Pages/EditPartnerships.tsx +++ b/src/Pages/EditPartnerships.tsx @@ -144,9 +144,9 @@ const EditPartnerships = () => {
-
+

Edit Partnership

-
+
-
+

Nama

{ title: item.posisi, location: item.lokasi, category: item.kelompok_peminatan, - status: item.status_lowongan === "dibuka" ? "Opened" : "Closed", + status: item.status_lowongan === "dibuka" ? "Open" : "Closed", statusColor: item.status_lowongan === "dibuka" ? "#4EAD00" : "#C3423F", isPaid: item.paid === "paid", @@ -171,12 +171,12 @@ const Internships = () => {
{ - if (intern.status === "Opened") { + if (intern.status === "Open") { navigate(`/details/${intern.id}`); } }} @@ -189,7 +189,7 @@ const Internships = () => { className="p-2 border-2 text-[12px] lg:text-[14px] font-bold rounded-4xl" style={{ backgroundColor: - intern.status === "Opened" ? "#F6FFF4" : "#FAF0EF", + intern.status === "Open" ? "#F6FFF4" : "#FAF0EF", color: intern.statusColor, borderColor: intern.statusColor, }} diff --git a/src/Pages/ListFaq.tsx b/src/Pages/ListFaq.tsx new file mode 100644 index 0000000..2fa41e8 --- /dev/null +++ b/src/Pages/ListFaq.tsx @@ -0,0 +1,208 @@ +import NavbarAdmin from "../Layout/NavbarAdmin"; +import SidebarAdmin from "../Layout/SidebarAdmin"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; + +type Faq = { + id: number; + pertanyaan: string; + jawaban: string; + created_at: string; +}; + +const ListFaq = () => { + const navigate = useNavigate(); + const [faq, setFaq] = useState([]); + + useEffect(() => { + const fetchFaq = async () => { + try { + const response = await fetch( + `${import.meta.env.VITE_API_BASE_URL}/faq-api/get` + ); + const result = await response.json(); + if (response.ok) { + setFaq(result.data); + } else { + console.error("Gagal mengambil data FAQ:", result.message); + } + } catch (error) { + console.error("Terjadi kesalahan:", error); + } + }; + + fetchFaq(); + }, []); + + const handleDelete = async (id: number) => { + const confirmDelete = confirm("Apakah Anda yakin ingin menghapus FAQ ini?"); + if (!confirmDelete) return; + + const token = document.cookie + .split("; ") + .find((row) => row.startsWith("token=")) + ?.split("=")[1]; + + if (!token) { + alert("Token tidak ditemukan. Silakan login ulang."); + return; + } + + try { + const response = await fetch( + `${import.meta.env.VITE_API_BASE_URL}/faq-api/delete/${id}`, + { + method: "DELETE", + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + + const result = await response.json(); + if (response.ok) { + alert("Feedback berhasil dihapus!"); + setFaq((prev) => prev.filter((faq) => faq.id !== id)); + } else { + alert(result.message || "Gagal menghapus FAQ."); + } + } catch (error) { + console.error("Gagal menghapus:", error); + alert("Terjadi kesalahan saat menghapus FAQ."); + } + }; + + return ( +
+ + +
+ +
+
+

+ Frequently Asked Questions +

+ +
+ +
+ + + + + + + + + + + {faq.map((faq) => ( + + + + + + + + ))} + +
QuestionsAnswersTanggal UnggahAksi
{faq.pertanyaan} +
+
+ {new Date(faq.created_at).toLocaleDateString()} + + {/* Tombol View */} + + + {/* Tombol Edit */} + + + {/* Tombol Hapus */} + +
+ + {faq.length === 0 && ( +

+ Belum ada data FAQ. +

+ )} +
+
+
+
+ ); +}; + +export default ListFaq; diff --git a/src/Pages/ListFeedback.tsx b/src/Pages/ListFeedback.tsx new file mode 100644 index 0000000..137d742 --- /dev/null +++ b/src/Pages/ListFeedback.tsx @@ -0,0 +1,207 @@ +import NavbarAdmin from "../Layout/NavbarAdmin"; +import SidebarAdmin from "../Layout/SidebarAdmin"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; + +type Feedback = { + id: number; + nama: string; + image: string; + created_at: string; +}; + +const ListFeedback = () => { + const navigate = useNavigate(); + const [feedbacks, setFeedbacks] = useState([]); + + useEffect(() => { + const fetchFeedbacks = async () => { + try { + const response = await fetch( + `${import.meta.env.VITE_API_BASE_URL}/feedback-api/get` + ); + const result = await response.json(); + if (response.ok) { + setFeedbacks(result.data); + } else { + console.error("Gagal mengambil data feedback:", result.message); + } + } catch (error) { + console.error("Terjadi kesalahan:", error); + } + }; + + fetchFeedbacks(); + }, []); + + const handleDelete = async (id: number) => { + const confirmDelete = confirm( + "Apakah Anda yakin ingin menghapus feedback ini?" + ); + if (!confirmDelete) return; + + const token = document.cookie + .split("; ") + .find((row) => row.startsWith("token=")) + ?.split("=")[1]; + + if (!token) { + alert("Token tidak ditemukan. Silakan login ulang."); + return; + } + + try { + const response = await fetch( + `${import.meta.env.VITE_API_BASE_URL}/feedback-api/delete/${id}`, + { + method: "DELETE", + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + + const result = await response.json(); + if (response.ok) { + alert("Feedback berhasil dihapus!"); + setFeedbacks((prev) => prev.filter((feedback) => feedback.id !== id)); + } else { + alert(result.message || "Gagal menghapus feedback."); + } + } catch (error) { + console.error("Gagal menghapus:", error); + alert("Terjadi kesalahan saat menghapus feedback."); + } + }; + + return ( +
+ + +
+ +
+
+

Feedback

+ +
+ +
+ + + + + + + + + + + {feedbacks.map((feedback) => ( + + + + + + + ))} + +
NamaFotoTanggal UnggahAksi
{feedback.nama} + {feedback.nama} + + {new Date(feedback.created_at).toLocaleDateString()} + + {/* Tombol View */} + + + {/* Tombol Edit */} + + + {/* Tombol Hapus */} + +
+ + {feedbacks.length === 0 && ( +

Belum ada data feedback.

+ )} +
+
+
+
+ ); +}; + +export default ListFeedback; diff --git a/src/Pages/ListProduct.tsx b/src/Pages/ListProduct.tsx index 339b9a5..262b37e 100644 --- a/src/Pages/ListProduct.tsx +++ b/src/Pages/ListProduct.tsx @@ -190,7 +190,7 @@ const ListProduct = () => { {projects.length === 0 && ( -

Belum ada data project.

+

Belum ada data project.

)}
diff --git a/src/Pages/RegisterInternships.tsx b/src/Pages/RegisterInternships.tsx index 3caf48b..78fdb8a 100644 --- a/src/Pages/RegisterInternships.tsx +++ b/src/Pages/RegisterInternships.tsx @@ -17,7 +17,10 @@ const RegisterInternships = () => { nama_belakang: "", email: "", kontak: "", + universitas: "", + negara: "", jurusan: "", + batch: "", motivasi: "", relevant_skills: "", cv: null as File | null, @@ -44,7 +47,10 @@ const RegisterInternships = () => { } data.append("email", formData.email); data.append("kontak", formData.kontak); + data.append("universitas", formData.universitas); + data.append("negara", formData.negara); data.append("jurusan", formData.jurusan); + data.append("batch", formData.batch); data.append("motivasi", formData.motivasi); data.append("relevant_skills", formData.relevant_skills); data.append("g-recaptcha-response", recaptchaToken); @@ -131,12 +137,13 @@ const RegisterInternships = () => { setFormData({ ...formData, nama_depan: e.target.value }) } /> +

*required

@@ -144,13 +151,13 @@ const RegisterInternships = () => { setFormData({ ...formData, nama_belakang: e.target.value }) } /> -

*not required

+

*required

@@ -179,6 +186,32 @@ const RegisterInternships = () => { } />
+ +
+ + + setFormData({ ...formData, universitas: e.target.value }) + } + /> +
+ +
+ + + setFormData({ ...formData, negara: e.target.value }) + } + /> +
@@ -207,6 +240,19 @@ const RegisterInternships = () => {

*separate with commas

+
+ + + setFormData({ ...formData, batch: e.target.value }) + } + /> +
+