Browse Source

add and edit good

Graf15 1 year ago
parent
commit
d2d73a27ae
27 changed files with 1765 additions and 394 deletions
  1. 318 24
      my-diplom/package-lock.json
  2. 7 0
      my-diplom/package.json
  3. 10 6
      my-diplom/src/App.js
  4. 119 0
      my-diplom/src/components/DnD.jsx
  5. 129 0
      my-diplom/src/components/ImageUploader.jsx
  6. 5 17
      my-diplom/src/components/OrdersTableCollaps.jsx
  7. 157 0
      my-diplom/src/components/ReactDropzone.jsx
  8. 129 0
      my-diplom/src/components/Test1 copy.jsx
  9. 0 171
      my-diplom/src/components/Test1.jsx
  10. 111 0
      my-diplom/src/components/adminPage/AddNewGood.jsx
  11. 138 0
      my-diplom/src/components/adminPage/AdminGoodsTable.jsx
  12. 24 20
      my-diplom/src/components/adminPage/AdminHeader.jsx
  13. 59 44
      my-diplom/src/components/adminPage/AdminMenu.jsx
  14. 0 1
      my-diplom/src/components/adminPage/LayoutAdmin.js
  15. 140 0
      my-diplom/src/components/adminPage/pages/AdminGoodMutation.jsx
  16. 60 0
      my-diplom/src/components/adminPage/pages/AdminGoods.jsx
  17. 22 0
      my-diplom/src/components/adminPage/pages/AdminOrders.jsx
  18. 19 0
      my-diplom/src/components/adminPage/pages/AdminUsers.jsx
  19. 152 0
      my-diplom/src/components/adminPage/pages/MuiTreeWithCheckBox.jsx
  20. 1 1
      my-diplom/src/components/public-pages/CategoryOnePage.jsx
  21. 1 0
      my-diplom/src/components/public-pages/ProductOnePage.jsx
  22. 3 4
      my-diplom/src/components/public-pages/ProfilePage.jsx
  23. 0 0
      my-diplom/src/serverRoles.js
  24. 30 0
      my-diplom/src/store/ImgUploadApi.js
  25. 0 96
      my-diplom/src/store/categoriesApi copy.js
  26. 126 8
      my-diplom/src/store/categoriesApi.js
  27. 5 2
      my-diplom/src/store/index.js

+ 318 - 24
my-diplom/package-lock.json

@@ -8,23 +8,30 @@
       "name": "my-diplom",
       "version": "0.1.0",
       "dependencies": {
+        "@dnd-kit/core": "^6.0.8",
+        "@dnd-kit/sortable": "^7.0.2",
+        "@dnd-kit/utilities": "^3.2.1",
         "@emotion/react": "^11.10.6",
         "@emotion/styled": "^11.10.6",
         "@fontsource/roboto": "^4.5.8",
         "@mui/icons-material": "^5.11.11",
+        "@mui/lab": "^5.0.0-alpha.124",
         "@mui/material": "^5.11.11",
         "@reduxjs/toolkit": "^1.9.3",
         "@rtk-query/graphql-request-base-query": "^2.2.0",
         "@testing-library/jest-dom": "^5.16.5",
         "@testing-library/react": "^13.4.0",
         "@testing-library/user-event": "^13.5.0",
+        "array-move": "^4.0.0",
         "graphql-request": "^5.2.0",
         "react": "^18.2.0",
         "react-dom": "^18.2.0",
+        "react-dropzone": "^14.2.3",
         "react-material-ui-carousel": "^3.4.2",
         "react-redux": "^8.0.5",
         "react-router-dom": "^6.8.1",
         "react-scripts": "5.0.1",
+        "redux-persist": "^6.0.0",
         "web-vitals": "^2.1.4"
       }
     },
@@ -2141,6 +2148,55 @@
         "postcss-selector-parser": "^6.0.10"
       }
     },
+    "node_modules/@dnd-kit/accessibility": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.0.1.tgz",
+      "integrity": "sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg==",
+      "dependencies": {
+        "tslib": "^2.0.0"
+      },
+      "peerDependencies": {
+        "react": ">=16.8.0"
+      }
+    },
+    "node_modules/@dnd-kit/core": {
+      "version": "6.0.8",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.0.8.tgz",
+      "integrity": "sha512-lYaoP8yHTQSLlZe6Rr9qogouGUz9oRUj4AHhDQGQzq/hqaJRpFo65X+JKsdHf8oUFBzx5A+SJPUvxAwTF2OabA==",
+      "dependencies": {
+        "@dnd-kit/accessibility": "^3.0.0",
+        "@dnd-kit/utilities": "^3.2.1",
+        "tslib": "^2.0.0"
+      },
+      "peerDependencies": {
+        "react": ">=16.8.0",
+        "react-dom": ">=16.8.0"
+      }
+    },
+    "node_modules/@dnd-kit/sortable": {
+      "version": "7.0.2",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-7.0.2.tgz",
+      "integrity": "sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==",
+      "dependencies": {
+        "@dnd-kit/utilities": "^3.2.0",
+        "tslib": "^2.0.0"
+      },
+      "peerDependencies": {
+        "@dnd-kit/core": "^6.0.7",
+        "react": ">=16.8.0"
+      }
+    },
+    "node_modules/@dnd-kit/utilities": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.1.tgz",
+      "integrity": "sha512-OOXqISfvBw/1REtkSK2N3Fi2EQiLMlWUlqnOK/UpOISqBZPWpE6TqL+jcPtMOkE8TqYGiURvRdPSI9hltNUjEA==",
+      "dependencies": {
+        "tslib": "^2.0.0"
+      },
+      "peerDependencies": {
+        "react": ">=16.8.0"
+      }
+    },
     "node_modules/@emotion/babel-plugin": {
       "version": "11.10.6",
       "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz",
@@ -3229,6 +3285,84 @@
         }
       }
     },
+    "node_modules/@mui/lab": {
+      "version": "5.0.0-alpha.124",
+      "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.124.tgz",
+      "integrity": "sha512-K/XEv1zYyLi3tS63tyDta1mqWql+at5w7rWp5Yd63Jx1NjPUtgopAvyRZG2SVYPs/yBwfyDPW1iqQXpRw8h9Xw==",
+      "dependencies": {
+        "@babel/runtime": "^7.21.0",
+        "@mui/base": "5.0.0-alpha.122",
+        "@mui/system": "^5.11.14",
+        "@mui/types": "^7.2.3",
+        "@mui/utils": "^5.11.13",
+        "clsx": "^1.2.1",
+        "prop-types": "^15.8.1",
+        "react-is": "^18.2.0"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mui"
+      },
+      "peerDependencies": {
+        "@emotion/react": "^11.5.0",
+        "@emotion/styled": "^11.3.0",
+        "@mui/material": "^5.0.0",
+        "@types/react": "^17.0.0 || ^18.0.0",
+        "react": "^17.0.0 || ^18.0.0",
+        "react-dom": "^17.0.0 || ^18.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@emotion/react": {
+          "optional": true
+        },
+        "@emotion/styled": {
+          "optional": true
+        },
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@mui/lab/node_modules/@mui/base": {
+      "version": "5.0.0-alpha.122",
+      "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.122.tgz",
+      "integrity": "sha512-IgZEFQyHa39J1+Q3tekVdhPuUm1fr3icddaNLmiAIeYTVXmR7KR5FhBAIL0P+4shlPq0liUPGlXryoTm0iCeFg==",
+      "dependencies": {
+        "@babel/runtime": "^7.21.0",
+        "@emotion/is-prop-valid": "^1.2.0",
+        "@mui/types": "^7.2.3",
+        "@mui/utils": "^5.11.13",
+        "@popperjs/core": "^2.11.6",
+        "clsx": "^1.2.1",
+        "prop-types": "^15.8.1",
+        "react-is": "^18.2.0"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mui"
+      },
+      "peerDependencies": {
+        "@types/react": "^17.0.0 || ^18.0.0",
+        "react": "^17.0.0 || ^18.0.0",
+        "react-dom": "^17.0.0 || ^18.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@mui/lab/node_modules/react-is": {
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+      "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+    },
     "node_modules/@mui/material": {
       "version": "5.11.11",
       "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.11.tgz",
@@ -3279,12 +3413,12 @@
       "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
     },
     "node_modules/@mui/private-theming": {
-      "version": "5.11.11",
-      "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.11.tgz",
-      "integrity": "sha512-yLgTkjNC1mpye2SOUkc+zQQczUpg8NvQAETvxwXTMzNgJK1pv4htL7IvBM5vmCKG7IHAB3hX26W2u6i7bxwF3A==",
+      "version": "5.11.13",
+      "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.13.tgz",
+      "integrity": "sha512-PJnYNKzW5LIx3R+Zsp6WZVPs6w5sEKJ7mgLNnUXuYB1zo5aX71FVLtV7geyPXRcaN2tsoRNK7h444ED0t7cIjA==",
       "dependencies": {
         "@babel/runtime": "^7.21.0",
-        "@mui/utils": "^5.11.11",
+        "@mui/utils": "^5.11.13",
         "prop-types": "^15.8.1"
       },
       "engines": {
@@ -3336,15 +3470,15 @@
       }
     },
     "node_modules/@mui/system": {
-      "version": "5.11.11",
-      "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.11.tgz",
-      "integrity": "sha512-a9gaOAJBjpzypDfhbGZQ8HzdcxdxsKkFvbp1aAWZhFHBPdehEkARNh7mj851VfEhD/GdffYt85PFKFKdUta5Eg==",
+      "version": "5.11.15",
+      "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.15.tgz",
+      "integrity": "sha512-vCatoWCTnAPquoNifHbqMCMnOElEbLosVUeW0FQDyjCq+8yMABD9E6iY0s14O7iq1wD+qqU7rFAuDIVvJ/AzzA==",
       "dependencies": {
         "@babel/runtime": "^7.21.0",
-        "@mui/private-theming": "^5.11.11",
+        "@mui/private-theming": "^5.11.13",
         "@mui/styled-engine": "^5.11.11",
         "@mui/types": "^7.2.3",
-        "@mui/utils": "^5.11.11",
+        "@mui/utils": "^5.11.13",
         "clsx": "^1.2.1",
         "csstype": "^3.1.1",
         "prop-types": "^15.8.1"
@@ -3388,9 +3522,9 @@
       }
     },
     "node_modules/@mui/utils": {
-      "version": "5.11.11",
-      "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.11.tgz",
-      "integrity": "sha512-neMM5rrEXYQrOrlxUfns/TGgX4viS8K2zb9pbQh11/oUUYFlGI32Tn+PHePQx7n6Fy/0zq6WxdBFC9VpnJ5JrQ==",
+      "version": "5.11.13",
+      "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.13.tgz",
+      "integrity": "sha512-5ltA58MM9euOuUcnvwFJqpLdEugc9XFsRR8Gt4zZNb31XzMfSKJPR4eumulyhsOTK1rWf7K4D63NKFPfX0AxqA==",
       "dependencies": {
         "@babel/runtime": "^7.21.0",
         "@types/prop-types": "^15.7.5",
@@ -5432,6 +5566,17 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/array-move": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/array-move/-/array-move-4.0.0.tgz",
+      "integrity": "sha512-+RY54S8OuVvg94THpneQvFRmqWdAHeqtMzgMW6JNurHxe8rsS07cHQdfGkXnTUXiBcyZ0j3SiDIxxj0RPiqCkQ==",
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/array-union": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -5532,6 +5677,14 @@
         "node": ">= 4.0.0"
       }
     },
+    "node_modules/attr-accept": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+      "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/autoprefixer": {
       "version": "10.4.13",
       "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz",
@@ -8542,6 +8695,17 @@
         "webpack": "^4.0.0 || ^5.0.0"
       }
     },
+    "node_modules/file-selector": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
+      "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
+      "dependencies": {
+        "tslib": "^2.4.0"
+      },
+      "engines": {
+        "node": ">= 12"
+      }
+    },
     "node_modules/filelist": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
@@ -15013,6 +15177,22 @@
         "react": "^18.2.0"
       }
     },
