CatalogPage.jsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  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 imgUrl from "../img/not-found/1.png";
  28. import imgNotFound from "../img/catalog/imgNotFound.png";
  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[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.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 GoodNotFound = () => {
  150. const matches2 = useMediaQuery('(max-width:450px)');
  151. return (
  152. <Container maxWidth="lg">
  153. <Box sx={{
  154. backgroundColor: "#fff",
  155. height: matches2 ? "250px" : "350px",
  156. display: "flex",
  157. flexDirection: "column",
  158. justifyContent: "center",
  159. alignItems: "center"
  160. }}>
  161. <img style={{
  162. maxWidth: matches2 ? "100px" : "150px"
  163. }} src={imgUrl} alt="PAGE NOT FOUND"/>
  164. <Typography
  165. variant={matches2 ? "h6" : "h5"}
  166. fontFamily="sarif"
  167. fontWeight="300"
  168. marginBottom="20px"
  169. marginTop="20px"
  170. textAlign="center"
  171. >
  172. OOPS! THAT PAGE CAN’T BE FOUND
  173. </Typography>
  174. <Typography
  175. variant={matches2 ? "body1" : "h7"}
  176. textAlign="center"
  177. fontWeight="300"
  178. >
  179. The page you are trying to reach is not available.
  180. </Typography>
  181. </Box>
  182. </Container>
  183. )
  184. }
  185. const Goods = ({_id='5dc49f4d5df9d670df48cc64', category={}}) => {
  186. const itemsPerPage = 6
  187. const [page, setPage] = useState(1)
  188. const [count, setCount] = useState(1)
  189. const [goods, setGoods] = useState([])
  190. const [sort, setSort] = useState(0)
  191. const sortDefault = () => {
  192. setGoods([goods[0].sort((a, b) => a['name'] > b['name'] ? 1 : -1)])
  193. }
  194. const sortLatest = () => {
  195. setGoods([goods[0].sort((a, b) => b['createdAt'] > a['createdAt'] ? 1 : -1)])
  196. }
  197. const sortLowToHigh = () => {
  198. setGoods([goods[0].sort((a, b) => a['price'] > b['price'] ? 1 : -1)])
  199. }
  200. const sortHighToLow = () => {
  201. setGoods([goods[0].sort((a, b) => b['price'] > a['price'] ? 1 : -1)])
  202. }
  203. const handleChange = (event, value) => {
  204. setPage(value);
  205. }
  206. const handleChangeSelect = (event) => {
  207. setSort(event.target.value);
  208. if (event.target.value === 0) sortDefault()
  209. else if (event.target.value === 1) sortLatest()
  210. else if (event.target.value === 2) sortLowToHigh()
  211. else if (event.target.value === 3) sortHighToLow()
  212. }
  213. useEffect(() => {
  214. let arr = (Object.values(category) || []).map(item => {
  215. if (item['_id'] === _id) {
  216. if (Array.isArray(item?.goods) && item?.goods.length > 0) {
  217. setCount(Math.ceil(item.goods.length / itemsPerPage))
  218. return item.goods
  219. }
  220. }
  221. else if(Array.isArray(item['subCategories'])) {
  222. let arr = item['subCategories'].map(subItem => {
  223. if (subItem['_id'] === _id) {
  224. if (Array.isArray(subItem?.goods) && subItem?.goods.length > 0) {
  225. setCount(Math.ceil(subItem.goods.length / itemsPerPage))
  226. return subItem.goods
  227. }
  228. }
  229. else if(Array.isArray(subItem['subCategories'])) {
  230. let arr = subItem['subCategories'].map(subSubItem => {
  231. if (subSubItem['_id'] === _id) {
  232. if (Array.isArray(subSubItem?.goods && subSubItem?.goods.length > 0)) {
  233. setCount(Math.ceil(subSubItem.goods.length / itemsPerPage))
  234. return subSubItem.goods
  235. }
  236. }
  237. else {
  238. return 0
  239. }
  240. }).filter(item => item)
  241. return arr.length > 0 ? [...arr[0]] : 0
  242. }
  243. else {
  244. return 0
  245. }
  246. }).filter(item => item)
  247. return arr.length > 0 ? [...arr[0]] : 0
  248. }
  249. else {
  250. return 0
  251. }
  252. }).filter(item => item)
  253. setGoods(arr)
  254. }, [_id, category])
  255. return (
  256. <>
  257. {(goods.length > 0 ?
  258. <Box sx={{height:'100%', display: 'flex', flexDirection: 'column', justifyContent: 'space-between'}}>
  259. <Box display='flex' alignItems='center' justifyContent='space-between' padding='0 20px'>
  260. <Typography
  261. variant='body1'
  262. color='#616161'
  263. letterSpacing='1px'
  264. >
  265. 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
  266. </Typography>
  267. <FormControl variant="standard">
  268. <Select
  269. labelId="demo-simple-select-label"
  270. id="demo-simple-select"
  271. value={sort}
  272. label="Sort"
  273. onChange={handleChangeSelect}
  274. sx={{textTransform: 'uppercase', color: '#616161'}}
  275. >
  276. <MenuItem value={0}>Default sorting</MenuItem>
  277. <MenuItem value={1}>Sort by latest</MenuItem>
  278. <MenuItem value={2}>Sort by price: low to high</MenuItem>
  279. <MenuItem value={3}>Sort by price: high to low</MenuItem>
  280. </Select>
  281. </FormControl>
  282. </Box>
  283. <Box flexGrow='1'>
  284. <Grid container justifyContent='space-between'>
  285. {[...goods[0]].slice((page - 1) * itemsPerPage, page * itemsPerPage)
  286. .map(good => <CGoodCard key={good['_id']} good={good}/>)}
  287. </Grid>
  288. </Box>
  289. <Box width='100%' flexGrow='0'>
  290. <Divider sx={{margin: '20px'}}/>
  291. <Box display='flex' justifyContent='center' width='100%'>
  292. <Pagination
  293. count={count}
  294. page={page}
  295. onChange={handleChange}
  296. defaultPage={1}
  297. color="primary"
  298. size="large"
  299. showFirstButton
  300. showLastButton
  301. />
  302. </Box>
  303. </Box>
  304. </Box>
  305. : <GoodNotFound/>)
  306. }
  307. </>
  308. )
  309. }
  310. const CGoods = connect(state => ({category: state.category}))(Goods)
  311. const BlockGood = ({match:{params:{_id}}, getData}) => {
  312. useEffect(() => {
  313. getData(_id)
  314. },[_id, getData])
  315. return(
  316. <CGoods key={_id} _id={_id} />
  317. )
  318. }
  319. const CBlockGood= connect(null, {getData: actionFullCatById})(BlockGood)
  320. const Products = () => {
  321. return (
  322. <Grid xs={12} lg={9} item>
  323. <Switch>
  324. <Route path="/catalog/category/:_id" component={CBlockGood} />
  325. <Route path="*" component={GoodNotFound} />
  326. </Switch>
  327. </Grid>
  328. )
  329. }
  330. const CatalogPage = ({category={}, actionRootCat}) => {
  331. const matches = useMediaQuery('(max-width:899px)')
  332. if(Object.entries(category).length === 0) actionRootCat()
  333. return (
  334. <>
  335. <Breadcrumb links={['catalog']}/>
  336. <main style={{backgroundColor: "#f3f3f3", padding: matches ? "20px 0" : "50px 0"}}>
  337. <Container maxWidth="lg">
  338. <Grid container justifyContent='space-between'>
  339. <CategoryAside category={category}/>
  340. <Products/>
  341. </Grid>
  342. </Container>
  343. </main>
  344. </>
  345. )
  346. }
  347. const CCatalogPage = connect(state => ({category: state.category}), {actionRootCat: actionFullRootCats})(CatalogPage)
  348. export default CCatalogPage
  349. //TODO MOBILE VERSION