started admin ui

This commit is contained in:
Vyacheslav K
2025-05-06 13:34:07 +03:00
parent a249143cde
commit f4c4fd9f4c
13 changed files with 1195 additions and 156 deletions

371
package-lock.json generated
View File

@@ -8,17 +8,25 @@
"name": "hackaton-form",
"version": "0.1.0",
"dependencies": {
"@headlessui/react": "^2.2.2",
"@heroicons/react": "^2.2.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"chart.js": "^4.4.9",
"faker": "^6.6.6",
"react": "^19.1.0",
"react-chartjs-2": "^5.3.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.5.3",
"react-scripts": "5.0.1",
"react-toastify": "^11.0.5",
"socket.io-client": "^4.8.1",
"web-vitals": "^2.1.4"
},
"devDependencies": {
"@faker-js/faker": "^9.7.0",
"tailwindcss": "^3.4.17"
}
},
@@ -2462,6 +2470,105 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@faker-js/faker": {
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.7.0.tgz",
"integrity": "sha512-aozo5vqjCmDoXLNUJarFZx2IN/GgGaogY4TMJ6so/WLZOWpSV7fvj2dmrV6sEAnUm1O7aCrhTibjpzeDFgNqbg==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/fakerjs"
}
],
"license": "MIT",
"engines": {
"node": ">=18.0.0",
"npm": ">=9.0.0"
}
},
"node_modules/@floating-ui/core": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.0.tgz",
"integrity": "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.9"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.0.tgz",
"integrity": "sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.7.0",
"@floating-ui/utils": "^0.2.9"
}
},
"node_modules/@floating-ui/react": {
"version": "0.26.28",
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz",
"integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==",
"license": "MIT",
"dependencies": {
"@floating-ui/react-dom": "^2.1.2",
"@floating-ui/utils": "^0.2.8",
"tabbable": "^6.0.0"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@floating-ui/react-dom": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz",
"integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==",
"license": "MIT",
"dependencies": {
"@floating-ui/dom": "^1.0.0"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
"license": "MIT"
},
"node_modules/@headlessui/react": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.2.tgz",
"integrity": "sha512-zbniWOYBQ8GHSUIOPY7BbdIn6PzUOq0z41RFrF30HbjsxG6Rrfk+6QulR8Kgf2Vwj2a/rE6i62q5vo+2gI5dJA==",
"license": "MIT",
"dependencies": {
"@floating-ui/react": "^0.26.16",
"@react-aria/focus": "^3.17.1",
"@react-aria/interactions": "^3.21.3",
"@tanstack/react-virtual": "^3.13.6",
"use-sync-external-store": "^1.5.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": "^18 || ^19 || ^19.0.0-rc",
"react-dom": "^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/@heroicons/react": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
"integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
"license": "MIT",
"peerDependencies": {
"react": ">= 16 || ^19.0.0-rc"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@@ -2964,6 +3071,12 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@kurkle/color": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
"license": "MIT"
},
"node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
@@ -3094,6 +3207,103 @@
}
}
},
"node_modules/@react-aria/focus": {
"version": "3.20.2",
"resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.20.2.tgz",
"integrity": "sha512-Q3rouk/rzoF/3TuH6FzoAIKrl+kzZi9LHmr8S5EqLAOyP9TXIKG34x2j42dZsAhrw7TbF9gA8tBKwnCNH4ZV+Q==",
"license": "Apache-2.0",
"dependencies": {
"@react-aria/interactions": "^3.25.0",
"@react-aria/utils": "^3.28.2",
"@react-types/shared": "^3.29.0",
"@swc/helpers": "^0.5.0",
"clsx": "^2.0.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/interactions": {
"version": "3.25.0",
"resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.0.tgz",
"integrity": "sha512-GgIsDLlO8rDU/nFn6DfsbP9rfnzhm8QFjZkB9K9+r+MTSCn7bMntiWQgMM+5O6BiA8d7C7x4zuN4bZtc0RBdXQ==",
"license": "Apache-2.0",
"dependencies": {
"@react-aria/ssr": "^3.9.8",
"@react-aria/utils": "^3.28.2",
"@react-stately/flags": "^3.1.1",
"@react-types/shared": "^3.29.0",
"@swc/helpers": "^0.5.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/ssr": {
"version": "3.9.8",
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.8.tgz",
"integrity": "sha512-lQDE/c9uTfBSDOjaZUJS8xP2jCKVk4zjQeIlCH90xaLhHDgbpCdns3xvFpJJujfj3nI4Ll9K7A+ONUBDCASOuw==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0"
},
"engines": {
"node": ">= 12"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/utils": {
"version": "3.28.2",
"resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.28.2.tgz",
"integrity": "sha512-J8CcLbvnQgiBn54eeEvQQbIOfBF3A1QizxMw9P4cl9MkeR03ug7RnjTIdJY/n2p7t59kLeAB3tqiczhcj+Oi5w==",
"license": "Apache-2.0",
"dependencies": {
"@react-aria/ssr": "^3.9.8",
"@react-stately/flags": "^3.1.1",
"@react-stately/utils": "^3.10.6",
"@react-types/shared": "^3.29.0",
"@swc/helpers": "^0.5.0",
"clsx": "^2.0.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-stately/flags": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.1.tgz",
"integrity": "sha512-XPR5gi5LfrPdhxZzdIlJDz/B5cBf63l4q6/AzNqVWFKgd0QqY5LvWJftXkklaIUpKSJkIKQb8dphuZXDtkWNqg==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0"
}
},
"node_modules/@react-stately/utils": {
"version": "3.10.6",
"resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.6.tgz",
"integrity": "sha512-O76ip4InfTTzAJrg8OaZxKU4vvjMDOpfA/PGNOytiXwBbkct2ZeZwaimJ8Bt9W1bj5VsZ81/o/tW4BacbdDOMA==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-types/shared": {
"version": "3.29.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz",
"integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==",
"license": "Apache-2.0",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -3448,6 +3658,42 @@
"url": "https://github.com/sponsors/gregberge"
}
},
"node_modules/@swc/helpers": {
"version": "0.5.17",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
"integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@tanstack/react-virtual": {
"version": "3.13.6",
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.6.tgz",
"integrity": "sha512-WT7nWs8ximoQ0CDx/ngoFP7HbQF9Q2wQe4nh2NB+u2486eX3nZRE40P9g6ccCVq7ZfTSH5gFOuCoVH5DLNS/aA==",
"license": "MIT",
"dependencies": {
"@tanstack/virtual-core": "3.13.6"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@tanstack/virtual-core": {
"version": "3.13.6",
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.6.tgz",
"integrity": "sha512-cnQUeWnhNP8tJ4WsGcYiX24Gjkc9ALstLbHcBj1t3E7EimN6n6kHH+DPV4PpDnuw00NApQp+ViojMj1GRdwYQg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@testing-library/dom": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
@@ -5541,6 +5787,18 @@
"node": ">=10"
}
},
"node_modules/chart.js": {
"version": "4.4.9",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz",
"integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==",
"license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=8"
}
},
"node_modules/check-types": {
"version": "11.2.3",
"resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz",
@@ -5645,6 +5903,15 @@
"wrap-ansi": "^7.0.0"
}
},
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -7986,6 +8253,12 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/faker": {
"version": "6.6.6",
"resolved": "https://registry.npmjs.org/faker/-/faker-6.6.6.tgz",
"integrity": "sha512-9tCqYEDHI5RYFQigXFwF1hnCwcWCOJl/hmll0lr5D2Ljjb0o4wphb69wikeJDz5qCEzXCoPvG6ss5SDP6IfOdg==",
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -13858,6 +14131,16 @@
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"license": "MIT"
},
"node_modules/react-chartjs-2": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz",
"integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==",
"license": "MIT",
"peerDependencies": {
"chart.js": "^4.1.1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-dev-utils": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
@@ -13996,6 +14279,54 @@
"node": ">=0.10.0"
}
},
"node_modules/react-router": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.3.tgz",
"integrity": "sha512-3iUDM4/fZCQ89SXlDa+Ph3MevBrozBAI655OAfWQlTm9nBR0IKlrmNwFow5lPHttbwvITZfkeeeZFP6zt3F7pw==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0",
"turbo-stream": "2.4.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/react-router-dom": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.3.tgz",
"integrity": "sha512-cK0jSaTyW4jV9SRKAItMIQfWZ/D6WEZafgHuuCb9g+SjhLolY78qc+De4w/Cz9ybjvLzShAmaIMEXt8iF1Cm+A==",
"license": "MIT",
"dependencies": {
"react-router": "7.5.3"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/react-router/node_modules/cookie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/react-scripts": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@@ -14069,6 +14400,19 @@
}
}
},
"node_modules/react-toastify": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz",
"integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==",
"license": "MIT",
"dependencies": {
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": "^18 || ^19",
"react-dom": "^18 || ^19"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -14911,6 +15255,12 @@
"node": ">= 0.8.0"
}
},
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -15992,6 +16342,12 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"license": "MIT"
},
"node_modules/tabbable": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
"license": "MIT"
},
"node_modules/tailwindcss": {
"version": "3.4.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
@@ -16351,6 +16707,12 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"license": "0BSD"
},
"node_modules/turbo-stream": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
"license": "ISC"
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -16659,6 +17021,15 @@
"requires-port": "^1.0.0"
}
},
"node_modules/use-sync-external-store": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@@ -3,13 +3,20 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@headlessui/react": "^2.2.2",
"@heroicons/react": "^2.2.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"chart.js": "^4.4.9",
"faker": "^6.6.6",
"react": "^19.1.0",
"react-chartjs-2": "^5.3.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.5.3",
"react-scripts": "5.0.1",
"react-toastify": "^11.0.5",
"socket.io-client": "^4.8.1",
"web-vitals": "^2.1.4"
},
@@ -38,6 +45,7 @@
]
},
"devDependencies": {
"@faker-js/faker": "^9.7.0",
"tailwindcss": "^3.4.17"
}
}