+    "node_modules/react-dropzone": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz",
+      "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==",
+      "dependencies": {
+        "attr-accept": "^2.2.2",
+        "file-selector": "^0.6.0",
+        "prop-types": "^15.8.1"
+      },
+      "engines": {
+        "node": ">= 10.13"
+      },
+      "peerDependencies": {
+        "react": ">= 16.8 || 18.0.0"
+      }
+    },
     "node_modules/react-error-overlay": {
       "version": "6.0.11",
       "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
@@ -15276,6 +15456,14 @@
         "@babel/runtime": "^7.9.2"
       }
     },
+    "node_modules/redux-persist": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
+      "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==",
+      "peerDependencies": {
+        "redux": ">4.0.0"
+      }
+    },
     "node_modules/redux-thunk": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
@@ -19421,6 +19609,41 @@
       "integrity": "sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==",
       "requires": {}
     },
+    "@dnd-kit/accessibility": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.0.1.tgz",
+      "integrity": "sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg==",
+      "requires": {
+        "tslib": "^2.0.0"
+      }
+    },
+    "@dnd-kit/core": {
+      "version": "6.0.8",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.0.8.tgz",
+      "integrity": "sha512-lYaoP8yHTQSLlZe6Rr9qogouGUz9oRUj4AHhDQGQzq/hqaJRpFo65X+JKsdHf8oUFBzx5A+SJPUvxAwTF2OabA==",
+      "requires": {
+        "@dnd-kit/accessibility": "^3.0.0",
+        "@dnd-kit/utilities": "^3.2.1",
+        "tslib": "^2.0.0"
+      }
+    },
+    "@dnd-kit/sortable": {
+      "version": "7.0.2",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-7.0.2.tgz",
+      "integrity": "sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==",
+      "requires": {
+        "@dnd-kit/utilities": "^3.2.0",
+        "tslib": "^2.0.0"
+      }
+    },
+    "@dnd-kit/utilities": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.1.tgz",
+      "integrity": "sha512-OOXqISfvBw/1REtkSK2N3Fi2EQiLMlWUlqnOK/UpOISqBZPWpE6TqL+jcPtMOkE8TqYGiURvRdPSI9hltNUjEA==",
+      "requires": {
+        "tslib": "^2.0.0"
+      }
+    },
     "@emotion/babel-plugin": {
       "version": "11.10.6",
       "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz",
@@ -20223,6 +20446,43 @@
         "@babel/runtime": "^7.21.0"
       }
     },
+    "@mui/lab": {
+      "version": "5.0.0-alpha.124",
+      "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.124.tgz",
+      "integrity": "sha512-K/XEv1zYyLi3tS63tyDta1mqWql+at5w7rWp5Yd63Jx1NjPUtgopAvyRZG2SVYPs/yBwfyDPW1iqQXpRw8h9Xw==",
+      "requires": {
+        "@babel/runtime": "^7.21.0",
+        "@mui/base": "5.0.0-alpha.122",
+        "@mui/system": "^5.11.14",
+        "@mui/types": "^7.2.3",
+        "@mui/utils": "^5.11.13",
+        "clsx": "^1.2.1",
+        "prop-types": "^15.8.1",
+        "react-is": "^18.2.0"
+      },
+      "dependencies": {
+        "@mui/base": {
+          "version": "5.0.0-alpha.122",
+          "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.122.tgz",
+          "integrity": "sha512-IgZEFQyHa39J1+Q3tekVdhPuUm1fr3icddaNLmiAIeYTVXmR7KR5FhBAIL0P+4shlPq0liUPGlXryoTm0iCeFg==",
+          "requires": {
+            "@babel/runtime": "^7.21.0",
+            "@emotion/is-prop-valid": "^1.2.0",
+            "@mui/types": "^7.2.3",
+            "@mui/utils": "^5.11.13",
+            "@popperjs/core": "^2.11.6",
+            "clsx": "^1.2.1",
+            "prop-types": "^15.8.1",
+            "react-is": "^18.2.0"
+          }
+        },
+        "react-is": {
+          "version": "18.2.0",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+          "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+        }
+      }
+    },
     "@mui/material": {
       "version": "5.11.11",
       "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.11.tgz",
@@ -20250,12 +20510,12 @@
       }
     },
     "@mui/private-theming": {
-      "version": "5.11.11",
-      "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.11.tgz",
-      "integrity": "sha512-yLgTkjNC1mpye2SOUkc+zQQczUpg8NvQAETvxwXTMzNgJK1pv4htL7IvBM5vmCKG7IHAB3hX26W2u6i7bxwF3A==",
+      "version": "5.11.13",
+      "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.13.tgz",
+      "integrity": "sha512-PJnYNKzW5LIx3R+Zsp6WZVPs6w5sEKJ7mgLNnUXuYB1zo5aX71FVLtV7geyPXRcaN2tsoRNK7h444ED0t7cIjA==",
       "requires": {
         "@babel/runtime": "^7.21.0",
-        "@mui/utils": "^5.11.11",
+        "@mui/utils": "^5.11.13",
         "prop-types": "^15.8.1"
       }
     },
@@ -20271,15 +20531,15 @@
       }
     },
     "@mui/system": {
-      "version": "5.11.11",
-      "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.11.tgz",
-      "integrity": "sha512-a9gaOAJBjpzypDfhbGZQ8HzdcxdxsKkFvbp1aAWZhFHBPdehEkARNh7mj851VfEhD/GdffYt85PFKFKdUta5Eg==",
+      "version": "5.11.15",
+      "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.15.tgz",
+      "integrity": "sha512-vCatoWCTnAPquoNifHbqMCMnOElEbLosVUeW0FQDyjCq+8yMABD9E6iY0s14O7iq1wD+qqU7rFAuDIVvJ/AzzA==",
       "requires": {
         "@babel/runtime": "^7.21.0",
-        "@mui/private-theming": "^5.11.11",
+        "@mui/private-theming": "^5.11.13",
         "@mui/styled-engine": "^5.11.11",
         "@mui/types": "^7.2.3",
-        "@mui/utils": "^5.11.11",
+        "@mui/utils": "^5.11.13",
         "clsx": "^1.2.1",
         "csstype": "^3.1.1",
         "prop-types": "^15.8.1"
@@ -20292,9 +20552,9 @@
       "requires": {}
     },
     "@mui/utils": {
-      "version": "5.11.11",
-      "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.11.tgz",
-      "integrity": "sha512-neMM5rrEXYQrOrlxUfns/TGgX4viS8K2zb9pbQh11/oUUYFlGI32Tn+PHePQx7n6Fy/0zq6WxdBFC9VpnJ5JrQ==",
+      "version": "5.11.13",
+      "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.13.tgz",
+      "integrity": "sha512-5ltA58MM9euOuUcnvwFJqpLdEugc9XFsRR8Gt4zZNb31XzMfSKJPR4eumulyhsOTK1rWf7K4D63NKFPfX0AxqA==",
       "requires": {
         "@babel/runtime": "^7.21.0",
         "@types/prop-types": "^15.7.5",
@@ -21817,6 +22077,11 @@
         "is-string": "^1.0.7"
       }
     },
+    "array-move": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/array-move/-/array-move-4.0.0.tgz",
+      "integrity": "sha512-+RY54S8OuVvg94THpneQvFRmqWdAHeqtMzgMW6JNurHxe8rsS07cHQdfGkXnTUXiBcyZ0j3SiDIxxj0RPiqCkQ=="
+    },
     "array-union": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -21893,6 +22158,11 @@
       "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
       "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="
     },
+    "attr-accept": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+      "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg=="
+    },
     "autoprefixer": {
       "version": "10.4.13",
       "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz",
@@ -24099,6 +24369,14 @@
         "schema-utils": "^3.0.0"
       }
     },
+    "file-selector": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
+      "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
+      "requires": {
+        "tslib": "^2.4.0"
+      }
+    },
     "filelist": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
@@ -28586,6 +28864,16 @@
         "scheduler": "^0.23.0"
       }
     },
+    "react-dropzone": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz",
+      "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==",
+      "requires": {
+        "attr-accept": "^2.2.2",
+        "file-selector": "^0.6.0",
+        "prop-types": "^15.8.1"
+      }
+    },
     "react-error-overlay": {
       "version": "6.0.11",
       "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
@@ -28768,6 +29056,12 @@
         "@babel/runtime": "^7.9.2"
       }
     },
+    "redux-persist": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
+      "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==",
+      "requires": {}
+    },
     "redux-thunk": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",

+ 7 - 0
my-diplom/package.json

@@ -3,23 +3,30 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "@dnd-kit/core": "^6.0.8",
+    "@dnd-kit/sortable": "^7.0.2",
+    "@dnd-kit/utilities": "^3.2.1",
     "@emotion/react": "^11.10.6",
     "@emotion/styled": "^11.10.6",
     "@fontsource/roboto": "^4.5.8",
     "@mui/icons-material": "^5.11.11",
+    "@mui/lab": "^5.0.0-alpha.124",
     "@mui/material": "^5.11.11",
     "@reduxjs/toolkit": "^1.9.3",
     "@rtk-query/graphql-request-base-query": "^2.2.0",
     "@testing-library/jest-dom": "^5.16.5",
     "@testing-library/react": "^13.4.0",
     "@testing-library/user-event": "^13.5.0",
+    "array-move": "^4.0.0",
     "graphql-request": "^5.2.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
+    "react-dropzone": "^14.2.3",
     "react-material-ui-carousel": "^3.4.2",
     "react-redux": "^8.0.5",
     "react-router-dom": "^6.8.1",
     "react-scripts": "5.0.1",
