Explorar o código

rtk_input_dnd__wo gql

Gennadysht %!s(int64=2) %!d(string=hai) anos
pai
achega
50f9799dbe

+ 80 - 6
package-lock.json

@@ -23,6 +23,7 @@
         "@testing-library/react": "^13.4.0",
         "@testing-library/user-event": "^13.5.0",
         "array-move": "^4.0.0",
+        "form-data": "^4.0.0",
         "graphql-request": "^5.1.0",
         "http-proxy-middleware": "^2.0.6",
         "install": "^0.13.0",
@@ -3757,6 +3758,19 @@
         "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
       }
     },
+    "node_modules/@rtk-query/graphql-request-base-query/node_modules/form-data": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
+      "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/@rtk-query/graphql-request-base-query/node_modules/graphql-request": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-4.3.0.tgz",
@@ -8939,9 +8953,9 @@
       }
     },
     "node_modules/form-data": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
-      "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
       "dependencies": {
         "asynckit": "^0.4.0",
         "combined-stream": "^1.0.8",
@@ -9254,6 +9268,19 @@
         "graphql": "14 - 16"
       }
     },
+    "node_modules/graphql-request/node_modules/form-data": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
+      "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/gzip-size": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
@@ -12273,6 +12300,19 @@
         }
       }
     },
+    "node_modules/jsdom/node_modules/form-data": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
+      "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/jsesc": {
       "version": "2.5.2",
       "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@@ -23295,6 +23335,16 @@
         "graphql-request": "^4.0.0"
       },
       "dependencies": {
+        "form-data": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
+          "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
+          "requires": {
+            "asynckit": "^0.4.0",
+            "combined-stream": "^1.0.8",
+            "mime-types": "^2.1.12"
+          }
+        },
         "graphql-request": {
           "version": "4.3.0",
           "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-4.3.0.tgz",
@@ -27157,9 +27207,9 @@
       }
     },
     "form-data": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
