Browse Source

+ OrdersPage | + OrderPAge

ilya_shyian 2 years ago
parent
commit
423f392eb7

+ 50 - 0
src/actions/actionOrderById.js

@@ -0,0 +1,50 @@
+import { mock, query } from '../helpers';
+
+import { actionPromise } from '../reducers';
+
+export const actionOrderById =
+    ({ _id, promiseName = 'orderById' }) =>
+    async (dispatch, getState) => {
+        dispatch(
+            actionPromise(
+                promiseName,
+                new Promise((resolve) => {
+                    setTimeout(
+                        Math.random() > 0.01
+                            ? resolve({
+                                  data: {
+                                      _id: 4,
+                                      email: 'example@gmail.com',
+                                      phoneNumber: '0667213260',
+                                      orderGoods: [
+                                          {
+                                              _id: 1,
+                                              price: 999,
+                                              count: 1,
+                                              good: {
+                                                  _id: 1,
+                                                  name: 'Good 1',
+                                                  price: '999',
+                                              },
+                                          },
+                                      ],
+                                      price: 999,
+                                      status: 3,
+                                  },
+                              })
+                            : resolve({
+                                  errors: [{ message: 'Error adsasdadas' }],
+                              }),
+                        400
+                    );
+                })
+                    // .then((res) => res.json())
+                    .then((data) => {
+                        console.log(data);
+                        if (data.errors) {
+                            throw new Error(JSON.stringify(data.errors));
+                        } else return data.data;
+                    })
+            )
+        );
+    };

+ 7 - 0
src/actions/actionOrderUpdate.js

@@ -0,0 +1,7 @@
+import { actionOrdersAll } from './actionOrdersAll';
+import { actionOrderUpsert } from './actionOrderUpsert';
+
+export const actionOrderUpdate = (good) => async (dispatch, getState) => {
+    await dispatch(actionOrderUpsert(good));
+    await dispatch(actionOrdersAll());
+};

+ 39 - 0
src/actions/actionOrderUpsert.js

@@ -0,0 +1,39 @@
+import { actionPromise } from '../reducers';
+
+export const actionOrderUpsert = (order) => async (dispatch) => {
+    dispatch(
+        actionPromise(
+            'orderUpsert',
+            new Promise((resolve) => {
+                setTimeout(
+                    Math.random() > 0.01
+                        ? resolve({
+                              data: {
+                                  _id: 4,
+                                  email: 'example@gmail.com',
+                                  phoneNumber: '0667213260',
+                                  orderGoods: [
+                                      {
+                                          _id: 1,
+                                          price: 999,
+                                          count: 1,
+                                          good: {
+                                              _id: 1,
+                                              name: 'Good 1',
+                                              price: '999',
+                                          },
+                                      },
+                                  ],
+                                  price: 999,
+                                  status: 3,
+                              },
+                          })
+                        : resolve({
+                              errors: [{ message: 'Error adsasdadas' }],
+                          }),
+                    400
+                );
+            })
+        )
+    );
+};

+ 145 - 0
src/actions/actionOrdersAll.js

@@ -0,0 +1,145 @@
+import { actionPromise } from '../reducers';
+import { gql } from '../helpers';
+
+export const actionOrdersAll =
+    ({ limit = 0, skip = 0, promiseName = 'adminOrdersAll' } = {}) =>
+    async (dispatch, getState) => {
+        dispatch(
+            actionPromise(
+                promiseName,
+                new Promise((resolve) => {
+                    setTimeout(
+                        Math.random() > 0.01
+                            ? resolve({
+                                  data: [
+                                      {
+                                          _id: 1,
+                                          email: 'example@gmail.com',
+                                          phoneNumber: '0667213260',
+                                          orderGoods: [
+                                              {
+                                                  _id: 1,
+                                                  price: 999,
+                                                  count: 1,
+                                                  good: {
+                                                      _id: 1,
+                                                      name: 'Good 1',
+                                                      price: '999',
+                                                      images: [
+                                                          {
+                                                              _id: 1,
+                                                              url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                                          },
+                                                          {
+                                                              _id: 2,
+                                                              url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                                          },
+                                                      ],
+                                                  },
+                                              },
+                                          ],
+                                          price: 999,
+                                          status: 1,
+                                      },
+                                      {
+                                          _id: 2,
+                                          email: 'example@gmail.com',
+                                          phoneNumber: '0667213260',
+                                          orderGoods: [
+                                              {
+                                                  _id: 1,
+                                                  price: 999,
+                                                  count: 1,
+                                                  good: {
+                                                      _id: 1,
+                                                      name: 'Good 1',
+                                                      price: '999',
+                                                      images: [
+                                                          {
+                                                              _id: 1,
+                                                              url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                                          },
+                                                          {
+                                                              _id: 2,
+                                                              url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                                          },
+                                                      ],
+                                                  },
+                                              },
+                                          ],
+                                          price: 999,
+                                          status: 2,
+                                      },
+                                      {
+                                          _id: 3,
+                                          email: 'example@gmail.com',
+                                          phoneNumber: '0667213260',
+                                          orderGoods: [
+                                              {
+                                                  _id: 1,
+                                                  price: 999,
+                                                  count: 1,
+                                                  good: {
+                                                      _id: 1,
+                                                      name: 'Good 1',
+                                                      price: '999',
+                                                      images: [
+                                                          {
+                                                              _id: 1,
+                                                              url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                                          },
+                                                          {
+                                                              _id: 2,
+                                                              url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                                          },
+                                                      ],
+                                                  },
+                                              },
+                                          ],
+                                          price: 999,
+                                          status: 3,
+                                      },
+                                      {
+                                          _id: 4,
+                                          email: 'example@gmail.com',
+                                          phoneNumber: '0667213260',
+                                          orderGoods: [
+                                              {
+                                                  _id: 1,
+                                                  price: 999,
+                                                  count: 1,
+                                                  good: {
+                                                      _id: 1,
+                                                      name: 'Good 1',
+                                                      price: '999',
+                                                      images: [
+                                                          {
+                                                              _id: 1,
+                                                              url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                                          },
+                                                          {
+                                                              _id: 2,
+                                                              url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                                          },
+                                                      ],
+                                                  },
+                                              },
+                                          ],
+                                          price: 999,
+                                          status: 4,
+                                      },
+                                  ],
+                              })
+                            : resolve({
+                                  errors: [{ message: 'Error adsasdadas' }],
+                              }),
+                        400
+                    );
+                }).then((data) => {
+                    if (data.errors) {
+                        throw new Error(JSON.stringify(data.errors));
+                    } else return data.data;
+                })
+            )
+        );
+    };

