CatalogPage.jsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. import Breadcrumb from "../components/Breadcrumbs";
  2. import {
  3. Accordion,
  4. AccordionDetails,
  5. AccordionSummary, Box, Button,
  6. Card, CardActionArea, CardActions, CardContent, CardMedia,
  7. Container, Divider, FormControl,
  8. Grid, MenuItem, Select,
  9. Typography,
  10. useMediaQuery
  11. } from "@mui/material";
  12. import {connect} from "react-redux";
  13. import {actionFullCatById, actionFullRootCats} from "../actions/ActionCategory";
  14. import Link from "react-router-dom/es/Link";
  15. import Route from "react-router-dom/es/Route";
  16. import Switch from "react-router-dom/es/Switch";
  17. import {useEffect, useState} from "react";
  18. import {backURL} from "../actions/PathDB";
  19. import {actionCardRemove, actionCartAdd} from "../reducers/CartReducer";
  20. import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
  21. import FavoriteBorderIcon from "@mui/icons-material/FavoriteBorder";
  22. import AddShoppingCartIcon from '@mui/icons-material/AddShoppingCart';
  23. import {Pagination} from "@mui/material";
  24. import {actionWishListAdd, actionWishListRemove} from "../reducers/WishListReducer";
  25. import FavoriteIcon from '@mui/icons-material/Favorite';
  26. import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
  27. import imgNotFound from "../img/catalog/imgNotFound.png";
  28. import {NotFoundBlock} from "../components/NotFoundBlock";
  29. const CategoryItem = ({object: {_id, name, subCategories}={}}) => {
  30. const [expanded, setExpanded] = useState(false);
  31. const handleChange = (panel) => (event, isExpanded) => {
  32. setExpanded(isExpanded ? panel : false);
  33. };
  34. return (
  35. <>
  36. {subCategories === null || !subCategories ?
  37. <li>
  38. <Link style={{textDecoration: 'none'}} to={`/catalog/category/${_id}`}>
  39. <Typography
  40. variant='body1'
  41. color='#616161'
  42. marginBottom='10px'
  43. >
  44. {name}
  45. </Typography>
  46. </Link>
  47. </li>
  48. :
  49. <li>
  50. <Accordion style={{border: 'none', borderRadius: '0',marginTop: '-10px', boxShadow: 'none'}} expanded={expanded === 'panel1'} onChange={handleChange('panel1')}>
  51. <AccordionSummary
  52. sx={{padding: '0'}}
  53. expandIcon={<ExpandMoreIcon />}
  54. aria-controls="panel1bh-content"
  55. id="panel1bh-header"
  56. >
  57. <Link style={{textDecoration: 'none'}} to={`/catalog/category/${_id}`}>
  58. <Typography
  59. variant='body1'
  60. color='#616161'
  61. padding='0'
  62. >
  63. {name}
  64. </Typography>
  65. </Link>
  66. </AccordionSummary>
  67. <AccordionDetails>
  68. <ul style={{listStyle: 'none', padding: '0 0 0 10px', marginBottom: '10px'}}>
  69. {subCategories && Object.values(subCategories).map(item =>
  70. <CategoryItem key={item['_id']} object={item}/>
  71. )}
  72. </ul>
  73. </AccordionDetails>
  74. </Accordion>
  75. </li>
  76. }
  77. </>
  78. )
  79. }
  80. const CategoryAside = ({category}) => {
  81. return (
  82. <Grid sx={{backgroundColor: '#fff', padding: '30px'}} xs={12} lg={3} item>
  83. <Typography
  84. variant='h6'
  85. letterSpacing='3px'
  86. lineHeight='1.3em'
  87. marginBottom='20px'
  88. >
  89. PRODUCT CATEGORIES
  90. </Typography>
  91. <ul style={{listStyle: 'none', padding: '0'}}>
  92. {category && Object.values(category).map(item =>
  93. <CategoryItem key={item['_id']} object={item}/>
  94. )}
  95. </ul>
  96. </Grid>
  97. )
  98. }
  99. const GoodCard = ({good:{_id, name, description, price, images}={}, wishlist={}, cart={}, onCartAdd, onWishListAdd, onCartRemove, onWishListRemove}) => {
  100. return (
  101. <Grid xs={12} lg={4} item margin='20px 0'>
  102. <Card sx={{ maxWidth: 345, height: '100%', display: 'flex', flexDirection: 'column', margin: 'auto 20px'}}>
  103. <CardActionArea sx={{padding: '0', flexGrow: '1', position: 'relative'}}>
  104. <Link to={`/good/${_id}`} style={{position: 'relative', textDecoration: 'none'}}>
  105. <CardMedia sx={{marginBottom: '20px', marginTop: '20px'}}
  106. component="img"
  107. height="230"
  108. image={images && images[0]?.url ? `${backURL}/${images[0]?.url}` : imgNotFound}
  109. alt="Good title image"
  110. />
  111. <CardContent sx={{display: 'flex', flexDirection: 'column', height: '200px', justifyContent: 'space-between'}}>
  112. <Typography
  113. textAlign='center'
  114. fontFamily='sarif'
  115. letterSpacing='2px'
  116. marginBottom='20px'
  117. fontSize='19px'
  118. sx={{textTransform: 'uppercase', flexGrow: '1'}}
  119. color='#000'
  120. >
  121. {name.length > 30 ? name.split(' ').splice(0, 6).join(' ') : name}
  122. </Typography>
  123. <Typography textAlign='center' variant="body2" color='#616161' marginBottom='20px' sx={{ flexGrow: '0'}}>
  124. {description && description.length > 60 ?
  125. 'Lorem ipsum dolor sit amet, consectetur adipisicing elit.' :
  126. description
  127. }
  128. </Typography>
  129. <Typography textAlign='center' variant="h5" color="#000" sx={{ flexGrow: '0'}}>
  130. $ {parseFloat(price).toFixed(2)}
  131. </Typography>
  132. </CardContent>
  133. </Link>
  134. </CardActionArea>
  135. <CardActions sx={{flexGrow: '0', justifyContent: 'space-between'}}>
  136. <Button onClick={() => {_id in cart ? onCartRemove({_id, name, price, images}) : onCartAdd({_id, name, price, images})}} size="small" color="primary">
  137. {_id in cart ? <ShoppingCartIcon/> : <AddShoppingCartIcon />}
  138. </Button>
  139. <Button onClick={() => {_id in wishlist ? onWishListRemove({_id, name, price, images}) : onWishListAdd({_id, name, price, images})}} size="small" color="primary">
  140. {_id in wishlist ? <FavoriteIcon/> : <FavoriteBorderIcon />}
  141. </Button>
  142. </CardActions>
  143. </Card>
  144. </Grid>
  145. )
  146. }
  147. const CGoodCard = connect(state => ({wishlist: state.wishlist, cart: state.cart}),
  148. {onCartAdd: actionCartAdd, onWishListAdd: actionWishListAdd, onCartRemove: actionCardRemove, onWishListRemove: actionWishListRemove})(GoodCard)
  149. const Goods = ({_id, category={}}) => {
  150. const itemsPerPage = 6
  151. const [page, setPage] = useState(1)
  152. const [count, setCount] = useState(1)
  153. const [goods, setGoods] = useState([])
  154. const [sort, setSort] = useState(0)
  155. const sortDefault = () => {
  156. setGoods([goods[0].sort((a, b) => a['name'] > b['name'] ? 1 : -1)])
  157. }
  158. const sortLatest = () => {
  159. setGoods([goods[0].sort((a, b) => b['createdAt'] > a['createdAt'] ? 1 : -1)])
  160. }
  161. const sortLowToHigh = () => {
  162. setGoods([goods[0].sort((a, b) => a['price'] > b['price'] ? 1 : -1)])
  163. }
  164. const sortHighToLow = () => {
  165. setGoods([goods[0].sort((a, b) => b['price'] > a['price'] ? 1 : -1)])
  166. }
  167. const handleChange = (event, value) => {
  168. setPage(value);
  169. }
  170. const handleChangeSelect = (event) => {
  171. setSort(event.target.value);
  172. if (event.target.value === 0) sortDefault()
  173. else if (event.target.value === 1) sortLatest()
  174. else if (event.target.value === 2) sortLowToHigh()
  175. else if (event.target.value === 3) sortHighToLow()
  176. }
  177. useEffect(() => {
  178. let arr = (Object.values(category) || []).map(item => {
  179. if (item['_id'] === _id) {
  180. if (Array.isArray(item?.goods) && item?.goods.length > 0) {
  181. setCount(Math.ceil(item.goods.length / itemsPerPage))
  182. return item.goods
  183. }
  184. }
  185. else if(Array.isArray(item['subCategories'])) {
  186. let arr = item['subCategories'].map(subItem => {
  187. if (subItem['_id'] === _id) {
  188. if (Array.isArray(subItem?.goods) && subItem?.goods.length > 0) {
  189. setCount(Math.ceil(subItem.goods.length / itemsPerPage))
  190. return subItem.goods
  191. }
  192. }
  193. else if(Array.isArray(subItem['subCategories'])) {
  194. let arr = subItem['subCategories'].map(subSubItem => {
  195. if (subSubItem['_id'] === _id) {
  196. if (Array.isArray(subSubItem?.goods && subSubItem?.goods.length > 0)) {
  197. setCount(Math.ceil(subSubItem.goods.length / itemsPerPage))
  198. return subSubItem.goods
  199. }
  200. }
  201. else {
  202. return 0
  203. }
  204. }).filter(item => item)
  205. return arr.length > 0 ? [...arr[0]] : 0
  206. }
  207. else {
  208. return 0
  209. }
  210. }).filter(item => item)
  211. return arr.length > 0 ? [...arr[0]] : 0
  212. }
  213. else {
  214. return 0
  215. }
  216. }).filter(item => item)
  217. setGoods(arr)
  218. }, [_id, category])
  219. return (
  220. <>
  221. {(goods.length > 0 ?
  222. <Box sx={{height:'100%', display: 'flex', flexDirection: 'column', justifyContent: 'space-between'}}>
  223. <Box display='flex' alignItems='center' justifyContent='space-between' padding='0 20px'>
  224. <Typography
  225. variant='body1'
  226. color='#616161'
  227. letterSpacing='1px'
  228. >
  229. SHOWING {goods[0].length > itemsPerPage ? `${((page-1) * itemsPerPage)+1}-${page * itemsPerPage > goods[0].length ? goods[0].length : page * itemsPerPage}` : goods[0].length} OF {goods[0].length} RESULTS
  230. </Typography>
  231. <FormControl variant="standard">
  232. <Select
  233. labelId="demo-simple-select-label"
  234. id="demo-simple-select"
  235. value={sort}
  236. label="Sort"
  237. onChange={handleChangeSelect}
  238. sx={{textTransform: 'uppercase', color: '#616161'}}
  239. >
  240. <MenuItem value={0}>Default sorting</MenuItem>
  241. <MenuItem value={1}>Sort by latest</MenuItem>
  242. <MenuItem value={2}>Sort by price: low to high</MenuItem>
  243. <MenuItem value={3}>Sort by price: high to low</MenuItem>
  244. </Select>
  245. </FormControl>
  246. </Box>
  247. <Box flexGrow='1'>
  248. <Grid container justifyContent='space-between'>
  249. {[...goods[0]].slice((page - 1) * itemsPerPage, page * itemsPerPage)
  250. .map(good => <CGoodCard key={good['_id']} good={good}/>)}
  251. </Grid>
  252. </Box>
  253. <Box width='100%' flexGrow='0'>
  254. <Divider sx={{margin: '20px'}}/>
  255. <Box display='flex' justifyContent='center' width='100%'>
  256. <Pagination
  257. count={count}
  258. page={page}
  259. onChange={handleChange}
  260. defaultPage={1}
  261. color="primary"
  262. size="large"
  263. showFirstButton
  264. showLastButton
  265. />
  266. </Box>
  267. </Box>
  268. </Box>
  269. : <NotFoundBlock/>)
  270. }
  271. </>
  272. )
  273. }
  274. const CGoods = connect(state => ({category: state.category}))(Goods)
  275. const BlockGood = ({match:{params:{_id}}, getData}) => {
  276. useEffect(() => {
  277. getData(_id)
  278. },[_id, getData])
  279. return(
  280. <CGoods key={_id} _id={_id} />
  281. )
  282. }
  283. const CBlockGood= connect(null, {getData: actionFullCatById})(BlockGood)
  284. const Products = () => {
  285. return (
  286. <Grid xs={12} lg={9} item>
  287. <Switch>
  288. <Route path="/catalog/category/:_id" component={CBlockGood} />
  289. <Route path="*" component={NotFoundBlock} />
  290. </Switch>
  291. </Grid>
  292. )
  293. }
  294. const CatalogPage = ({category={}, actionRootCat}) => {
  295. const matches = useMediaQuery('(max-width:899px)')
  296. if(Object.entries(category).length === 0) actionRootCat()
  297. return (
  298. <>
  299. <Breadcrumb links={['catalog']}/>
  300. <main style={{backgroundColor: "#f3f3f3", padding: matches ? "20px 0" : "50px 0"}}>
  301. <Container maxWidth="lg">
  302. <Grid container justifyContent='space-between'>
  303. <CategoryAside category={category}/>
  304. <Products/>
  305. </Grid>
  306. </Container>
  307. </main>
  308. </>
  309. )
  310. }
  311. const CCatalogPage = connect(state => ({category: state.category}), {actionRootCat: actionFullRootCats})(CatalogPage)
  312. export default CCatalogPage
  313. //TODO MOBILE VERSION