View File

@@ -1,157 +1,49 @@
import SendIcon from './img/paper-plane.png';
import { useState } from 'react';
import Message from './misc/Message';
import SendForm from './misc/SendForm';
import Spinner from './misc/Spinner';
import WaitResponse from './misc/WaitResponse';
import { NewRequest } from './misc/ChatGpt';
import './App.css';
import { useParams } from "react-router-dom";
import React, { Suspense } from 'react';
import { ToastContainer, toast } from 'react-toastify';
const FormComponent = React.lazy(() => import('./pages/Form'));
const DashboardComponent = React.lazy(() => import('./pages/Dashboard'));
const LoginComponent = React.lazy(() => import('./pages/Login'));
function App() {
const [Stage, setStage] = useState(0);
const [Loading, setLoading] = useState(false);
const [GenerationProcess, setGenerationProcess] = useState(false);
export default function App(args) {
const { page } = useParams(); // Получаем параметр page из URL
const CurrentPage = args.page || page;
let content;
const [LoadingText, setLoadingText] = useState("");
switch (CurrentPage) {
case "form":
content = <FormComponent CurrentPage={CurrentPage} />;
break;
case "dashboard":
content = <DashboardComponent CurrentPage={CurrentPage} />;
break;
case "login":
content = <LoginComponent CurrentPage={CurrentPage} />;
break;
case "network":
content = <DashboardComponent CurrentPage={CurrentPage} />;
break;
}
const [formData, setFormData] = useState({
name: '',
work_place: '',
problem: '',
message: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value
}));
};
const [messages, setMessages] = useState([]);
const addMessage = (text, self = true) => {
const newMessage = {
id: Date.now(), // уникальный ключ для нового элемента
text: text,
self: self
};
setMessages(prev => [...prev, newMessage]);
};
window.addMessage = addMessage;
const handleSubmit = (e) => {
e.preventDefault(); // Останавливаем стандартную отправку формы
console.log('Отправленные данные:', formData);
setLoading(true);
NewRequest(formData, addMessage, setGenerationProcess, setStage, setLoading);
};
return (
<div className="flex w-full h-full justify-center items-center dark:bg-gray-800">
<div className="relative flex md:h-[550px] h-full md:w-[500px] w-full flex-col justify-center md:rounded-xl bg-gray-50 p-5 text-sm/7 text-gray-800 shadow-lg dark:bg-gray-900 dark:text-gray-200">
<Spinner show={Loading} />
{Stage == 0 &&
<div className="p-0 md:p-4">
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
Сообщить о проблеме
</h1>
<div className="space-y-6">
<p className="leading-5">
Чем подробнее вы опишете ситуацию, тем быстрее мы найдём решение
</p>
<form className="space-y-3" onSubmit={handleSubmit}>
<input
type="text"
name="name"
className="block w-full rounded-xl border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-orange-500 focus:ring-orange-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-orange-500 dark:focus:ring-orange-500"
placeholder="Ваше имя *"
required={true}
value={formData.name}
onChange={handleChange}
/>
<input
type="text"
name="work_place"
className="block w-full rounded-xl border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-orange-500 focus:ring-orange-500 disabled:text-gray-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-orange-500 dark:focus:ring-orange-500"
defaultValue="Рабочее место 192.168.0.24"
placeholder="Адрес рабочего места *"
required={true}
disabled={false}
value={formData.work_place}
onChange={handleChange}
/>
<select
name="problem"
className="mt-10 block w-full rounded-xl border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-orange-500 focus:ring-orange-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-orange-500 dark:focus:ring-orange-500"
value={formData.problem}
onChange={handleChange}
>
<option value="0">Выберите тип проблемы *</option>
<option value="1">Ошибка в работе ПО</option>
<option value="2">Проблемы с доступом/паролями</option>
<option value="3">Не работает оборудование</option>
<option value="4">Другое</option>
</select>
<textarea
name="message"
rows={4}
className="block w-full rounded-xl border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-orange-500 focus:ring-orange-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-orange-500 dark:focus:ring-orange-500"
placeholder="Добавьте описание *"
required={true}
value={formData.message}
onChange={handleChange}
/>
<button
type="submit"
className="text-md dark:bg-orange-600dark:focus:ring-orange-900 mb-2 w-full cursor-pointer rounded-xl bg-orange-500 px-5 py-2.5 font-bold text-white shadow-md duration-300 hover:scale-105 focus:outline-none"
>
Отправить
</button>
</form>
</div>
</div>
}
{Stage == 1 &&
<>
<div className="flex flex-col gap-3 w-full h-full items-start scroll-box">
{messages.map(message => (
<Message key={message.id} self={message.self} text={message.text} />
))}
<WaitResponse show={GenerationProcess} />
</div>
<div className="pt-4">
<form className='flex gap-3'>
<input
type="text"
id="name"
className="block w-full rounded-xl border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-orange-500 focus:ring-orange-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-orange-500 dark:focus:ring-orange-500"
placeholder="Сообщение"
required=""
/>
<button type='submit' className='transition-transform duration-300 hover:rotate-45'>
<img src={SendIcon} className='h-6 w-6' />
</button>
</form>
</div>
</>
}
</div>
</div >
<Suspense fallback={<div className="flex animate-[fadeIn_0.5s_ease-out] absolute inset-0 flex-col items-center justify-center z-10 dark:bg-gray-700">
<div className="w-10 h-10 border-4 border-orange-500 border-t-transparent rounded-full animate-spin" style={{ animationDuration: "0.5s" }} ></div>
</div>}>
<ToastContainer
position="top-center"
autoClose={2500}
hideProgressBar={false}
newestOnTop={false}
closeOnClick={false}
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
theme="colored"
/>
{content}
</Suspense>
);
}
export default App;