+ 144 - 0
src/actions/actionOrdersFind.js

@@ -0,0 +1,144 @@
+import { actionPromise } from '../reducers';
+
+export const actionOrdersFind =
+    ({ text = '', limit = 0, skip = 0, promiseName = 'ordersFind' }) =>
+    async (dispatch, getState) => {
+        await dispatch(
+            actionPromise(
+                promiseName,
+                new Promise((resolve) => {
+                    setTimeout(
+                        Math.random() > 0.01
+                            ? resolve({
+                                  data: [
+                                      {
+                                          _id: 1,
+                                          email: 'example@gmail.com',
+                                          phoneNumber: '0667213260',
+                                          orderGoods: [
+                                              {
+                                                  _id: 1,
+                                                  price: 999,
+                                                  count: 1,
+                                                  good: {
+                                                      _id: 1,
+                                                      name: 'Good 1',
+                                                      price: '999',
+                                                      images: [
+                                                          {
+                                                              _id: 1,
+                                                              url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                                          },
+                                                          {
+                                                              _id: 2,
+                                                              url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                                          },
+                                                      ],
+                                                  },
+                                              },
+                                          ],
+                                          price: 999,
+                                          status: 1,
+                                      },
+                                      {
+                                          _id: 2,
+                                          email: 'example@gmail.com',
+                                          phoneNumber: '0667213260',
+                                          orderGoods: [
+                                              {
+                                                  _id: 1,
+                                                  price: 999,
+                                                  count: 1,
+                                                  good: {
+                                                      _id: 1,
+                                                      name: 'Good 1',
+                                                      price: '999',
+                                                      images: [
+                                                          {
+                                                              _id: 1,
+                                                              url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                                          },
+                                                          {
+                                                              _id: 2,
+                                                              url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                                          },
+                                                      ],
+                                                  },
+                                              },
+                                          ],
+                                          price: 999,
+                                          status: 2,
+                                      },
+                                      {
+                                          _id: 3,
+                                          email: 'example@gmail.com',
+                                          phoneNumber: '0667213260',
+                                          orderGoods: [
+                                              {
+                                                  _id: 1,
+                                                  price: 999,
+                                                  count: 1,
+                                                  good: {
+                                                      _id: 1,
+                                                      name: 'Good 1',
+                                                      price: '999',
+                                                      images: [
+                                                          {
+                                                              _id: 1,
+                                                              url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                                          },
+                                                          {
+                                                              _id: 2,
+                                                              url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                                          },
+                                                      ],
+                                                  },
+                                              },
+                                          ],
+                                          price: 999,
+                                          status: 3,
+                                      },
+                                      {
+                                          _id: 4,
+                                          email: 'example@gmail.com',
+                                          phoneNumber: '0667213260',
+                                          orderGoods: [
+                                              {
+                                                  _id: 1,
+                                                  price: 999,
+                                                  count: 1,
+                                                  good: {
+                                                      _id: 1,
+                                                      name: 'Good 1',
+                                                      price: '999',
+                                                      images: [
+                                                          {
+                                                              _id: 1,
+                                                              url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                                          },
+                                                          {
+                                                              _id: 2,
+                                                              url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                                          },
+                                                      ],
+                                                  },
+                                              },
+                                          ],
+                                          price: 999,
+                                          status: 4,
+                                      },
+                                  ],
+                              })
+                            : resolve({
+                                  errors: [{ message: 'Error adsasdadas' }],
+                              }),
+                        400
+                    );
+                }).then((data) => {
+                    if (data.errors) {
+                        throw new Error(JSON.stringify(data.errors));
+                    } else return data.data;
+                })
+            )
+        );
+    };

+ 2 - 2
src/components/GoodPage/index.js

