diff --git a/.env b/.env new file mode 100644 index 0000000..5b50641 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +REACT_APP_API_URL=http://localhost:8000 \ No newline at end of file diff --git a/gatsby-browser.js b/gatsby-browser.js new file mode 100644 index 0000000..4e3af69 --- /dev/null +++ b/gatsby-browser.js @@ -0,0 +1,9 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import { store } from './src/redux/store'; + +export const wrapRootElement = ({ element }) => ( + + {element} + +); \ No newline at end of file diff --git a/gatsby-ssr.js b/gatsby-ssr.js new file mode 100644 index 0000000..4e3af69 --- /dev/null +++ b/gatsby-ssr.js @@ -0,0 +1,9 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import { store } from './src/redux/store'; + +export const wrapRootElement = ({ element }) => ( + + {element} + +); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a5551f2..d28afd8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,13 @@ "version": "1.0.0", "dependencies": { "@fortawesome/fontawesome-free": "^6.7.2", - "axios": "^1.7.9", + "@reduxjs/toolkit": "^2.6.0", + "axios": "^1.8.1", "gatsby": "^5.13.3", "gatsby-plugin-sass": "^6.14.0", "react": "^18", "react-dom": "^18", + "react-redux": "^9.2.0", "react-router-dom": "^7.1.5", "react-tsparticles": "^2.12.2", "sass": "^1.32.7" @@ -4130,6 +4132,40 @@ "node": ">=12" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.0.tgz", + "integrity": "sha512-mWJCYpewLRyTuuzRSEC/IwIBBkYg2dKtQas8mty5MaV2iXzcmicS3gW554FDeOvLnY3x13NIk8MB1e8wHO7rqQ==", + "license": "MIT", + "dependencies": { + "immer": "^10.0.3", + "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/@reduxjs/toolkit/node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -4469,6 +4505,12 @@ "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz", "integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0=" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@types/yoga-layout": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz", @@ -5415,9 +5457,9 @@ } }, "node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", + "integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -9774,6 +9816,15 @@ "node": ">=18.0.0" } }, + "node_modules/gatsby-cli/node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/gatsby-core-utils": { "version": "4.14.0", "resolved": "https://registry.npmjs.org/gatsby-core-utils/-/gatsby-core-utils-4.14.0.tgz", @@ -10293,6 +10344,24 @@ "webpack": "^5.59.0" } }, + "node_modules/gatsby/node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/gatsby/node_modules/redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "license": "MIT", + "peerDependencies": { + "redux": "^4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -14391,6 +14460,29 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "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==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -14602,19 +14694,18 @@ } }, "node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" }, "node_modules/redux-thunk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", - "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", "peerDependencies": { - "redux": "^4" + "redux": "^5.0.0" } }, "node_modules/reflect.getprototypeof": { @@ -14850,6 +14941,12 @@ "resolved": "https://registry.npmjs.org/require-package-name/-/require-package-name-2.0.1.tgz", "integrity": "sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==" }, + "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==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -16985,6 +17082,15 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "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", @@ -20330,6 +20436,24 @@ "config-chain": "^1.1.11" } }, + "@reduxjs/toolkit": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.0.tgz", + "integrity": "sha512-mWJCYpewLRyTuuzRSEC/IwIBBkYg2dKtQas8mty5MaV2iXzcmicS3gW554FDeOvLnY3x13NIk8MB1e8wHO7rqQ==", + "requires": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "dependencies": { + "immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==" + } + } + }, "@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -20627,6 +20751,11 @@ "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz", "integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0=" }, + "@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" + }, "@types/yoga-layout": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz", @@ -21282,9 +21411,9 @@ "integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==" }, "axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", + "integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==", "requires": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -24551,6 +24680,20 @@ "loose-envify": "^1.1.0", "neo-async": "^2.6.1" } + }, + "redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "requires": { + "@babel/runtime": "^7.9.2" + } + }, + "redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "requires": {} } } }, @@ -24599,6 +24742,16 @@ "yargs": "^15.4.1", "yoga-layout-prebuilt": "^1.10.0", "yurnalist": "^2.1.0" + }, + "dependencies": { + "redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "requires": { + "@babel/runtime": "^7.9.2" + } + } } }, "gatsby-core-utils": { @@ -27608,6 +27761,15 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "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==", + "requires": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + } + }, "react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -27738,17 +27900,14 @@ } }, "redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "requires": { - "@babel/runtime": "^7.9.2" - } + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" }, "redux-thunk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", - "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", "requires": {} }, "reflect.getprototypeof": { @@ -27923,6 +28082,11 @@ "resolved": "https://registry.npmjs.org/require-package-name/-/require-package-name-2.0.1.tgz", "integrity": "sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==" }, + "reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + }, "resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -29420,6 +29584,12 @@ "schema-utils": "^3.0.0" } }, + "use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 8ce3379..cb6cb73 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,13 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^6.7.2", - "axios": "^1.7.9", + "@reduxjs/toolkit": "^2.6.0", + "axios": "^1.8.1", "gatsby": "^5.13.3", "gatsby-plugin-sass": "^6.14.0", "react": "^18", "react-dom": "^18", + "react-redux": "^9.2.0", "react-router-dom": "^7.1.5", "react-tsparticles": "^2.12.2", "sass": "^1.32.7" diff --git a/src/components/forms/model_crud/modelShowModelsAndDownload.js b/src/components/forms/3d_model_crud/threeDModelShowModelsAndDownload.js similarity index 100% rename from src/components/forms/model_crud/modelShowModelsAndDownload.js rename to src/components/forms/3d_model_crud/threeDModelShowModelsAndDownload.js diff --git a/src/components/forms/model_crud/modelUpload.js b/src/components/forms/3d_model_crud/threeDModelUpload.js similarity index 73% rename from src/components/forms/model_crud/modelUpload.js rename to src/components/forms/3d_model_crud/threeDModelUpload.js index f875771..d49ab54 100644 --- a/src/components/forms/model_crud/modelUpload.js +++ b/src/components/forms/3d_model_crud/threeDModelUpload.js @@ -2,27 +2,25 @@ import React, { useState } from 'react' import { useSelector, useDispatch } from 'react-redux' import { userAuthSelector } from '../../../redux/slices/userAuthSlice' -import { modelCrudSelector } from '../../../redux/slices/modelCrudSlice' -import modelCrudAsyncThunk from '../../../redux/asyncThunks/modelCrudAsyncThunk' +import { threeDModelCrudSelector } from '../../../redux/slices/threeDModelCrudSlice' +import { uploadModel } from '../../../redux/asyncThunks/threeDModelCrudAsyncThunk' import FormGenerator from '../formGenerator' - const ModelUploadForm = () => { - const dispatch = useDispatch() const [blend, setBlend] = useState('') const [blendInfo, setBlendInfo] = useState('Drop/Click\nfor upload "*.blend" file') - const { upload_blend_file_status } = useSelector( modelCrudSelector ) + const { upload_blend_file_status } = useSelector( threeDModelCrudSelector ) const { user, token } = useSelector( userAuthSelector ) - let inputList = [ + const inputList = [ { type: 'info', action: 'Upload', - endpint: 'model/upload', + endpoint: 'model/upload', button_value: 'Upload Model' }, { @@ -37,20 +35,19 @@ const ModelUploadForm = () => { ] const handleModelUpload = () => { - let body = { + if (!blend) { + return; + } + + dispatch( uploadModel({ user_id: user.id, file: blend, token: token - } - console.log( body ) - dispatch( modelCrudAsyncThunk.fetchUploadModel( body ) ) + })); } return ( -
+
{ + const dispatch = useDispatch() + + const [model, setModel] = useState('') + const [modelInfo, setModelInfo] = useState('Drop/Click\nfor upload AI model file') + + const { upload_model_status } = useSelector(aiModelCrudSelector) + const { user, token } = useSelector(userAuthSelector) + + const inputList = [ + { + type: 'info', + action: 'Upload', + endpoint: 'ai-model/upload', + button_value: 'Upload AI Model' + }, + { + type: 'file', + name: 'Model', + fileType: 'ai', + dropInfo: modelInfo, + setDropInfo: setModelInfo, + file: model, + setFile: setModel + } + ] + + const handleModelUpload = () => { + if (!model) { + return; + } + + dispatch(uploadModel({ + user_id: user.id, + file: model, + token: token + })); + } + + return ( +
+ +

+ { + !upload_model_status + ? '' + : typeof upload_model_status === 'string' + ? '' + : 'info' in upload_model_status + ? upload_model_status.info + : '' + } +

+
+ ) +} + +export default AIModelUploadForm \ No newline at end of file diff --git a/src/components/forms/formGenerator.js b/src/components/forms/formGenerator.js index dee4d89..69542da 100644 --- a/src/components/forms/formGenerator.js +++ b/src/components/forms/formGenerator.js @@ -392,7 +392,7 @@ const DownloadFilesListInputGenerator = ({ * Text input generator, example: * @param { * { - * type: 'chice-listing', + * type: 'choice-listing', * name: 'name', * values: list, * ref: React.createRef() @@ -488,21 +488,35 @@ const UploadInputGenerator = ({ const setDropInfos = (name, size) => { input.setDropInfo( - 'name: "' - + name - + '"\nsize: ' - + (Math.round(size / 100 + 'e-2') / 100) - + ' MB' + { + name: name, + size: (Math.round(size / 100 + 'e-2') / 100) + ' MB' + } ) } return ( -
onLoadFileDrop(event)} > -
-                {input.dropInfo}
-            
+
onLoadFileDrop(event)} + className='upload_input_container' + > +

+ { + typeof input.dropInfo === 'string' ? + input.dropInfo + : + input.dropInfo.name + } +

+

+ { + typeof input.dropInfo === 'string' ? + "" + : + input.dropInfo.size + } +

{ + const startIndex = (currentPage - 1) * itemsPerPage; + return data.slice(startIndex, startIndex + itemsPerPage); + }, [data, currentPage, itemsPerPage]); + + // Obsługa zmiany strony + const handlePageChange = (newPage) => { + setCurrentPage(newPage); + setSelectedItem(null); + }; + + // Obsługa zmiany liczby elementów na stronie + const handleItemsPerPageChange = (event) => { + const newItemsPerPage = parseInt(event.target.value); + setItemsPerPage(newItemsPerPage); + setCurrentPage(1); + setSelectedItem(null); + }; // Toggle the "create" form const handleToggleCreate = () => { @@ -135,7 +160,7 @@ export const ListGenerator = ({ No items found. {onCreate && `Click '+ ${title || 'Item'}' to add new items.`}
) : ( - data.map((item) => ( + currentItems.map((item) => (
+ {data.length > 0 && ( +
+
+ Items per page: + +
+
+ + + + Page {currentPage} of {totalPages} + + + +
+
+ )} +
{selectedItem ? (
diff --git a/src/pages/dashboard.js b/src/pages/dashboard.js index 6fb5665..da19c62 100644 --- a/src/pages/dashboard.js +++ b/src/pages/dashboard.js @@ -63,10 +63,10 @@ const DashboardPage = () => { onClick={() => handleNavigation('servers')} > -

Dashboard

+

GPU Instances

-

Rendering

+

3D Stuff

  1. { onClick={() => handleNavigation('renders')} > -

    Rendered Materials

    +

    3D Rendering

-

AI Training

+

AI Stuff

  1. { onClick={() => handleNavigation('ai-tasks')} > -

    AI Training Tasks

    +

    AI Training

User

diff --git a/src/pages/dashboards/3d-models.js b/src/pages/dashboards/3d-models.js index b6d5e57..16e5b34 100644 --- a/src/pages/dashboards/3d-models.js +++ b/src/pages/dashboards/3d-models.js @@ -1,31 +1,244 @@ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { ListGenerator } from '../../components/forms/listGenerator'; -import { FormGenerator } from '../../components/forms/formGenerator'; +import ModelUploadForm from '../../components/forms/3d_model_crud/threeDModelUpload'; const ThreeDModelsDashboard = () => { const [selectedModel, setSelectedModel] = useState(null); + const [searchQuery, setSearchQuery] = useState(''); const [models, setModels] = useState([ { id: 1, - name: 'Model A', - type: '3D', + name: 'Dragon Model', + type: 'Blender', status: 'Active', lastModified: '2024-03-20', - size: '2.5MB' + version: '2.1' }, { id: 2, - name: 'Model B', - type: '3D', + name: 'Medieval Castle', + type: 'Maya', status: 'Inactive', lastModified: '2024-03-19', - size: '1.8MB' + version: '1.5' + }, + { + id: 3, + name: 'Sci-fi Weapon', + type: '3ds Max', + status: 'Active', + lastModified: '2024-03-20', + version: '1.0' + }, + { + id: 4, + name: 'Forest Scene', + type: 'Blender', + status: 'Active', + lastModified: '2024-03-18', + version: '3.2' + }, + { + id: 5, + name: 'Robot Character', + type: 'Maya', + status: 'Inactive', + lastModified: '2024-03-17', + version: '2.0' + }, + { + id: 6, + name: 'Space Ship', + type: '3ds Max', + status: 'Active', + lastModified: '2024-03-20', + version: '1.8' + }, + { + id: 7, + name: 'Ancient Temple', + type: 'Blender', + status: 'Active', + lastModified: '2024-03-19', + version: '2.4' + }, + { + id: 8, + name: 'Fantasy Sword', + type: 'Maya', + status: 'Inactive', + lastModified: '2024-03-16', + version: '1.2' + }, + { + id: 9, + name: 'City Block', + type: '3ds Max', + status: 'Active', + lastModified: '2024-03-20', + version: '2.7' + }, + { + id: 10, + name: 'Warrior Character', + type: 'Blender', + status: 'Active', + lastModified: '2024-03-18', + version: '1.9' + }, + { + id: 11, + name: 'Futuristic Car', + type: 'Maya', + status: 'Inactive', + lastModified: '2024-03-15', + version: '1.3' + }, + { + id: 12, + name: 'Mountain Range', + type: '3ds Max', + status: 'Active', + lastModified: '2024-03-20', + version: '2.2' + }, + { + id: 13, + name: 'Alien Creature', + type: 'Blender', + status: 'Active', + lastModified: '2024-03-19', + version: '1.6' + }, + { + id: 14, + name: 'Magic Staff', + type: 'Maya', + status: 'Active', + lastModified: '2024-03-20', + version: '1.4' + }, + { + id: 15, + name: 'Underground Cave', + type: '3ds Max', + status: 'Inactive', + lastModified: '2024-03-17', + version: '2.3' } ]); const [isFormVisible, setIsFormVisible] = useState(false); - const [formMode, setFormMode] = useState('create'); // 'create' lub 'edit' + const [formMode, setFormMode] = useState('create'); const [message, setMessage] = useState({ type: '', text: '' }); + const mockModels = [ + { + id: 1, + name: "Dragon Model", + type: "Character", + status: "Completed", + progress: 100 + }, + { + id: 2, + name: "Medieval Castle", + type: "Environment", + status: "In Progress", + progress: 65 + }, + { + id: 3, + name: "Sci-fi Weapon", + type: "Prop", + status: "Queued", + progress: 0 + }, + { + id: 4, + name: "Forest Scene", + type: "Environment", + status: "Completed", + progress: 100 + }, + { + id: 5, + name: "Robot Character", + type: "Character", + status: "In Progress", + progress: 45 + }, + { + id: 6, + name: "Space Ship", + type: "Vehicle", + status: "Completed", + progress: 100 + }, + { + id: 7, + name: "Ancient Temple", + type: "Environment", + status: "In Progress", + progress: 78 + }, + { + id: 8, + name: "Fantasy Sword", + type: "Prop", + status: "Queued", + progress: 0 + }, + { + id: 9, + name: "City Block", + type: "Environment", + status: "Completed", + progress: 100 + }, + { + id: 10, + name: "Warrior Character", + type: "Character", + status: "In Progress", + progress: 89 + }, + { + id: 11, + name: "Futuristic Car", + type: "Vehicle", + status: "Queued", + progress: 0 + }, + { + id: 12, + name: "Mountain Range", + type: "Environment", + status: "Completed", + progress: 100 + }, + { + id: 13, + name: "Alien Creature", + type: "Character", + status: "In Progress", + progress: 34 + }, + { + id: 14, + name: "Magic Staff", + type: "Prop", + status: "Completed", + progress: 100 + }, + { + id: 15, + name: "Underground Cave", + type: "Environment", + status: "In Progress", + progress: 56 + } + ]; + const handleModelSelect = (model) => { setSelectedModel(model); }; @@ -60,28 +273,7 @@ const ThreeDModelsDashboard = () => { if (selectedModel?.id === modelId) { setSelectedModel(null); } - setMessage({ type: 'success', text: 'Model has been deleted' }); - }; - - const handleFormSubmit = (formData) => { - if (formMode === 'create') { - const newModel = { - id: models.length + 1, - ...formData, - lastModified: new Date().toISOString().split('T')[0], - status: 'Active' - }; - setModels([...models, newModel]); - setMessage({ type: 'success', text: 'Model has been created' }); - } else { - setModels(models.map(model => - model.id === selectedModel.id - ? { ...model, ...formData, lastModified: new Date().toISOString().split('T')[0] } - : model - )); - setMessage({ type: 'success', text: 'Model has been updated' }); - } - setIsFormVisible(false); + setMessage({ type: 'success', text: '3D Model has been deleted' }); }; const handleFormCancel = () => { @@ -104,49 +296,44 @@ const ThreeDModelsDashboard = () => { ]; }; - const formFields = [ - { - name: 'name', - label: 'Model Name', - type: 'text', - required: true, - value: selectedModel?.name || '' - }, - { - name: 'type', - label: 'Model Type', - type: 'select', - required: true, - options: [ - { value: '3D', label: '3D' }, - { value: '2D', label: '2D' } - ], - value: selectedModel?.type || '3D' - }, - { - name: 'status', - label: 'Status', - type: 'select', - required: true, - options: [ - { value: 'Active', label: 'Active' }, - { value: 'Inactive', label: 'Inactive' } - ], - value: selectedModel?.status || 'Active' - } - ]; + const filteredModels = useMemo(() => { + return models.filter(model => + model.name.toLowerCase().includes(searchQuery.toLowerCase()) || + model.type.toLowerCase().includes(searchQuery.toLowerCase()) || + model.status.toLowerCase().includes(searchQuery.toLowerCase()) + ); + }, [models, searchQuery]); return (

3D Models

- +
+
+ setSearchQuery(e.target.value)} + className="search-input" + /> + {searchQuery && ( + + )} +
+ +
{message.text && ( @@ -158,18 +345,13 @@ const ThreeDModelsDashboard = () => { {isFormVisible && (
- +
)} {
+
Version: {model.version}
Last Modified: {model.lastModified}
-
Size: {model.size}
)} renderDetails={(model) => (
-

Model Details

+

3D Model Details

ID: {model.id}
- Nazwa: + Name: {model.name}
- Typ: + Type: {model.type}
+
+ Version: + {model.version} +
Status: {model.status} @@ -212,10 +398,6 @@ const ThreeDModelsDashboard = () => { Last Modified: {model.lastModified}
-
- Size: - {model.size} -
)} /> diff --git a/src/pages/dashboards/ai.models.js b/src/pages/dashboards/ai.models.js index d16caa6..b164294 100644 --- a/src/pages/dashboards/ai.models.js +++ b/src/pages/dashboards/ai.models.js @@ -1,9 +1,11 @@ -import React, { useState } from 'react'; +import React, { useState, useRef, useMemo } from 'react'; import { ListGenerator } from '../../components/forms/listGenerator'; import FormGenerator from '../../components/forms/formGenerator'; +import AIModelUploadForm from '../../components/forms/ai_model_crud/AIModelUpload'; const AIModelsDashboard = () => { const [selectedModel, setSelectedModel] = useState(null); + const [searchQuery, setSearchQuery] = useState(''); const [models, setModels] = useState([ { id: 1, @@ -20,12 +22,178 @@ const AIModelsDashboard = () => { status: 'Inactive', lastModified: '2024-03-19', version: '1.0' + }, + { + id: 3, + name: 'yolo-v8', + type: 'object-detection', + status: 'Active', + lastModified: '2024-03-20', + version: '1.2' + }, + { + id: 4, + name: 'wav2vec', + type: 'speech-recognition', + status: 'Active', + lastModified: '2024-03-18', + version: '3.0' + }, + { + id: 5, + name: 'bert-base', + type: 'text-classification', + status: 'Inactive', + lastModified: '2024-03-17', + version: '2.0' + }, + { + id: 6, + name: 'resnet-50', + type: 'image-classification', + status: 'Active', + lastModified: '2024-03-20', + version: '1.8' + }, + { + id: 7, + name: 'detr', + type: 'object-detection', + status: 'Active', + lastModified: '2024-03-19', + version: '2.4' + }, + { + id: 8, + name: 'whisper', + type: 'speech-recognition', + status: 'Inactive', + lastModified: '2024-03-16', + version: '1.2' + }, + { + id: 9, + name: 'dalle-3', + type: 'text-to-image', + status: 'Active', + lastModified: '2024-03-20', + version: '2.7' + }, + { + id: 10, + name: 'llama-2', + type: 'text-generation', + status: 'Active', + lastModified: '2024-03-18', + version: '1.9' + }, + { + id: 11, + name: 'mask-rcnn', + type: 'image-segmentation', + status: 'Inactive', + lastModified: '2024-03-15', + version: '1.3' + }, + { + id: 12, + name: 'roberta', + type: 'text-classification', + status: 'Active', + lastModified: '2024-03-20', + version: '2.2' + }, + { + id: 13, + name: 'dino', + type: 'image-classification', + status: 'Active', + lastModified: '2024-03-19', + version: '1.6' + }, + { + id: 14, + name: 'sam', + type: 'image-segmentation', + status: 'Active', + lastModified: '2024-03-20', + version: '1.4' + }, + { + id: 15, + name: 'clip', + type: 'image-text', + status: 'Inactive', + lastModified: '2024-03-17', + version: '2.3' } ]); const [isFormVisible, setIsFormVisible] = useState(false); const [formMode, setFormMode] = useState('create'); const [message, setMessage] = useState({ type: '', text: '' }); + const nameInput = React.createRef(); + const typeInput = React.createRef(); + const versionInput = React.createRef(); + const statusInput = React.createRef(); + + const formRefs = [ + nameInput, + typeInput, + versionInput, + statusInput + ]; + + const inputList = [ + { + type: 'info', + action: formMode === 'create' ? 'Create' : 'Update', + endpoint: 'ai/models', + button_value: formMode === 'create' ? '+ AI MODEL' : 'UPDATE', + allowButtonAction: false + }, + { + type: 'text', + name: 'NAME', + ref: nameInput, + value: selectedModel?.name || '', + onChange: null, + validationInfo: null + }, + { + type: 'select', + name: 'TYPE', + ref: typeInput, + options: [ + { value: 'text-to-image', label: 'Text to Image' }, + { value: 'image-to-text', label: 'Image to Text' } + ], + value: selectedModel?.type || 'text-to-image', + onChange: null, + validationInfo: null + }, + { + type: 'text', + name: 'VERSION', + ref: versionInput, + value: selectedModel?.version || '1.0', + onChange: null, + validationInfo: null + }, + { + type: 'select', + name: 'STATUS', + ref: statusInput, + options: [ + { value: 'Active', label: 'Active' }, + { value: 'Inactive', label: 'Inactive' } + ], + value: selectedModel?.status || 'Active', + onChange: null, + validationInfo: null + } + ]; + const handleModelSelect = (model) => { setSelectedModel(model); }; @@ -104,56 +272,44 @@ const AIModelsDashboard = () => { ]; }; - const formFields = [ - { - name: 'name', - label: 'Model Name', - type: 'text', - required: true, - value: selectedModel?.name || '' - }, - { - name: 'type', - label: 'Model Type', - type: 'select', - required: true, - options: [ - { value: 'text-to-image', label: 'Text to Image' }, - { value: 'image-to-text', label: 'Image to Text' } - ], - value: selectedModel?.type || 'text-to-image' - }, - { - name: 'version', - label: 'Version', - type: 'text', - required: true, - value: selectedModel?.version || '1.0' - }, - { - name: 'status', - label: 'Status', - type: 'select', - required: true, - options: [ - { value: 'Active', label: 'Active' }, - { value: 'Inactive', label: 'Inactive' } - ], - value: selectedModel?.status || 'Active' - } - ]; + const filteredModels = useMemo(() => { + return models.filter(model => + model.name.toLowerCase().includes(searchQuery.toLowerCase()) || + model.type.toLowerCase().includes(searchQuery.toLowerCase()) || + model.status.toLowerCase().includes(searchQuery.toLowerCase()) + ); + }, [models, searchQuery]); return (

AI Models

- +
+
+ setSearchQuery(e.target.value)} + className="search-input" + /> + {searchQuery && ( + + )} +
+ +
{message.text && ( @@ -165,18 +321,13 @@ const AIModelsDashboard = () => { {isFormVisible && (
- +
)} { {model.id}
- Nazwa: + Name: {model.name}
- Typ: + Type: {model.type}
- Wersja: + Version: {model.version}
@@ -220,7 +371,7 @@ const AIModelsDashboard = () => { {model.status}
- Ostatnia modyfikacja: + Last Modified: {model.lastModified}
diff --git a/src/pages/dashboards/ai.tasks.js b/src/pages/dashboards/ai.tasks.js index 3f0fb88..611f8fa 100644 --- a/src/pages/dashboards/ai.tasks.js +++ b/src/pages/dashboards/ai.tasks.js @@ -1,31 +1,232 @@ -import React, { useState } from 'react'; +import React, { useState, useRef, useMemo } from 'react'; import { ListGenerator } from '../../components/forms/listGenerator'; -import { FormGenerator } from '../../components/forms/formGenerator'; +import FormGenerator from '../../components/forms/formGenerator'; const AITasksDashboard = () => { const [selectedTask, setSelectedTask] = useState(null); + const [searchQuery, setSearchQuery] = useState(''); const [tasks, setTasks] = useState([ { id: 1, - name: 'Task A', - type: 'Training', - status: 'In Progress', - lastModified: '2024-03-20', - progress: 45 + name: "Model Training - CNN", + type: "Training", + status: "Completed", + progress: 100, + model: "Object Detection v2", + startTime: "2024-03-20 09:00:00", + endTime: "2024-03-20 14:30:00" }, { id: 2, - name: 'Task B', - type: 'Inference', - status: 'Completed', - lastModified: '2024-03-19', - progress: 100 + name: "BERT Fine-tuning", + type: "Fine-tuning", + status: "In Progress", + progress: 75, + model: "Text Generator", + startTime: "2024-03-20 10:15:00", + endTime: null + }, + { + id: 3, + name: "Model Evaluation", + type: "Evaluation", + status: "Queued", + progress: 0, + model: "Style Transfer v1", + startTime: null, + endTime: null + }, + { + id: 4, + name: "Performance Testing", + type: "Testing", + status: "Completed", + progress: 100, + model: "Face Recognition", + startTime: "2024-03-19 15:00:00", + endTime: "2024-03-19 17:30:00" + }, + { + id: 5, + name: "Model Training - RNN", + type: "Training", + status: "In Progress", + progress: 60, + model: "Language Translator", + startTime: "2024-03-20 08:45:00", + endTime: null + }, + { + id: 6, + name: "GAN Training", + type: "Training", + status: "Completed", + progress: 100, + model: "Image Generation", + startTime: "2024-03-19 11:00:00", + endTime: "2024-03-19 18:30:00" + }, + { + id: 7, + name: "Model Optimization", + type: "Fine-tuning", + status: "In Progress", + progress: 82, + model: "Voice Synthesis", + startTime: "2024-03-20 09:30:00", + endTime: null + }, + { + id: 8, + name: "Accuracy Testing", + type: "Testing", + status: "Queued", + progress: 0, + model: "Pose Estimation", + startTime: null, + endTime: null + }, + { + id: 9, + name: "Transfer Learning", + type: "Training", + status: "Completed", + progress: 100, + model: "Scene Understanding", + startTime: "2024-03-19 13:15:00", + endTime: "2024-03-19 16:45:00" + }, + { + id: 10, + name: "Model Validation", + type: "Evaluation", + status: "In Progress", + progress: 45, + model: "Text Summarizer", + startTime: "2024-03-20 11:00:00", + endTime: null + }, + { + id: 11, + name: "Hyperparameter Tuning", + type: "Fine-tuning", + status: "Queued", + progress: 0, + model: "Speech Recognition", + startTime: null, + endTime: null + }, + { + id: 12, + name: "Model Training - YOLO", + type: "Training", + status: "Completed", + progress: 100, + model: "Object Tracking", + startTime: "2024-03-19 09:00:00", + endTime: "2024-03-19 15:30:00" + }, + { + id: 13, + name: "Performance Optimization", + type: "Fine-tuning", + status: "In Progress", + progress: 68, + model: "Image Segmentation", + startTime: "2024-03-20 10:00:00", + endTime: null + }, + { + id: 14, + name: "Model Deployment Test", + type: "Testing", + status: "Completed", + progress: 100, + model: "Sentiment Analysis", + startTime: "2024-03-19 14:00:00", + endTime: "2024-03-19 16:00:00" + }, + { + id: 15, + name: "Model Training - GPT", + type: "Training", + status: "In Progress", + progress: 92, + model: "Chatbot Model", + startTime: "2024-03-20 07:30:00", + endTime: null } ]); const [isFormVisible, setIsFormVisible] = useState(false); const [formMode, setFormMode] = useState('create'); const [message, setMessage] = useState({ type: '', text: '' }); + const nameInput = React.createRef(); + const typeInput = React.createRef(); + const statusInput = React.createRef(); + const progressInput = React.createRef(); + + const formRefs = [ + nameInput, + typeInput, + statusInput, + progressInput + ]; + + const inputList = [ + { + type: 'info', + action: formMode === 'create' ? 'Create' : 'Update', + endpoint: 'ai/tasks', + button_value: formMode === 'create' ? '+ AI TASK' : 'UPDATE', + allowButtonAction: false + }, + { + type: 'text', + name: 'NAME', + ref: nameInput, + value: selectedTask?.name || '', + onChange: null, + validationInfo: null + }, + { + type: 'select', + name: 'TYPE', + ref: typeInput, + options: [ + { value: 'text-to-image', label: 'Text to Image' }, + { value: 'image-to-text', label: 'Image to Text' } + ], + value: selectedTask?.type || 'text-to-image', + onChange: null, + validationInfo: null + }, + { + type: 'select', + name: 'STATUS', + ref: statusInput, + options: [ + { value: 'In Progress', label: 'In Progress' }, + { value: 'Completed', label: 'Completed' }, + { value: 'Failed', label: 'Failed' }, + { value: 'Cancelled', label: 'Cancelled' } + ], + value: selectedTask?.status || 'In Progress', + onChange: null, + validationInfo: null + }, + { + type: 'number', + name: 'PROGRESS', + ref: progressInput, + value: selectedTask?.progress || 0, + min: 0, + max: 100, + onChange: null, + validationInfo: null + } + ]; + const handleTaskSelect = (task) => { setSelectedTask(task); }; @@ -114,6 +315,14 @@ const AITasksDashboard = () => { setSelectedTask(null); }; + const handleFormAction = (refs) => { + const formData = {}; + refs.forEach((ref, index) => { + formData[inputList[index].name] = ref.current.value; + }); + handleFormSubmit(formData); + }; + const getTaskActions = (task) => { const actions = []; @@ -147,52 +356,45 @@ const AITasksDashboard = () => { return actions; }; - const formFields = [ - { - name: 'name', - label: 'Task Name', - type: 'text', - required: true, - value: selectedTask?.name || '' - }, - { - name: 'type', - label: 'Task Type', - type: 'select', - required: true, - options: [ - { value: 'Training', label: 'Training' }, - { value: 'Inference', label: 'Inference' }, - { value: 'Evaluation', label: 'Evaluation' } - ], - value: selectedTask?.type || 'Training' - }, - { - name: 'status', - label: 'Status', - type: 'select', - required: true, - options: [ - { value: 'In Progress', label: 'In Progress' }, - { value: 'Completed', label: 'Completed' }, - { value: 'Failed', label: 'Failed' }, - { value: 'Cancelled', label: 'Cancelled' } - ], - value: selectedTask?.status || 'In Progress' - } - ]; + const filteredTasks = useMemo(() => { + return tasks.filter(task => + task.name.toLowerCase().includes(searchQuery.toLowerCase()) || + task.type.toLowerCase().includes(searchQuery.toLowerCase()) || + task.status.toLowerCase().includes(searchQuery.toLowerCase()) || + task.model.toLowerCase().includes(searchQuery.toLowerCase()) + ); + }, [tasks, searchQuery]); return (
-

AI Tasks

- +

AI Training

+
+
+ setSearchQuery(e.target.value)} + className="search-input" + /> + {searchQuery && ( + + )} +
+ +
{message.text && ( @@ -205,17 +407,17 @@ const AITasksDashboard = () => {
)} { const [renders, setRenders] = useState([ { id: 1, - name: 'Render A', - type: '3D', - status: 'Completed', - lastModified: '2024-03-20', - resolution: '1920x1080', - progress: 100 + name: "Character Animation", + type: "Animation", + status: "Completed", + progress: 100, + model: "Hero Character", + startTime: "2024-03-20 09:00:00", + endTime: "2024-03-20 11:30:00" }, { id: 2, - name: 'Render B', - type: '2D', - status: 'In Progress', - lastModified: '2024-03-19', - resolution: '3840x2160', - progress: 45 + name: "Environment Lighting", + type: "Still", + status: "In Progress", + progress: 75, + model: "Forest Scene", + startTime: "2024-03-20 10:15:00", + endTime: null + }, + { + id: 3, + name: "Product Showcase", + type: "360 View", + status: "Queued", + progress: 0, + model: "Sports Car", + startTime: null, + endTime: null + }, + { + id: 4, + name: "Battle Scene", + type: "Animation", + status: "Completed", + progress: 100, + model: "Warriors", + startTime: "2024-03-19 15:00:00", + endTime: "2024-03-19 18:30:00" + }, + { + id: 5, + name: "Architectural Visualization", + type: "Still", + status: "In Progress", + progress: 60, + model: "Modern House", + startTime: "2024-03-20 08:45:00", + endTime: null + }, + { + id: 6, + name: "Character Portrait", + type: "Still", + status: "Completed", + progress: 100, + model: "Fantasy Character", + startTime: "2024-03-19 11:00:00", + endTime: "2024-03-19 12:30:00" + }, + { + id: 7, + name: "Vehicle Animation", + type: "Animation", + status: "In Progress", + progress: 82, + model: "Racing Car", + startTime: "2024-03-20 09:30:00", + endTime: null + }, + { + id: 8, + name: "Product Display", + type: "360 View", + status: "Queued", + progress: 0, + model: "Smartphone", + startTime: null, + endTime: null + }, + { + id: 9, + name: "Nature Scene", + type: "Still", + status: "Completed", + progress: 100, + model: "Mountain Landscape", + startTime: "2024-03-19 13:15:00", + endTime: "2024-03-19 15:45:00" + }, + { + id: 10, + name: "Character Walk Cycle", + type: "Animation", + status: "In Progress", + progress: 45, + model: "Robot Character", + startTime: "2024-03-20 11:00:00", + endTime: null + }, + { + id: 11, + name: "Jewelry Showcase", + type: "360 View", + status: "Queued", + progress: 0, + model: "Diamond Ring", + startTime: null, + endTime: null + }, + { + id: 12, + name: "City Flythrough", + type: "Animation", + status: "Completed", + progress: 100, + model: "Future City", + startTime: "2024-03-19 09:00:00", + endTime: "2024-03-19 14:30:00" + }, + { + id: 13, + name: "Interior Design", + type: "Still", + status: "In Progress", + progress: 68, + model: "Living Room", + startTime: "2024-03-20 10:00:00", + endTime: null + }, + { + id: 14, + name: "Product Animation", + type: "Animation", + status: "Completed", + progress: 100, + model: "Gaming Console", + startTime: "2024-03-19 14:00:00", + endTime: "2024-03-19 16:00:00" + }, + { + id: 15, + name: "Character Showcase", + type: "360 View", + status: "In Progress", + progress: 92, + model: "Superhero", + startTime: "2024-03-20 07:30:00", + endTime: null } ]); const [isFormVisible, setIsFormVisible] = useState(false); const [formMode, setFormMode] = useState('create'); const [message, setMessage] = useState({ type: '', text: '' }); + const [searchQuery, setSearchQuery] = useState(''); + + const nameInput = React.createRef(); + const typeInput = React.createRef(); + const resolutionInput = React.createRef(); + const threeDModelInput = React.createRef(); + + const formRefs = [ + nameInput, + typeInput, + resolutionInput, + threeDModelInput + ]; + + const inputList = [ + { + type: 'info', + action: formMode === 'create' ? 'Create' : 'Update', + endpoint: 'renders', + button_value: formMode === 'create' ? '+ RENDER' : 'UPDATE', + allowButtonAction: false + }, + { + type: 'text', + name: 'Name', + ref: nameInput, + value: selectedRender?.name || '', + onChange: null, + validationInfo: null + }, + { + type: 'text', + name: 'Resolution', + ref: resolutionInput, + value: selectedRender?.resolution || '', + onChange: null, + validationInfo: null + }, + { + type: 'choice-listing', + name: '3D Model', + ref: threeDModelInput, + values: selectedRender?.threeDModel|| '', + onChange: null, + validationInfo: null + }, + ]; const handleRenderSelect = (render) => { setSelectedRender(render); @@ -107,71 +286,198 @@ const RendersDashboard = () => { ]; }; - const formFields = [ + const mockRenders = [ { - name: 'name', - label: 'Nazwa renderu', - type: 'text', - required: true, - value: selectedRender?.name || '' + id: 1, + name: "Dragon Scene", + type: "Animation", + status: "Completed", + progress: 100, + model: "Dragon Model", + startTime: "2024-03-01 10:00:00", + endTime: "2024-03-01 12:30:00" }, { - name: 'type', - label: 'Typ renderu', - type: 'select', - required: true, - options: [ - { value: '3D', label: '3D' }, - { value: '2D', label: '2D' } - ], - value: selectedRender?.type || '3D' + id: 2, + name: "Castle Exterior", + type: "Still", + status: "In Progress", + progress: 65, + model: "Medieval Castle", + startTime: "2024-03-02 09:00:00", + endTime: null }, { - name: 'resolution', - label: 'Rozdzielczość', - type: 'select', - required: true, - options: [ - { value: '1920x1080', label: 'Full HD (1920x1080)' }, - { value: '3840x2160', label: '4K (3840x2160)' }, - { value: '7680x4320', label: '8K (7680x4320)' } - ], - value: selectedRender?.resolution || '1920x1080' + id: 3, + name: "Weapon Showcase", + type: "360 View", + status: "Queued", + progress: 0, + model: "Sci-fi Weapon", + startTime: null, + endTime: null }, { - name: 'status', - label: 'Status', - type: 'select', - required: true, - options: [ - { value: 'In Progress', label: 'W trakcie' }, - { value: 'Completed', label: 'Zakończony' }, - { value: 'Failed', label: 'Nieudany' } - ], - value: selectedRender?.status || 'In Progress' + id: 4, + name: "Forest Flythrough", + type: "Animation", + status: "Completed", + progress: 100, + model: "Forest Scene", + startTime: "2024-03-01 14:00:00", + endTime: "2024-03-01 16:00:00" }, { - name: 'progress', - label: 'Postęp', - type: 'number', - required: true, - min: 0, - max: 100, - value: selectedRender?.progress || 0 + id: 5, + name: "Robot Animation", + type: "Animation", + status: "In Progress", + progress: 45, + model: "Robot Character", + startTime: "2024-03-02 11:00:00", + endTime: null + }, + { + id: 6, + name: "Spaceship Launch", + type: "Animation", + status: "Completed", + progress: 100, + model: "Space Ship", + startTime: "2024-03-01 13:00:00", + endTime: "2024-03-01 15:30:00" + }, + { + id: 7, + name: "Temple Interior", + type: "Still", + status: "In Progress", + progress: 78, + model: "Ancient Temple", + startTime: "2024-03-02 10:00:00", + endTime: null + }, + { + id: 8, + name: "Sword Display", + type: "360 View", + status: "Queued", + progress: 0, + model: "Fantasy Sword", + startTime: null, + endTime: null + }, + { + id: 9, + name: "City Timelapse", + type: "Animation", + status: "Completed", + progress: 100, + model: "City Block", + startTime: "2024-03-01 09:00:00", + endTime: "2024-03-01 11:30:00" + }, + { + id: 10, + name: "Warrior Battle", + type: "Animation", + status: "In Progress", + progress: 89, + model: "Warrior Character", + startTime: "2024-03-02 13:00:00", + endTime: null + }, + { + id: 11, + name: "Car Showcase", + type: "360 View", + status: "Queued", + progress: 0, + model: "Futuristic Car", + startTime: null, + endTime: null + }, + { + id: 12, + name: "Mountain Vista", + type: "Still", + status: "Completed", + progress: 100, + model: "Mountain Range", + startTime: "2024-03-01 15:00:00", + endTime: "2024-03-01 16:30:00" + }, + { + id: 13, + name: "Alien Movement", + type: "Animation", + status: "In Progress", + progress: 34, + model: "Alien Creature", + startTime: "2024-03-02 14:00:00", + endTime: null + }, + { + id: 14, + name: "Staff Effects", + type: "Animation", + status: "Completed", + progress: 100, + model: "Magic Staff", + startTime: "2024-03-01 16:00:00", + endTime: "2024-03-01 18:30:00" + }, + { + id: 15, + name: "Cave Exploration", + type: "Animation", + status: "In Progress", + progress: 56, + model: "Underground Cave", + startTime: "2024-03-02 12:00:00", + endTime: null } ]; + const filteredRenders = useMemo(() => { + return renders.filter(render => + render.name.toLowerCase().includes(searchQuery.toLowerCase()) || + render.type.toLowerCase().includes(searchQuery.toLowerCase()) || + render.status.toLowerCase().includes(searchQuery.toLowerCase()) || + render.model.toLowerCase().includes(searchQuery.toLowerCase()) + ); + }, [renders, searchQuery]); + return (
-

Rendered Materials

- +

3D Rendering

+
+
+ setSearchQuery(e.target.value)} + className="search-input" + /> + {searchQuery && ( + + )} +
+ +
{message.text && ( @@ -184,17 +490,16 @@ const RendersDashboard = () => {
)} { const [selectedServer, setSelectedServer] = useState(null); + const [searchQuery, setSearchQuery] = useState(''); const [servers, setServers] = useState([ { id: 1, - name: 'Server A', - type: 'Render', - status: 'Online', - lastModified: '2024-03-20', - ip: '192.168.1.100' + name: "Render Node 1", + type: "Render Node", + status: "Active", + progress: 100, + ip: "192.168.1.101", + lastActive: "2024-03-20 11:30:00" }, { id: 2, - name: 'Server B', - type: 'AI', - status: 'Offline', - lastModified: '2024-03-19', - ip: '192.168.1.101' + name: "AI Training Node 1", + type: "AI Training Node", + status: "In Progress", + progress: 75, + ip: "192.168.1.102", + lastActive: "2024-03-20 11:29:00" + }, + { + id: 3, + name: "Storage Server 1", + type: "Storage Server", + status: "Active", + progress: 100, + ip: "192.168.1.103", + lastActive: "2024-03-20 11:30:00" + }, + { + id: 4, + name: "Render Node 2", + type: "Render Node", + status: "Inactive", + progress: 0, + ip: "192.168.1.104", + lastActive: "2024-03-20 10:15:00" + }, + { + id: 5, + name: "AI Training Node 2", + type: "AI Training Node", + status: "Active", + progress: 100, + ip: "192.168.1.105", + lastActive: "2024-03-20 11:30:00" + }, + { + id: 6, + name: "Storage Server 2", + type: "Storage Server", + status: "Active", + progress: 100, + ip: "192.168.1.106", + lastActive: "2024-03-20 11:30:00" + }, + { + id: 7, + name: "Render Node 3", + type: "Render Node", + status: "In Progress", + progress: 82, + ip: "192.168.1.107", + lastActive: "2024-03-20 11:29:00" + }, + { + id: 8, + name: "AI Training Node 3", + type: "AI Training Node", + status: "Queued", + progress: 0, + ip: "192.168.1.108", + lastActive: "2024-03-20 11:00:00" + }, + { + id: 9, + name: "Storage Server 3", + type: "Storage Server", + status: "Active", + progress: 100, + ip: "192.168.1.109", + lastActive: "2024-03-20 11:30:00" + }, + { + id: 10, + name: "Render Node 4", + type: "Render Node", + status: "In Progress", + progress: 45, + ip: "192.168.1.110", + lastActive: "2024-03-20 11:29:00" + }, + { + id: 11, + name: "AI Training Node 4", + type: "AI Training Node", + status: "Active", + progress: 100, + ip: "192.168.1.111", + lastActive: "2024-03-20 11:30:00" + }, + { + id: 12, + name: "Storage Server 4", + type: "Storage Server", + status: "Inactive", + progress: 0, + ip: "192.168.1.112", + lastActive: "2024-03-20 10:45:00" + }, + { + id: 13, + name: "Render Node 5", + type: "Render Node", + status: "Active", + progress: 100, + ip: "192.168.1.113", + lastActive: "2024-03-20 11:30:00" + }, + { + id: 14, + name: "AI Training Node 5", + type: "AI Training Node", + status: "In Progress", + progress: 68, + ip: "192.168.1.114", + lastActive: "2024-03-20 11:29:00" + }, + { + id: 15, + name: "Storage Server 5", + type: "Storage Server", + status: "Active", + progress: 100, + ip: "192.168.1.115", + lastActive: "2024-03-20 11:30:00" } ]); const [isFormVisible, setIsFormVisible] = useState(false); const [formMode, setFormMode] = useState('create'); const [message, setMessage] = useState({ type: '', text: '' }); + const nameInput = React.createRef(); + const typeInput = React.createRef(); + const ipInput = React.createRef(); + const statusInput = React.createRef(); + + const formRefs = [ + nameInput, + typeInput, + ipInput, + statusInput + ]; + + const inputList = [ + { + type: 'info', + action: formMode === 'create' ? 'Create' : 'Update', + endpoint: 'servers', + button_value: formMode === 'create' ? '+ SERVER' : 'UPDATE' + }, + { + type: 'text', + name: 'NAME', + ref: nameInput, + value: selectedServer?.name || '', + onChange: null, + validationInfo: null + }, + { + type: 'select', + name: 'TYPE', + ref: typeInput, + options: [ + { value: 'Render', label: 'Render' }, + { value: 'AI', label: 'AI' }, + { value: 'Storage', label: 'Storage' } + ], + value: selectedServer?.type || 'Render', + onChange: null, + validationInfo: null + }, + { + type: 'text', + name: 'IP', + ref: ipInput, + value: selectedServer?.ip || '', + onChange: null, + validationInfo: null + }, + { + type: 'select', + name: 'STATUS', + ref: statusInput, + options: [ + { value: 'Online', label: 'Online' }, + { value: 'Offline', label: 'Offline' }, + { value: 'Maintenance', label: 'Maintenance' } + ], + value: selectedServer?.status || 'Offline', + onChange: null, + validationInfo: null + } + ]; + const handleServerSelect = (server) => { setSelectedServer(server); }; @@ -104,58 +287,53 @@ const ServersDashboard = () => { ]; }; - const formFields = [ - { - name: 'name', - label: 'Nazwa serwera', - type: 'text', - required: true, - value: selectedServer?.name || '' - }, - { - name: 'type', - label: 'Typ serwera', - type: 'select', - required: true, - options: [ - { value: 'Render', label: 'Render' }, - { value: 'AI', label: 'AI' }, - { value: 'Storage', label: 'Storage' } - ], - value: selectedServer?.type || 'Render' - }, - { - name: 'ip', - label: 'Adres IP', - type: 'text', - required: true, - value: selectedServer?.ip || '' - }, - { - name: 'status', - label: 'Status', - type: 'select', - required: true, - options: [ - { value: 'Online', label: 'Online' }, - { value: 'Offline', label: 'Offline' }, - { value: 'Maintenance', label: 'Maintenance' } - ], - value: selectedServer?.status || 'Offline' - } - ]; + const handleFormAction = (refs) => { + const formData = {}; + refs.forEach((ref, index) => { + formData[inputList[index].name] = ref.current.value; + }); + handleFormSubmit(formData); + }; + + const filteredServers = useMemo(() => { + return servers.filter(server => + server.name.toLowerCase().includes(searchQuery.toLowerCase()) || + server.type.toLowerCase().includes(searchQuery.toLowerCase()) || + server.status.toLowerCase().includes(searchQuery.toLowerCase()) || + server.ip.toLowerCase().includes(searchQuery.toLowerCase()) + ); + }, [servers, searchQuery]); return (
-

Servers

- +

GPU Instances

+
+
+ setSearchQuery(e.target.value)} + className="search-input" + /> + {searchQuery && ( + + )} +
+ +
{message.text && ( @@ -168,17 +346,16 @@ const ServersDashboard = () => {
)} { return baseInputs; }; + const formRefs = [usernameInput, emailInput, currentPasswordInput, newPasswordInput, confirmPasswordInput]; + + const handleFormAction = (refs) => { + const formData = {}; + formRefs.forEach((ref, index) => { + formData[getInputList()[index].name] = ref.current.value; + }); + handleSubmit(formRefs); + }; + return (
@@ -157,17 +167,24 @@ const UserSettings = () => {
)} - + {isEditing && ( +
+
+ ({ + ...field, + ref: formRefs[index], + type: field.type, + name: field.name, + onChange: field.onChange, + validationInfo: field.validationInfo + }))} + refList={formRefs} + action={handleFormAction} + /> +
+
+ )}
); diff --git a/src/pages/index.js b/src/pages/index.js new file mode 100644 index 0000000..653173a --- /dev/null +++ b/src/pages/index.js @@ -0,0 +1,11 @@ +import React from 'react'; + +const IndexPage = () => { + return ( +
+ {/* Reszta komponentów aplikacji */} +
+ ); +}; + +export default IndexPage; \ No newline at end of file diff --git a/src/redux/asyncThunks/aiModelCrudAsyncThunk.js b/src/redux/asyncThunks/aiModelCrudAsyncThunk.js new file mode 100644 index 0000000..73c061d --- /dev/null +++ b/src/redux/asyncThunks/aiModelCrudAsyncThunk.js @@ -0,0 +1,100 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; +import axios from 'axios'; + +const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000'; + +// Helper function to get auth header +const getAuthHeader = (token) => ({ + headers: { Authorization: `Bearer ${token}` } +}); + +export const fetchAiModels = createAsyncThunk( + 'aiModelCrud/fetchModels', + async (_, { getState }) => { + const { token } = getState().userAuth; + const response = await axios.get(`${API_URL}/ai/models`, getAuthHeader(token)); + return response.data; + } +); + +export const fetchAiModel = createAsyncThunk( + 'aiModelCrud/fetchModel', + async (modelId, { getState }) => { + const { token } = getState().userAuth; + const response = await axios.get(`${API_URL}/ai/models/${modelId}`, getAuthHeader(token)); + return response.data; + } +); + +export const createAiModel = createAsyncThunk( + 'aiModelCrud/createModel', + async (modelData, { getState }) => { + const { token } = getState().userAuth; + const response = await axios.post(`${API_URL}/ai/models`, modelData, getAuthHeader(token)); + return response.data; + } +); + +export const updateAiModel = createAsyncThunk( + 'aiModelCrud/updateModel', + async ({ modelId, updates }, { getState }) => { + const { token } = getState().userAuth; + const response = await axios.put(`${API_URL}/ai/models/${modelId}`, updates, getAuthHeader(token)); + return response.data; + } +); + +export const deleteAiModel = createAsyncThunk( + 'aiModelCrud/deleteModel', + async (modelId, { getState }) => { + const { token } = getState().userAuth; + await axios.delete(`${API_URL}/ai/models/${modelId}`, getAuthHeader(token)); + return modelId; + } +); + +export const fetchAiTasks = createAsyncThunk( + 'aiModelCrud/fetchTasks', + async ({ status, page = 1, limit = 10 }, { getState }) => { + const { token } = getState().userAuth; + const params = { page, limit }; + if (status) params.status = status; + + const response = await axios.get(`${API_URL}/ai/tasks`, { + ...getAuthHeader(token), + params + }); + return response.data; + } +); + +export const createAiTask = createAsyncThunk( + 'aiModelCrud/createTask', + async (taskData, { getState }) => { + const { token } = getState().userAuth; + const response = await axios.post(`${API_URL}/ai/tasks`, taskData, getAuthHeader(token)); + return response.data; + } +); + +export const updateAiTask = createAsyncThunk( + 'aiModelCrud/updateTask', + async ({ taskId, action }, { getState }) => { + const { token } = getState().userAuth; + const response = await axios.put( + `${API_URL}/ai/tasks/${taskId}`, + { action }, + getAuthHeader(token) + ); + return response.data; + } +); + +export const deleteAiTask = createAsyncThunk( + 'aiModelCrud/deleteTask', + async (taskId, { getState }) => { + const { token } = getState().userAuth; + await axios.delete(`${API_URL}/ai/tasks/${taskId}`, getAuthHeader(token)); + return taskId; + } +); \ No newline at end of file diff --git a/src/redux/asyncThunks/renderCrudAsyncThunk.js b/src/redux/asyncThunks/renderCrudAsyncThunk.js new file mode 100644 index 0000000..c1eb835 --- /dev/null +++ b/src/redux/asyncThunks/renderCrudAsyncThunk.js @@ -0,0 +1,94 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; +import axios from 'axios'; + +const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000'; + +// Helper function to get auth header +const getAuthHeader = (token) => ({ + headers: { Authorization: `Bearer ${token}` } +}); + +export const fetchRenders = createAsyncThunk( + 'renderCrud/fetchRenders', + async ({ page = 1, limit = 10 }, { getState }) => { + const { token } = getState().userAuth; + const response = await axios.get(`${API_URL}/renders`, { + ...getAuthHeader(token), + params: { page, limit } + }); + return response.data; + } +); + +export const fetchRender = createAsyncThunk( + 'renderCrud/fetchRender', + async (renderId, { getState }) => { + const { token } = getState().userAuth; + const response = await axios.get(`${API_URL}/renders/${renderId}`, getAuthHeader(token)); + return response.data; + } +); + +export const updateRender = createAsyncThunk( + 'renderCrud/updateRender', + async ({ renderId, updates }, { getState }) => { + const { token } = getState().userAuth; + const response = await axios.put(`${API_URL}/renders/${renderId}`, updates, getAuthHeader(token)); + return response.data; + } +); + +export const deleteRender = createAsyncThunk( + 'renderCrud/deleteRender', + async (renderId, { getState }) => { + const { token } = getState().userAuth; + await axios.delete(`${API_URL}/renders/${renderId}`, getAuthHeader(token)); + return renderId; + } +); + +export const fetchRenderTasks = createAsyncThunk( + 'renderCrud/fetchTasks', + async ({ status, page = 1, limit = 10 }, { getState }) => { + const { token } = getState().userAuth; + const params = { page, limit }; + if (status) params.status = status; + + const response = await axios.get(`${API_URL}/renders/tasks`, { + ...getAuthHeader(token), + params + }); + return response.data; + } +); + +export const createRenderTask = createAsyncThunk( + 'renderCrud/createTask', + async (taskData, { getState }) => { + const { token } = getState().userAuth; + const response = await axios.post(`${API_URL}/renders/tasks`, taskData, getAuthHeader(token)); + return response.data; + } +); + +export const updateRenderTask = createAsyncThunk( + 'renderCrud/updateTask', + async ({ taskId, action }, { getState }) => { + const { token } = getState().userAuth; + const response = await axios.put( + `${API_URL}/renders/tasks/${taskId}`, + { action }, + getAuthHeader(token) + ); + return response.data; + } +); + +export const deleteRenderTask = createAsyncThunk( + 'renderCrud/deleteTask', + async (taskId, { getState }) => { + const { token } = getState().userAuth; + await axios.delete(`${API_URL}/renders/tasks/${taskId}`, getAuthHeader(token)); + return taskId; + } +); \ No newline at end of file diff --git a/src/redux/asyncThunks/threeDModelCrudAsyncThunk.js b/src/redux/asyncThunks/threeDModelCrudAsyncThunk.js new file mode 100644 index 0000000..ba57981 --- /dev/null +++ b/src/redux/asyncThunks/threeDModelCrudAsyncThunk.js @@ -0,0 +1,72 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; +import axios from 'axios'; + +const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000'; + +// Helper function to get auth header +const getAuthHeader = (token) => ({ + headers: { Authorization: `Bearer ${token}` } +}); + +export const fetchModels = createAsyncThunk( + 'threeDModelCrud/fetchModels', + async (_, { getState }) => { + const { token } = getState().userAuth; + const response = await axios.get(`${API_URL}/models`, getAuthHeader(token)); + return response.data; + } +); + +export const fetchModel = createAsyncThunk( + 'threeDModelCrud/fetchModel', + async (modelId, { getState }) => { + const { token } = getState().userAuth; + const response = await axios.get(`${API_URL}/models/${modelId}`, getAuthHeader(token)); + return response.data; + } +); + +export const createModel = createAsyncThunk( + 'threeDModelCrud/createModel', + async (modelData, { getState }) => { + const { token } = getState().userAuth; + const response = await axios.post(`${API_URL}/models`, modelData, getAuthHeader(token)); + return response.data; + } +); + +export const updateModel = createAsyncThunk( + 'threeDModelCrud/updateModel', + async ({ modelId, updates }, { getState }) => { + const { token } = getState().userAuth; + const response = await axios.put(`${API_URL}/models/${modelId}`, updates, getAuthHeader(token)); + return response.data; + } +); + +export const deleteModel = createAsyncThunk( + 'threeDModelCrud/deleteModel', + async (modelId, { getState }) => { + const { token } = getState().userAuth; + await axios.delete(`${API_URL}/models/${modelId}`, getAuthHeader(token)); + return modelId; + } +); + +export const uploadModel = createAsyncThunk( + 'threeDModelCrud/uploadModel', + async ({ user_id, file, token }) => { + const formData = new FormData(); + formData.append('user_id', user_id); + formData.append('file', file); + formData.append('token', token); + + const response = await axios.post(`${API_URL}/models/upload`, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + Authorization: `Bearer ${token}` + } + }); + return response.data; + } +); \ No newline at end of file diff --git a/src/redux/asyncThunks/userAuthAsyncThunk.js b/src/redux/asyncThunks/userAuthAsyncThunk.js new file mode 100644 index 0000000..55264dd --- /dev/null +++ b/src/redux/asyncThunks/userAuthAsyncThunk.js @@ -0,0 +1,32 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; +import axios from 'axios'; + +const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000'; + +export const loginUser = createAsyncThunk( + 'userAuth/login', + async (credentials) => { + const formData = new FormData(); + formData.append('username', credentials.username); + formData.append('password', credentials.password); + + const response = await axios.post(`${API_URL}/auth`, formData); + return response.data; + } +); + +export const registerUser = createAsyncThunk( + 'userAuth/register', + async (userData) => { + const response = await axios.post(`${API_URL}/register`, userData); + return response.data; + } +); + +export const changePassword = createAsyncThunk( + 'userAuth/changePassword', + async (passwordData) => { + const response = await axios.post(`${API_URL}/change-password`, passwordData); + return response.data; + } +); \ No newline at end of file diff --git a/src/redux/slices/aiModelCrudSlice.js b/src/redux/slices/aiModelCrudSlice.js new file mode 100644 index 0000000..4c5c980 --- /dev/null +++ b/src/redux/slices/aiModelCrudSlice.js @@ -0,0 +1,170 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { + fetchAiModels, + fetchAiModel, + createAiModel, + updateAiModel, + deleteAiModel, + createAiTask, + updateAiTask, + deleteAiTask, + fetchAiTasks +} from '../asyncThunks/aiModelCrudAsyncThunk'; + +const initialState = { + models: [], + selectedModel: null, + tasks: [], + isLoading: false, + error: null +}; + +const aiModelCrudSlice = createSlice({ + name: 'aiModelCrud', + initialState, + reducers: { + clearError: (state) => { + state.error = null; + }, + setSelectedModel: (state, action) => { + state.selectedModel = action.payload; + } + }, + extraReducers: (builder) => { + // Fetch Models + builder.addCase(fetchAiModels.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(fetchAiModels.fulfilled, (state, action) => { + state.isLoading = false; + state.models = action.payload; + }); + builder.addCase(fetchAiModels.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Fetch Single Model + builder.addCase(fetchAiModel.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(fetchAiModel.fulfilled, (state, action) => { + state.isLoading = false; + state.selectedModel = action.payload; + }); + builder.addCase(fetchAiModel.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Create Model + builder.addCase(createAiModel.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(createAiModel.fulfilled, (state, action) => { + state.isLoading = false; + state.models.push(action.payload); + }); + builder.addCase(createAiModel.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Update Model + builder.addCase(updateAiModel.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(updateAiModel.fulfilled, (state, action) => { + state.isLoading = false; + const index = state.models.findIndex(model => model.id === action.payload.id); + if (index !== -1) { + state.models[index] = action.payload; + } + }); + builder.addCase(updateAiModel.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Delete Model + builder.addCase(deleteAiModel.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(deleteAiModel.fulfilled, (state, action) => { + state.isLoading = false; + state.models = state.models.filter(model => model.id !== action.payload); + }); + builder.addCase(deleteAiModel.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Fetch Tasks + builder.addCase(fetchAiTasks.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(fetchAiTasks.fulfilled, (state, action) => { + state.isLoading = false; + state.tasks = action.payload; + }); + builder.addCase(fetchAiTasks.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Create Task + builder.addCase(createAiTask.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(createAiTask.fulfilled, (state, action) => { + state.isLoading = false; + state.tasks.push(action.payload); + }); + builder.addCase(createAiTask.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Update Task + builder.addCase(updateAiTask.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(updateAiTask.fulfilled, (state, action) => { + state.isLoading = false; + const index = state.tasks.findIndex(task => task.id === action.payload.id); + if (index !== -1) { + state.tasks[index] = action.payload; + } + }); + builder.addCase(updateAiTask.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Delete Task + builder.addCase(deleteAiTask.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(deleteAiTask.fulfilled, (state, action) => { + state.isLoading = false; + state.tasks = state.tasks.filter(task => task.id !== action.payload); + }); + builder.addCase(deleteAiTask.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + } +}); + +export const { clearError, setSelectedModel } = aiModelCrudSlice.actions; +export const aiModelCrudSelector = (state) => state.aiModelCrud; +export default aiModelCrudSlice.reducer; \ No newline at end of file diff --git a/src/redux/slices/renderCrudSlice.js b/src/redux/slices/renderCrudSlice.js new file mode 100644 index 0000000..092f1fd --- /dev/null +++ b/src/redux/slices/renderCrudSlice.js @@ -0,0 +1,155 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { + fetchRenders, + fetchRender, + updateRender, + deleteRender, + fetchRenderTasks, + createRenderTask, + updateRenderTask, + deleteRenderTask +} from '../asyncThunks/renderCrudAsyncThunk'; + +const initialState = { + renders: [], + selectedRender: null, + tasks: [], + isLoading: false, + error: null +}; + +const renderCrudSlice = createSlice({ + name: 'renderCrud', + initialState, + reducers: { + clearError: (state) => { + state.error = null; + }, + setSelectedRender: (state, action) => { + state.selectedRender = action.payload; + } + }, + extraReducers: (builder) => { + // Fetch Renders + builder.addCase(fetchRenders.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(fetchRenders.fulfilled, (state, action) => { + state.isLoading = false; + state.renders = action.payload; + }); + builder.addCase(fetchRenders.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Fetch Single Render + builder.addCase(fetchRender.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(fetchRender.fulfilled, (state, action) => { + state.isLoading = false; + state.selectedRender = action.payload; + }); + builder.addCase(fetchRender.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Update Render + builder.addCase(updateRender.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(updateRender.fulfilled, (state, action) => { + state.isLoading = false; + const index = state.renders.findIndex(render => render.id === action.payload.id); + if (index !== -1) { + state.renders[index] = action.payload; + } + }); + builder.addCase(updateRender.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Delete Render + builder.addCase(deleteRender.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(deleteRender.fulfilled, (state, action) => { + state.isLoading = false; + state.renders = state.renders.filter(render => render.id !== action.payload); + }); + builder.addCase(deleteRender.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Fetch Tasks + builder.addCase(fetchRenderTasks.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(fetchRenderTasks.fulfilled, (state, action) => { + state.isLoading = false; + state.tasks = action.payload; + }); + builder.addCase(fetchRenderTasks.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Create Task + builder.addCase(createRenderTask.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(createRenderTask.fulfilled, (state, action) => { + state.isLoading = false; + state.tasks.push(action.payload); + }); + builder.addCase(createRenderTask.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Update Task + builder.addCase(updateRenderTask.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(updateRenderTask.fulfilled, (state, action) => { + state.isLoading = false; + const index = state.tasks.findIndex(task => task.id === action.payload.id); + if (index !== -1) { + state.tasks[index] = action.payload; + } + }); + builder.addCase(updateRenderTask.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Delete Task + builder.addCase(deleteRenderTask.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(deleteRenderTask.fulfilled, (state, action) => { + state.isLoading = false; + state.tasks = state.tasks.filter(task => task.id !== action.payload); + }); + builder.addCase(deleteRenderTask.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + } +}); + +export const { clearError, setSelectedRender } = renderCrudSlice.actions; +export const renderCrudSelector = (state) => state.renderCrud; +export default renderCrudSlice.reducer; \ No newline at end of file diff --git a/src/redux/slices/threeDModelCrudSlice.js b/src/redux/slices/threeDModelCrudSlice.js new file mode 100644 index 0000000..a1f875d --- /dev/null +++ b/src/redux/slices/threeDModelCrudSlice.js @@ -0,0 +1,124 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { + fetchModels, + fetchModel, + createModel, + updateModel, + deleteModel, + uploadModel +} from '../asyncThunks/threeDModelCrudAsyncThunk'; + +const initialState = { + models: [], + selectedModel: null, + isLoading: false, + error: null, + upload_blend_file_status: '' +}; + +const threeDModelCrudSlice = createSlice({ + name: 'threeDModelCrud', + initialState, + reducers: { + clearError: (state) => { + state.error = null; + }, + setSelectedModel: (state, action) => { + state.selectedModel = action.payload; + } + }, + extraReducers: (builder) => { + // Fetch Models + builder.addCase(fetchModels.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(fetchModels.fulfilled, (state, action) => { + state.isLoading = false; + state.models = action.payload; + }); + builder.addCase(fetchModels.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Fetch Single Model + builder.addCase(fetchModel.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(fetchModel.fulfilled, (state, action) => { + state.isLoading = false; + state.selectedModel = action.payload; + }); + builder.addCase(fetchModel.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Create Model + builder.addCase(createModel.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(createModel.fulfilled, (state, action) => { + state.isLoading = false; + state.models.push(action.payload); + }); + builder.addCase(createModel.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Update Model + builder.addCase(updateModel.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(updateModel.fulfilled, (state, action) => { + state.isLoading = false; + const index = state.models.findIndex(model => model.id === action.payload.id); + if (index !== -1) { + state.models[index] = action.payload; + } + }); + builder.addCase(updateModel.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Delete Model + builder.addCase(deleteModel.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(deleteModel.fulfilled, (state, action) => { + state.isLoading = false; + state.models = state.models.filter(model => model.id !== action.payload); + }); + builder.addCase(deleteModel.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Upload Model + builder.addCase(uploadModel.pending, (state) => { + state.isLoading = true; + state.error = null; + state.upload_blend_file_status = 'uploading'; + }); + builder.addCase(uploadModel.fulfilled, (state, action) => { + state.isLoading = false; + state.upload_blend_file_status = { info: 'Upload successful' }; + }); + builder.addCase(uploadModel.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + state.upload_blend_file_status = { info: 'Upload failed' }; + }); + } +}); + +export const { clearError, setSelectedModel } = threeDModelCrudSlice.actions; +export const threeDModelCrudSelector = (state) => state.threeDModelCrud; +export default threeDModelCrudSlice.reducer; \ No newline at end of file diff --git a/src/redux/slices/userAuthSlice.js b/src/redux/slices/userAuthSlice.js new file mode 100644 index 0000000..a6efb00 --- /dev/null +++ b/src/redux/slices/userAuthSlice.js @@ -0,0 +1,72 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { loginUser, registerUser, changePassword } from '../asyncThunks/userAuthAsyncThunk'; + +const initialState = { + user: null, + token: null, + isLoading: false, + error: null +}; + +const userAuthSlice = createSlice({ + name: 'userAuth', + initialState, + reducers: { + logout: (state) => { + state.user = null; + state.token = null; + state.error = null; + }, + clearError: (state) => { + state.error = null; + } + }, + extraReducers: (builder) => { + // Login + builder.addCase(loginUser.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(loginUser.fulfilled, (state, action) => { + state.isLoading = false; + state.user = action.payload.user; + state.token = action.payload.token; + }); + builder.addCase(loginUser.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Register + builder.addCase(registerUser.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(registerUser.fulfilled, (state, action) => { + state.isLoading = false; + state.user = action.payload.user; + state.token = action.payload.token; + }); + builder.addCase(registerUser.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + + // Change Password + builder.addCase(changePassword.pending, (state) => { + state.isLoading = true; + state.error = null; + }); + builder.addCase(changePassword.fulfilled, (state) => { + state.isLoading = false; + }); + builder.addCase(changePassword.rejected, (state, action) => { + state.isLoading = false; + state.error = action.error.message; + }); + } +}); + +export const { logout, clearError } = userAuthSlice.actions; +export const userAuthSelector = (state) => state.userAuth; +export default userAuthSlice.reducer; \ No newline at end of file diff --git a/src/redux/store.js b/src/redux/store.js new file mode 100644 index 0000000..f01abdc --- /dev/null +++ b/src/redux/store.js @@ -0,0 +1,14 @@ +import { configureStore } from '@reduxjs/toolkit'; +import userAuthReducer from './slices/userAuthSlice'; +import threeDModelCrudReducer from './slices/threeDModelCrudSlice'; +import aiModelCrudReducer from './slices/aiModelCrudSlice'; +import renderCrudReducer from './slices/renderCrudSlice'; + +export const store = configureStore({ + reducer: { + userAuth: userAuthReducer, + threeDModelCrud: threeDModelCrudReducer, + aiModelCrud: aiModelCrudReducer, + renderCrud: renderCrudReducer + } +}); \ No newline at end of file diff --git a/src/styles/general.scss b/src/styles/general.scss index c7668a1..d8eb7d7 100644 --- a/src/styles/general.scss +++ b/src/styles/general.scss @@ -252,15 +252,15 @@ body, html { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); .list-generator-container { - width: 100%; + width: 100%; display: flex; flex-direction: column; gap: 20px; .table-header { - display: flex; + display: flex; justify-content: space-between; - align-items: center; + align-items: center; h2 { margin: 0; @@ -313,13 +313,13 @@ body, html { background-color: rgba($background-color, 0.1); border-radius: 4px; - .item-column-row { + .item-column-row { font-weight: 600; color: $title-color; - } } + } - .items-list { + .items-list { display: flex; flex-direction: column; gap: 10px; @@ -332,7 +332,7 @@ body, html { border-radius: 4px; } - .item-row { + .item-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; @@ -352,8 +352,8 @@ body, html { } .item-info { - display: flex; - align-items: center; + display: flex; + align-items: center; gap: 10px; .progress-bar { @@ -504,6 +504,75 @@ body, html { } } } + + .pagination-controls { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 0; + margin-top: 20px; + border-top: 1px solid rgba($subtitle-color, 0.1); + + .items-per-page { + display: flex; + align-items: center; + gap: 10px; + + span { + color: $subtitle-color; + } + + select { + padding: 5px 10px; + background: $background-color; + color: $title-color; + border: 1px solid $border-color; + border-radius: 4px; + cursor: pointer; + + &:focus { + outline: none; + border-color: $first-color; + } + + option { + background: $background-color; + color: $title-color; + } + } + } + + .pagination-buttons { + display: flex; + align-items: center; + gap: 10px; + + .pagination-button { + padding: 5px 10px; + background: $background-color; + color: $title-color; + border: 1px solid $border-color; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s; + + &:hover:not(:disabled) { + background: $first-color; + border-color: $first-color; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + } + + .page-info { + color: $subtitle-color; + margin: 0 10px; + } + } + } } .list-row { @@ -513,7 +582,7 @@ body, html { padding: 15px; background-color: white; border-radius: 4px; - margin-bottom: 10px; + margin-bottom: 10px; cursor: pointer; transition: all 0.2s; @@ -521,7 +590,7 @@ body, html { background-color: rgba($background-color, 0.05); } - .item-info { + .item-info { display: flex; flex-direction: column; gap: 5px; @@ -585,7 +654,7 @@ body, html { gap: 10px; .progress-bar { - width: 100%; + width: 100%; height: 8px; background-color: rgba($subtitle-color, 0.1); border-radius: 4px; @@ -1046,25 +1115,29 @@ body, html { } } -.float_form_model { - position: fixed; - width: 400px; - padding: 50px; +.upload_input_container { + border: dashed 2px rgba(0,128,0,1); border-radius: 10px; - color: green; - background-color: rgba(22,28,29,1); - margin-left: 50%; + width: 350px - 4px - 20px - 20px; + padding-left: 20px; + padding-right: 20px; + + p { + font-weight: bold; + font-size: 12px; + text-align: center; + } .upload_input { width: 0px !important; height: 0px !important; - padding-top: 70px; - padding-left: 400px; - margin-left: 0px; + padding-top: 10px; + padding-left: 100%; + padding-bottom: 10px; color: rgba(0,128,0,1); font-family: Ubuntu; - border: dashed 2px rgba(0,128,0,1); overflow: hidden; + cursor: pointer; } button { @@ -1332,34 +1405,79 @@ body, html { display: flex; justify-content: space-between; align-items: center; - padding: 20px; + margin-bottom: 20px; h2 { - color: $title-color; margin: 0; - font-size: 24px; } - .create-button { + .dashboard-controls { display: flex; align-items: center; - gap: 8px; - padding: 10px 20px; - background: $first-color; - color: white; - border: none; - border-radius: 5px; - cursor: pointer; - font-weight: 600; - transition: all 0.3s ease; + gap: 16px; - i { - font-size: 16px; + .search-container { + position: relative; + + .search-input { + width: 250px; + padding: 8px 32px 8px 12px; + background: $input-background; + border: 1px solid $border-color; + border-radius: 5px; + color: white; + transition: border-color 0.3s; + + &:hover { + border-color: rgba(111,108,106,1); + } + + &:focus { + outline: none; + border-color: $first-color; + } + } + + .clear-search { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: #666; + cursor: pointer; + font-size: 18px; + padding: 0; + line-height: 1; + + &:hover { + color: #333; + } + } } - &:hover { - background: darken($first-color, 10%); - transform: translateY(-2px); + .create-button { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + background: $first-color; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + font-weight: 600; + transition: all 0.3s ease; + + i { + font-size: 16px; + } + + &:hover { + background: darken($first-color, 10%); + transform: translateY(-2px); + } } } } @@ -1377,12 +1495,65 @@ body, html { z-index: 1000; .form-container { - background: $form-background; - padding: 30px; - border-radius: 10px; - width: 500px; max-width: 90%; max-height: 90vh; overflow-y: auto; } } + +/* Stylizacja natywnych suwaków */ +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-track { + background: $form-background; + border-radius: 5px; +} + +::-webkit-scrollbar-thumb { + background: $first-color; + border-radius: 5px; + + &:hover { + background: darken($first-color, 10%); + } + + &:active { + background: darken($first-color, 20%); + } +} + +::-webkit-scrollbar-button { + background: $first-color; + + &:hover { + background: darken($first-color, 10%); + } +} + +::-webkit-scrollbar-button:vertical:start:decrement, +::-webkit-scrollbar-button:vertical:end:increment, +::-webkit-scrollbar-button:horizontal:start:decrement, +::-webkit-scrollbar-button:horizontal:end:increment { + background-color: $first-color; +} + +/* Firefox */ +* { + scrollbar-width: thin; + scrollbar-color: $first-color $form-background; +} + +/* Style dla wyłączonych suwaków */ +::-webkit-scrollbar-thumb:disabled, +::-webkit-scrollbar-button:disabled { + background: rgba($first-color, 1); + cursor: not-allowed; +} + +/* Wymuszenie stylów nawet gdy content nie wymaga przewijania */ +.dashboard-content { + min-height: 100%; /* Wymusza pojawienie się suwaka */ +}