+    "redux-persist": "^6.0.0",
     "web-vitals": "^2.1.4"
   },
   "scripts": {

+ 10 - 6
my-diplom/src/App.js

@@ -11,6 +11,10 @@ import Registration from "./components/public-pages/Registration";
 import RequireAuth from "./hoc/RequireAuth";
 import NoRequireAuth from "./hoc/NoRequireAuth";
 import LayoutAdmin from "./components/adminPage/LayoutAdmin";
+import AdminOrders from "./components/adminPage/pages/AdminOrders";
+import AdminGoods from "./components/adminPage/pages/AdminGoods";
+import AdminGoodMutation from "./components/adminPage/pages/AdminGoodMutation";
+import AddNewGood from "./components/adminPage/AddNewGood";
 
 
 
@@ -48,12 +52,12 @@ function App() {
         <Route path='admin' element={<LayoutAdmin />}>
           <Route index element={<p>index</p>}/>
           <Route path='*' element={<p>Страница не найдена</p>}/>
-        </Route>
-        {/* <Route path='admin/*' element={
-        <RequireAuthIsAdmin>
-          <p>админка</p>
-        </RequireAuthIsAdmin>
-        /> */}
+          <Route path='orders' element={<AdminOrders/>}/>
+          <Route path='goods' element={<AdminGoods/>}/>
+          <Route path='addNewGood' element={<AddNewGood/>}/>
+          <Route path="good/:goodId" element={<AdminGoodMutation/>}/>
+        </Route> 
+        
       </Routes>
     </>
   );

+ 119 - 0
my-diplom/src/components/DnD.jsx

@@ -0,0 +1,119 @@
+import React, { useEffect, useState } from 'react';
+
+import {
+  DndContext,
+  KeyboardSensor,
+  PointerSensor,
+  useSensor,
+  useSensors, useDroppable
+} from "@dnd-kit/core";
+import { sortableKeyboardCoordinates, rectSortingStrategy, SortableContext, useSortable } from "@dnd-kit/sortable";
+import { CSS } from "@dnd-kit/utilities";
+import { arrayMoveImmutable } from 'array-move';
+
+
+
+const SortableItem = (props) => {
+  const {
+    attributes,
+    listeners,
+    setNodeRef,
+    transform,
+    transition
+  } = useSortable({ id: props.id });
+
+  const itemStyle = {
+    transform: CSS.Transform.toString(transform),
+    transition,
+    //width: 110,
+    //height: 30,
+    //display: "flex",
+    //alignItems: "center",
+    //paddingLeft: 5,
+    //border: "1px solid gray",
+    //borderRadius: 5,
+    //marginBottom: 5,
+    //userSelect: "none",
+    cursor: "grab",
+    //boxSizing: "border-box"
+  };
+
+  const Render = props.render;
+
+  return (
+    <div style={itemStyle} ref={setNodeRef} {...attributes} {...listeners}>
+      <Render {...{ [props.itemProp]: props.item }} />
+    </div>
+  )
+};
+
+
+const Droppable = ({ id, items, itemProp, keyField, render }) => {
+  const { setNodeRef } = useDroppable({ id });
+
+  const droppableStyle = {
+    //padding: "20px 10px",
+    //border: "1px solid black",
+    //borderRadius: "5px",
+    //minWidth: 110
+  };
+
+  return (
+    <SortableContext id={id} items={items} strategy={rectSortingStrategy}>
+      {items.map((item) => (
+        <SortableItem render={render} key={item[keyField]} id={item}
+          itemProp={itemProp} item={item} />
+      ))}
+    </SortableContext>
+  )
+};
+
+
+function Dnd({ items: startItems, render, itemProp, keyField, onChange, horizontal }) {
+  const [items, setItems] = useState(
+    startItems
+  );
+  useEffect(() => setItems(startItems), [startItems]);
+
+  useEffect(() => {
+    if (typeof onChange === 'function') {
+      onChange(items)
+    }
+  }, [items]);
+
+  const sensors = useSensors(
+    useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
+    useSensor(KeyboardSensor, {
+      coordinateGetter: sortableKeyboardCoordinates
+    })
+  );
+
+  const handleDragEnd = ({ active, over }) => {
+    const activeIndex = active.data.current.sortable.index;
+    const overIndex = over.data.current?.sortable.index || 0;
+
+    setItems((items) => {
+      return arrayMoveImmutable(items, activeIndex, overIndex);
+    });
+  }
+
+  const containerStyle = { display: horizontal ? "flex" : '' };
+
+  return (
+    <DndContext
+      sensors={sensors}
+      onDragEnd={handleDragEnd}
+    >
+      <div style={containerStyle}>
+        <Droppable id="aaa"
+          items={items}
+          itemProp={itemProp}
+          keyField={keyField}
+          render={render} />
+      </div>
+    </DndContext>
+  );
+}
+
+
+export default Dnd;

+ 129 - 0
my-diplom/src/components/ImageUploader.jsx

@@ -0,0 +1,129 @@
+import React, { useMemo, useState } from 'react';
+import { useDropzone } from 'react-dropzone';
+import { Box } from '@mui/material';
+import Dnd from './DnD';
+import { useSetUploadImageMutation } from '../store/ImgUploadApi';
+
+function Image({ url, click }) {
+
+  return (
+    <img className="good-image" src={`http://shop-roles.node.ed.asmer.org.ua/${url}`} onClick={click} style={{
+      width: "100%",
+      height: "100%",
+      objectFit: "contain",
+    }}/>
+  )
+}
+
+const baseStyle = {
+  flex: 1,
+  display: 'flex',
+  flexDirection: 'column',
+  alignItems: 'center',
+  padding: '20px',
+  borderWidth: 2,
+  borderRadius: 2,
+  borderColor: '#eeeeee',
+  borderStyle: 'dashed',
+  backgroundColor: '#fafafa',
+  color: '#bdbdbd',
+  outline: 'none',
+  transition: 'border .24s ease-in-out'
+};
+
+const focusedStyle = {
+  borderColor: '#2196f3'
+};
+
+const acceptStyle = {
+  borderColor: '#00e676'
+};
+
+const rejectStyle = {
+  borderColor: '#ff1744'
+};
+
+const ImageOnDnd = ({ image, onDelete }) =>
+  <Box sx={{ width: '100px', mr: '20px' }} key={image._id}>
+    <Image url={image?.url} click={() => onDelete(image)} />
+  </Box>
+
+function ImageUploader({ passImages, onChange = Function.prototype }) { // onChange функция из пропсов запускаемая при обновлении массива картинок.
+  const [images, setImages] = useState(passImages || []); //passImages массив картинок из пропсов, 
+  const [uploadImage, result] = useSetUploadImageMutation(); //   uploadImage функция мутации (отправки данных на сервер)
+  console.log('result', result);
+
+  // const onDrop = useCallback(acceptedFiles => {
+  //   filesUpload(acceptedFiles).then(imagesResponse => {
+  //     setImages(images.concat(imagesResponse));
+  //   });
+  // }, [])
+
+  const updateImages = images => {
+    console.log('images', images)
+    setImages(images);
+    onChange(images);
+  }
+
+  const onDrop = acceptedFiles => {
+    console.log('дропнуло', acceptedFiles)
+
+     filesUpload(acceptedFiles).then(imagesResponse => {
+       updateImages(images.concat(imagesResponse));
+     });
+  }
+
+  const filesUpload = (acceptedFiles) => {
+    return Promise.all(acceptedFiles.map(fileUpload));
+  }
+
+  const fileUpload = (file) => {
+   
+    const formData = new FormData();
+    
+    formData.append('photo', file);
+    console.log('formData', formData.get("photo"))
+    return uploadImage(formData).then(response => {
+      console.log('response', response);
+      return Promise.resolve(response.data);
+    });
+  }
+
+  const { getRootProps, getInputProps, isDragActive, isFocused, isDragAccept, isDragReject } = useDropzone({ accept: { 'image/*': [] }, onDrop });
+
+  const style = useMemo(() => ({
+    ...baseStyle,
+    ...(isFocused ? focusedStyle : {}),
+    ...(isDragAccept ? acceptStyle : {}),
+    ...(isDragReject ? rejectStyle : {})
+  }), [
+    isFocused,
+    isDragAccept,
+    isDragReject
+  ]);
+
+  
+
+  
+  const deleteImage = image => updateImages(images.filter(i => i != image));
+
+  const localImage = ({ image }) => <ImageOnDnd image={image} onDelete={imgToDelete => deleteImage(imgToDelete)} />
+
+  return (
+    <>
+      <div {...getRootProps({ style })}>
+        <input {...getInputProps()} />
+        {
+          isDragActive ?
+            <p>Drop the files here ...</p> :
+            <p>Перетащите несколько файлов сюда или нажмите, чтобы выбрать файлы</p>
+        }
+      </div>
+      <Box sx={{ display: 'flex', m: '20px auto 0' }}>
+        <Dnd items={images} render={localImage} onChange={images => updateImages(images)} itemProp="image" keyField="_id" horizontal />
+      </Box>
+    </>
+  )
+}
+
+export default ImageUploader;

+ 5 - 17
my-diplom/src/components/OrdersTableCollaps.jsx

@@ -10,7 +10,8 @@ import TableRow from '@mui/material/TableRow';
 import { Box, Collapse, IconButton, Typography } from '@mui/material';
 import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
 import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
-import { useGetOrdersCountQuery, useGetUserOrdersQuery } from '../store/categoriesApi';
+import { useGetOrdersCountQuery, useGetUserOrders2Query, useGetUserOrdersQuery } from '../store/categoriesApi';
+import { useSelector } from 'react-redux';
 
 
 
@@ -29,22 +30,15 @@ function TimestampConv(value) {
 
 
 export default function StickyHeadTable() {
+  const userId = useSelector(state => state.auth.user?.sub.id)
   const [page, setPage] = React.useState(0);
   const [rowsPerPage, setRowsPerPage] = React.useState(10);
   const {data: OrderCount} = useGetOrdersCountQuery()
   const count = OrderCount?.OrderCount || 1
-  const orders = useGetUserOrdersQuery({limit: rowsPerPage, skip: page * rowsPerPage})
+  const orders = useGetUserOrdersQuery({userId, limit: rowsPerPage, skip: page * rowsPerPage, sort: {_id: -1}})
   const rows = orders?.data ? Object.values(orders.data)[0] : []
   
-  // React.useEffect(
-  //   () => {refetch()}, [page || rowsPerPage]
-  // )
 
-  //const orders = useGetUserOrdersQuery({limit: rowsPerPage, skip: page * rowsPerPage})
-  
-  
-  // const rows = orders?.data ? Object.values(orders.data)[0] : null
-  // console.log(orders)
   const handleChangePage = (event, newPage) => {
     setPage(newPage);
   };
@@ -78,13 +72,7 @@ export default function StickyHeadTable() {
             </TableRow>
           </TableHead>
           <TableBody>
-            {/* {rows
-              .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
-              .map((row) => {
-                return (
-                  <Row row={row}></Row>
-                );
-              })} */}
+
               {rows.map((row) => {
                 return (
                   <Row row={row} key={row._id}></Row>

+ 157 - 0
my-diplom/src/components/ReactDropzone.jsx

@@ -0,0 +1,157 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import {useDropzone} from 'react-dropzone';
+
+const imgUpload = async () => {
+    await fetch('shop-roles.node.ed.asmer.org.ua/upload')
+}
+
+const baseStyle = {
+    flex: 1,
+    display: 'flex',
+    flexDirection: 'column',
+    alignItems: 'center',
+    padding: '20px',
+    borderWidth: 2,
+    borderRadius: 2,
+    borderColor: '#eeeeee',
+    borderStyle: 'dashed',
+    backgroundColor: '#fafafa',
+    color: '#bdbdbd',
+    outline: 'none',
+    transition: 'border .24s ease-in-out'
+  };
+  
+  const focusedStyle = {
+    borderColor: '#2196f3'
+  };
+  
+  const acceptStyle = {
+    borderColor: '#00e676'
+  };
+  
+  const rejectStyle = {
+    borderColor: '#ff1744'
+  };
+
+  const thumbsContainer = {
+    display: 'flex',
+    flexDirection: 'row',
+    flexWrap: 'wrap',
+    marginTop: 16
+  };
+  
+  const thumb = {
+    display: 'inline-flex',
+    borderRadius: 2,
+    border: '1px solid #eaeaea',
+    marginBottom: 8,
+    marginRight: 8,
+    width: 100,
+    height: 100,
+    padding: 4,
+    boxSizing: 'border-box'
+  };
+  
+  const thumbInner = {
+    //  display: 'flex',
+    //  minWidth: 0,
+    //  overflow: 'hidden'
+  };
+  
+  const img = {
+    // display: 'block',
+    width: "90px",
+    height: "90px",
+    objectFit: 'contain'
+  };
+
+export default function ReactDropzone(props) {
+    const [files, setFiles] = useState([]);
+
+    const {
+    getRootProps,
+    getInputProps,
+    isFocused,
+    isDragAccept,
+    isDragReject,
+    acceptedFiles,
+    fileRejections,
+  } = useDropzone({
+    accept: {
+      'image/*': ['.jpeg', '.png']
+    },
+    maxFiles:0,
+    onDrop: acceptedFiles => {
+        setFiles(acceptedFiles.map(file => Object.assign(file, {
+          preview: URL.createObjectURL(file)
+        })));
+      }
+  });
+  
+  useEffect(() => {
+    // Make sure to revoke the data uris to avoid memory leaks, will run on unmount
+    return () => files.forEach(file => URL.revokeObjectURL(file.preview));
+  }, []);
+
+
+  console.log("acceptedFiles", acceptedFiles, "files", files)
+
+  const thumbs = files.map(file => (
+    <div style={thumb} key={file.name}>
+      <div style={thumbInner}>
+        <img
+          src={file.preview}
+          style={img}
+          // Revoke data uri after image is loaded
+          onLoad={() => { URL.revokeObjectURL(file.preview) }}
+        />
+      </div>
+    </div>
+  ));
+
+
+
+  const filesArr = acceptedFiles.map(file => <li key={file.path}>{file.path}</li>);
+  const fileRejectionItems = fileRejections.map(({ file, errors }) => (
+    <li key={file.path}>
+      {file.path} - {file.size} bytes
+      <ul>
+        {errors.map(e => (
+          <li key={e.code}>{e.message}</li>
+        ))}
+      </ul>
+    </li>
+  ));
+ 
+  const style = useMemo(() => ({
+    ...baseStyle,
+    ...(isFocused ? focusedStyle : {}),
+    ...(isDragAccept ? acceptStyle : {}),
+    ...(isDragReject ? rejectStyle : {})
+  }), [
+    isFocused,
+    isDragAccept,
+    isDragReject
+  ]);
+
+  return (
+    <div className="container">
+      <div {...getRootProps({style})}>
+        <input {...getInputProps()} />
+        <p>Перетащите картинку аватар сюда или нажмите, чтобы выбрать файл</p>
+        <p>Вы можете загрузить только один файл!</p>
+      </div>
+      <aside style={thumbsContainer}>
+        {thumbs}
+      </aside>
+      <aside>
+        <h4>Files</h4>
+        <ul>{filesArr}</ul>
+        <h4>rejected Files</h4>
+        <ul>{fileRejectionItems}</ul>
+      </aside>
+      
+    </div>
+  );
+}
+

+ 129 - 0
my-diplom/src/components/Test1 copy.jsx

@@ -0,0 +1,129 @@
+import React, { useMemo, useState } from 'react';
+import { useDropzone } from 'react-dropzone';
+import { Box } from '@mui/material';
+import Dnd from './DnD';
+import { useSetUploadImageMutation } from '../store/ImgUploadApi';
+
+function Image({ url, click }) {
+
+  return (
+    <img className="good-image" src={`http://shop-roles.node.ed.asmer.org.ua/${url}`} onClick={click} style={{
+      width: "100%",
+      height: "100%",
+      objectFit: "contain",
+    }}/>
+  )
+}
+
+const baseStyle = {
+  flex: 1,
+  display: 'flex',
+  flexDirection: 'column',
+  alignItems: 'center',
+  padding: '20px',
+  borderWidth: 2,
+  borderRadius: 2,
+  borderColor: '#eeeeee',
+  borderStyle: 'dashed',
+  backgroundColor: '#fafafa',
+  color: '#bdbdbd',
+  outline: 'none',
+  transition: 'border .24s ease-in-out'
+};
+
+const focusedStyle = {
+  borderColor: '#2196f3'
+};
+
+const acceptStyle = {
+  borderColor: '#00e676'
+};
+
+const rejectStyle = {
+  borderColor: '#ff1744'
+};
+
+const ImageOnDnd = ({ image, onDelete }) =>
+  <Box sx={{ width: '100px', mr: '20px' }} key={image._id}>
+    <Image url={image?.url} click={() => onDelete(image)} />
+  </Box>
+
+function ImageUploader({ passImages, onChange = Function.prototype }) { // onChange функция из пропсов запускаемая при обновлении массива картинок.
+  const [images, setImages] = useState(passImages || []); //passImages массив картинок из пропсов, 
+  const [uploadImage, result] = useSetUploadImageMutation(); //   uploadImage функция мутации (отправки данных на сервер)
+  console.log('result', result);
+
+  // const onDrop = useCallback(acceptedFiles => {
+  //   filesUpload(acceptedFiles).then(imagesResponse => {
+  //     setImages(images.concat(imagesResponse));
+  //   });
+  // }, [])
+
+  const updateImages = images => {
+    console.log('images', images)
+    setImages(images);
+    onChange(images);
+  }
+
+  const onDrop = acceptedFiles => {
+    console.log('дропнуло', acceptedFiles)
+
+     filesUpload(acceptedFiles).then(imagesResponse => {
+       updateImages(images.concat(imagesResponse));
+     });
+  }
+
+  const filesUpload = (acceptedFiles) => {
+    return Promise.all(acceptedFiles.map(fileUpload));
+  }
+
+  const fileUpload = (file) => {
+   
+    const formData = new FormData();
+    
+    formData.append('photo', file);
+    console.log('formData', formData.get("photo"))
+    return uploadImage(formData).then(response => {
+      console.log('response', response);
+      return Promise.resolve(response.data);
+    });
+  }
+
+  const { getRootProps, getInputProps, isDragActive, isFocused, isDragAccept, isDragReject } = useDropzone({ accept: { 'image/*': [] }, onDrop });
+
+  const style = useMemo(() => ({
+    ...baseStyle,
+    ...(isFocused ? focusedStyle : {}),
+    ...(isDragAccept ? acceptStyle : {}),
+    ...(isDragReject ? rejectStyle : {})
+  }), [
+    isFocused,
+    isDragAccept,
+    isDragReject
+  ]);
+
+  
+
+  
+  const deleteImage = image => updateImages(images.filter(i => i != image));
+
+  const localImage = ({ image }) => <ImageOnDnd image={image} onDelete={imgToDelete => deleteImage(imgToDelete)} />
+
+  return (
+    <>
+      <div {...getRootProps({ style })}>
+        <input {...getInputProps()} />
+        {
+          isDragActive ?
+            <p>Drop the files here ...</p> :
+            <p>Drag 'n' drop some files here, or click to select files</p>
+        }
+      </div>
+      <Box sx={{ display: 'flex', m: '20px auto 0' }}>
+        <Dnd items={images} render={localImage} onChange={images => updateImages(images)} itemProp="image" keyField="_id" horizontal />
+      </Box>
+    </>
+  )
+}
+
+export default ImageUploader;

+ 0 - 171
my-diplom/src/components/Test1.jsx

@@ -1,171 +0,0 @@
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import Box from '@mui/material/Box';
-import Collapse from '@mui/material/Collapse';
-import IconButton from '@mui/material/IconButton';
-import Table from '@mui/material/Table';
-import TableBody from '@mui/material/TableBody';
-import TableCell from '@mui/material/TableCell';
-import TableContainer from '@mui/material/TableContainer';
-import TableHead from '@mui/material/TableHead';
-import TableRow from '@mui/material/TableRow';
-import Typography from '@mui/material/Typography';
-import Paper from '@mui/material/Paper';
-import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
-import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
-
-// function createData(name, calories, fat, carbs, protein, price) {
-//   return {
-//     name,
-//     calories,
-//     fat,
-//     carbs,
-//     protein,
-//     price,
-//     history: [
-//       {
-//         date: '2020-01-05',
-//         customerId: '11091700',
-//         amount: 3,
-//       },
-//       {
-//         date: '2020-01-02',
-//         customerId: 'Anonymous',
-//         amount: 1,
-//       },
-//     ],
-//   };
-// }
-
-function Row(props) {
-  const { row } = props;
-  const [open, setOpen] = React.useState(false);
- 
-
-  return (
-    <React.Fragment>
-      <TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
-        <TableCell>
-          <IconButton
-            aria-label="expand row"
-            size="small"
-            onClick={() => setOpen(!open)}
-          >
-            {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
-          </IconButton>
-        </TableCell>
-        <TableCell component="th" scope="row">
-          {row._id}
-        </TableCell>
-        <TableCell align="right">{row. createdAt}</TableCell>
-        <TableCell align="right">{row.total}</TableCell>
-      </TableRow>
-      <TableRow>
-        <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
-          <Collapse in={open} timeout="auto" unmountOnExit>
-            <Box sx={{ margin: 1 }}>
-              <Typography variant="h6" gutterBottom component="div">
-                Подробности заказа
-              </Typography>
-              <Table size="small" aria-label="purchases">
-                <TableHead>
-                  <TableRow>
-                    <TableCell>ID товара</TableCell>
-                    <TableCell>Название</TableCell>
-                    <TableCell align="right">Количество (шт)</TableCell>
-                    <TableCell align="right">Цена (грн.)</TableCell>
-                    <TableCell align="right">Сумма (грн.)</TableCell>
-                  </TableRow>
-                </TableHead>
-                <TableBody>
-
-                {row.orderGoods ? 
-                  row.orderGoods.map((orderGood) => {
-                    console.log('orderGood true', orderGood)
-                    if(orderGood){
-                      return (
-                        <TableRow key={orderGood._id}>
-                        <TableCell component="th" scope="row">
-                        {orderGood?._id}
-                      </TableCell>
-                      <TableCell >
-                        {orderGood?.goodName || ''}
-                      </TableCell>
-                      <TableCell align="right">
-                        {orderGood?.count}
-                      </TableCell>
-                      <TableCell align="right">
-                        {orderGood?.price}
-                      </TableCell>
-                      <TableCell align="right">
-                        {orderGood?.total}
-                      </TableCell>
-                        </TableRow>
-                      )
-                    }
-                    } )
-                :
-                null 
-                }
-
-                </TableBody>
-              </Table>
-            </Box>
-          </Collapse>
-        </TableCell>
-      </TableRow>
-    </React.Fragment>
-  );
-}
-
-// Row.propTypes = {
-//   row: PropTypes.shape({
-//     calories: PropTypes.number.isRequired,
-//     carbs: PropTypes.number.isRequired,
-//     fat: PropTypes.number.isRequired,
-//     history: PropTypes.arrayOf(
-//       PropTypes.shape({
-//         amount: PropTypes.number.isRequired,
-//         customerId: PropTypes.string.isRequired,
-//         date: PropTypes.string.isRequired,
-//       }),
-//     ).isRequired,
-//     name: PropTypes.string.isRequired,
-//     price: PropTypes.number.isRequired,
-//     protein: PropTypes.number.isRequired,
-//   }).isRequired,
-// };
-
-// const rows = [
-//   createData('Frozen yoghurt', 159, 6.0, 24, 4.0, 3.99),
-//   createData('Ice cream sandwich', 237, 9.0, 37, 4.3, 4.99),
-//   createData('Eclair', 262, 16.0, 24, 6.0, 3.79),
-//   createData('Cupcake', 305, 3.7, 67, 4.3, 2.5),
-//   createData('Gingerbread', 356, 16.0, 49, 3.9, 1.5),
-// ];
-
-export default function CollapsibleTable({rows}) {
-
-
-  return (
-    <TableContainer component={Paper}>
-      <Table aria-label="collapsible table">
-        <TableHead>
-          <TableRow>
-            <TableCell />
-            <TableCell>Dessert (100g serving)</TableCell>
-            <TableCell align="right">Calories</TableCell>
-            <TableCell align="right">Fat&nbsp;(g)</TableCell>
-            <TableCell align="right">Carbs&nbsp;(g)</TableCell>
-            <TableCell align="right">Protein&nbsp;(g)</TableCell>
-          </TableRow>
-        </TableHead>
-        <TableBody>
-          {rows.map((row) => (
-            <Row key={row._id} row={row} />
-          ))}
-        </TableBody>
-      </Table>
-    </TableContainer>
-  );
-}

+ 111 - 0
my-diplom/src/components/adminPage/AddNewGood.jsx

@@ -0,0 +1,111 @@
+import { Box, Button, Container, CssBaseline, Grid, TextField, Typography } from "@mui/material"
+import { useState } from "react"
+import { useSetGoodUpsertMutation } from "../../store/categoriesApi"
+import ImageUploader from "../ImageUploader"
+import RecursiveTreeView from "./pages/MuiTreeWithCheckBox"
+
+
+const AddNewGood = () => {
+    const [selected, setSelect] = useState([])
+    const [images, setImages] = useState([])
+    const [GoodUpsert, result] = useSetGoodUpsertMutation()
+
+
+
+
+
+    const handlerGoodUpsert = (event) => {
+        event.preventDefault();
+        const data = new FormData(event.currentTarget);
+        
+        const goodToUpsert = {
+            name: data.get('goodsName') || null,
+            price: +data.get('goodsPrice') || null,
+            description: data.get('goodDescription') || null,
+            categories: selected.map(id=>({ _id: id })),
+            images: images.map(image=>({_id: image._id}))
+        }
+        GoodUpsert(goodToUpsert).then(res => console.log(res))
+    }
+
+    return (
+        <>
+        <Container component="main" maxWidth="sm">
+        <CssBaseline />
+        <Box
+          sx={{
+            marginTop: 2,
+            display: 'flex',
+            flexDirection: 'column',
+            alignItems: 'center',
+          }}
+        >
+
+          <Typography component="h1" variant="h5">
+            Создание товара
+          </Typography>
+          <Box component="form" noValidate onSubmit={handlerGoodUpsert} sx={{ mt: 3 }}>
+            <Grid container spacing={2}>
+              <Grid item xs={12} sm={6}>
+                <TextField
+                  name="goodsName"
+                  required
+                  fullWidth
+                  id="goodsName"
+                  label="Название"
+                  autoFocus
+                />
+              </Grid>
+              <Grid item xs={12} sm={6}>
+                <TextField
+                  required
+                  fullWidth
+                  id="goodsPrice"
+                  label="Цена, грн."
+                  name="goodsPrice"
+                  type={"number"}
+                />
+              </Grid>
+
+              <Grid item xs={12}>
+                <TextField
+                  fullWidth
+                  multiline
+                  rows={10}
+                  name="goodDescription"
+                  label="Описание"
+                  id="goodDescription"
+                />
+              </Grid>
+
+            </Grid>
+            <Grid item xs={12} mt={1}>
+            <Typography mb={2}>Выберите категорию: </Typography>
+            <Box maxHeight={"300px"} overflow={"scroll"}>
+            <RecursiveTreeView callback={(arr)=>setSelect(arr)}/>
+            </Box>
+            </Grid>
+            <Grid item xs={12} mt={1}>
+            <Typography mb={2}>Добавьте изображения товара:</Typography>
+            <ImageUploader onChange={(images)=>setImages(images)}/>
+            </Grid>
+            
+            <Button
+              type="submit"
+              fullWidth
+              variant="contained"
+              sx={{ mt: 3, mb: 2 }}
+            >
+              Создать товар
+            </Button>
+
+          </Box>
+        </Box>
+      </Container>
+      
+        </>
+        
+    )
+}
+
+export default AddNewGood

+ 138 - 0
my-diplom/src/components/adminPage/AdminGoodsTable.jsx

@@ -0,0 +1,138 @@
+import * as React from 'react';
+import Paper from '@mui/material/Paper';
+import Table from '@mui/material/Table';
+import TableBody from '@mui/material/TableBody';
+import TableCell from '@mui/material/TableCell';
+import TableContainer from '@mui/material/TableContainer';
+import TableHead from '@mui/material/TableHead';
+import TablePagination from '@mui/material/TablePagination';
+import TableRow from '@mui/material/TableRow';
+import { Box, Button, Collapse, IconButton, Link, Typography } from '@mui/material';
+import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
+import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
+import { useSelector } from 'react-redux';
+import { useGetGoodCountQuery, useGetGoodFindQuery } from '../../store/categoriesApi';
+import { Link as RouterLink } from "react-router-dom"
+
+
+
+
+
+
+function TimestampConv(value) {
+  const createdAtFormat = new Date(+value)
+  const year = createdAtFormat.getFullYear()
+  const month = createdAtFormat.getMonth() < 9 ? "0" + (createdAtFormat.getMonth() + 1) : createdAtFormat.getMonth() + 1
+  const day = createdAtFormat.getDate() < 10 ? "0" + (createdAtFormat.getDate()) : createdAtFormat.getDate()
+  const hours = createdAtFormat.getHours() < 10 ? "0" + (createdAtFormat.getHours()) : createdAtFormat.getHours()
+  const minutes = createdAtFormat.getMinutes() < 10 ? "0" + (createdAtFormat.getMinutes()) : createdAtFormat.getMinutes()
+  const createdAtForTable = `${year}.${month}.${day} ${hours}:${minutes} `
+  return createdAtForTable
+}
+
+
+export default function AdminGoodsTable() {
+  const userId = useSelector(state => state.auth.user?.sub.id)
+  const [page, setPage] = React.useState(0);
+  const [rowsPerPage, setRowsPerPage] = React.useState(10);
+  const {data: GoodCount} = useGetGoodCountQuery({})
+  const count = GoodCount?.GoodCount || 1
+  const goods = useGetGoodFindQuery({limit: rowsPerPage, skip: page * rowsPerPage, sort: {_id: -1}})
+  const rows = goods?.data ? Object.values(goods.data)[0] : []
+  
+
+  const handleChangePage = (event, newPage) => {
+    setPage(newPage);
+  };
+
+  const handleChangeRowsPerPage = (event) => {
+    setRowsPerPage(+event.target.value);
+    setPage(0);
+  };
+
+  return (
+    <Paper sx={{ width: '100%', overflow: 'hidden', display: "flex", flexDirection: "column", alignItems: "center" }}>
+      <Typography variant='h4'>Каталог товаров</Typography>
+      <TablePagination
+        rowsPerPageOptions={[10, 25, 50, 100]}
+        component="div"
+        count={GoodCount?.GoodCount || 1}
+        rowsPerPage={rowsPerPage}
+        page={page}
+        onPageChange={handleChangePage}
+        onRowsPerPageChange={handleChangeRowsPerPage}
+      />
+      <TableContainer sx={{ maxWidth: 1200 }}>
+        <Table stickyHeader size="small" aria-label="sticky table">
+          <TableHead>
+            <TableRow>
+              <TableCell />
+              <TableCell align="left">ID</TableCell>
+              <TableCell align="left" sortDirection='asc'>Дата создания</TableCell>
+              <TableCell align="left">Название</TableCell>              
+              <TableCell align="left">Цена, грн.</TableCell>
+              <TableCell align="left">Категория</TableCell>
+            </TableRow>
+          </TableHead>
+          <TableBody>
+
+              {rows.map((row) => {
+                return (
+                  <Row row={row} key={row._id}></Row>
+                );
+              })}
+          </TableBody>
+        </Table>
+      </TableContainer>
+      <TablePagination
+        rowsPerPageOptions={[10, 25, 50, 100]}
+        component="div"
+        count={count || 1}
+        rowsPerPage={rowsPerPage}
+        page={page}
+        onPageChange={handleChangePage}
+        onRowsPerPageChange={handleChangeRowsPerPage}
+      />
+    </Paper>
+  );
+}
+
+function Row(props) {
+  const { row } = props;
+  const [open, setOpen] = React.useState(false);
+
+  return (
+    <React.Fragment>
+      <TableRow sx={{ '*': { borderBottom: 'unset' } }}>
+        <TableCell>
+          <IconButton
+            aria-label="expand row"
+            size="small"
+            onClick={() => setOpen(!open)}
+          >
+            {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
+          </IconButton>
+        </TableCell>
+        <TableCell component="th" scope="row" align="left">
+          {row._id}
+        </TableCell>
+        <TableCell align="center">{TimestampConv(row.createdAt)}</TableCell>
+        <TableCell align="left">{(row.name)}</TableCell>
+        <TableCell align="left">{row.price}</TableCell>
+        {/* <TableCell align='center'>{}</TableCell> */}
+        <TableCell align='left'>{Array.isArray(row.categories) ? row.categories[0].name : 0}</TableCell>
+      </TableRow>
+      <TableRow>
+        <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
+          <Collapse in={open} timeout="auto" unmountOnExit>
+            <Box sx={{ margin: 1 }}>
+              <Link to={`/admin/good/${row._id}`} underline="none" variant="button" sx={{ color: "ButtonText" }} component={RouterLink}>
+                <Button variant='outlined'>Редактировать</Button>
+              </Link>
+            </Box>
+          </Collapse>
+        </TableCell>
+      </TableRow>
+    </React.Fragment>
+  );
+}

+ 24 - 20
my-diplom/src/components/adminPage/AdminHeader.jsx

@@ -1,36 +1,40 @@
 import { ShoppingBasket, Person, Menu } from "@mui/icons-material"
 import { AppBar, IconButton, Toolbar, Link, Box, Badge, Button, Typography } from "@mui/material"
-import { Link as RouterLink} from "react-router-dom"
+import { Link as RouterLink } from "react-router-dom"
 import { useSelector, useDispatch } from "react-redux"
 import { setCloseAdminMenu, setIsOpenAdminMenu } from "../../store/adminMenuSlice"
 
 
 
 
-const AdminHeader = ({handleCart, orderLenght}) => {
-    
+const AdminHeader = ({ handleCart, orderLenght }) => {
+
     const dispatch = useDispatch()
-   
+
 
     return (
 
-        <AppBar position="static" sx={{mb:2}}>
-            <Toolbar>
-            <IconButton
-                    color="inherit"
-                    onClick={() => dispatch(setIsOpenAdminMenu())}
-                    sx={{mr:1}}>
-                    <Menu/>
-
-                </IconButton>
-                <Box sx={{ flexGrow: 1}}>
-                    <Link to="/admin" underline="none" variant="h5" sx={{ color: "white" }} component={RouterLink} >
-                        My Shop <Typography component={'span'}>(панель администратора)</Typography>
-                    </Link>
+        <AppBar position="static" sx={{ mb: 2}}>
+            <Toolbar sx={{justifyContent: "space-between"}} >
+                <Box display={"flex"} alignItems={"center"}>
+                    <IconButton
+                        color="inherit"
+                        onClick={() => dispatch(setIsOpenAdminMenu())}
+                        sx={{ mr: 1 }}>
+                        <Menu />
+
+                    </IconButton>
+                    <Box sx={{ flexGrow: 1 }}>
+                        <Link to="/admin" underline="none" variant="h5" sx={{ color: "white" }} component={RouterLink} >
+                            My Shop <Typography component={'p'}>(панель администратора)</Typography>
+                        </Link>
+                    </Box>
+                </Box>
+                <Box>
+                <Link to="/" underline="none" variant="h5" sx={{ color: "white" }} component={RouterLink} >
+                            <Button size="small" variant="contained" color="secondary">магазин</Button>
+                        </Link>
                 </Box>
-
-
-                
             </Toolbar>
         </AppBar>
 

+ 59 - 44
my-diplom/src/components/adminPage/AdminMenu.jsx

@@ -1,8 +1,8 @@
-import { Close, ShoppingBasket } from "@mui/icons-material"
-import { Box, Button, Divider, Drawer, IconButton, InputAdornment, List, ListItem, ListItemIcon, ListItemText, TextField, Typography } from "@mui/material"
+import { AccountTree, BarChart, Category, Close, FmdGood, GppGood, Inventory, LocalGroceryStoreTwoTone, LocalMall, PeopleAlt, ShoppingBasket } from "@mui/icons-material"
+import { Box, Button, Divider, Drawer, IconButton, InputAdornment, List, ListItem, ListItemButton, ListItemIcon, ListItemText, TextField, Typography } from "@mui/material"
 import { lightGreen } from "@mui/material/colors"
 import { useDispatch, useSelector } from "react-redux"
-import { Link } from "react-router-dom"
+import { Link, useNavigate } from "react-router-dom"
 import { setCloseAdminMenu } from "../../store/adminMenuSlice"
 
 
@@ -11,53 +11,68 @@ const AdminMenu = (props) => {
     const adminMenuIsOpen = (useSelector(state => state.adminMenu.isOpen))
     console.log('adminMenuIsOpen', adminMenuIsOpen)
     const dispatch = useDispatch()
-    
+    const navigate = useNavigate()
+
     return (
         <Drawer
             anchor="left"
             open={adminMenuIsOpen}
-            onClose={()=> dispatch(setCloseAdminMenu()) }
-            sx={{position: "relative" }}
-
+            onClose={() => dispatch(setCloseAdminMenu())}
+            sx={{ position: "relative" }}
         >
-        <Box display={"flex"} justifyContent={"end"} mt={0.5} mb={0.5}>
-            <IconButton size="small" onClick={()=> dispatch(setCloseAdminMenu())}>
-                <Close fontSize="small"/>
-            </IconButton>
-        </Box>
-            <List sx={{
-
-                // display: "flex",
-                // justifyContent: "space-between",
-                backgroundColor: "red",
-                zIndex: 1,
-                width: '100px'
-            }}>
-                {/* <ListItem >
-                    <ListItemIcon sx={{ minWidth: "30px" }}>
-                        <ShoppingBasket />
-                    </ListItemIcon>
-                    <ListItemText primary="Корзина" />
-                </ListItem>
-                <ListItem>
-                <Button sx={{ mr: 1 }}  variant="outlined" onClick={() => dispatch(CartClear())}>Очитить корзину</Button>
-                    {userAuth ? 
-                    <Button variant="outlined" onClick={() => dispatch(actionOrderUpsert(cartStateForOrderUpsert))}>Оформить заказ</Button> :
-                    <Button disabled variant="outlined" >Оформить заказ</Button>
-                    }                    
-                </ListItem>
-
-            </List>
-            
-            <Divider />
+            <Box sx={{ minWidth: "320px" }}>
+                <Box display={"flex"} justifyContent={"end"} mt={0.5} mb={0.5}>
+                    <IconButton size="small" onClick={() => dispatch(setCloseAdminMenu())}>
+                        <Close fontSize="small" />
+                    </IconButton>
+                </Box>
+                <List sx={{
+                    zIndex: 1,
 
-            <List sx={{ mt: "80px",  minWidth: "350px",
-                maxWidth: "600px"  }}>
-                {!userAuth && <Typography mt={'10px'} ml={1} color={"red"}>Для оформления заказа необходимa <Link to='/login'>авторизация</Link></Typography>}
-                {cartState.length ?
-                    cartState.map(productsRender) : <ListItem>Ваша корзина пока пуста! </ListItem>
-                } */}
-            </List>
+                }}>
+                    <ListItem >
+                    <ListItemButton onClick={() => navigate('/admin')}>
+                        <ListItemIcon >
+                            <BarChart />
+                        </ListItemIcon>
+                        <ListItemText primary="Статистика" />
+                        </ListItemButton>
+                    </ListItem>
+                    <ListItem >
+                    <ListItemButton onClick={() => navigate('/admin/orders')}>
+                        <ListItemIcon >
+                            <ShoppingBasket />
+                        </ListItemIcon>
+                        <ListItemText primary="Заказы" />
+                        </ListItemButton>
+                    </ListItem>
+                    
+                    <ListItem >
+                    <ListItemButton onClick={() => navigate('/admin/goods')}>
+                        <ListItemIcon >
+                            <Inventory />
+                        </ListItemIcon>
+                        <ListItemText primary="Товары" />
+                        </ListItemButton>
+                    </ListItem>
+                    <ListItem >
+                    <ListItemButton onClick={() => navigate('/admin/categories')}>
+                        <ListItemIcon >
+                            <AccountTree />
+                        </ListItemIcon>
+                        <ListItemText primary="Категории" />
+                        </ListItemButton>
+                    </ListItem>
+                    <ListItem >
+                    <ListItemButton onClick={() => navigate('/admin/users')}>
+                        <ListItemIcon >
+                            <PeopleAlt />
+                        </ListItemIcon>
+                        <ListItemText primary="Клиенты" />
+                        </ListItemButton>
+                    </ListItem>
+                </List>
+            </Box>
 
         </Drawer>
     )

+ 0 - 1
my-diplom/src/components/adminPage/LayoutAdmin.js

@@ -18,7 +18,6 @@ const LayoutAdmin = ({children}) => {
      }
     return <>
     <AdminHeader/>
-    <p>зашли</p>
     <Outlet/>
     <AdminMenu/>
     </>

+ 140 - 0
my-diplom/src/components/adminPage/pages/AdminGoodMutation.jsx

@@ -0,0 +1,140 @@
+import { Box, Button, Container, CssBaseline, Grid, TextField, Typography } from "@mui/material"
+import { useEffect, useState } from "react"
+import { useGetProductQuery, useSetGoodUpsertMutation } from "../../../store/categoriesApi"
+import ImageUploader from "../../ImageUploader"
+import RecursiveTreeView from "./MuiTreeWithCheckBox"
+import { useParams } from "react-router-dom"
+
+
+const AdminGoodMutation = () => {
+    const [selected, setSelect] = useState([])
+    const [images, setImages] = useState([])
+    const [stateGood, setStateGood] = useState({})
+
+    const [GoodUpsert, result] = useSetGoodUpsertMutation()
+    const { goodId } = useParams()
+    const { data: payload = null } = useGetProductQuery(goodId)
+    const good = payload ? Object.values(payload)[0] : null
+    console.log("stateGood", stateGood)
+    useEffect(() => {
+       setStateGood(good)
+       setSelect(good?.categories ? good?.categories.map((cat)=>cat._id) : [])
+    }, [payload])
+
+
+    console.log("selected", selected)
+
+
+
+    const handlerGoodUpsert = (event) => {
+        event.preventDefault();
+        const data = new FormData(event.currentTarget);
+
+        const goodToUpsert = {
+            _id: stateGood._id,
+            name: stateGood.name || null,
+            price: +stateGood.price || null,
+            description: stateGood.description || null,
+            categories: selected.map(id => ({ _id: id })),
+            images: images.map(image => ({ _id: image._id }))
+        }
+        console.log("goodToUpsert", goodToUpsert)
+        GoodUpsert(goodToUpsert).then(res => console.log(res))
+    }
+
+    return (
+        <>
+            <Container component="main" maxWidth="sm">
+                <CssBaseline />
+                <Box
+                    sx={{
+                        marginTop: 2,
+                        display: 'flex',
+                        flexDirection: 'column',
+                        alignItems: 'center',
+                    }}
+                >
+
+                    <Typography component="h1" variant="h5">
+                        Редактирование товара
+                    </Typography>
+                    <Box component="form" noValidate onSubmit={handlerGoodUpsert} sx={{ mt: 3 }}>
+                        <Grid container spacing={2}>
+                            <Grid item xs={12} sm={6}>
+                                <TextField
+                                    name="goodsName"
+                                    required
+                                    fullWidth
+                                    id="goodsName"
+                                    label="Название"
+                                    autoFocus
+                                    value={stateGood?.name || ""}
+                                    onChange={ 
+                                        (event) => setStateGood({...stateGood, name: event.target.value  })
+                                        } 
+                                ></TextField>
+                            </Grid>
+                            <Grid item xs={12} sm={6}>
+                                <TextField
+                                    required
+                                    fullWidth
+                                    id="goodsPrice"
+                                    label="Цена, грн."
+                                    name="goodsPrice"
+                                    type={"number"}
+                                    value={stateGood?.price || ""}
+                                    onChange={ 
+                                        (event) => setStateGood({...stateGood, price: event.target.value  })
+                                        } 
+                                />
+                            </Grid>
+
+                            <Grid item xs={12}>
+                                <TextField
+                                    fullWidth
+                                    multiline
+                                    rows={10}
+                                    name="goodDescription"
+                                    label="Описание"
+                                    id="goodDescription"
+                                    value={stateGood?.description || ""}
+                                    onChange={ 
+                                        (event) => setStateGood({...stateGood, description: event.target.value  })
+                                        } 
+                                />
+                            </Grid>
+
+                        </Grid>
+                        <Grid item xs={12} mt={1}>
+                            <Typography mb={2}>Измените категории: </Typography>
+                            <Box maxHeight={"300px"} overflow={"scroll"}>
+                            { stateGood?._id && <RecursiveTreeView
+                                callback={(arr) => setSelect(arr)}
+                                selectedCat={selected} />
+                            }
+                            </Box>
+                        </Grid>
+                        <Grid item xs={12} mt={1}>
+                            <Typography mb={2}>Добавьте изображения товара или нажмите на фото для удаления:</Typography>
+                            { stateGood?._id && <ImageUploader onChange={(images) => setImages(images)} passImages={stateGood?.images} />}
+                        </Grid>
+
+                        <Button
+                            type="submit"
+                            fullWidth
+                            variant="contained"
+                            sx={{ mt: 3, mb: 2 }}
+                        >
+                            Сохранить изменения
+                        </Button>
+
+                    </Box>
+                </Box>
+            </Container>
+
+        </>
+
+    )
+}
+
+export default AdminGoodMutation

+ 60 - 0
my-diplom/src/components/adminPage/pages/AdminGoods.jsx

@@ -0,0 +1,60 @@
+import { Box, Button } from "@mui/material"
+import { Link } from "react-router-dom"
+import { useGetGoodFindQuery } from "../../../store/categoriesApi"
+import AdminGoodsTable from "../AdminGoodsTable"
+
+
+
+
+
+const AdminGoods = () => {
+  const getGoodFind = useGetGoodFindQuery(
+    {
+    categoryId: null,
+    sort: {price: 1},
+    skip: 0, 
+    limit: 100
+})
+
+console.log(getGoodFind)
+
+//   const sortQeryArr = [
+//     {
+//         categoryId: null,
+//         sort: {price: 1},
+//         skip: (page - 1) * limit, 
+//         limit
+//     },
+//     {
+//         categoryId: null,
+//         sort: {price: -1},
+//         skip: (page - 1) * limit,
+//         limit
+//     },
+//     {
+//         categoryId: null,
+//         sort: {name: 1},
+//         skip: (page - 1) * limit,
+//         limit
+//     },
+//     {
+//         categoryId: null,
+//         sort: {createdAt: -1},
+//         skip: (page - 1) * limit,
+//         limit
+//     },
+// ]
+
+
+  return (
+    <Box width={'100%'} >
+    <Link to='/admin/addNewGood'><Button>Добавить товар</Button></Link>
+    
+      <Box width={'100%'} mb={2} p={1} sx={{ display: 'flex', justifyContent: "space-between" }}>
+        <AdminGoodsTable/>
+      </Box>
+    </Box>
+  )
+}
+
+export default AdminGoods

+ 22 - 0
my-diplom/src/components/adminPage/pages/AdminOrders.jsx

@@ -0,0 +1,22 @@
+import { Box, Button } from "@mui/material"
+import StickyHeadTable from "../../OrdersTableCollaps"
+import EnhancedTable from "../../ImageUploader"
+
+
+
+const AdminOrders = () => {
+  
+
+
+  return (
+    <Box width={'100%'} >
+    
+      <Box width={'100%'} mb={2} p={1} sx={{ display: 'flex', justifyContent: "space-between" }}>
+        {/* <StickyHeadTable /> */}
+        <EnhancedTable/>
+      </Box>
+    </Box>
+  )
+}
+
+export default AdminOrders

+ 19 - 0
my-diplom/src/components/adminPage/pages/AdminUsers.jsx

@@ -0,0 +1,19 @@
+import { Box, Button } from "@mui/material"
+
+
+
+
+const AdminOrders = () => {
+  
+
+
+  return (
+    <Box width={'100%'} >
+      <Box width={'100%'} mb={2} p={1} sx={{ display: 'flex', justifyContent: "space-between" }}>
+        
+      </Box>
+    </Box>
+  )
+}
+
+export default AdminOrders

+ 152 - 0
my-diplom/src/components/adminPage/pages/MuiTreeWithCheckBox.jsx

@@ -0,0 +1,152 @@
+import { TreeItem, TreeView } from "@mui/lab";
+import { Checkbox, FormControlLabel } from "@mui/material";
+import React, { useEffect } from "react";
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+import ChevronRightIcon from '@mui/icons-material/ChevronRight';
+import { useGetCategoriesAllQuery } from "../../../store/categoriesApi";
+
+let defaultExpandedArr = ["0"]
+
+const fullTree = (arrCategories) => {
+    arrCategories = JSON.parse(JSON.stringify(arrCategories));
+    const recursion = (categoryObj) => {
+      defaultExpandedArr.push(categoryObj._id)
+     categoryObj.children = []
+    
+
+    if (categoryObj.subCategories !== null && categoryObj.subCategories?.length !== 0 ) {
+        categoryObj.subCategories.forEach(element =>{
+            arrChild.forEach( (value) => { if (value._id === element._id) {
+                categoryObj.children.push(value)
+                recursion(value)
+            }} )
+        })
+        } 
+    }
+    
+    const arrParent = arrCategories.filter((value) => value.parent === null )
+    const arrChild = arrCategories.filter((value) => value.parent !== null )
+   
+
+    arrParent.forEach(recursion)
+    
+    return arrParent
+    }
+  
+
+ const RecursiveTreeView = ({callback, selectedCat = []}) => {
+  // console.log('selectedCat', selectedCat)
+  const [selected, setSelected] = React.useState(selectedCat);
+  // console.log('setSelect', selected)
+
+  const {data: payloaddata} = useGetCategoriesAllQuery()
+  const categories = payloaddata?.CategoryFind
+  
+useEffect((()=>setSelected(selectedCat)),[selectedCat])
+  
+  let data = {}
+
+  if (categories) {
+ 
+  
+
+  data = {
+        _id: "0",
+        name: "Все категории",
+        children: fullTree(categories)
+  }
+}
+
+  // console.log('data', data)
+  
+ 
+  function getChildById(node, _id) {
+    let array = [];
+
+    function getAllChild(nodes) {
+      if (nodes === null) return [];
+      array.push(nodes._id);
+      if (Array.isArray(nodes.children)) {
+        nodes.children.forEach(node => {
+          array = [...array, ...getAllChild(node)];
+          array = array.filter((v, i) => array.indexOf(v) === i);
+        });
+      }
+      return array;
+    }
+
+    function getNodeById(nodes, _id) {
+      if (nodes._id === _id) {
+        return nodes;
+      } else if (Array.isArray(nodes.children)) {
+        let result = null;
+        nodes.children.forEach(node => {
+          if (!!getNodeById(node, _id)) {
+            result = getNodeById(node, _id);
+          }
+        });
+        return result;
+      }
+
+      return null;
+    }
+
+    return getAllChild(getNodeById(node, _id));
+  }
+
+  function getOnChange(checked, nodes) {
+    const allNode = getChildById(data, nodes._id);
+    let array = checked
+      ? [...selected, ...allNode]
+      : selected.filter(value => !allNode.includes(value));
+
+    array = array.filter((v, i) => array.indexOf(v) === i);
+
+    callback(array)
+    setSelected(array);
+    
+  }
+
+  const renderTree = (nodes) => {
+
+    return (
+    <TreeItem
+      key={nodes._id}
+      nodeId={nodes._id}
+      label={
+        <FormControlLabel
+          control={
+            <Checkbox
+              checked={selected.some(item => item === nodes._id)}
+              onChange={event =>
+                getOnChange(event.currentTarget.checked, nodes)
+              }
+              onClick={e => e.stopPropagation()}
+            />
+          }
+          label={<>{nodes.name}</>}
+          key={nodes._id}
+        />
+      }
+    >
+      {Array.isArray(nodes.children)
+        ? nodes.children.map(node => renderTree(node))
+        : null}
+    </TreeItem>
+  )};
+
+  return (
+    <TreeView
+      defaultCollapseIcon={<ExpandMoreIcon />}
+
+      defaultExpanded={defaultExpandedArr}
+      defaultExpandIcon={<ChevronRightIcon />}
+      
+      
+    >
+      {renderTree(data)}
+    </TreeView>
+  );
+}
+
+export default RecursiveTreeView

+ 1 - 1
my-diplom/src/components/public-pages/CategoryOnePage.jsx

@@ -38,7 +38,7 @@ const CategoryOnePage = () => {
         },
         {
             categoryId: catId,
-            sort: {createdAt: 1},
+            sort: {createdAt: -1},
             skip: (page - 1) * limit,
             limit
         },

+ 1 - 0
my-diplom/src/components/public-pages/ProductOnePage.jsx

@@ -2,6 +2,7 @@ import { Grid, Typography } from "@mui/material"
 import { useParams } from "react-router-dom"
 import { useGetProductQuery } from "../../store/categoriesApi"
 import { CarouselProductOnePage } from "../CarouselProductOnePage"
+import ImageUploader from "../ImageUploader"
 
 
 const ProductOnePage = () => {

+ 3 - 4
my-diplom/src/components/public-pages/ProfilePage.jsx

@@ -7,7 +7,8 @@ import { setCredentialsInitial, setNull } from "../../store/authSlice";
 import { useGetOrdersCountQuery, useGetUserByIdQuery } from "../../store/categoriesApi";
 import OrdersTableCollaps from "../OrdersTableCollaps";
 import ProfileMutationForm from "../ProfileMutationForm";
-import Test1 from "../Test1"
+import ReactDropzone from "../ReactDropzone";
+import Test1 from "../ImageUploader"
 
 const ProfilePage = () => {
   const dispatch = useDispatch()
@@ -28,8 +29,6 @@ const ProfilePage = () => {
         <h2>привет: {user?.nick}!</h2>
         <Box sx={{ '& > *': { margin: '2px' } }}>
           {userRole === "admin" && <Button variant="outlined" onClick={() => navigate('/admin')}>панель администратора</Button>}
-          {userRole === "admin" && <Button variant="outlined" onClick={() => navigate('/admin/sdfg')}>панель администратора1</Button>}
-          {userRole === "admin" && <Button variant="outlined" onClick={() => navigate('/admin/sdfg')}>панель администратора1</Button>}
           <Button variant="outlined" onClick={() => {setEditProfileIsOpen(!EditProfileIsOpen) }}>редактировать профиль</Button>
           <Button variant="outlined" onClick={() => { dispatch(setCredentialsInitial()); navigate('/') }}>выйти</Button>
         </Box>
@@ -37,7 +36,7 @@ const ProfilePage = () => {
       <Box sx={{margin: '50px 10px'}}>
         {user && EditProfileIsOpen && <ProfileMutationForm user = {user}/>}
       </Box>
-      
+      <ReactDropzone/>
       <Box >
         <OrdersTableCollaps />
       </Box>

+ 0 - 0
my-diplom/src/serverRoles.js


+ 30 - 0
my-diplom/src/store/ImgUploadApi.js

@@ -0,0 +1,30 @@
+import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
+
+export const ImgUploadApi = createApi({
+    reducerPath: 'ImgUploadApi',
+    // tagTypes: ['Categories'],
+    baseQuery: fetchBaseQuery({
+         baseUrl: 'http://shop-roles.node.ed.asmer.org.ua/upload',
+         prepareHeaders: (headers, { getState }) => {
+            // By default, if we have a token in the store, let's use that for authenticated requests
+            const token = getState().auth.token;
+            if (token) {
+              headers.set("Authorization", `Bearer ${token}`);
+            }
+            return headers;
+          }
+        }),
+    endpoints: (builder) => ({
+        setUploadImage: builder.mutation({
+          query: (payload) => ({
+            method: 'POST',
+            body: payload
+          }),
+        }),
+      }),
+})
+
+export const {useSetUploadImageMutation} = ImgUploadApi
+
+
+

+ 0 - 96
my-diplom/src/store/categoriesApi copy.js

@@ -1,96 +0,0 @@
-import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
-import { gqlRootCats1, CategoryFindOne, GoodFindOne } from '../components/actions/actions'
-
-// export const categoriesApi = createApi({
-//     reducerPath: 'categoriesApi',
-//     // tagTypes: ['Categories'],
-//     baseQuery: fetchBaseQuery({ baseUrl: 'http://shop-roles.node.ed.asmer.org.ua/graphql' }),
-//     endpoints: (build) => ({
-//         getCategories: build.query({
-//             query: ()=> ({
-//                 method: 'POST',
-//                 headers: {
-//                     'Content-Type': 'application/json;charset=utf-8',
-//                     'Accept': 'application/json'
-//                 },
-//                 body: JSON.stringify({
-//                     query: `query categories{
-//                         CategoryFind(query:"[{}]"){
-//                             _id name 
-//                         }
-//                     }`
-//                 })
-//             }),
-//         //     providesTags: (result) =>
-//         // result
-//         //   ? [
-//         //       ...Object.values(result.data)[0].map(({ _id }) => ({ type: 'Categories', _id })),
-//         //       { type: 'Categories', id: 'LIST' },
-//         //     ]
-//         //   : [{ type: 'Categories', id: 'LIST' }],
-//         })
-//     })
-// })
-
-export const categoriesApi = createApi({
-    reducerPath: 'categoriesApi',
-    // tagTypes: ['Categories'],
-    baseQuery: fetchBaseQuery({ baseUrl: 'http://shop-roles.node.ed.asmer.org.ua/graphql' }),
-    endpoints: (build) => ({
-        getCategories: build.query({
-            query: ()=> ({
-                method: 'POST',
-                headers: {
-                    'Content-Type': 'application/json;charset=utf-8',
-                    'Accept': 'application/json'
-                },
-                body: JSON.stringify(gqlRootCats1)
-            }),
-        //     providesTags: (result) =>
-        // result
-        //   ? [
-        //       ...Object.values(result.data)[0].map(({ _id }) => ({ type: 'Categories', _id })),
-        //       { type: 'Categories', id: 'LIST' },
-        //     ]
-        //   : [{ type: 'Categories', id: 'LIST' }],
-        }),
-        getCategory: build.query({
-            query: (_id)=> ({
-                method: 'POST',
-                headers: {
-                    'Content-Type': 'application/json;charset=utf-8',
-                    'Accept': 'application/json'
-                },
-                body: JSON.stringify(CategoryFindOne(_id))
-            }),
-        //     providesTags: (result) =>
-        // result
-        //   ? [
-        //       ...Object.values(result.data)[0].map(({ _id }) => ({ type: 'Categories', _id })),
-        //       { type: 'Categories', id: 'LIST' },
-        //     ]
-        //   : [{ type: 'Categories', id: 'LIST' }],
-        }),
-        getProduct: build.query({
-            query: (_id)=> ({
-                method: 'POST',
-                headers: {
-                    'Content-Type': 'application/json;charset=utf-8',
-                    'Accept': 'application/json'
-                },
-                body: JSON.stringify(GoodFindOne(_id))
-            }),
-        //     providesTags: (result) =>
-        // result
-        //   ? [
-        //       ...Object.values(result.data)[0].map(({ _id }) => ({ type: 'Categories', _id })),
-        //       { type: 'Categories', id: 'LIST' },
-        //     ]
-        //   : [{ type: 'Categories', id: 'LIST' }],
-        })
-
-    })
-})
-
-export const {useGetCategoriesQuery, useGetCategoryQuery, useGetProductQuery} = categoriesApi
-

+ 126 - 8
my-diplom/src/store/categoriesApi.js

@@ -71,10 +71,12 @@ export const categoriesApi = createApi({
                       name
                       price
                       categories{
+                        _id
                         name
                       }
                       images{
-                        url
+                        _id
+                        url                        
                       }
                     }  
                   }`,
@@ -92,7 +94,8 @@ export const categoriesApi = createApi({
           login,
           password
         }
-      })
+      }),
+      invalidatesTags: (result, error, arg) => ([{ type: 'UserOrders' }])
     }),
     getUser: builder.query({
       query: (_id) => ({
@@ -127,7 +130,7 @@ export const categoriesApi = createApi({
       providesTags: (result, error, id) => ([{ type: 'User', id }])
     }),
     getUserOrders: builder.query({
-      query: ({limit, skip}) => ({
+      query: ({limit, skip, sort}) => ({
         document: gql`
         query( $q: String ) {
           OrderFind(query: $q) {
@@ -145,7 +148,7 @@ export const categoriesApi = createApi({
           }
       }`,
         variables: {
-          q: JSON.stringify([{}, {limit: [limit], skip: [skip]}])
+          q: JSON.stringify([{}, {limit: [limit], skip: [skip], sort: [sort]}])
          }
       }),
       providesTags: (result, error, id) => ([{ type: 'UserOrders' }])
@@ -185,7 +188,7 @@ export const categoriesApi = createApi({
           `,
         variables: { login, password, nick: args?.nick || ""}
       }),
-      // invalidatesTags: (result, error, arg) => ([{ type: 'User', id: arg._id }])
+      invalidatesTags: (result, error, arg) => ([{ type: 'UserOrders' }])
     }),
     setOrderUpsert: builder.mutation({
       query: (arrGoods) => ({
@@ -216,6 +219,7 @@ export const categoriesApi = createApi({
       description
       categories{
         _id
+        name
       }
       images{
         url
@@ -224,13 +228,13 @@ export const categoriesApi = createApi({
     }
         `,
         variables: {
-          q: JSON.stringify([{"categories._id": categoryId}, {limit:[limit], sort :[sort], skip: [skip]  }]) 
+          q: JSON.stringify([categoryId ? {"categories._id": categoryId} : {}, {limit:[limit], sort :[sort], skip: [skip]  }]) 
           // sort должен быть обьект вида {поле сортировки: 1 или -1} где 1 сортировка по возрастаниюб -1 по убыванию.
           // limit число от 1 до 100
           // categoryId обязательный параметр иначе вернет пустой массив goods
         }
       }),
-      providesTags: (result, error, id) => ([{ type: 'UserOrders' }])
+      providesTags: (result, error, arg) => ([{ type: 'Good'}])
     }),
     getGoodCount: builder.query({
       query: ({categoryId = null}) => ({
@@ -240,11 +244,120 @@ export const categoriesApi = createApi({
         }
         `,
         variables: {
-          q: JSON.stringify([{"categories._id": categoryId}])
+          q: JSON.stringify([categoryId ? {"categories._id": categoryId} : {}])
         }
       }),
+      providesTags: (result, error, arg) => ([{ type: 'Good'}])
     }),
+    getUserOrders2: builder.query({
+      query: ({limit, skip, sort, userId = null}) => ({
+        document: gql`
+        query( $q: String ) {
+          OrderFind(query: $q) {
+              _id
+              createdAt
+              total
+              orderGoods {
+                _id
+                createdAt
+                price
+                count
+                goodName
+                total
+              }
+          }
+      }`,
+        variables: {
+          q: JSON.stringify([{___owner: userId}, {limit: [limit], skip: [skip], sort: [sort]}])
+         }
+      }),
+      providesTags: (result, error, id) => ([{ type: 'UserOrders' }])
+    }),
+    setGoodUpsert: builder.mutation({
+      query: (good) => ({
+        document: gql`
+        mutation GoodUpsert($good: GoodInput){
+          GoodUpsert(good:$good)
+           {
+              _id
+              createdAt
+              name
+              description
+              price
+              categories {_id name}
+              images {_id url}
+            }
+        }
+          `,
+        variables: { 
+          good 
+        }
+      }),
+      invalidatesTags: (result, error, arg) => ([{ type: 'Good'}])
     
+   }),
+   getCategoriesAll: builder.query({
+    query: () => ({
+      document: gql`
+      query CategoryFindAll{
+        CategoryFind(query:"[{}]") {
+          _id
+          name
+          parent{ _id }
+          subCategories {_id}
+          
+        }
+      }
+      `,
+      variables: {
+        
+      }
+    }),
+    providesTags: (result, error, arg) => ([{ type: 'Category'}])
+  }),
+
+//    setGoodUpsert: builder.mutation({
+//     query: ({
+//       _id = null,
+//       name = null,
+//       description = null,
+//       price = null,
+//       categories = [],
+//       images = []
+//      }) => ({
+//       document: gql`
+//       mutation GoodUpsert($login:String, $password: String,  $nick: String){
+//         GoodUpsert(good: {
+//           _id: $_id,
+//           name: $name,
+//           description: $description,
+//           price: $price,
+//           categories: $categories
+//           images: $images
+//          })
+//          {
+//             _id
+//             createdAt
+//             name
+//             description
+//             price
+//             categories {_id name}
+//             images {_id url}
+//           }
+//       }
+//         `,
+//       variables: { 
+//         _id,
+//         name,
+//         description,
+//         price,
+//         categories,
+//         images
+//       }
+//     }),
+ 
+  
+//  })
   })
 })
 
@@ -259,6 +372,9 @@ export const {
   useGetOrdersCountQuery,
   useGetGoodFindQuery,
   useGetGoodCountQuery,
+  useGetUserOrders2Query,
+  useSetGoodUpsertMutation,
+  useGetCategoriesAllQuery,
 } = categoriesApi
 
 /*
@@ -273,3 +389,5 @@ query {
 
 
 
+
+

+ 5 - 2
my-diplom/src/store/index.js

@@ -3,6 +3,7 @@ import cartReducer from './cartSlice'
 import authReducer from './authSlice'
 import adminMenuReducer from './adminMenuSlice'
 import { categoriesApi } from './categoriesApi'
+import { ImgUploadApi } from "./ImgUploadApi";
 
  
 export default configureStore({
@@ -11,6 +12,8 @@ export default configureStore({
         auth: authReducer,
         adminMenu: adminMenuReducer,
         [categoriesApi.reducerPath]: categoriesApi.reducer,
+        [ImgUploadApi.reducerPath]: ImgUploadApi.reducer,
     },
-        middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(categoriesApi.middleware)
-})
+        middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(categoriesApi.middleware).concat(ImgUploadApi.middleware)
+})
+