GoodEdit.jsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. import {useEffect, useState} from "react";
  2. import {useDropzone} from "react-dropzone";
  3. import {sortableContainer, sortableElement} from "react-sortable-hoc";
  4. import Box from "@mui/material/Box";
  5. import {backURL} from "../../../actions/PathDB";
  6. import {Button, CircularProgress, Grid, TextField} from "@mui/material";
  7. import {arrayMoveImmutable} from "array-move";
  8. import Typography from "@mui/material/Typography";
  9. import Autocomplete from "@mui/material/Autocomplete";
  10. import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
  11. import {Link} from "react-router-dom";
  12. import {connect} from "react-redux";
  13. import {actionAllCategory} from "../../../actions/ActionCategory";
  14. import {actionGoodUpsert} from "../../../actions/ActionCreateGood";
  15. import {actionGoodCount} from "../../../actions/ActionGoodFind";
  16. import {actionUploadFile, actionUploadFiles} from "../../../actions/ActionUploadFile";
  17. import {actionClearPromise} from "../../../reducers/PromiseReducer";
  18. const GoodEdit = ({entity={images: [], categories: []},
  19. onSave,
  20. onFileDrop,
  21. fileStatus,
  22. categoryState,
  23. actionRootCat,
  24. goodCount,
  25. goods,
  26. actionClear,
  27. result}) => {
  28. const [state, setState] = useState(entity)
  29. const {getRootProps, getInputProps, isDragActive} = useDropzone({
  30. accept: 'image/*', onDrop: acceptedFiles => {
  31. // acceptedFiles.forEach(async file => {
  32. // await onFileDrop(file)
  33. // })
  34. onFileDrop(acceptedFiles)
  35. }})
  36. const SortableItem = sortableElement(({value}) => {
  37. return (
  38. <Box
  39. key={value?._id}
  40. sx={{
  41. display: 'flex',
  42. justifyContent: 'center',
  43. borderRadius: 2,
  44. border: '1px solid #eaeaea',
  45. marginBottom: 2,
  46. width: 200,
  47. height: 200,
  48. padding: '5px',
  49. boxSizing: 'border-box'
  50. }}
  51. >
  52. <Box
  53. sx={{
  54. display: 'flex',
  55. justifyContent: 'center',
  56. minWidth: 0,
  57. overflow: 'hidden',
  58. position: 'relative'
  59. }}
  60. >
  61. {value?.url ?
  62. <>
  63. <img
  64. src={backURL+ '/' + value.url}
  65. style={{
  66. display: 'block',
  67. width: 'auto',
  68. height: '100%',
  69. objectFit: 'cover',
  70. objectPosition: 'center center'
  71. }}
  72. alt={value.name}
  73. />
  74. </> :
  75. <Box
  76. sx={{
  77. display: 'flex',
  78. justifyContent: 'center',
  79. alignItems: 'center'
  80. }}
  81. >
  82. <CircularProgress />
  83. </Box>
  84. }
  85. </Box>
  86. </Box>
  87. )
  88. });
  89. const SortableContainer = sortableContainer(({children}) => {
  90. return (
  91. <aside
  92. style={{
  93. display:'flex',
  94. justifyContent: 'space-between',
  95. flexWrap: 'wrap'
  96. }}
  97. >
  98. {children}
  99. </aside>
  100. )
  101. })
  102. const onSortEnd = ({oldIndex, newIndex}) => {
  103. setState(({images}) => ({
  104. ...state,
  105. images: arrayMoveImmutable(images, oldIndex, newIndex),
  106. }));
  107. }
  108. const handleClear = () => {
  109. setState(entity)
  110. }
  111. const handleOnSave = () => {
  112. let query = {...state}
  113. state.images?.length > 0 ?
  114. query.images = state.images.map(item => {return {'_id': item['_id']}})
  115. :
  116. delete query.images
  117. state.categories?.length > 0 ?
  118. query.categories = state.categories.map(item => {return {'_id': item['_id'], 'name': item['name']}})
  119. :
  120. delete query.categories
  121. onSave(query)
  122. goodCount()
  123. }
  124. const handleFullClear = () => {
  125. goodCount()
  126. setState(entity)
  127. actionClear('goodUpsert')
  128. actionClear('uploadFile')
  129. }
  130. useEffect(() => {
  131. if(!categoryState) actionRootCat()
  132. if(!goods) goodCount()
  133. if(fileStatus?.status === 'RESOLVED'){
  134. state.images?.length > 0 ?
  135. setState({...state, images: [...state.images, fileStatus?.payload]})
  136. :
  137. setState({...state, images: [fileStatus?.payload]})
  138. }
  139. },[categoryState, goods, fileStatus])
  140. return (
  141. <>
  142. {!result ?
  143. <>
  144. <Typography
  145. variant='h6'
  146. letterSpacing='2px'
  147. marginBottom='20px'
  148. >
  149. Total products: {goods?.payload || 0}
  150. </Typography>
  151. <Box
  152. style={{
  153. minHeight: "200px",
  154. border: '1px dashed #616161',
  155. borderRadius: '20px',
  156. padding: '20px'
  157. }}
  158. {...getRootProps()}
  159. >
  160. <input {...getInputProps()} />
  161. {isDragActive ?
  162. <Typography
  163. variant='body1'
  164. textAlign='center'
  165. color='#616161'
  166. >
  167. Drop the file here ...
  168. </Typography>
  169. :
  170. <Typography
  171. variant='body1'
  172. textAlign='center'
  173. color='#616161'
  174. marginBottom='20px'
  175. >
  176. Drag 'n' drop image files here, or click to select file
  177. </Typography>
  178. }
  179. <SortableContainer
  180. axis="xy"
  181. onSortEnd={onSortEnd}
  182. >
  183. {state.images?.length > 0 && state.images.map((value, index) => (
  184. <SortableItem
  185. key={`item-${value?._id || index}`}
  186. index={index}
  187. value={value}
  188. />
  189. ))}
  190. </SortableContainer>
  191. </Box>
  192. <Grid
  193. container
  194. justifyContent='space-between'
  195. alignItems='flex-end'
  196. marginTop='30px'
  197. >
  198. <Grid item xs={5.5}>
  199. <TextField
  200. fullWidth
  201. id="filled-basic"
  202. label="Title product"
  203. variant="standard"
  204. value={state?.name || ''}
  205. onChange={e => setState({...state, name: e.target.value})}
  206. />
  207. </Grid>
  208. <Grid item xs={5.5}>
  209. {categoryState && categoryState?.payload && categoryState.payload?.length > 0 &&
  210. <>
  211. {state.categories?.length > 0 ?
  212. <Autocomplete
  213. multiple
  214. id="tags-standard"
  215. options={Object.values(categoryState.payload)}
  216. defaultValue={state.categories}
  217. onChange={(event, newValue) => {
  218. setState({...state, categories: [...newValue]})
  219. }}
  220. getOptionLabel={(option) => option?.name || 'no name'}
  221. key={option => option?.id}
  222. renderInput={(params) => (
  223. <TextField
  224. {...params}
  225. variant="standard"
  226. label="Select categories"
  227. placeholder="categories"
  228. />
  229. )}
  230. /> :
  231. <Autocomplete
  232. multiple
  233. id="tags-standard"
  234. options={Object.values(categoryState.payload)}
  235. onChange={(event, newValue) => {
  236. setState({...state, categories: [...newValue]})
  237. }}
  238. getOptionLabel={(option) => option?.name || 'no name'}
  239. key={option => option?.id}
  240. renderInput={(params) => (
  241. <TextField
  242. {...params}
  243. variant="standard"
  244. label="Select categories"
  245. placeholder="categories"
  246. />
  247. )}
  248. />
  249. }
  250. </>
  251. }
  252. </Grid>
  253. </Grid>
  254. <Grid
  255. container
  256. justifyContent='space-between'
  257. alignItems='flex-end'
  258. marginTop='30px'
  259. >
  260. <Grid item xs={5.5}>
  261. <TextField fullWidth
  262. id='Price'
  263. type='number'
  264. label='Price'
  265. variant='standard'
  266. value={state?.price || ''}
  267. onChange={e => setState({...state,
  268. price: parseFloat(e.target.value < 0 ? 0 : e.target.value)})
  269. }
  270. />
  271. </Grid>
  272. <Grid item xs={5.5}>
  273. <TextField fullWidth
  274. id='filled-basic'
  275. label='Description product'
  276. variant='standard'
  277. multiline
  278. value={state?.description || ''}
  279. onChange={e => setState({...state, description: e.target.value})}
  280. />
  281. </Grid>
  282. </Grid>
  283. <Grid
  284. container
  285. justifyContent='space-between'
  286. marginTop='30px'
  287. >
  288. <Grid
  289. item xs={5.5}
  290. display='flex'
  291. justifyContent='center'
  292. >
  293. <Button
  294. fullWidth
  295. onClick={handleClear}
  296. variant="outlined"
  297. color='warning'
  298. >
  299. Clear
  300. </Button>
  301. </Grid>
  302. <Grid
  303. item xs={5.5}
  304. display='flex'
  305. justifyContent='center'
  306. >
  307. <Button
  308. fullWidth
  309. variant="outlined"
  310. color='primary'
  311. onClick={handleOnSave}
  312. >
  313. Save
  314. </Button>
  315. </Grid>
  316. </Grid>
  317. </> :
  318. result?.payload?._id ?
  319. <>
  320. <Box
  321. display='flex'
  322. alignItems='center'
  323. flexDirection='column'
  324. >
  325. <Typography
  326. variant='h5'
  327. letterSpacing='2px'
  328. textAlign='center'
  329. color='#616161'
  330. marginBottom='20px'
  331. >
  332. Product successfully created!
  333. </Typography>
  334. <CheckCircleOutlineIcon sx={{marginBottom: '20px'}}/>
  335. <Link
  336. to={`/good/${result.payload._id}`}
  337. style={{
  338. color:'#616161',
  339. marginBottom:'20px'
  340. }}
  341. >
  342. <Typography
  343. variant='h5'
  344. letterSpacing='2px'
  345. textAlign='center'
  346. color='#616161'
  347. >
  348. View results
  349. </Typography>
  350. </Link>
  351. <Button
  352. variant='outlined'
  353. onClick={handleFullClear}
  354. >
  355. Add more
  356. </Button>
  357. </Box>
  358. </> :
  359. result?.error ?
  360. <Box
  361. display='flex'
  362. alignItems='center'
  363. flexDirection='column'
  364. >
  365. <Typography
  366. variant='h5'
  367. letterSpacing='2px'
  368. textAlign='center'
  369. color='#f00'
  370. marginBottom='20px'
  371. >
  372. Fatal error, try again!
  373. </Typography>
  374. <Button
  375. variant='outlined'
  376. onClick={handleFullClear}
  377. >
  378. Add more
  379. </Button>
  380. </Box>
  381. :
  382. <Box sx={{ display: 'flex' }}>
  383. <CircularProgress />
  384. </Box>
  385. }
  386. </>
  387. )
  388. }
  389. export const CGoodEdit = connect(state => ({fileStatus: state.promise['uploadFile'],
  390. categoryState: state.promise['allCategory'], goods: state.promise['goodCount'], result: state.promise['goodUpsert']}),
  391. {actionRootCat: actionAllCategory, onSave: actionGoodUpsert, goodCount: actionGoodCount,
  392. onFileDrop: actionUploadFiles, actionClear: actionClearPromise})(GoodEdit)