View File

@@ -0,0 +1,152 @@
import React, { useState } from "react";
import Gauge from "../misc/Gauge";
import { InformationCircleIcon, ComputerDesktopIcon, CodeBracketIcon, CpuChipIcon, ClockIcon, ChevronDoubleRightIcon } from "@heroicons/react/24/outline";
import CpuChart from "../misc/CpuChart";
const Tabs = () => {
// Состояние для отслеживания выбранной вкладки
const [selectedTab, setSelectedTab] = useState("system");
const [Percent, setPercent] = useState(80);
window.setPercent = setPercent;
// Данные для вкладок
const tabs = [
{ id: "system", label: "Система" },
{ id: "deviceManager", label: "Диспетчер устройств" },
];
// Функция для обработки клика по вкладке
const handleTabClick = (tabId) => {
setSelectedTab(tabId);
};
return (
<>
<div className="flex justify-center md:justify-start">
{/* Контейнер для вкладок */}
<div className="flex p-2 bg-gray-100 dark:bg-gray-900 gap-2 rounded-lg shadow-sm">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => handleTabClick(tab.id)}
className={`rounded-lg px-4 py-2 text-gray-900 dark:text-gray-100 ${selectedTab === tab.id
? "bg-gray-200 dark:bg-gray-800 text-orange-600 font-semibold"
: "hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-orange-600 duration-300"
}`}
>
{tab.label}
</button>
))}
</div>
</div>
<div className="mt-6">
{selectedTab === "system" && (
<div className="flex gap-3 flex-wrap flex-col md:flex-row dark:text-gray-100">
<div className="md:basis-3/6 w-full border border-gray-300 dark:border-gray-700 p-4 rounded-lg shadow-sm">
<h1 className="text-lg font-semibold">Информация о системе</h1>
<div className="flex flex-col gap-2 mt-3">
<div className="flex items-start gap-2">
<div className="flex items-center gap-2">
<ClockIcon className="h-6 text-gray-500" />
<span className="font-bold text-gray-600 dark:text-gray-400">Время работы:</span>
</div>
<span>1d 15:05:10</span>
</div>
<div className="flex items-center gap-2">
<div className="flex items-start gap-2">
<InformationCircleIcon className="h-6 text-gray-500" />
<span className="font-bold text-gray-600 dark:text-gray-400">Система:</span>
</div>
<span>Windows</span>
</div>
<div className="flex items-start gap-2">
<div className="flex items-center gap-2">
<ComputerDesktopIcon className="h-6 text-gray-500" />
<span className="font-bold text-gray-600 dark:text-gray-400">Hostname:</span>
</div>
<span className="break-normal">DESKTOP-380ABCDE</span>
</div>
<div className="flex items-start gap-2">
<div className="flex items-center gap-2">
<CodeBracketIcon className="h-6 text-gray-500" />
<span className="font-bold text-gray-600 dark:text-gray-400">Версия:</span>
</div>
<span>10.0.19049</span>
</div>
<div className="flex items-start gap-2">
<div className="flex items-center gap-2">
<CpuChipIcon className="h-6 text-gray-500" />
<span className="font-bold text-gray-600 dark:text-gray-400">Архитектура:</span>
</div>
<span>AMD64</span>
</div>
</div>
</div>
<div className="md:basis-1/6 border border-gray-300 dark:border-gray-700 p-4 rounded-lg shadow-sm">
<h1 className="text-lg font-semibold dark:text-gray-100">Загрузка CPU</h1>
<div className=" flex flex-col items-center">
<Gauge value={20} title="%" color="green" className="mt-3" />
</div>
</div>
<div className="md:basis-1/6 border border-gray-300 dark:border-gray-700 p-4 rounded-lg shadow-sm">
<h1 className="text-lg font-semibold dark:text-gray-100">Занято ОЗУ</h1>
<div className=" flex flex-col items-center">
<Gauge value={Percent} title="%" color="orange" className="mt-3" />
</div>
</div>
<div className="md:basis-3/6 w-full border border-gray-300 dark:border-gray-700 p-4 rounded-lg shadow-sm">
<h1 className="text-lg font-semibold">Аппаратное обеспечение</h1>
<div className="flex flex-col gap-2 mt-3">
<div className="flex items-start gap-2">
<div className="flex items-center gap-2">
<CpuChipIcon className="h-6 text-gray-500" />
<span className="font-bold text-gray-600 dark:text-gray-400">Процессор:</span>
</div>
<span>AMD Ryzen 5 5600x 6-core processor</span>
</div>
<div className="flex items-start gap-2">
<div className="flex items-center gap-2">
<ChevronDoubleRightIcon className="h-6 text-gray-500" />
<span className="font-bold text-gray-600 dark:text-gray-400">Физические ядра:</span>
</div>
<span>6</span>
</div>
<div className="flex items-start gap-2">
<div className="flex items-center gap-2">
<ChevronDoubleRightIcon className="h-6 text-gray-500" />
<span className="font-bold text-gray-600 dark:text-gray-400">Логические ядра:</span>
</div>
<span>12</span>
</div>
<div className="flex items-start gap-2">
<div className="flex items-center gap-2">
<ChevronDoubleRightIcon className="h-6 text-gray-500" />
<span className="font-bold text-gray-600 dark:text-gray-400">ОЗУ:</span>
</div>
<span>32 ГБ</span>
</div>
</div>
</div>
<div className="md:basis-[34.3%] w-full border border-gray-300 dark:border-gray-700 p-4 rounded-lg shadow-sm">
<h1 className="text-lg font-semibold">График загрузки CPU</h1>
<div className="flex flex-col gap-2 mt-3">
<CpuChart />
</div>
</div>
</div>
)}
{selectedTab === "deviceManager" && (
<p>Вот содержимое вкладки "Диспетчер устройств"</p>
)}
</div>
</>
);
};
export default Tabs;