@@ -11,7 +11,7 @@ export const GoodPage = () => {
     const { _id = '', name = '', price = '', description = '', images = [] } = good || {};
     return (
         <Box className="GoodPage">
-            <Grid container spacing={4} className="images">
+            <Grid container spacing={4} className="images" >
                 <Grid item xs={12} md={4}>
                     <Carousel showIndicators={false} showStatus={false}>
                         {(good.images || [{ url: defaultGoodImage }]).map((image) => (
@@ -19,7 +19,7 @@ export const GoodPage = () => {
                         ))}
                     </Carousel>
                 </Grid>
-                <Grid item xs={12} md={8} className="content">
+                <Grid item xs={12} md={8} className="content" >
                     <Stack spacing={2}>
                         <Stack direction="row" alignItems="center" spacing={2}>
                             <Typography variant="body1" color="#1C1B1F">

+ 1 - 1
src/components/admin/AdminCategoriesPage/AdminCategoryItem.js

@@ -7,7 +7,7 @@ const AdminCategoryItem = ({ category }) => (
         <TableCell>{category.name ? category.name : '-'}</TableCell>
         <TableCell>{category.parent?.name ? category.parent.name : '-'}</TableCell>
         <TableCell className="edit">
-            <Button component={Link} className="Link" to={`/admin/category/${category._id}/`}>
+            <Button component={Link} className="Link" to={`/admin/category/${category._id}/`} variant="contained">
                 Редагувати
             </Button>
         </TableCell>

+ 1 - 1
src/components/admin/AdminCategoriesPage/AdminCategoryList.js

@@ -19,7 +19,7 @@ const AdminCategoryList = ({ categories }) => {
             <Box className="searchBarWrapper">
                 <CSearchBar
                     render={CSearchResults}
-                    searchLink="/admin/category/search/"
+                    searchLink="/admin/categories/search/"
                     renderParams={{ itemLink: '/admin/category/' }}
                 />
             </Box>

+ 0 - 199
src/components/admin/AdminCategoryPage/GoodForm.js

@@ -1,199 +0,0 @@
-import { connect } from 'react-redux';
-import React, { useState, useEffect } from 'react';
-import { actionPromise, actionPromiseClear } from '../../../reducers';
-import Select from 'react-select';
-import { actionGoodUpdate } from '../../../actions/actionGoodUpdate';
-import { EntityEditor } from '../../common/EntityEditor';
-import { actionUploadFiles } from '../../../actions/actionUploadFiles';
-import {
-    Box,
-    Button,
-    Chip,
-    FormControl,
-    InputLabel,
-    MenuItem,
-    OutlinedInput,
-    Stack,
-    TextareaAutosize,
-    TextField,
-    Typography,
-} from '@mui/material';
-import { useFormik } from 'formik';
-import * as Yup from 'yup';
-import { Error } from '../../common/Error';
-
-const goodSchema = Yup.object().shape({
-    name: Yup.string().required("Обов'язкове"),
-    description: Yup.string().required("Обов'язкове"),
-    price: Yup.number().min(0, 'більше або равно 0').required("Обов'язкове"),
-    amount: Yup.number().min(0, 'більше або равно 0').required("Обов'язкове"),
-});
-
-const CGoodEditor = connect(
-    (state) => ({
-        entity: state.promise?.adminGoodById?.payload || {},
-        uploadFiles: state.promise?.uploadFiles,
-    }),
-    {
-        onFileDrop: (files) => actionUploadFiles(files),
-    }
-)(EntityEditor);
-
-export const GoodForm = ({
-    serverErrors,
-    onSaveClick,
-    onSave,
-    onClose,
-    promiseStatus,
-    catList = [],
-    good = {},
-} = {}) => {
-    const [inputCategories, setInputCategories] = useState([]);
-    const [inputImages, setInputImages] = useState([]);
-
-    const formik = useFormik({
-        initialValues: {
-            name: '',
-            description: '',
-            price: 0,
-            amount: 0,
-        },
-        validationSchema: goodSchema,
-        validateOnChange: true,
-        onSubmit: () => {
-            let goodToSave = {};
-            good?._id && (goodToSave._id = good._id);
-            goodToSave.name = formik.values.name;
-            goodToSave.description = formik.values.description;
-            goodToSave.price = +formik.values.price;
-            goodToSave.amount = +formik.values.amount;
-            goodToSave.categories = inputCategories;
-            goodToSave.images = inputImages?.map(({ _id }) => ({ _id })) || [];
-
-            onSaveClick && onSaveClick();
-            onSave(goodToSave);
-        },
-    });
-
-    useEffect(() => {
-        setInputCategories(good?.categories || []);
-        setInputImages(good?.images || []);
-        formik.setFieldValue('name', good.name || '');
-        formik.setFieldValue('description', good.description || '');
-        formik.setFieldValue('amount', good.amount || 0);
-        formik.setFieldValue('price', good.price || 0);
-    }, [good]);
-
-    useEffect(() => {
-        return () => {
-            onClose && onClose();
-        };
-    }, []);
-    return (
-        <Box className="GoodForm" component="form" onSubmit={formik.handleSubmit}>
-            {(serverErrors || []).map((error) => (
-                <Error>{error?.message}</Error>
-            ))}
-
-            <TextField
-                id="name"
-                name="name"
-                variant="outlined"
-                label="Назва"
-                size="small"
-                error={formik.touched.name && Boolean(formik.errors.name)}
-                value={formik.values.name}
-                onBlur={formik.handleBlur}
-                onChange={formik.handleChange}
-                helperText={formik.touched.name && formik.errors.name}
-                multiline
-                fullWidth
-                sx={{ mt: 2 }}
-            />
-
-            <Box sx={{ mt: 3 }}>
-                <InputLabel>Зображення</InputLabel>
-                <CGoodEditor onImagesSave={(images) => setInputImages(images)} />
-            </Box>
-
-            <TextField
-                variant="outlined"
-                id="description"
-                name="description"
-                label="Опис"
-                size="small"
-                error={formik.touched.description && Boolean(formik.errors.description)}
-                value={formik.values.description}
-                onBlur={formik.handleBlur}
-                onChange={formik.handleChange}
-                helperText={formik.touched.description && formik.errors.description}
-                multiline
-                fullWidth
-                sx={{ mt: 2 }}
-            />
-
-            <Box sx={{ mt: 3 }}>
-                <TextField
-                    variant="outlined"
-                    id="price"
-                    name="price"
-                    label="Ціна"
-                    size="small"
-                    error={formik.touched.price && Boolean(formik.errors.price)}
-                    value={formik.values.price}
-                    onBlur={formik.handleBlur}
-                    onChange={formik.handleChange}
-                    helperText={formik.touched.price && formik.errors.price}
-                    multiline
-                    fullWidth
-                    sx={{ mt: 2 }}
-                />
-            </Box>
-
-            <Box sx={{ mt: 3 }}>
-                <InputLabel>Категорії</InputLabel>
-                <Select
-                    placeholder="Обрати категорії"
-                    value={inputCategories.map(({ _id, name }) => ({ value: _id, label: name }))}
-                    closeMenuOnSelect={false}
-                    onChange={(e) => setInputCategories(e.map(({ label, value }) => ({ _id: value, name: label })))}
-                    options={catList?.map(({ _id, name }) => ({ value: _id, label: name }))}
-                    isMulti={true}
-                />
-                {/* <TextField
-                        classes={{ root: classes.root }}
-                        select
-                        name="userRoles"
-                        id="userRoles"
-                        variant="outlined"
-                        label="userRoles"
-                        SelectProps={{
-                            multiple: true,
-                            value: formState.userRoles,
-                            onChange: catList?.map(({ _id, name }) => ({ value: _id, label: name })),
-                        }}
-                    >
-                        {catList?.map(({ _id, name }) => ({ value: _id, label: name }))}
-                    </TextField> */}
-            </Box>
-
-            <Box direction="row" sx={{ mt: 3 }} justifyContent="flex-end">
-                <Button variant="contained" disabled={!formik.isValid || formik.isSubmitting} type="submit" fullWidth>
-                    Зберегти
-                </Button>
-            </Box>
-        </Box>
-    );
-};
-
-export const CGoodForm = connect(
-    (state) => ({
-        catList: state.promise.catAll?.payload || [],
-        promiseStatus: state.promise.goodUpsert?.status || null,
-        good: state.promise?.adminGoodById?.payload || {},
-    }),
-    {
-        onSave: (good) => actionGoodUpdate(good),
-        onClose: () => actionPromiseClear('goodUpsert'),
-    }
-)(GoodForm);

+ 1 - 1
src/components/admin/AdminGoodsPage/AdminGoodItem.js

@@ -25,7 +25,7 @@ const AdminGoodItem = ({ good }) => (
                 : '-'}
         </TableCell>
         <TableCell className="edit">
-            <Button component={Link} className="Link" to={`/admin/good/${good._id}/`}>
+            <Button component={Link} className="Link" to={`/admin/good/${good._id}/`} variant="contained">
                 Редагувати
             </Button>
         </TableCell>

+ 0 - 2
src/components/admin/AdminGoodsPage/AdminGoodList.js

@@ -16,8 +16,6 @@ const CSearchBar = connect(null, {
 const CSearchResults = connect((state) => ({ items: state.promise.adminGoodsFind?.payload || [] }))(SearchResults);
 
 const AdminGoodList = ({ goods }) => {
-    const [isSortReverse, setSortReverse] = useState(false);
-
     return (
         <Box className="AdminGoodList">
             <Box className="searchBarWrapper">

+ 54 - 1
src/components/admin/AdminLayoutPage/index.js

@@ -5,12 +5,15 @@ import { Route, Routes, useParams } from 'react-router-dom';
 import { actionGoodById } from '../../../actions/actionGoodById';
 import { actionCatById } from '../../../actions/actionCatById';
 import { actionPromiseClear, store, actionFeedCats } from '../../../reducers';
-import { actionFeedAdd, actionFeedClear, actionFeedGoods } from '../../../reducers/feedReducer';
+import { actionFeedAdd, actionFeedClear, actionFeedGoods, actionFeedOrders } from '../../../reducers/feedReducer';
 import { CProtectedRoute } from '../../common/ProtectedRoute';
 import { CAdminGoodPage } from '../AdminGoodPage';
 import { AdminGoodsPage } from '../AdminGoodsPage';
 import { AdminCategoriesPage } from '../AdminCategoriesPage';
 import { CAdminCategoryPage } from '../AdminCategoryPage';
+import { AdminOrdersPage } from '../AdminOrdersPage';
+import { CAdminOrderPage } from '../AdminOrderPage';
+import { actionOrderById } from '../../../actions/actionOrderById';
 
 const AdminCategoryPageContainer = ({}) => {
     const dispatch = useDispatch();
@@ -98,10 +101,57 @@ const AdminGoodsPageContainer = ({ goods }) => {
     return <AdminGoodsPage />;
 };
 
+const AdminOrdersPageContainer = ({ orders }) => {
+    const dispatch = useDispatch();
+    useEffect(() => {
+        dispatch(actionFeedOrders());
+        window.onscroll = (e) => {
+            if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
+                const {
+                    feed,
+                    promise: { feedOrdersAll },
+                } = store.getState();
+
+                if (feedOrdersAll.status !== 'PENDING') {
+                    dispatch(actionFeedOrders(feed.payload?.length || 0));
+                }
+            }
+        };
+        return () => {
+            dispatch(actionFeedClear());
+            dispatch(actionPromiseClear('feedOrdersAll'));
+            dispatch(actionPromiseClear('orderUpsert'));
+            window.onscroll = null;
+        };
+    }, []);
+
+    useEffect(() => {
+        if (orders?.length) store.dispatch(actionFeedAdd(orders));
+    }, [orders]);
+    return <AdminOrdersPage />;
+};
+
+const AdminOrderPageContainer = () => {
+    const params = useParams();
+    const dispatch = useDispatch();
+    useEffect(() => {
+        if (params._id) {
+            dispatch(actionOrderById({ _id: params._id, promiseName: 'adminOrderById' }));
+        } else {
+            dispatch(actionOrderById('adminOrderById'));
+        }
+    }, [params._id]);
+    return <CAdminOrderPage />;
+};
+
 const CAdminGoodsPageContainer = connect((state) => ({ goods: state.promise?.feedGoodsAll?.payload || [] }))(
     AdminGoodsPageContainer
 );
 
+const CAdminOrdersPageContainer = connect((state) => ({ orders: state.promise?.feedOrdersAll?.payload || [] }))(
+    AdminOrdersPageContainer
+);
+
 const CAdminCategoriesPageContainer = connect((state) => ({ cats: state.promise?.feedCatAll?.payload || [] }))(
     AdminCategoriesPageContainer
 );
@@ -116,6 +166,9 @@ const AdminLayoutPage = () => {
                 <Route path="/categories/" element={<CAdminCategoriesPageContainer />} />
                 <Route path="/category/" element={<AdminCategoryPageContainer />} />
                 <Route path="/category/:_id" element={<AdminCategoryPageContainer />} />
+                <Route path="/orders/" element={<CAdminOrdersPageContainer />} />
+                <Route path="/order/" element={<CAdminOrdersPageContainer />} />
+                <Route path="/order/:_id" element={<AdminOrderPageContainer />} />
             </Routes>
         </Box>
     );

+ 161 - 0
src/components/admin/AdminOrderPage/OrderForm.js

@@ -0,0 +1,161 @@
+import { connect } from 'react-redux';
+import React, { useState, useEffect } from 'react';
+import { actionPromise, actionPromiseClear } from '../../../reducers';
+import Select from 'react-select';
+import { actionOrderUpdate } from '../../../actions/actionOrderUpdate';
+import { EntityEditor } from '../../common/EntityEditor';
+import { actionUploadFiles } from '../../../actions/actionUploadFiles';
+import {
+    Box,
+    Button,
+    Chip,
+    FormControl,
+    Grid,
+    InputLabel,
+    MenuItem,
+    OutlinedInput,
+    Stack,
+    TextareaAutosize,
+    TextField,
+    Typography,
+} from '@mui/material';
+import { useFormik } from 'formik';
+import * as Yup from 'yup';
+import { Error } from '../../common/Error';
+import { statusNumber, statusOptions } from '../../../helpers';
+
+const orderSchema = Yup.object().shape({
+    email: Yup.string().required("Обов'язкове"),
+    phoneNumber: Yup.string().required("Обов'язкове"),
+});
+
+export const OrderForm = ({
+    serverErrors,
+    onSaveClick,
+    onSave,
+    onClose,
+    promiseStatus,
+    catList = [],
+    order = {},
+} = {}) => {
+    const [inputCategories, setInputCategories] = useState([]);
+    const [inputStatus, setInputStatus] = useState(null);
+
+    useEffect(() => {
+        console.log(inputStatus);
+    }, [inputStatus]);
+
+    const formik = useFormik({
+        initialValues: {
+            email: '',
+            phoneNumber: '',
+            price: 0,
+            amount: 0,
+        },
+        validationSchema: orderSchema,
+        validateOnChange: true,
+        onSubmit: () => {
+            let orderToSave = {};
+            order?._id && (orderToSave._id = order._id);
+            orderToSave.name = formik.values.name;
+            orderToSave.description = formik.values.description;
+            // orderToSave.price = +formik.values.price;
+            // orderToSave.amount = +formik.values.amount;
+            // orderToSave.categories = inputCategories;
+            // orderToSave.images = inputImages?.map(({ _id }) => ({ _id })) || [];
+
+            onSaveClick && onSaveClick();
+            onSave(orderToSave);
+        },
+    });
+
+    useEffect(() => {
+        // setInputCategories(order?.categories || []);
+        setInputStatus(order?.status || null);
+        formik.setFieldValue('email', order.email || '');
+        formik.setFieldValue('phoneNumber', order.phoneNumber || '');
+    }, [order]);
+
+    useEffect(() => {
+        return () => {
+            onClose && onClose();
+        };
+    }, []);
+    return (
+        <Box className="OrderForm" component="form" onSubmit={formik.handleSubmit}>
+            {(serverErrors || []).map((error) => (
+                <Error>{error?.message}</Error>
+            ))}
+            <Grid container spacing={5}>
+                <Grid item xs={6}>
+                    <TextField
+                        id="email"
+                        name="email"
+                        variant="outlined"
+                        label="Email"
+                        size="small"
+                        error={formik.touched.email && Boolean(formik.errors.email)}
+                        value={formik.values.email}
+                        onBlur={formik.handleBlur}
+                        onChange={formik.handleChange}
+                        helperText={formik.touched.email && formik.errors.email}
+                        fullWidth
+                        sx={{ mt: 2 }}
+                    />
+
+                    <Box sx={{ mt: 3 }}>
+                        <InputLabel className="form-label">Статус</InputLabel>
+                        <Select
+                            value={{
+                                value: inputStatus || null,
+                                label: inputStatus ? statusNumber[inputStatus] : null,
+                            }}
+                            onChange={(e) => setInputStatus(e.value)}
+                            options={statusOptions}
+                        />
+                    </Box>
+
+                    <Box direction="row" sx={{ mt: 3 }} justifyContent="flex-end">
+                        <Button
+                            variant="contained"
+                            disabled={!formik.isValid || formik.isSubmitting}
+                            type="submit"
+                            fullWidth
+                        >
+                            Зберегти
+                        </Button>
+                    </Box>
+                </Grid>
+                <Grid item xs={6}>
+                    <TextField
+                        variant="outlined"
+                        id="phoneNumber"
+                        name="phoneNumber"
+                        label="Номер"
+                        size="small"
+                        error={formik.touched.phoneNumber && Boolean(formik.errors.phoneNumber)}
+                        value={formik.values.phoneNumber}
+                        onBlur={formik.handleBlur}
+                        onChange={formik.handleChange}
+                        helperText={formik.touched.phoneNumber && formik.errors.phoneNumber}
+                        multiline
+                        fullWidth
+                        sx={{ mt: 2 }}
+                    />
+                </Grid>
+            </Grid>
+        </Box>
+    );
+};
+
+export const COrderForm = connect(
+    (state) => ({
+        catList: state.promise.catAll?.payload || [],
+        promiseStatus: state.promise.orderUpsert?.status || null,
+        order: state.promise?.adminOrderById?.payload || {},
+    }),
+    {
+        onSave: (order) => actionOrderUpdate(order),
+        onClose: () => actionPromiseClear('orderUpsert'),
+    }
+)(OrderForm);

+ 12 - 0
src/components/admin/AdminOrderPage/index.js

@@ -0,0 +1,12 @@
+import { Box } from '@mui/material';
+import { COrderForm } from './OrderForm';
+import { connect } from 'react-redux';
+
+export const AdminOrderPage = ({ order }) => (
+    <Box className="AdminOrderPage">
+        <COrderForm order={order} />
+    </Box>
+);
+export const CAdminOrderPage = connect((state) => ({ order: state.promise?.adminOrderById?.payload || {} }))(
+    AdminOrderPage
+);

+ 30 - 0
src/components/admin/AdminOrdersPage/AdminOrderItem.js

@@ -0,0 +1,30 @@
+import { Link } from 'react-router-dom';
+
+import { Box, Button, TableCell, TableRow, Typography } from '@mui/material';
+import { statusNumber } from '../../../helpers';
+
+const AdminOrderItem = ({ order }) => (
+    <TableRow className="AdminOrderItem">
+        <TableCell scope="row">{order._id}</TableCell>
+        <TableCell>{order.email ? order.email : '-'}</TableCell>
+        <TableCell>{order.phoneNumber ? order.phoneNumber : '-'}</TableCell>
+        <TableCell>
+            {order.orderGoods
+                ? (order.orderGoods || []).map((orderGood) => (
+                      <Typography variant="body2">
+                          {orderGood.good.name} - {orderGood.count}
+                      </Typography>
+                  ))
+                : '-'}
+        </TableCell>
+        <TableCell>{order.price ? order.price : '-'}</TableCell>
+        <TableCell>{'' + order?.status?.length ? statusNumber[order.status] : '-'}</TableCell>
+        <TableCell className="edit">
+            <Button component={Link} className="Link" to={`/admin/order/${order._id}/`} variant="contained">
+                Редагувати
+            </Button>
+        </TableCell>
+    </TableRow>
+);
+
+export { AdminOrderItem };

+ 43 - 0
src/components/admin/AdminOrdersPage/AdminOrderList.js

@@ -0,0 +1,43 @@
+import { AdminOrderListHeader } from './AdminOrderListHeader';
+import { connect } from 'react-redux';
+
+import { SearchBar, SearchResults } from '../../common/SearchBar';
+import { actionOrdersFind } from '../../../actions/actionOrdersFind';
+import { actionPromiseClear } from '../../../reducers';
+import { Box, Table, TableBody, TableHead } from '@mui/material';
+import { AdminOrderItem } from './AdminOrderItem';
+
+const CSearchBar = connect(null, {
+    onSearch: (text) => actionOrdersFind({ promiseName: 'adminOrdersFind', text, limit: 5 }),
+    onSearchButtonClick: () => actionPromiseClear('adminOrdersFind'),
+})(SearchBar);
+
+const CSearchResults = connect((state) => ({ items: state.promise.adminOrdersFind?.payload || [] }))(SearchResults);
+
+const AdminOrderList = ({ orders }) => {
+    return (
+        <Box className="AdminOrderList">
+            <Box className="searchBarWrapper">
+                <CSearchBar
+                    render={CSearchResults}
+                    searchLink="/admin/orders/search/"
+                    renderParams={{ itemLink: '/admin/order/' }}
+                />
+            </Box>
+            <Table>
+                <TableHead>
+                    <AdminOrderListHeader />
+                </TableHead>
+                <TableBody>
+                    {(orders || []).map((order) => (
+                        <AdminOrderItem order={order} key={order._id} />
+                    ))}
+                </TableBody>
+            </Table>
+        </Box>
+    );
+};
+
+const CAdminOrderList = connect((state) => ({ orders: state.feed?.payload || [] }))(AdminOrderList);
+
+export { AdminOrderList, CAdminOrderList };

+ 28 - 0
src/components/admin/AdminOrdersPage/AdminOrderListHeader.js

@@ -0,0 +1,28 @@
+import { connect } from 'react-redux';
+
+import { AddButton } from '../../common/AddButton';
+import { TableCell, TableRow, TableSortLabel } from '@mui/material';
+import { useNavigate } from 'react-router-dom';
+
+const AdminOrderListHeader = ({ onSortChange, sort, sortReversed, onSortReverseChange }) => {
+    const navigate = useNavigate();
+    return (
+        <TableRow className="AdminOrderListHeader">
+            <TableCell scope="col">#</TableCell>
+            <TableCell scope="col">Email</TableCell>
+            <TableCell scope="col">Номер</TableCell>
+            <TableCell scope="col">Товари</TableCell>
+            <TableCell scope="col">Ціна</TableCell>
+            <TableCell scope="col">Статус</TableCell>
+            <TableCell scope="col">
+                <AddButton
+                    onClick={() => {
+                        navigate('/admin/order/');
+                    }}
+                />
+            </TableCell>
+        </TableRow>
+    );
+};
+
+export { AdminOrderListHeader };

+ 13 - 0
src/components/admin/AdminOrdersPage/index.js

@@ -0,0 +1,13 @@
+import { Box, Typography } from '@mui/material';
+import { CAdminOrderList } from './AdminOrderList';
+
+export const AdminOrdersPage = () => {
+    return (
+        <Box className="AdminOrdersPage">
+            <Typography variant="h5" sx={{ marginBottom: '10px', marginTop: '10px' }}>
+                Замовлення
+            </Typography>
+            <CAdminOrderList />
+        </Box>
+    );
+};

+ 2 - 2
src/components/common/AddButton/index.js

@@ -2,7 +2,7 @@ import { Button } from '@mui/material';
 import { AiOutlinePlus } from 'react-icons/ai';
 
 export const AddButton = ({ isDisable = false, onClick }) => (
-    <Button onClick={() => onClick()} className="AddButton" disable={isDisable.toString()}>
-        <AiOutlinePlus />
+    <Button onClick={() => onClick()} className="AddButton" disable={isDisable.toString()} variant="contained">
+        Додати
     </Button>
 );

+ 35 - 0
src/components/common/SearchBar/SearchOrderResultItem.js

@@ -0,0 +1,35 @@
+import { Link } from 'react-router-dom';
+import { Box, Grid, Stack, Typography } from '@mui/material';
+const SearchOrderResultItem = ({ order, onClick, link = '' } = {}) => {
+    const { _id = null, email = '', phoneNumber = '' } = order || {};
+
+    return (
+        <Grid
+            container
+            component={Link}
+            to={`${link}${_id}/`}
+            className="SearchOrderResultItem Link"
+            onClick={() => onClick && onClick()}
+            spacing={1}
+        >
+            <Grid item xs={2}>
+                <Box sx={{ p: 1 }}>
+                    <Typography variant="body1">{_id}</Typography>
+                </Box>
+            </Grid>
+            <Grid item xs={5}>
+                <Box sx={{ p: 1 }}>
+                    <Typography>{email.length > 30 ? `${email.substring(0, 30)}...` : email}</Typography>
+                </Box>
+            </Grid>
+
+            <Grid item xs={5}>
+                <Box sx={{ p: 1 }}>
+                    <Typography>{phoneNumber}</Typography>
+                </Box>
+            </Grid>
+        </Grid>
+    );
+};
+
+export default SearchOrderResultItem;

+ 17 - 1
src/components/common/SearchBar/SearchResults.js

@@ -6,11 +6,15 @@ import SearchCategoryResultItem from './SearchCategoryResultItem';
 import { Divider, Paper, Stack, Typography } from '@mui/material';
 import { Box } from '@mui/system';
 import { Error } from '../Error';
+import SearchOrderResultItem from './SearchOrderResultItem';
 
 export const SearchResults = ({ items, onItemClick, itemLink = '' }) => {
     useEffect(() => {
         console.log(items);
     }, [items]);
+    useEffect(() => {
+        console.log(itemLink.match(/.+(order || orders).+/));
+    }, [itemLink]);
     return (
         <Paper className="SearchResults">
             <Stack>
@@ -27,7 +31,7 @@ export const SearchResults = ({ items, onItemClick, itemLink = '' }) => {
                                 <Divider sx={{ my: 1 }} />
                             </Box>
                         ))
-                    ) : itemLink.match(/.+(category || categories).+/) ? (
+                    ) : itemLink.match(/.+(category|categories).+/) ? (
                         items.map((cat) => (
                             <Box>
                                 <SearchCategoryResultItem
@@ -39,6 +43,18 @@ export const SearchResults = ({ items, onItemClick, itemLink = '' }) => {
                                 <Divider sx={{ my: 1 }} />
                             </Box>
                         ))
+                    ) : itemLink.match(/.+(order|orders).+/) ? (
+                        items.map((order) => (
+                            <Box>
+                                <SearchOrderResultItem
+                                    link={itemLink}
+                                    order={order}
+                                    key={order._id}
+                                    onClick={() => onItemClick && onItemClick()}
+                                />
+                                <Divider sx={{ my: 1 }} />
+                            </Box>
+                        ))
                     ) : (
                         []
                     )

+ 2 - 1
src/helpers/index.js

@@ -2,7 +2,8 @@ import { jwtDecode } from './jwtDecode';
 import { getQuery } from './getQuery';
 import { mock } from './mock';
 import { delay } from './delay';
+import { statusNumber, statusOptions } from './orderStatus';
 
 export const query = getQuery('/');
 
-export { jwtDecode, getQuery, mock, delay };
+export { jwtDecode, getQuery, mock, delay, statusNumber, statusOptions };

+ 0 - 12
src/helpers/mock.js

@@ -1,12 +0,0 @@
-export const mock = (success, error, timeout) => {
-    return new Promise(() => {
-        setTimeout((resolve, reject) => {
-            if (Math.random() > 0.1) {
-                console.log(success());
-                resolve(success());
-            } else {
-                reject(error());
-            }
-        }, timeout || 1000);
-    });
-};

+ 25 - 0
src/helpers/orderStatus.js

@@ -0,0 +1,25 @@
+export const statusNumber = {
+    1: 'В обробці',
+    2: 'Підтверджено',
+    3: 'Виконано',
+    4: 'Скасовано',
+};
+
+export const statusOptions = [
+    {
+        value: 1,
+        label: statusNumber[1],
+    },
+    {
+        value: 2,
+        label: statusNumber[2],
+    },
+    {
+        value: 3,
+        label: statusNumber[3],
+    },
+    {
+        value: 4,
+        label: statusNumber[4],
+    },
+];

+ 10 - 0
src/index.scss

@@ -168,6 +168,10 @@
       padding: 10px;
       padding-bottom: 400px;
 
+      & .AdminOrderPage{
+        text-align: left;
+      }
+
       & .AdminGoodPage{
         & .GoodForm{
           width:40%;
@@ -252,6 +256,12 @@
                 background: #F1F2F4;
               }
             }
+            & .SearchOrderResultItem{
+              text-align: center;
+              &:hover{
+                background: #F1F2F4;
+              }
+            }
           }
 
         }

+ 16 - 0
src/reducers/feedReducer.js

@@ -4,6 +4,8 @@ import { actionGoodsFind } from '../actions/actionGoodsFind';
 import { actionCatsFind } from '../actions/actionCatsFind';
 import { actionGoodsAll } from '../actions/actionGoodsAll';
 import { gql } from '../helpers';
+import { actionOrdersAll } from '../actions/actionOrdersAll';
+import { actionOrdersFind } from '../actions/actionOrdersFind';
 
 function feedReducer(state = { payload: [] }, { type, payload = [] }) {
     if (type === 'FEED_ADD') {
@@ -45,6 +47,18 @@ const actionFeedCats =
         await dispatch(actionCatAll({ promiseName: 'feedCatAll', skip, limit: 50 }));
     };
 
+const actionFeedOrders =
+    (skip = 0) =>
+    async (dispatch, getState) => {
+        await dispatch(actionOrdersAll({ skip, limit: 50, promiseName: 'feedOrdersAll' }));
+    };
+
+const actionFeedOrdersFind =
+    ({ skip = 0, text = '' }) =>
+    async (dispatch, getState) => {
+        await dispatch(actionOrdersFind({ skip, limit: 50, promiseName: 'feedOrdersFind', text }));
+    };
+
 export {
     actionFeedCats,
     actionFeedCatsFind,
@@ -53,4 +67,6 @@ export {
     actionFeedAdd,
     actionFeedGoodsFind,
     feedReducer,
+    actionFeedOrders,
+    actionFeedOrdersFind,
 };

+ 4 - 0
src/reducers/index.js

@@ -18,6 +18,8 @@ import {
     actionFeedGoodsFind,
     actionFeedClear,
     actionFeedAdd,
+    actionFeedOrdersFind,
+    actionFeedOrders,
     feedReducer,
 } from './feedReducer';
 import { createStoreHook } from 'react-redux';
@@ -32,6 +34,8 @@ export {
     actionFeedGoodsFind,
     actionFeedClear,
     actionFeedAdd,
+    actionFeedOrdersFind,
+    actionFeedOrders,
     feedReducer,
 };
 export const store = createStore(