GoodEdit.jsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  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. setState(entity)
  126. actionClear('goodUpsert')
  127. actionClear('uploadFile')
  128. }
  129. useEffect(() => {
  130. if(!categoryState) actionRootCat()
  131. if(!goods) goodCount()
  132. if(fileStatus?.status === 'RESOLVED'){
  133. state.images?.length > 0 ?
  134. setState({...state, images: [...state.images, fileStatus?.payload]})
  135. :
  136. setState({...state, images: [fileStatus?.payload]})
  137. }
  138. },[categoryState, goods, fileStatus])
  139. return (
  140. <>
  141. {!result ?
  142. <>
  143. <Typography
  144. variant='h6'
  145. letterSpacing='2px'
  146. marginBottom='20px'
  147. >
  148. Total products: {goods?.payload || 0}
  149. </Typography>
  150. <Box
  151. style={{
  152. minHeight: "200px",
  153. border: '1px dashed #616161',
  154. borderRadius: '20px',
  155. padding: '20px'
  156. }}
  157. {...getRootProps()}
  158. >
  159. <input {...getInputProps()} />
  160. {isDragActive ?
  161. <Typography
  162. variant='body1'
  163. textAlign='center'
  164. color='#616161'
  165. >
  166. Drop the file here ...
  167. </Typography>
  168. :
  169. <Typography
  170. variant='body1'
  171. textAlign='center'
  172. color='#616161'
  173. marginBottom='20px'
  174. >
  175. Drag 'n' drop image files here, or click to select file
  176. </Typography>
  177. }
  178. <SortableContainer
  179. axis="xy"
  180. onSortEnd={onSortEnd}
  181. >
  182. {state.images?.length > 0 && state.images.map((value, index) => (
  183. <SortableItem
  184. key={`item-${value?._id || index}`}
  185. index={index}
  186. value={value}
  187. />
  188. ))}
  189. </SortableContainer>
  190. </Box>
  191. <Grid
  192. container
  193. justifyContent='space-between'
  194. alignItems='flex-end'
  195. marginTop='30px'
  196. >
  197. <Grid item xs={5.5}>
  198. <TextField
  199. fullWidth
  200. id="filled-basic"
  201. label="Title product"
  202. variant="standard"
  203. value={state?.name || ''}
  204. onChange={e => setState({...state, name: e.target.value})}
  205. />
  206. </Grid>
  207. <Grid item xs={5.5}>
  208. {categoryState && categoryState?.payload && categoryState.payload?.length > 0 &&
  209. <>
  210. {state.categories?.length > 0 ?
  211. <Autocomplete
  212. multiple
  213. id="tags-standard"
  214. options={Object.values(categoryState.payload)}
  215. defaultValue={state.categories}
  216. onChange={(event, newValue) => {
  217. setState({...state, categories: [...newValue]})
  218. }}
  219. getOptionLabel={(option) => option?.name || 'no name'}
  220. key={option => option?.id}
  221. renderInput={(params) => (
  222. <TextField
  223. {...params}
  224. variant="standard"
  225. label="Select categories"
  226. placeholder="categories"
  227. />
  228. )}
  229. /> :
  230. <Autocomplete
  231. multiple
  232. id="tags-standard"
  233. options={Object.values(categoryState.payload)}
  234. onChange={(event, newValue) => {
  235. setState({...state, categories: [...newValue]})
  236. }}
  237. getOptionLabel={(option) => option?.name || 'no name'}
  238. key={option => option?.id}
  239. renderInput={(params) => (
  240. <TextField
  241. {...params}
  242. variant="standard"
  243. label="Select categories"
  244. placeholder="categories"
  245. />
  246. )}
  247. />
  248. }
  249. </>
  250. }
  251. </Grid>
  252. </Grid>
  253. <Grid
  254. container
  255. justifyContent='space-between'
  256. alignItems='flex-end'
  257. marginTop='30px'
  258. >
  259. <Grid item xs={5.5}>
  260. <TextField fullWidth
  261. id='Price'
  262. type='number'
  263. label='Price'
  264. variant='standard'
  265. value={state?.price || ''}
  266. onChange={e => setState({...state,
  267. price: parseFloat(e.target.value < 0 ? 0 : e.target.value)})
  268. }
  269. />
  270. </Grid>
  271. <Grid item xs={5.5}>
  272. <TextField fullWidth
  273. id='filled-basic'
  274. label='Description product'
  275. variant='standard'
  276. multiline
  277. value={state?.description || ''}
  278. onChange={e => setState({...state, description: e.target.value})}
  279. />
  280. </Grid>
  281. </Grid>
  282. <Grid
  283. container
  284. justifyContent='space-between'
  285. marginTop='30px'
  286. >
  287. <Grid
  288. item xs={5.5}
  289. display='flex'
  290. justifyContent='center'
  291. >
  292. <Button
  293. fullWidth
  294. onClick={handleClear}
  295. variant="outlined"
  296. color='warning'
  297. >
  298. Clear
  299. </Button>
  300. </Grid>
  301. <Grid
  302. item xs={5.5}
  303. display='flex'
  304. justifyContent='center'
  305. >
  306. <Button
  307. fullWidth
  308. variant="outlined"
  309. color='primary'
  310. onClick={handleOnSave}
  311. >
  312. Save
  313. </Button>
  314. </Grid>
  315. </Grid>
  316. </> :
  317. result?.payload?._id ?
  318. <>
  319. <Box
  320. display='flex'
  321. alignItems='center'
  322. flexDirection='column'
  323. >
  324. <Typography
  325. variant='h5'
  326. letterSpacing='2px'
  327. textAlign='center'
  328. color='#616161'
  329. marginBottom='20px'
  330. >
  331. Product successfully created!
  332. </Typography>
  333. <CheckCircleOutlineIcon sx={{marginBottom: '20px'}}/>
  334. <Link
  335. to={`/good/${result.payload._id}`}
  336. style={{
  337. color:'#616161',
  338. marginBottom:'20px'
  339. }}
  340. >
  341. <Typography
  342. variant='h5'
  343. letterSpacing='2px'
  344. textAlign='center'
  345. color='#616161'
  346. >
  347. View results
  348. </Typography>
  349. </Link>
  350. <Button
  351. variant='outlined'
  352. onClick={handleFullClear}
  353. >
  354. Add more
  355. </Button>
  356. </Box>
  357. </> :
  358. result?.error ?
  359. <Box
  360. display='flex'
  361. alignItems='center'
  362. flexDirection='column'
  363. >
  364. <Typography
  365. variant='h5'
  366. letterSpacing='2px'
  367. textAlign='center'
  368. color='#f00'
  369. marginBottom='20px'
  370. >
  371. Fatal error, try again!
  372. </Typography>
  373. <Button
  374. variant='outlined'
  375. onClick={handleFullClear}
  376. >
  377. Add more
  378. </Button>
  379. </Box>
  380. :
  381. <Box sx={{ display: 'flex' }}>
  382. <CircularProgress />
  383. </Box>
  384. }
  385. </>
  386. )
  387. }
  388. export const CGoodEdit = connect(state => ({fileStatus: state.promise['uploadFile'],
  389. categoryState: state.promise['allCategory'], goods: state.promise['goodCount'], result: state.promise['goodUpsert']}),
  390. {actionRootCat: actionAllCategory, onSave: actionGoodUpsert, goodCount: actionGoodCount,
  391. onFileDrop: actionUploadFiles, actionClear: actionClearPromise})(GoodEdit)