3
src/img/icon-network.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.288 15.038a5.25 5.25 0 0 1 7.424 0M5.106 11.856c3.807-3.808 9.98-3.808 13.788 0M1.924 8.674c5.565-5.565 14.587-5.565 20.152 0M12.53 18.22l-.53.53-.53-.53a.75.75 0 0 1 1.06 0Z" />
</svg>

After

Width:  |  Height:  |  Size: 376 B

3
src/img/icon-servers.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 14.25h13.5m-13.5 0a3 3 0 0 1-3-3m3 3a3 3 0 1 0 0 6h13.5a3 3 0 1 0 0-6m-16.5-3a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3m-19.5 0a4.5 4.5 0 0 1 .9-2.7L5.737 5.1a3.375 3.375 0 0 1 2.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 0 1 .9 2.7m0 0a3 3 0 0 1-3 3m0 3h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Zm-3 6h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Z" />
</svg>

After

Width:  |  Height:  |  Size: 571 B

View File

@@ -1,14 +1,14 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {
RouterProvider,
} from "react-router-dom";
import {router} from "./misc/router"
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
<RouterProvider router={router} />
);
// If you want to start measuring performance in your app, pass a function

52
src/misc/CpuChart.js Normal file
View File

@@ -0,0 +1,52 @@
import React from 'react';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
} from 'chart.js';
import { Line } from 'react-chartjs-2';
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
);
export const options = {
responsive: true,
plugins: {
legend: {
display: false,
},
title: {
display: false,
},
},
};
const labels = ['10:00',"11:00","12:00","13:00","14:00","15:00","16:00"];
export const data = {
labels,
datasets: [
{
label: 'Загрузка CPU',
data: [10,20,30,10,10,45,20],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.5)',
}
],
};
export default function CpuChart() {
return <Line options={options} data={data} />;
}