-      "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
       "requires": {
         "asynckit": "^0.4.0",
         "combined-stream": "^1.0.8",
@@ -27377,6 +27427,18 @@
         "cross-fetch": "^3.1.5",
         "extract-files": "^9.0.0",
         "form-data": "^3.0.0"
+      },
+      "dependencies": {
+        "form-data": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
+          "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
+          "requires": {
+            "asynckit": "^0.4.0",
+            "combined-stream": "^1.0.8",
+            "mime-types": "^2.1.12"
+          }
+        }
       }
     },
     "gzip-size": {
@@ -29555,6 +29617,18 @@
         "whatwg-url": "^8.5.0",
         "ws": "^7.4.6",
         "xml-name-validator": "^3.0.0"
+      },
+      "dependencies": {
+        "form-data": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
+          "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
+          "requires": {
+            "asynckit": "^0.4.0",
+            "combined-stream": "^1.0.8",
+            "mime-types": "^2.1.12"
+          }
+        }
       }
     },
     "jsesc": {

+ 1 - 0
package.json

@@ -18,6 +18,7 @@
     "@testing-library/react": "^13.4.0",
     "@testing-library/user-event": "^13.5.0",
     "array-move": "^4.0.0",
+    "form-data": "^4.0.0",
     "graphql-request": "^5.1.0",
     "http-proxy-middleware": "^2.0.6",
     "install": "^0.13.0",

+ 3 - 1
src/App.js

@@ -3,7 +3,7 @@ import { combineReducers, configureStore } from '@reduxjs/toolkit';
 import { Router, Route, Switch } from 'react-router-dom';
 import { createBrowserHistory } from "history";
 import { Provider } from 'react-redux';
-import { promiseReducer, frontEndReducer, cartReducer, actionRestoreCart, goodsApi, cartGoodsApi } from './reducers';
+import { promiseReducer, frontEndReducer, cartReducer, actionRestoreCart, goodsApi, cartGoodsApi, uploadAPI } from './reducers';
 import { CEditableGood, CGood, CGoodsList, CLoginForm, CMainAppBar, COrder, COrdersList, CSortedFileDropZone } from "./Components";
 import { CLogout } from './Components';
 import { CSidebar } from './Components/Sidebar';
@@ -42,6 +42,7 @@ const rootReducer = combineReducers({
   [goodsApi.reducerPath]: goodsApi.reducer,
   [ordersApi.reducerPath]: ordersApi.reducer,
   [cartGoodsApi.reducerPath]: cartGoodsApi.reducer,
+  [uploadAPI.reducerPath]: uploadAPI.reducer,
   promise: promiseReducer,
   frontend: frontEndReducer,
   cart: cartReducer,
@@ -54,6 +55,7 @@ export const store = configureStore({
     goodsApi.middleware,
     ordersApi.middleware,
     loginApi.middleware,
+    uploadAPI.middleware,
     cartGoodsApi.middleware],
   reducer: rootReducer
 });

+ 21 - 8
src/Components/EditableGood.js

@@ -4,7 +4,7 @@ import { styled } from '@mui/material/styles';
 import { Container, Grid, Card, CardContent, CardMedia, AvatarGroup, CardActions, IconButton, TextField, InputAdornment, Box } from '@mui/material';
 import { getFullImageUrl } from "./../utills";
 import { useDispatch } from 'react-redux';
-import { useGetGoodByIdQuery, useSaveGoodMutation } from '../reducers';
+import { useGetGoodByIdQuery, useSaveGoodMutation, useUploadSingleFileMutation } from '../reducers';
 import { useParams } from 'react-router-dom';
 import { actionSetCurrentGood } from '../reducers/frontEndReducer';
 import { CSortedFileDropZone } from './SortedFileDropZone';
@@ -37,14 +37,25 @@ export const AvatarGroupOriented = styled((props) => {
     ".MuiAvatar-root": { /*width: 20, height: 20,*/ marginLeft: 1 }
 }));
 
-const EditableGood = ({ good: goodExt, maxWidth = 'md', saveGood }) => {
+const EditableGood = ({ good: goodExt, maxWidth = 'md', saveGood, uploadFile }) => {
     let [good, setGood] = useState(goodExt);
+    let [imagesContainer, setImagesContainer] = useState({ images: goodExt.images });
     const setGoodData = (data) => {
         let goodData = { ...good, ...data };
         setGood(goodData);
         return goodData;
     }
+    const onChangeImages = images => {
+        setImagesContainer({ images });
+    }
+
+    const saveFullGood = () => {
+        for (let image of imagesContainer.images){
+            uploadFile(image);
+        }
+        //saveGood({ good });
 
+    }
 
     return good && (
         <Container maxWidth={maxWidth}>
@@ -70,7 +81,7 @@ const EditableGood = ({ good: goodExt, maxWidth = 'md', saveGood }) => {
                                                 label="Name"
                                                 value={good.name}
                                                 onChange={event => setGoodData({ name: event.target.value })}
-                                                fullWidth 
+                                                fullWidth
                                             />
                                         </Grid>
                                         <Grid item width="100%">
@@ -82,7 +93,7 @@ const EditableGood = ({ good: goodExt, maxWidth = 'md', saveGood }) => {
                                                 startAdornment={<InputAdornment position="start">$</InputAdornment>}
                                                 value={good.price}
                                                 onChange={event => setGoodData({ price: +event.target.value })}
-                                                fullWidth 
+                                                fullWidth
                                             />
                                         </Grid>
                                         <Grid item width="100%">
@@ -94,7 +105,7 @@ const EditableGood = ({ good: goodExt, maxWidth = 'md', saveGood }) => {
                                                 onChange={event => setGoodData({ description: event.target.value })}
                                                 multiline={true}
                                                 rows={15}
-                                                fullWidth 
+                                                fullWidth
                                             />
                                         </Grid>
                                     </Grid>
@@ -103,12 +114,12 @@ const EditableGood = ({ good: goodExt, maxWidth = 'md', saveGood }) => {
                         </Grid>
                     </Grid>
                     <Grid>
-                        <CSortedFileDropZone items={good.images}/>
+                        <CSortedFileDropZone items={good.images} onChange={items => onChangeImages(items)} />
                     </Grid>
                 </Grid>
                 <CardActions>
                     <Button size='small' color='primary'
-                        onClick={() => saveGood({ good })}
+                        onClick={() => saveFullGood(good)}
                     >
                         Save
                     </Button>
@@ -130,8 +141,10 @@ const CEditableGood = ({ maxWidth = 'md' }) => {
     const dispatch = useDispatch();
     dispatch(actionSetCurrentGood(_id));
     const [saveGoodMutation, { }] = useSaveGoodMutation();
+    const [uploadSingleFileMutation, { }] = useUploadSingleFileMutation();
+
 
-    return <EditableGood saveGood={saveGoodMutation} good={good} maxWidth={maxWidth} />
+    return <EditableGood good={good} saveGood={saveGoodMutation} uploadFile={uploadSingleFileMutation} maxWidth={maxWidth} />
 }
 
 export { CEditableGood }

+ 1 - 1
src/Components/FileDropZone.js

@@ -7,7 +7,7 @@ function FileDropZone({ onDropFiles }) {
         // Do something with the files
         acceptedFiles = acceptedFiles.map(f => {
             let url = URL.createObjectURL(f)
-            return { _id: null, name: f.path, url }
+            return { _id: null, name: f.path, url, data: f }
         }
         );
         setPaths(acceptedFiles);

+ 109 - 0
src/Test/SortedDropZone.js

@@ -0,0 +1,109 @@
+import { DndContext, KeyboardSensor, PointerSensor, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
+import { rectSortingStrategy, SortableContext, sortableKeyboardCoordinates, useSortable } from "@dnd-kit/sortable";
+import { arrayMoveImmutable } from "array-move";
+import { useEffect, useState } from "react";
+import { CSS } from "@dnd-kit/utilities";
+
+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 SortedDropZone({ 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} >
+                </Droppable>
+            </div>
+        </DndContext>
+    );
+}
+export { SortedDropZone };

+ 1 - 0
src/reducers/index.js

@@ -7,5 +7,6 @@ export { frontEndReducer, getCurrentCategory, actionSetGoodsPaging, actionSetOrd
 export { useGetRootCategoriesQuery, useGetCategoryByIdQuery } from './categoryReducer';
 export { ordersApi, useGetOrderByIdQuery, useGetOrdersCountQuery, useGetOrdersQuery, useAddOrderMutation } from './ordersReducer';
 export { goodsApi, useGetGoodByIdQuery, useGetGoodsCountQuery, useGetGoodsQuery, useSaveGoodMutation } from './goodsReducer';
+export { uploadAPI, useUploadSingleFileMutation } from './uploadReducer';
 
 

+ 0 - 2
src/reducers/ordersReducer.js

@@ -1,7 +1,6 @@
 import { createApi } from '@reduxjs/toolkit/query/react'
 import { graphqlRequestBaseQuery } from "@rtk-query/graphql-request-base-query"
 import { gql } from "graphql-request";
-import { useSelector } from 'react-redux';
 import { createFullQuery } from '../gql';
 
 const getOrderSearchParams = query => ({ searchStr: query, searchFieldNames: ["_id"] });
@@ -12,7 +11,6 @@ const prepareHeaders = (headers, { getState }) => {
     }
     return headers;
 }
-const getOrdersSearchParams = query => ({ searchStr: query, searchFieldNames: ["_id"] });
 const ordersApi = createApi({
     reducerPath: 'orders',
     baseQuery: graphqlRequestBaseQuery({

+ 55 - 0
src/reducers/uploadReducer.js

@@ -0,0 +1,55 @@
+import { resolveComponentProps } from '@mui/base';
+import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
+import { graphqlRequestBaseQuery } from "@rtk-query/graphql-request-base-query";
+var FormData = require('form-data');
+
+const prepareHeaders = (headers, { getState }) => {
+  const token = getState().auth.token;
+  if (token) {
+    headers.set("Authorization", `Bearer ${token}`);
+    headers.set("Content-Type", "multipart/form-data");
+  }
+  return headers;
+}
+
+const uploadAPI = createApi({
+  reducerPath: 'uploadAPI',
+  baseQuery: fetchBaseQuery(
+    {
+      baseUrl: "http://shop-roles.node.ed.asmer.org.ua/",
+      prepareHeaders
+    }),
+  endpoints: (builder) => ({
+    uploadSingleFile: builder.mutation({
+      async query(file) {
+        var formData = new FormData();
+        let fileData = await file.data.arrayBuffer();
+        formData.append('photo', fileData);
+        /*var reader = new FileReader();
+        const readFile = (event) => event.target.result; 
+        reader.addEventListener("loadend", readFile);
+        reader.readAsBinaryString(file.data);
+            <input type="file" name="photo" id='photo' value={file.url} />
+        let form = (
+          <form action="/upload" method="post" enctype="multipart/form-data" id='form'>
+            <input type="number" name="photo1" id='photo1' value={5} />
+          </form>
+        );
+        */
+
+        let res =
+        {
+          action:"/upload",
+          //url: 'upload',
+          method: 'POST',
+          credentials: 'include',
+          body: formData,
+        };
+        return res;
+      },
+    }),
+  }),
+});
+
+export const { useUploadSingleFileMutation } = uploadAPI;
+export { uploadAPI }