add dashboard, feedback, faq
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
VITE_API_BASE_URL=
|
VITE_API_BASE_URL=
|
||||||
|
VITE_RECAPTCHA_SITE_KEY
|
||||||
|
|||||||
Generated
+371
-48
@@ -13,12 +13,14 @@
|
|||||||
"@tiptap/starter-kit": "^2.23.0",
|
"@tiptap/starter-kit": "^2.23.0",
|
||||||
"axios": "^1.10.0",
|
"axios": "^1.10.0",
|
||||||
"froala-editor": "^4.5.2",
|
"froala-editor": "^4.5.2",
|
||||||
|
"lucide-react": "^0.553.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-froala-wysiwyg": "^4.5.2",
|
"react-froala-wysiwyg": "^4.5.2",
|
||||||
"react-google-recaptcha": "^3.1.0",
|
"react-google-recaptcha": "^3.1.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-router-dom": "^7.6.0",
|
"react-router-dom": "^7.6.0",
|
||||||
|
"recharts": "^3.4.1",
|
||||||
"tailwindcss": "^4.1.6"
|
"tailwindcss": "^4.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -871,13 +873,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/plugin-kit": {
|
"node_modules/@eslint/plugin-kit": {
|
||||||
"version": "0.3.3",
|
"version": "0.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
|
||||||
"integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==",
|
"integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint/core": "^0.15.1",
|
"@eslint/core": "^0.15.2",
|
||||||
"levn": "^0.4.1"
|
"levn": "^0.4.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -885,11 +886,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
|
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
|
||||||
"version": "0.15.1",
|
"version": "0.15.2",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
|
||||||
"integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
|
"integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/json-schema": "^7.0.15"
|
"@types/json-schema": "^7.0.15"
|
||||||
},
|
},
|
||||||
@@ -1071,6 +1071,31 @@
|
|||||||
"url": "https://opencollective.com/popperjs"
|
"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": {
|
"node_modules/@remirror/core-constants": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
|
||||||
@@ -1344,6 +1369,16 @@
|
|||||||
"win32"
|
"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": {
|
"node_modules/@tailwindcss/node": {
|
||||||
"version": "4.1.11",
|
"version": "4.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz",
|
||||||
@@ -2036,6 +2071,60 @@
|
|||||||
"@babel/types": "^7.20.7"
|
"@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": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
@@ -2075,7 +2164,7 @@
|
|||||||
"version": "19.1.8",
|
"version": "19.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
|
||||||
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
|
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
@@ -2453,17 +2542,15 @@
|
|||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.10.0",
|
"version": "1.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
||||||
"integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==",
|
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.4",
|
||||||
"proxy-from-env": "^1.1.0"
|
"proxy-from-env": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2535,7 +2622,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
@@ -2601,6 +2687,14 @@
|
|||||||
"node": ">=18"
|
"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": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
@@ -2625,7 +2719,6 @@
|
|||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"delayed-stream": "~1.0.0"
|
"delayed-stream": "~1.0.0"
|
||||||
},
|
},
|
||||||
@@ -2681,9 +2774,119 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
"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": {
|
"node_modules/deep-is": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||||
@@ -2713,7 +2921,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
@@ -2731,7 +2938,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.1",
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
@@ -2777,7 +2983,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
@@ -2786,7 +2991,6 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
@@ -2795,7 +2999,6 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0"
|
"es-errors": "^1.3.0"
|
||||||
},
|
},
|
||||||
@@ -2807,7 +3010,6 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
"get-intrinsic": "^1.2.6",
|
"get-intrinsic": "^1.2.6",
|
||||||
@@ -2818,6 +3020,15 @@
|
|||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.5",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
|
||||||
@@ -3058,6 +3269,11 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
@@ -3203,10 +3419,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||||
"integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
|
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
@@ -3242,7 +3457,6 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
@@ -3261,7 +3475,6 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.2",
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
"es-define-property": "^1.0.1",
|
"es-define-property": "^1.0.1",
|
||||||
@@ -3285,7 +3498,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dunder-proto": "^1.0.1",
|
"dunder-proto": "^1.0.1",
|
||||||
"es-object-atoms": "^1.0.0"
|
"es-object-atoms": "^1.0.0"
|
||||||
@@ -3324,7 +3536,6 @@
|
|||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@@ -3359,7 +3570,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@@ -3371,7 +3581,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-symbols": "^1.0.3"
|
"has-symbols": "^1.0.3"
|
||||||
},
|
},
|
||||||
@@ -3386,7 +3595,6 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
},
|
},
|
||||||
@@ -3413,6 +3621,15 @@
|
|||||||
"node": ">= 4"
|
"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": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||||
@@ -3440,6 +3657,14 @@
|
|||||||
"node": ">=0.8.19"
|
"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": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
@@ -3496,11 +3721,10 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
},
|
},
|
||||||
@@ -3861,6 +4085,14 @@
|
|||||||
"yallist": "^3.0.2"
|
"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": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.17",
|
"version": "0.30.17",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
||||||
@@ -3891,7 +4123,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
@@ -3929,7 +4160,6 @@
|
|||||||
"version": "1.52.0",
|
"version": "1.52.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
@@ -3938,7 +4168,6 @@
|
|||||||
"version": "2.1.35",
|
"version": "2.1.35",
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mime-db": "1.52.0"
|
"mime-db": "1.52.0"
|
||||||
},
|
},
|
||||||
@@ -4519,6 +4748,28 @@
|
|||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.17.0",
|
"version": "0.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||||
@@ -4567,6 +4818,53 @@
|
|||||||
"react-dom": ">=18"
|
"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": {
|
"node_modules/resolve-from": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||||
@@ -4781,6 +5079,11 @@
|
|||||||
"node": ">=18"
|
"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": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.14",
|
"version": "0.2.14",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
"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"
|
"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": {
|
"node_modules/vite": {
|
||||||
"version": "6.3.5",
|
"version": "6.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||||
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
|
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.4.4",
|
"fdir": "^6.4.4",
|
||||||
|
|||||||
@@ -15,12 +15,14 @@
|
|||||||
"@tiptap/starter-kit": "^2.23.0",
|
"@tiptap/starter-kit": "^2.23.0",
|
||||||
"axios": "^1.10.0",
|
"axios": "^1.10.0",
|
||||||
"froala-editor": "^4.5.2",
|
"froala-editor": "^4.5.2",
|
||||||
|
"lucide-react": "^0.553.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-froala-wysiwyg": "^4.5.2",
|
"react-froala-wysiwyg": "^4.5.2",
|
||||||
"react-google-recaptcha": "^3.1.0",
|
"react-google-recaptcha": "^3.1.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-router-dom": "^7.6.0",
|
"react-router-dom": "^7.6.0",
|
||||||
|
"recharts": "^3.4.1",
|
||||||
"tailwindcss": "^4.1.6"
|
"tailwindcss": "^4.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
+10
@@ -20,6 +20,11 @@ import ListLowongan from "./Pages/ListLowongan";
|
|||||||
import ListProduct from "./Pages/ListProduct";
|
import ListProduct from "./Pages/ListProduct";
|
||||||
import AddProductHumic from "./Pages/AddProductHumic";
|
import AddProductHumic from "./Pages/AddProductHumic";
|
||||||
import EditProductHumic from "./Pages/EditProductHumic";
|
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() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -51,6 +56,11 @@ function App() {
|
|||||||
<Route path="/partnership-admin" element={<PartnershipsList />} />
|
<Route path="/partnership-admin" element={<PartnershipsList />} />
|
||||||
<Route path="/add-partnership" element={<AddPartnershipAdmin />} />
|
<Route path="/add-partnership" element={<AddPartnershipAdmin />} />
|
||||||
<Route path="/edit-partnership/:id" element={<EditPartnerships />} />
|
<Route path="/edit-partnership/:id" element={<EditPartnerships />} />
|
||||||
|
<Route path="/feedback-list" element={<ListFeedback />} />
|
||||||
|
<Route path="/add-feedback" element={<AddFeedback />} />
|
||||||
|
<Route path="/faq-list" element={<ListFaq />} />
|
||||||
|
<Route path="/add-faq" element={<AddFaq />} />
|
||||||
|
<Route path="/dashboard" element={<Dashboard />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
@@ -52,9 +52,15 @@ const Footer = () => {
|
|||||||
<p>Tentang</p>
|
<p>Tentang</p>
|
||||||
<p>Life at Humic</p>
|
<p>Life at Humic</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="sec-2 flex flex-col gap-3">
|
<div className="sec-2 flex flex-col gap-3 hover:cursor-pointer">
|
||||||
<p className="font-bold">Lainnya</p>
|
<p className="font-bold">Lainnya</p>
|
||||||
<p>Humic Website</p>
|
<p
|
||||||
|
onClick={() =>
|
||||||
|
window.open("https://humic.telkomuniversity.ac.id", "_blank")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Humic Website
|
||||||
|
</p>
|
||||||
<p>Our Activity</p>
|
<p>Our Activity</p>
|
||||||
<p>Life at Humic</p>
|
<p>Life at Humic</p>
|
||||||
<p
|
<p
|
||||||
|
|||||||
@@ -11,7 +11,37 @@ const SidebarAdmin = () => {
|
|||||||
<div className="list-of-sidebar flex flex-col p-7 gap-4">
|
<div className="list-of-sidebar flex flex-col p-7 gap-4">
|
||||||
<div
|
<div
|
||||||
className={`text-container flex flex-row gap-3 p-3 items-center cursor-pointer rounded-2xl ${
|
className={`text-container flex flex-row gap-3 p-3 items-center cursor-pointer rounded-2xl ${
|
||||||
location.pathname === "/produc-lList"
|
location.pathname === "/product-list"
|
||||||
|
? "bg-[#D3DFEA] font-bold"
|
||||||
|
: "hover:bg-[#D3DFEA] "
|
||||||
|
}`}
|
||||||
|
onClick={() => navigate("/dashboard")}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
className="size-8 text-[#2C5282]"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M12 3 L5 8 A4 4 0 0 0 3 11 L3 15 A4 4 0 0 0 7 19 L17 19 A4 4 0 0 0 21 15 L21 11 A4 4 0 0 0 19 8 L12 3 Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M12 13 v4"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<h2 className="text-[#2C5282]">Dashboard</h2>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`text-container flex flex-row gap-3 p-3 items-center cursor-pointer rounded-2xl ${
|
||||||
|
location.pathname === "/product-list"
|
||||||
? "bg-[#D3DFEA] font-bold"
|
? "bg-[#D3DFEA] font-bold"
|
||||||
: "hover:bg-[#D3DFEA] "
|
: "hover:bg-[#D3DFEA] "
|
||||||
}`}
|
}`}
|
||||||
@@ -108,6 +138,67 @@ const SidebarAdmin = () => {
|
|||||||
|
|
||||||
<h2 className="text-[#2C5282]">Pelamar</h2>
|
<h2 className="text-[#2C5282]">Pelamar</h2>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className={`text-container flex flex-row gap-3 p-3 items-center cursor-pointer rounded-2xl ${
|
||||||
|
location.pathname === "/feedbacks-list"
|
||||||
|
? "bg-[#D3DFEA] font-bold"
|
||||||
|
: "hover:bg-[#D3DFEA]"
|
||||||
|
}`}
|
||||||
|
onClick={() => navigate("/feedback-list")}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-8 text-[#2C5282]"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M4 4h16a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-5l-3 3-3-3H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Z"
|
||||||
|
/>
|
||||||
|
<circle cx="8" cy="10" r="1.2" fill="currentColor" />
|
||||||
|
<circle cx="12" cy="10" r="1.2" fill="currentColor" />
|
||||||
|
<circle cx="16" cy="10" r="1.2" fill="currentColor" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<h2 className="text-[#2C5282]">Feedback</h2>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`text-container flex flex-row gap-3 p-3 items-center cursor-pointer rounded-2xl ${
|
||||||
|
location.pathname === "/faq-list"
|
||||||
|
? "bg-[#D3DFEA] font-bold"
|
||||||
|
: "hover:bg-[#D3DFEA]"
|
||||||
|
}`}
|
||||||
|
onClick={() => navigate("/faq-list")}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-8 text-[#2C5282]"
|
||||||
|
>
|
||||||
|
{/* Balon chat */}
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M4 4h16a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-5l-3 3-3-3H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Z"
|
||||||
|
/>
|
||||||
|
{/* Tanda tanya */}
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M9.75 9.5a2.25 2.25 0 1 1 4.5 0c0 1.25-.75 1.75-1.5 1.85s-1 1-1 1.75"
|
||||||
|
/>
|
||||||
|
<circle cx="12" cy="16" r="0.6" fill="currentColor" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<h2 className="text-[#2C5282]">FAQ</h2>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<div className="min-h-screen w-full overflow-hidden">
|
||||||
|
<NavbarAdmin />
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<SidebarAdmin />
|
||||||
|
<div className="p-10 bg-white flex-col flex-wrap w-full">
|
||||||
|
<div className="flex justify-between items-center mb-8">
|
||||||
|
<h2 className="text-xl font-semibold">
|
||||||
|
Tambah Frequently Asked Questions
|
||||||
|
</h2>
|
||||||
|
<button
|
||||||
|
className="bg-[#1F4A92] hover:bg-[#677c9f] cursor-pointer text-white px-6 py-2 rounded-md font-medium"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form className="flex flex-col gap-6 max-w-full">
|
||||||
|
{/* Questions */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">Questions</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="border border-gray-300 rounded-lg p-3"
|
||||||
|
value={questions}
|
||||||
|
onChange={(e) => setQuestions(e.target.value)}
|
||||||
|
placeholder="Tulis pertanyaan kamu..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Answers */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">Answers</label>
|
||||||
|
<FroalaEditor
|
||||||
|
tag="textarea"
|
||||||
|
model={answers}
|
||||||
|
onModelChange={(model: any) => setAnswers(model)}
|
||||||
|
config={{
|
||||||
|
placeholderText: "Tulis jawaban kamu...",
|
||||||
|
charCounterCount: true,
|
||||||
|
toolbarSticky: true,
|
||||||
|
heightMin: 200,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddFaq;
|
||||||
@@ -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<File | null>(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 (
|
||||||
|
<div className="min-h-screen w-full overflow-hidden">
|
||||||
|
<NavbarAdmin />
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<SidebarAdmin />
|
||||||
|
<div className="p-10 bg-white flex-col flex-wrap w-full">
|
||||||
|
<div className="flex justify-between items-center mb-8">
|
||||||
|
<h2 className="text-xl font-semibold">Tambah Feedback</h2>
|
||||||
|
<button
|
||||||
|
className="bg-[#1F4A92] hover:bg-[#677c9f] cursor-pointer text-white px-6 py-2 rounded-md font-medium"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form className="flex flex-col gap-6 max-w-full">
|
||||||
|
<div className="grid grid-cols-2 gap-6">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">Nama</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="border border-gray-300 rounded-lg p-3"
|
||||||
|
value={nama}
|
||||||
|
onChange={(e) => setNama(e.target.value)}
|
||||||
|
placeholder="John Doe"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">Universitas</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="border border-gray-300 rounded-lg p-3"
|
||||||
|
value={universitas}
|
||||||
|
onChange={(e) => setUniversitas(e.target.value)}
|
||||||
|
placeholder="Telkom University Bandung"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">Posisi</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="border border-gray-300 rounded-lg p-3"
|
||||||
|
value={posisi}
|
||||||
|
onChange={(e) => setPosisi(e.target.value)}
|
||||||
|
placeholder="UI/UX Designer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">Batch</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="border border-gray-300 rounded-lg p-3"
|
||||||
|
value={batch}
|
||||||
|
onChange={(e) => setBatch(e.target.value)}
|
||||||
|
placeholder="5"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">Tahun</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="border border-gray-300 rounded-lg p-3"
|
||||||
|
value={year}
|
||||||
|
onChange={(e) => setYear(e.target.value)}
|
||||||
|
placeholder="2025"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2 col-span-2">
|
||||||
|
<label className="text-sm font-medium">Image anda</label>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={(e) =>
|
||||||
|
setImage(e.target.files ? e.target.files[0] : null)
|
||||||
|
}
|
||||||
|
className="border border-gray-300 rounded-lg p-3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Isi Konten */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium">Isi Konten</label>
|
||||||
|
<FroalaEditor
|
||||||
|
tag="textarea"
|
||||||
|
model={feedback}
|
||||||
|
onModelChange={(model: any) => setFeedbacks(model)}
|
||||||
|
config={{
|
||||||
|
placeholderText: "Tulis feedback kamu...",
|
||||||
|
charCounterCount: true,
|
||||||
|
toolbarSticky: true,
|
||||||
|
heightMin: 200,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddFeedback;
|
||||||
@@ -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<StatCard[]>([]);
|
||||||
|
const [universitasData, setUniversitasData] = useState<ChartDataItem[]>([]);
|
||||||
|
const [penerimaanData, setPenerimaanData] = useState<ChartDataItem[]>([]);
|
||||||
|
const [negaraData, setNegaraData] = useState<ChartDataItem[]>([]);
|
||||||
|
|
||||||
|
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: <Users size={24} />,
|
||||||
|
iconColor: "#0088ff",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Total Diterima",
|
||||||
|
value: data.totalDiterima,
|
||||||
|
subtitle: `${(
|
||||||
|
(data.totalDiterima / (data.totalPendaftar || 1)) *
|
||||||
|
100
|
||||||
|
).toFixed(1)}% acceptance rate`,
|
||||||
|
icon: <UserCheck size={24} />,
|
||||||
|
iconColor: "#00cc00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Total Ditolak",
|
||||||
|
value: data.totalDitolak,
|
||||||
|
subtitle: `${(
|
||||||
|
(data.totalDitolak / (data.totalPendaftar || 1)) *
|
||||||
|
100
|
||||||
|
).toFixed(1)}% rejection rate`,
|
||||||
|
icon: <UserX size={24} />,
|
||||||
|
iconColor: "#ff3333",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Sedang Diproses",
|
||||||
|
value: data.totalDiproses,
|
||||||
|
subtitle: `${(
|
||||||
|
(data.totalDiproses / (data.totalPendaftar || 1)) *
|
||||||
|
100
|
||||||
|
).toFixed(1)}% pending review`,
|
||||||
|
icon: <Clock size={24} />,
|
||||||
|
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 (
|
||||||
|
<div className="min-h-screen w-full overflow-hidden">
|
||||||
|
<NavbarAdmin />
|
||||||
|
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<SidebarAdmin />
|
||||||
|
|
||||||
|
<div className="w-full flex-col flex-wrap bg-white p-10">
|
||||||
|
{/* Header Section */}
|
||||||
|
<div className="mb-[50px] flex flex-row items-center justify-between">
|
||||||
|
<h2 className="text-[24px] font-semibold">Dashboard</h2>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
{/* Batch Filter */}
|
||||||
|
<button className="flex items-center gap-2 rounded-xl border border-gray-300 bg-white px-[20px] py-[10px] text-gray-700 hover:bg-gray-50">
|
||||||
|
Batch 3 - 2025
|
||||||
|
<ChevronDown size={18} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Search Box */}
|
||||||
|
<div className="flex items-center gap-2 rounded-xl border border-gray-300 bg-white px-[20px] py-[10px]">
|
||||||
|
<Search size={18} className="text-gray-400" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Nama, Jurusan,"
|
||||||
|
className="w-52 text-sm text-gray-700 placeholder-gray-400 outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Loading / Error State */}
|
||||||
|
{loading && (
|
||||||
|
<p className="text-center text-gray-600">Memuat data dashboard...</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && errorMessage && (
|
||||||
|
<p className="text-center text-red-500">{errorMessage}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && !errorMessage && (
|
||||||
|
<>
|
||||||
|
{/* Stat Cards */}
|
||||||
|
<div className="mb-8 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||||
|
{statCards.map((card, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="rounded-lg border border-gray-300 bg-white p-6"
|
||||||
|
>
|
||||||
|
<div className="mb-4 flex items-center gap-3">
|
||||||
|
<div
|
||||||
|
className="rounded-lg bg-gray-100 p-2"
|
||||||
|
style={{ color: card.iconColor }}
|
||||||
|
>
|
||||||
|
{card.icon}
|
||||||
|
</div>
|
||||||
|
<h3 className="text-sm font-medium text-blue-600">
|
||||||
|
{card.title}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<p className="text-2xl font-bold text-gray-900">
|
||||||
|
{card.value}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs" style={{ color: card.iconColor }}>
|
||||||
|
{card.subtitle}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Charts Section */}
|
||||||
|
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||||
|
{/* Universitas Chart */}
|
||||||
|
<div className="rounded-lg border border-gray-300 bg-white p-6">
|
||||||
|
<h2 className="mb-6 text-lg font-bold text-gray-900">
|
||||||
|
Universitas
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="mb-6 flex justify-center">
|
||||||
|
<ResponsiveContainer width="100%" height={250}>
|
||||||
|
<PieChart>
|
||||||
|
<Pie
|
||||||
|
data={universitasData}
|
||||||
|
cx="50%"
|
||||||
|
cy="50%"
|
||||||
|
innerRadius={0}
|
||||||
|
outerRadius={100}
|
||||||
|
paddingAngle={2}
|
||||||
|
dataKey="value"
|
||||||
|
>
|
||||||
|
{universitasData.map((_, index) => (
|
||||||
|
<Cell
|
||||||
|
key={`universitas-cell-${index}`}
|
||||||
|
fill={
|
||||||
|
UNIVERSITAS_COLORS[
|
||||||
|
index % UNIVERSITAS_COLORS.length
|
||||||
|
]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Pie>
|
||||||
|
</PieChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Legend */}
|
||||||
|
<div className="space-y-3 text-sm">
|
||||||
|
{universitasData.map((item, index) => (
|
||||||
|
<div
|
||||||
|
key={`universitas-legend-${index}`}
|
||||||
|
className="flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div
|
||||||
|
className="h-3 w-3 rounded"
|
||||||
|
style={{
|
||||||
|
backgroundColor:
|
||||||
|
UNIVERSITAS_COLORS[
|
||||||
|
index % UNIVERSITAS_COLORS.length
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="text-gray-700">{item.name}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-semibold text-gray-900">
|
||||||
|
{item.value}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Column - Penerimaan & Negara */}
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Penerimaan Chart */}
|
||||||
|
<div className="rounded-lg border border-gray-300 bg-white p-6">
|
||||||
|
<h2 className="mb-6 text-lg font-bold text-gray-900">
|
||||||
|
Penerimaan
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="mb-6 flex justify-center">
|
||||||
|
<ResponsiveContainer width="100%" height={180}>
|
||||||
|
<PieChart>
|
||||||
|
<Pie
|
||||||
|
data={penerimaanData}
|
||||||
|
cx="50%"
|
||||||
|
cy="50%"
|
||||||
|
innerRadius={0}
|
||||||
|
outerRadius={70}
|
||||||
|
paddingAngle={2}
|
||||||
|
dataKey="value"
|
||||||
|
>
|
||||||
|
{penerimaanData.map((_, index) => (
|
||||||
|
<Cell
|
||||||
|
key={`penerimaan-cell-${index}`}
|
||||||
|
fill={
|
||||||
|
PENERIMAAN_COLORS[
|
||||||
|
index % PENERIMAAN_COLORS.length
|
||||||
|
]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Pie>
|
||||||
|
</PieChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Legend */}
|
||||||
|
<div className="space-y-2 text-sm">
|
||||||
|
{penerimaanData.map((item, index) => (
|
||||||
|
<div
|
||||||
|
key={`penerimaan-legend-${index}`}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="h-3 w-3 rounded"
|
||||||
|
style={{
|
||||||
|
backgroundColor:
|
||||||
|
PENERIMAAN_COLORS[
|
||||||
|
index % PENERIMAAN_COLORS.length
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="text-gray-700">{item.name}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Negara Chart */}
|
||||||
|
<div className="rounded-lg border border-gray-300 bg-white p-6">
|
||||||
|
<h2 className="mb-6 text-lg font-bold text-gray-900">
|
||||||
|
Negara
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="mb-6 flex justify-center">
|
||||||
|
<ResponsiveContainer width="100%" height={180}>
|
||||||
|
<PieChart>
|
||||||
|
<Pie
|
||||||
|
data={negaraData}
|
||||||
|
cx="50%"
|
||||||
|
cy="50%"
|
||||||
|
innerRadius={0}
|
||||||
|
outerRadius={70}
|
||||||
|
paddingAngle={2}
|
||||||
|
dataKey="value"
|
||||||
|
>
|
||||||
|
{negaraData.map((_, index) => (
|
||||||
|
<Cell
|
||||||
|
key={`negara-cell-${index}`}
|
||||||
|
fill={
|
||||||
|
NEGARA_COLORS[
|
||||||
|
index % NEGARA_COLORS.length
|
||||||
|
]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Pie>
|
||||||
|
</PieChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Legend */}
|
||||||
|
<div className="space-y-2 text-sm">
|
||||||
|
{negaraData.map((item, index) => (
|
||||||
|
<div
|
||||||
|
key={`negara-legend-${index}`}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="h-3 w-3 rounded"
|
||||||
|
style={{
|
||||||
|
backgroundColor:
|
||||||
|
NEGARA_COLORS[index % NEGARA_COLORS.length],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="text-gray-700">{item.name}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dashboard;
|
||||||
@@ -23,8 +23,10 @@ const DetailsProduct = () => {
|
|||||||
fetch(`${import.meta.env.VITE_API_BASE_URL}/hasil-research-api/get/${id}`)
|
fetch(`${import.meta.env.VITE_API_BASE_URL}/hasil-research-api/get/${id}`)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data && data.data && data.data.length > 0) {
|
if (data && data.data) {
|
||||||
setProduct(data.data[0]);
|
setProduct(data.data);
|
||||||
|
} else {
|
||||||
|
setProduct(null);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
})
|
||||||
@@ -43,6 +45,8 @@ const DetailsProduct = () => {
|
|||||||
<div className="text-center mt-10 text-red-500">Data tidak ditemukan</div>
|
<div className="text-center mt-10 text-red-500">Data tidak ditemukan</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
console.log(`${import.meta.env.VITE_API_BASE_URL}${product.image_path}`);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="body-details-of-product bg-[#F8F9FA] min-h-screen">
|
<div className="body-details-of-product bg-[#F8F9FA] min-h-screen">
|
||||||
|
|||||||
@@ -144,9 +144,9 @@ const EditPartnerships = () => {
|
|||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<SidebarAdmin />
|
<SidebarAdmin />
|
||||||
<div className="p-10 bg-white flex-col flex-wrap w-full">
|
<div className="p-10 bg-white flex-col flex-wrap w-full">
|
||||||
<div className="header-button flex justify-between items-center mb-[45px]">
|
<div className="flex justify-between items-center mb-[45px]">
|
||||||
<h2 className="text-[24px] font-semibold">Edit Partnership</h2>
|
<h2 className="text-[24px] font-semibold">Edit Partnership</h2>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4 pr-10">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
@@ -165,7 +165,7 @@ const EditPartnerships = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="flex flex-col gap-8 w-full">
|
<form onSubmit={handleSubmit} className="flex flex-col gap-8 w-full">
|
||||||
<div className="flex flex-row gap-16">
|
<div className="flex flex-row gap-10">
|
||||||
<div className="flex flex-col items-start gap-[15px]">
|
<div className="flex flex-col items-start gap-[15px]">
|
||||||
<h2 className="text-[16px] font-bold">Nama</h2>
|
<h2 className="text-[16px] font-bold">Nama</h2>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ const Internships = () => {
|
|||||||
title: item.posisi,
|
title: item.posisi,
|
||||||
location: item.lokasi,
|
location: item.lokasi,
|
||||||
category: item.kelompok_peminatan,
|
category: item.kelompok_peminatan,
|
||||||
status: item.status_lowongan === "dibuka" ? "Opened" : "Closed",
|
status: item.status_lowongan === "dibuka" ? "Open" : "Closed",
|
||||||
statusColor:
|
statusColor:
|
||||||
item.status_lowongan === "dibuka" ? "#4EAD00" : "#C3423F",
|
item.status_lowongan === "dibuka" ? "#4EAD00" : "#C3423F",
|
||||||
isPaid: item.paid === "paid",
|
isPaid: item.paid === "paid",
|
||||||
@@ -171,12 +171,12 @@ const Internships = () => {
|
|||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`card-1 flex flex-col items-start p-5 lg:p-7 rounded-xl shadow-2xl ${
|
className={`card-1 flex flex-col items-start p-5 lg:p-7 rounded-xl shadow-2xl ${
|
||||||
intern.status === "Opened"
|
intern.status === "Open"
|
||||||
? "bg-white cursor-pointer hover:shadow-xl transition"
|
? "bg-white cursor-pointer hover:shadow-xl transition"
|
||||||
: "bg-gray-200 cursor-not-allowed opacity-60"
|
: "bg-gray-200 cursor-not-allowed opacity-60"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (intern.status === "Opened") {
|
if (intern.status === "Open") {
|
||||||
navigate(`/details/${intern.id}`);
|
navigate(`/details/${intern.id}`);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -189,7 +189,7 @@ const Internships = () => {
|
|||||||
className="p-2 border-2 text-[12px] lg:text-[14px] font-bold rounded-4xl"
|
className="p-2 border-2 text-[12px] lg:text-[14px] font-bold rounded-4xl"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
intern.status === "Opened" ? "#F6FFF4" : "#FAF0EF",
|
intern.status === "Open" ? "#F6FFF4" : "#FAF0EF",
|
||||||
color: intern.statusColor,
|
color: intern.statusColor,
|
||||||
borderColor: intern.statusColor,
|
borderColor: intern.statusColor,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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<Faq[]>([]);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="min-h-screen w-full overflow-hidden">
|
||||||
|
<NavbarAdmin />
|
||||||
|
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<SidebarAdmin />
|
||||||
|
<div className="p-10 bg-white flex-col flex-wrap w-full">
|
||||||
|
<div className="flex flex-row justify-between items-center mb-[50px]">
|
||||||
|
<h2 className="text-[24px] font-semibold">
|
||||||
|
Frequently Asked Questions
|
||||||
|
</h2>
|
||||||
|
<button
|
||||||
|
className="px-[20px] py-[10px] rounded-xl bg-[#1F4A92] hover:bg-[#677c9f] cursor-pointer text-white font-semibold"
|
||||||
|
onClick={() => navigate("/add-faq")}
|
||||||
|
>
|
||||||
|
Tambah
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="min-w-full">
|
||||||
|
<thead>
|
||||||
|
<tr className="text-left">
|
||||||
|
<th className="px-6 py-3">Questions</th>
|
||||||
|
<th className="px-6 py-3">Answers</th>
|
||||||
|
<th className="px-6 py-3">Tanggal Unggah</th>
|
||||||
|
<th className="px-6 py-3">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{faq.map((faq) => (
|
||||||
|
<tr key={faq.id} className="hover:bg-gray-50">
|
||||||
|
<td className="px-6 py-3">{faq.pertanyaan}</td>
|
||||||
|
<td className="px-6 py-3">
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: faq.jawaban }} />
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td className="px-6 py-3">
|
||||||
|
{new Date(faq.created_at).toLocaleDateString()}
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-3 flex gap-2">
|
||||||
|
{/* Tombol View */}
|
||||||
|
<button
|
||||||
|
className="bg-[#3BE21D] cursor-pointer text-white p-2 rounded-xl"
|
||||||
|
onClick={() =>
|
||||||
|
window.open(`/details-product/${faq.id}`, "_blank")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Tombol Edit */}
|
||||||
|
<button
|
||||||
|
className="bg-[#2CAEFF] text-white cursor-pointer p-2 rounded-xl"
|
||||||
|
onClick={() => navigate(`/edit-faq/${faq.id}`)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M19.5 7.125 18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Tombol Hapus */}
|
||||||
|
<button
|
||||||
|
className="bg-[#E41E1E] text-white cursor-pointer p-2 rounded-xl"
|
||||||
|
onClick={() => handleDelete(faq.id)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{faq.length === 0 && (
|
||||||
|
<p className="text-gray-500 mt-4 text-center">
|
||||||
|
Belum ada data FAQ.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListFaq;
|
||||||
@@ -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<Feedback[]>([]);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="min-h-screen w-full overflow-hidden">
|
||||||
|
<NavbarAdmin />
|
||||||
|
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<SidebarAdmin />
|
||||||
|
<div className="p-10 bg-white flex-col flex-wrap w-full">
|
||||||
|
<div className="flex flex-row justify-between items-center mb-[50px]">
|
||||||
|
<h2 className="text-[24px] font-semibold">Feedback</h2>
|
||||||
|
<button
|
||||||
|
className="px-[20px] py-[10px] rounded-xl bg-[#1F4A92] hover:bg-[#677c9f] cursor-pointer text-white font-semibold"
|
||||||
|
onClick={() => navigate("/add-feedback")}
|
||||||
|
>
|
||||||
|
Tambah
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="min-w-full">
|
||||||
|
<thead>
|
||||||
|
<tr className="text-left">
|
||||||
|
<th className="px-6 py-3">Nama</th>
|
||||||
|
<th className="px-6 py-3">Foto</th>
|
||||||
|
<th className="px-6 py-3">Tanggal Unggah</th>
|
||||||
|
<th className="px-6 py-3">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{feedbacks.map((feedback) => (
|
||||||
|
<tr key={feedback.id} className="hover:bg-gray-50">
|
||||||
|
<td className="px-6 py-3">{feedback.nama}</td>
|
||||||
|
<td className="px-6 py-3">
|
||||||
|
<img src={feedback.image} alt={feedback.nama} />
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-3">
|
||||||
|
{new Date(feedback.created_at).toLocaleDateString()}
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-3 flex gap-2">
|
||||||
|
{/* Tombol View */}
|
||||||
|
<button
|
||||||
|
className="bg-[#3BE21D] cursor-pointer text-white p-2 rounded-xl"
|
||||||
|
onClick={() =>
|
||||||
|
window.open(`/details-product/${feedback.id}`, "_blank")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Tombol Edit */}
|
||||||
|
<button
|
||||||
|
className="bg-[#2CAEFF] text-white cursor-pointer p-2 rounded-xl"
|
||||||
|
onClick={() =>
|
||||||
|
navigate(`/edit-product-humic/${feedback.id}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M19.5 7.125 18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Tombol Hapus */}
|
||||||
|
<button
|
||||||
|
className="bg-[#E41E1E] text-white cursor-pointer p-2 rounded-xl"
|
||||||
|
onClick={() => handleDelete(feedback.id)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{feedbacks.length === 0 && (
|
||||||
|
<p className="text-gray-500 mt-4 text-center">Belum ada data feedback.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListFeedback;
|
||||||
@@ -190,7 +190,7 @@ const ListProduct = () => {
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
{projects.length === 0 && (
|
{projects.length === 0 && (
|
||||||
<p className="text-gray-500 mt-4">Belum ada data project.</p>
|
<p className="text-gray-500 mt-4 text-center">Belum ada data project.</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ const RegisterInternships = () => {
|
|||||||
nama_belakang: "",
|
nama_belakang: "",
|
||||||
email: "",
|
email: "",
|
||||||
kontak: "",
|
kontak: "",
|
||||||
|
universitas: "",
|
||||||
|
negara: "",
|
||||||
jurusan: "",
|
jurusan: "",
|
||||||
|
batch: "",
|
||||||
motivasi: "",
|
motivasi: "",
|
||||||
relevant_skills: "",
|
relevant_skills: "",
|
||||||
cv: null as File | null,
|
cv: null as File | null,
|
||||||
@@ -44,7 +47,10 @@ const RegisterInternships = () => {
|
|||||||
}
|
}
|
||||||
data.append("email", formData.email);
|
data.append("email", formData.email);
|
||||||
data.append("kontak", formData.kontak);
|
data.append("kontak", formData.kontak);
|
||||||
|
data.append("universitas", formData.universitas);
|
||||||
|
data.append("negara", formData.negara);
|
||||||
data.append("jurusan", formData.jurusan);
|
data.append("jurusan", formData.jurusan);
|
||||||
|
data.append("batch", formData.batch);
|
||||||
data.append("motivasi", formData.motivasi);
|
data.append("motivasi", formData.motivasi);
|
||||||
data.append("relevant_skills", formData.relevant_skills);
|
data.append("relevant_skills", formData.relevant_skills);
|
||||||
data.append("g-recaptcha-response", recaptchaToken);
|
data.append("g-recaptcha-response", recaptchaToken);
|
||||||
@@ -131,12 +137,13 @@ const RegisterInternships = () => {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="w-full border-2 border-[#A9B5C0] rounded-md p-2 mt-3 text-black"
|
className="w-full border-2 border-[#A9B5C0] rounded-md p-2 mt-3 text-black"
|
||||||
placeholder="John"
|
placeholder="Muhammad Budi"
|
||||||
value={formData.nama_depan}
|
value={formData.nama_depan}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({ ...formData, nama_depan: e.target.value })
|
setFormData({ ...formData, nama_depan: e.target.value })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<p className="text-sm text-[#C3423F] mt-1">*required</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -144,13 +151,13 @@ const RegisterInternships = () => {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="w-full border-2 border-[#A9B5C0] rounded-md p-2 mt-3 text-black"
|
className="w-full border-2 border-[#A9B5C0] rounded-md p-2 mt-3 text-black"
|
||||||
placeholder="Doe"
|
placeholder="Pekerti"
|
||||||
value={formData.nama_belakang}
|
value={formData.nama_belakang}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({ ...formData, nama_belakang: e.target.value })
|
setFormData({ ...formData, nama_belakang: e.target.value })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-[#C3423F] mt-1">*not required</p>
|
<p className="text-sm text-[#C3423F] mt-1">*required</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -180,6 +187,32 @@ const RegisterInternships = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block mb-1 font-bold">Universitas</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full border-2 border-[#A9B5C0] rounded-md p-2 mt-3 text-black"
|
||||||
|
placeholder="Telkom University Bandung"
|
||||||
|
value={formData.universitas}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData({ ...formData, universitas: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block mb-1 font-bold">Negara</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full border-2 border-[#A9B5C0] rounded-md p-2 mt-3 text-black"
|
||||||
|
placeholder="Indonesia"
|
||||||
|
value={formData.negara}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData({ ...formData, negara: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block mb-1 font-bold">Jurusan</label>
|
<label className="block mb-1 font-bold">Jurusan</label>
|
||||||
<input
|
<input
|
||||||
@@ -207,6 +240,19 @@ const RegisterInternships = () => {
|
|||||||
<p className="text-sm text-[#C3423F] mt-1">*separate with commas</p>
|
<p className="text-sm text-[#C3423F] mt-1">*separate with commas</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block mb-1 font-bold">Batch</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full border-2 border-[#A9B5C0] rounded-md p-2 mt-3 text-black"
|
||||||
|
placeholder="1"
|
||||||
|
value={formData.batch}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData({ ...formData, batch: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block mb-1 font-bold">
|
<label className="block mb-1 font-bold">
|
||||||
CV (Curriculum Vitae)
|
CV (Curriculum Vitae)
|
||||||
|
|||||||
Reference in New Issue
Block a user