80
src/misc/Gauge.js Normal file
View File

@@ -0,0 +1,80 @@
import React, { useEffect, useRef } from "react";
const CircularProgressIndicator = ({ value, color }) => {
const svgRef = useRef(null);
useEffect(() => {
if (!svgRef.current) return;
// Получаем элемент SVG
const svg = svgRef.current;
const circle = svg.querySelector("circle");
// Рассчитываем длину окружности
const circumference = circle.getAttribute("r") * 2 * Math.PI;
// Устанавливаем начальное значение (0%)
circle.style.strokeDasharray = `${circumference} ${circumference}`;
circle.style.strokeDashoffset = circumference;
// Анимируем заполнение
setTimeout(() => {
const offset = circumference - (value / 100) * circumference;
circle.style.transition = "stroke-dashoffset 500ms ease-in-out";
circle.style.strokeDashoffset = offset;
}, 0);
}, [value]);
console.log(color)
switch(color){
case "green":
color = "#1eeb07"
break;
case "red":
color = "#fc4a03"
break;
case "yellow":
color = "#f0fc03"
break;
case "orange":
color = "#ff9f43"
break;
}
return (
<div className="relative flex items-center justify-center mt-3">
{/* SVG для кругового прогресс-бара */}
<svg
ref={svgRef}
className="w-32 h-32"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="50"
cy="50"
r="45"
fill="transparent"
stroke={color}
strokeWidth="7"
className="fill-gray-200 dark:fill-gray-700"
strokeDasharray="0 282.74"
strokeDashoffset="282.74" // Начинаем снизу
strokeLinecap="round" // Закругляем торцы
transform="rotate(90 50 50)" // Поворачиваем начало вниз
/>
</svg>
{/* Текст в центре круга */}
<div className="absolute top-[44%] left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-3xl font-bold" style={{"color":color}}>
{value}
</div>
<div className="absolute top-[65%] left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-lg text-gray-500 dark:text-gray-400">
%
</div>
</div>
);
};
export default CircularProgressIndicator;

20
src/misc/router.js Normal file
View File

@@ -0,0 +1,20 @@
import {
createBrowserRouter
} from "react-router-dom";
import App from '../App';
export const router = createBrowserRouter([
{
path: "/",
element: <App page="form"/>,
},
{
path: "/dashboard",
element: <App page="dashboard"/>,
},
{
path: "/dashboard/:page",
element: <App/>,
},
]);

204
src/pages/Dashboard.js Normal file
View File

@@ -0,0 +1,204 @@
import { Disclosure, DisclosureButton, DisclosurePanel, Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
import { Bars3Icon, BellIcon, XMarkIcon } from '@heroicons/react/24/outline'
import { ServerStackIcon } from '@heroicons/react/16/solid'
import { WifiIcon } from '@heroicons/react/16/solid'
import { Link } from "react-router-dom";
import SystemMonitor from "../dashboard/SystemMonitor"
const user = {
name: 'Administrator',
email: '@admin',
imageUrl:
'https://api.dicebear.com/9.x/thumbs/svg?seed=Amaya',
}
const navigation = [
{ name: 'Системный мониторинг', href: '/dashboard', icon: ServerStackIcon, pageName: "dashboard" },
{ name: 'Сетевой мониторинг', href: '/dashboard/network', icon: WifiIcon, pageName: "network" },
]
const userNavigation = [
{ name: 'Выйти из аккаунта', href: '#' },
]
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Dashboard(args) {
let content;
switch (args.CurrentPage) {
case "dashboard":
content = <SystemMonitor/>;
break;
case "network":
content = <SystemMonitor/>;
break;
}
return (
<>
{/*
This example requires updating your template:
```
<html class="h-full bg-gray-100">
<body class="h-full">
```
*/}
<div className="min-h-full">
<Disclosure as="nav" className="bg-gray-700">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="flex h-16 items-center justify-between">
<div className="flex items-center">
<div className="shrink-0">
<img
alt="Your Company"
src="https://tailwindcss.com/plus-assets/img/logos/mark.svg?color=indigo&shade=500"
className="size-8"
/>
</div>
<div className="hidden md:block">
<div className="ml-10 flex items-baseline space-x-4">
{navigation.map((item) => (
<Link
key={item.name}
to={item.href}
aria-current={args.CurrentPage == item.pageName ? 'page' : undefined}
className={classNames(
args.CurrentPage == item.pageName ? 'bg-orange-600 text-white' : 'text-gray-300 hover:bg-orange-700 hover:text-white',
'rounded-md px-3 py-2 text-md font-medium',
'flex gap-2 justify-center'
)}
>
<item.icon className='h-6'/>
{item.name}
</Link>
))}
</div>
</div>
</div>
<div className="hidden md:block">
<div className="ml-4 flex items-center md:ml-6">
<button
type="button"
className="relative rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800 focus:outline-hidden"
>
<span className="absolute -inset-1.5" />
<span className="sr-only">View notifications</span>
<BellIcon aria-hidden="true" className="size-6" />
</button>
{/* Profile dropdown */}
<Menu as="div" className="relative ml-3">
<div>
<MenuButton className="relative flex max-w-xs items-center rounded-full bg-gray-800 text-sm focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800 focus:outline-hidden">
<span className="absolute -inset-1.5" />
<span className="sr-only">Open user menu</span>
<img alt="" src={user.imageUrl} className="size-8 rounded-full" />
</MenuButton>
</div>
<MenuItems
transition
className="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 transition focus:outline-hidden data-closed:scale-95 data-closed:transform data-closed:opacity-0 data-enter:duration-100 data-enter:ease-out data-leave:duration-75 data-leave:ease-in"
>
{userNavigation.map((item) => (
<MenuItem key={item.name}>
<Link
to={item.href}
className="block px-4 py-2 text-sm text-gray-700 data-focus:bg-gray-100 data-focus:outline-hidden"
>
<item.icon className='h-6' />
{item.name}
</Link>
</MenuItem>
))}
</MenuItems>
</Menu>
</div>
</div>
<div className="-mr-2 flex md:hidden">
{/* Mobile menu button */}
<DisclosureButton className="group relative inline-flex items-center justify-center rounded-md bg-gray-700 p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:ring-offset-gray-800 focus:outline-hidden">
<span className="absolute -inset-0.5" />
<span className="sr-only">Open main menu</span>
<Bars3Icon aria-hidden="true" className="block size-6 group-data-open:hidden" />
<XMarkIcon aria-hidden="true" className="hidden size-6 group-data-open:block" />
</DisclosureButton>
</div>
</div>
</div>
<DisclosurePanel
as="nav"
className={"overflow-hidden transition-all duration-300 ease-in-out animate-[fadeIn_0.5s_ease-out]"}
>
<div className="space-y-1 px-2 pt-2 pb-3 sm:px-3">
{navigation.map((item) => (
<DisclosureButton
key={item.name}
as="a"
href={item.href}
aria-current={args.CurrentPage == item.pageName ? 'page' : undefined}
className={classNames(
args.CurrentPage == item.pageName
? 'bg-gray-900 text-white'
: 'text-gray-300 hover:bg-gray-700 hover:text-white',
'block rounded-md px-3 py-2 text-base font-medium',
'flex items-center gap-2'
)}
>
<item.icon className='h-6' />
{item.name}
</DisclosureButton>
))}
</div>
<div className="border-t border-gray-700 pt-4 pb-3">
<div className="flex items-center px-5">
<div className="shrink-0">
<img alt="" src={user.imageUrl} className="size-10 rounded-full" />
</div>
<div className="ml-3">
<div className="text-base/5 font-medium text-white">{user.name}</div>
<div className="text-sm font-medium text-gray-400">{user.email}</div>
</div>
<button
type="button"
className="relative ml-auto shrink-0 rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800 focus:outline-hidden"
>
<span className="absolute -inset-1.5" />
<span className="sr-only">View notifications</span>
<BellIcon aria-hidden="true" className="size-6" />
</button>
</div>
<div className="mt-3 space-y-1 px-2">
{userNavigation.map((item) => (
<DisclosureButton
key={item.name}
as="a"
href={item.href}
className="block rounded-md px-3 py-2 text-base font-medium text-gray-400 hover:bg-gray-700 hover:text-white"
>
{item.name}
</DisclosureButton>
))}
</div>
</div>
</DisclosurePanel>
</Disclosure>
<main>
<div className="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8 dark:bg-gray-800">
{content}
</div>
</main>
</div>
</>
)
}

161
src/pages/Form.js Normal file
View File

@@ -0,0 +1,161 @@
import SendIcon from '../img/paper-plane.png';
import { useState } from 'react';
import Message from '../misc/Message';
import SendForm from '../misc/SendForm';
import Spinner from '../misc/Spinner';
import WaitResponse from '../misc/WaitResponse';
import { NewRequest } from '../misc/ChatGpt';
import { Link } from "react-router-dom";
import '../App.css';
function Form() {
const [Stage, setStage] = useState(0);
const [Loading, setLoading] = useState(false);
const [GenerationProcess, setGenerationProcess] = useState(false);
const [LoadingText, setLoadingText] = useState("");
const [formData, setFormData] = useState({
name: 'TestName',
work_place: '192.168.0.10',
problem: '1',
message: 'Test problem'
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value
}));
};
const [messages, setMessages] = useState([]);
const addMessage = (text, self = true) => {
const newMessage = {
id: Date.now(), // уникальный ключ для нового элемента
text: text,
self: self
};
setMessages(prev => [...prev, newMessage]);
};
window.addMessage = addMessage;
const handleSubmit = (e) => {
e.preventDefault(); // Останавливаем стандартную отправку формы
console.log('Отправленные данные:', formData);
setLoading(true);
NewRequest(formData, addMessage, setGenerationProcess, setStage, setLoading);
};
return (
<div className="flex flex-col w-full h-full justify-center items-center dark:bg-gray-800">
<div className="relative flex md:h-[550px] h-full md:w-[500px] w-full flex-col justify-center md:rounded-xl bg-gray-50 p-5 text-sm/7 text-gray-800 shadow-lg dark:bg-gray-900 dark:text-gray-200">
<Spinner show={Loading} />
{Stage == 0 &&
<div className="p-0 md:p-4">
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
Сообщить о проблеме
</h1>
<div className="space-y-6">
<p className="leading-5">
Чем подробнее вы опишете ситуацию, тем быстрее мы найдём решение
</p>
<form className="space-y-3" onSubmit={handleSubmit}>
<input
type="text"
name="name"
className="block w-full rounded-xl border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-orange-500 focus:ring-orange-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-orange-500 dark:focus:ring-orange-500"
placeholder="Ваше имя *"
required={true}
value={formData.name}
onChange={handleChange}
/>
<input
type="text"
name="work_place"
className="block w-full rounded-xl border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-orange-500 focus:ring-orange-500 disabled:text-gray-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-orange-500 dark:focus:ring-orange-500"
defaultValue="Рабочее место 192.168.0.24"
placeholder="Адрес рабочего места *"
required={true}
disabled={false}
value={formData.work_place}
onChange={handleChange}
/>
<select
name="problem"
className="mt-10 block w-full rounded-xl border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-orange-500 focus:ring-orange-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-orange-500 dark:focus:ring-orange-500"
value={formData.problem}
onChange={handleChange}
>
<option value="0">Выберите тип проблемы *</option>
<option value="1">Ошибка в работе ПО</option>
<option value="2">Проблемы с доступом/паролями</option>
<option value="3">Не работает оборудование</option>
<option value="4">Другое</option>
</select>
<textarea
name="message"
rows={4}
className="block w-full rounded-xl border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-orange-500 focus:ring-orange-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-orange-500 dark:focus:ring-orange-500"
placeholder="Добавьте описание *"
required={true}
value={formData.message}
onChange={handleChange}
/>
<button
type="submit"
className="text-md dark:bg-orange-600dark:focus:ring-orange-900 mb-2 w-full cursor-pointer rounded-xl bg-orange-500 px-5 py-2.5 font-bold text-white shadow-md duration-300 hover:scale-105 focus:outline-none"
>
Отправить
</button>
</form>
</div>
</div>
}
{Stage == 1 &&
<>
<div className="flex flex-col gap-3 w-full h-full items-start scroll-box">
{messages.map(message => (
<Message key={message.id} self={message.self} text={message.text} />
))}
<WaitResponse show={GenerationProcess} />
</div>
<div className="pt-4">
<form className='flex gap-3'>
<input
type="text"
id="name"
className="block w-full rounded-xl border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-orange-500 focus:ring-orange-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-orange-500 dark:focus:ring-orange-500"
placeholder="Сообщение"
required=""
/>
<button type='submit' className='transition-transform duration-300 hover:rotate-45'>
<img src={SendIcon} className='h-6 w-6' />
</button>
</form>
</div>
</>
}
<div className='flex w-full justify-center text-md'>
<Link to="/dashboard/login" className='block mt-3 text-orange-500 hover:text-orange-300 duration-300'>Перейти в админ-панель</Link>
</div>
</div>
</div >
);
}
export default Form;

93
src/pages/Login.js Normal file
View File

@@ -0,0 +1,93 @@
import Spinner from '../misc/Spinner';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { useNavigate } from "react-router-dom";
function Login(args) {
const [Loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
username: 'admin',
password: 'admin'
});
const navigate = useNavigate();
const handleChange = (e) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault(); // Останавливаем стандартную отправку формы
console.log('Отправленные данные:', formData);
setLoading(true);
////////
setTimeout(() => {
// setLoading(false);
// toast.error('Неверный логин или пароль');
toast.success('Добро пожаловать!');
navigate("/dashboard");
}, 1000)
};
return (
<>
<div className="flex w-full h-full justify-center items-center dark:bg-gray-800">
<div className="relative flex md:h-[550px] h-full md:w-[500px] w-full flex-col justify-center md:rounded-xl bg-gray-50 p-5 text-sm/7 text-gray-800 shadow-lg dark:bg-gray-900 dark:text-gray-200">
<Spinner show={Loading} />
<div className="p-0 md:p-4">
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
Авторизация
</h1>
<div className="space-y-6">
<p className="leading-5">
Для продолжения нужно войти в аккаунт
</p>
<form className="space-y-3" onSubmit={handleSubmit}>
<input
type="text"
name="username"
className="block w-full rounded-xl border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-orange-500 focus:ring-orange-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-orange-500 dark:focus:ring-orange-500"
placeholder="Имя пользователя"
required={true}
value={formData.username}
onChange={handleChange}
/>
<input
type="password"
name="password"
className="block w-full rounded-xl border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-orange-500 focus:ring-orange-500 disabled:text-gray-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-orange-500 dark:focus:ring-orange-500"
placeholder="Пароль"
required={true}
disabled={false}
value={formData.password}
onChange={handleChange}
/>
<button
type="submit"
className="text-md dark:bg-orange-600dark:focus:ring-orange-900 mb-2 w-full cursor-pointer rounded-xl bg-orange-500 px-5 py-2.5 font-bold text-white shadow-md duration-300 hover:scale-105 focus:outline-none"
>
Войти
</button>
</form>
</div>
</div>
</div>
</div >
</>
)
}
export default Login;