|
@@ -0,0 +1,317 @@
|
|
|
+import {useEffect, useState} from "react";
|
|
|
+import {useDropzone} from "react-dropzone";
|
|
|
+import {sortableContainer, sortableElement} from "react-sortable-hoc";
|
|
|
+import {arrayMoveImmutable} from "array-move";
|
|
|
+import Box from "@mui/material/Box";
|
|
|
+import Typography from "@mui/material/Typography";
|
|
|
+import {
|
|
|
+ Button,
|
|
|
+ CircularProgress, Container,
|
|
|
+ FormControl,
|
|
|
+ Grid, IconButton,
|
|
|
+ InputAdornment,
|
|
|
+ InputLabel,
|
|
|
+ MenuItem,
|
|
|
+ Select,
|
|
|
+ TextField
|
|
|
+} from "@mui/material";
|
|
|
+import {connect} from "react-redux";
|
|
|
+import {actionFullRootCats} from "../../actions/ActionCategory";
|
|
|
+import {actionGoodUpsert} from "../../actions/ActionCreateGood";
|
|
|
+import Autocomplete from "@mui/material/Autocomplete";
|
|
|
+import {actionFullGoodFind, actionGoodCount} from "../../actions/ActionGoodFind";
|
|
|
+import {actionUploadFile} from "../../actions/ActionUploadFile";
|
|
|
+import {backURL} from "../../actions/PathDB";
|
|
|
+import {actionClearPromise} from "../../reducers/PromiseReducer";
|
|
|
+import {Link} from "react-router-dom";
|
|
|
+import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
|
|
+import SearchIcon from "@material-ui/icons/Search";
|
|
|
+import imgNotFound from "../../img/catalog/imgNotFound.png";
|
|
|
+import {actionSearchRemove} from "../../reducers/SearchReducer";
|
|
|
+
|
|
|
+
|
|
|
+const GoodEdit = ({entity={images: [], categories: []}, onSave, onFileDrop, fileStatus, variant='create', categoryState, actionRootCat, goodCount, goods, actionClear, result}) => {
|
|
|
+ const [state, setState] = useState(entity)
|
|
|
+
|
|
|
+ const {getRootProps, getInputProps, isDragActive} = useDropzone({accept: 'image/*', onDrop: acceptedFiles => {
|
|
|
+ acceptedFiles.forEach(async file => {
|
|
|
+ await onFileDrop(file)
|
|
|
+ })
|
|
|
+ }})
|
|
|
+ const SortableItem = sortableElement(({value}) => {
|
|
|
+ return <div key={value?._id} style={{display: 'inline-flex', borderRadius: 2,border: '1px solid #eaeaea',marginBottom: 8, marginRight: 8, width: 200, height: 200, padding: 4, boxSizing: 'border-box'}}>
|
|
|
+ <div style={{display: 'flex', justifyContent: 'center', minWidth: 0, overflow: 'hidden'}}>
|
|
|
+ {value?.url ?
|
|
|
+ <img src={backURL+ '/' + value?.url} style={{display: 'block', width: 'auto', height: '100%', objectFit: 'cover', objectPosition: 'center center'}} alt={value.name}/>
|
|
|
+ :
|
|
|
+ <Box sx={{ display: 'flex' }}>
|
|
|
+ <CircularProgress />
|
|
|
+ </Box>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ });
|
|
|
+ const SortableContainer = sortableContainer(({children}) => {
|
|
|
+ return <aside style={{display:'flex', justifyContent: 'space-between', flexWrap: 'wrap'}}>
|
|
|
+ {children}
|
|
|
+ </aside>
|
|
|
+ })
|
|
|
+ const onSortEnd = ({oldIndex, newIndex}) => {
|
|
|
+ setState(({images}) => ({
|
|
|
+ ...state,
|
|
|
+ images: arrayMoveImmutable(images, oldIndex, newIndex),
|
|
|
+ }));
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleClear = () => {
|
|
|
+ setState(entity)
|
|
|
+ }
|
|
|
+ const handleOnSave = () => {
|
|
|
+ let query = {...state}
|
|
|
+ state.images.length > 0 ? query.images = state.images.map(item => {return {'_id': item['_id']}}) : delete query.images
|
|
|
+ state.categories.length > 0 ? query.categories = state.categories.map(item => {return {'_id': item['_id'], 'name': item['name']}}) : delete query.categories
|
|
|
+ onSave(query)
|
|
|
+ goodCount()
|
|
|
+ }
|
|
|
+ const handleFullClear = () => {
|
|
|
+ setState(entity)
|
|
|
+ actionClear('goodUpsert')
|
|
|
+ actionClear('uploadFile')
|
|
|
+ }
|
|
|
+ useEffect(() => {
|
|
|
+ if(!categoryState || Object.entries(categoryState).length === 0) actionRootCat()
|
|
|
+ if(!goods) goodCount()
|
|
|
+ if(fileStatus?.status === 'RESOLVED'){
|
|
|
+ setState({...state, images: [...state.images, fileStatus?.payload]})
|
|
|
+ }
|
|
|
+ },[categoryState, goods, fileStatus])
|
|
|
+
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ {!result ?
|
|
|
+ <>
|
|
|
+ <Typography variant='h6' letterSpacing='2px' marginBottom='20px'>Total products: {goods?.payload || 0}</Typography>
|
|
|
+ <Box style={{minHeight: "200px", border: '1px dashed #616161', borderRadius: '20px', padding: '20px'}} {...getRootProps()}>
|
|
|
+ <input {...getInputProps()} />
|
|
|
+ {isDragActive ?
|
|
|
+ <Typography variant='body1' textAlign='center' color='#616161'>Drop the file here ...</Typography> :
|
|
|
+ <Typography variant='body1' textAlign='center' color='#616161' marginBottom='20px'>Drag 'n' drop image files here, or click to select file</Typography>
|
|
|
+ }
|
|
|
+ <SortableContainer axis="xy" onSortEnd={onSortEnd}>
|
|
|
+ {state.images.length > 0 && state.images.map((value, index) => (
|
|
|
+ <SortableItem key={`item-${value?._id}`} index={index} value={value} />
|
|
|
+ ))}
|
|
|
+ </SortableContainer>
|
|
|
+ </Box>
|
|
|
+ <Grid container justifyContent='space-between' marginTop='30px'>
|
|
|
+ <Grid item xs={5.5}>
|
|
|
+ <TextField fullWidth id="filled-basic" label="Title product" variant="standard" value={state?.name || ''} onChange={e => setState({...state, name: e.target.value})}/>
|
|
|
+ </Grid>
|
|
|
+ <Grid item xs={5.5}>
|
|
|
+ {categoryState &&
|
|
|
+ <Autocomplete
|
|
|
+ multiple
|
|
|
+ id="tags-standard"
|
|
|
+ options={Object.values(categoryState)}
|
|
|
+ onChange={(event, newValue) => {
|
|
|
+ setState({...state, categories: [...newValue]})
|
|
|
+ }}
|
|
|
+ getOptionLabel={(option) => state?.categories ? [...state.categories] : option?.name || 'no name'}
|
|
|
+ key={option => option?.id}
|
|
|
+ renderInput={(params) => (
|
|
|
+ <TextField
|
|
|
+ {...params}
|
|
|
+ variant="standard"
|
|
|
+ label="Select categories"
|
|
|
+ placeholder="categories"
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ }
|
|
|
+ </Grid>
|
|
|
+ </Grid>
|
|
|
+ <Grid container justifyContent='space-between' marginTop='30px'>
|
|
|
+ <Grid item xs={5.5}>
|
|
|
+ <TextField fullWidth
|
|
|
+ id='Price'
|
|
|
+ type='number'
|
|
|
+ label='Price'
|
|
|
+ variant='standard'
|
|
|
+ value={state?.price || ''}
|
|
|
+ onChange={e => setState({...state, price: parseFloat(e.target.value < 0 ? 0 : e.target.value)})}
|
|
|
+ />
|
|
|
+ </Grid>
|
|
|
+ <Grid item xs={5.5}>
|
|
|
+ <TextField fullWidth
|
|
|
+ id='filled-basic'
|
|
|
+ label='Description product'
|
|
|
+ variant='standard'
|
|
|
+ multiline
|
|
|
+ value={state?.description || ''}
|
|
|
+ onChange={e => setState({...state, description: e.target.value})}
|
|
|
+ />
|
|
|
+ </Grid>
|
|
|
+ </Grid>
|
|
|
+ <Grid container justifyContent='space-between' marginTop='30px'>
|
|
|
+ <Grid item xs={5.5} display='flex' justifyContent='center'>
|
|
|
+ <Button
|
|
|
+ fullWidth
|
|
|
+ onClick={handleClear}
|
|
|
+ variant="outlined"
|
|
|
+ color='warning'
|
|
|
+ >
|
|
|
+ Clear
|
|
|
+ </Button>
|
|
|
+ </Grid>
|
|
|
+ <Grid item xs={5.5} display='flex' justifyContent='center'>
|
|
|
+ <Button
|
|
|
+ fullWidth
|
|
|
+ variant="outlined"
|
|
|
+ color='primary'
|
|
|
+ onClick={handleOnSave}
|
|
|
+ >
|
|
|
+ Save
|
|
|
+ </Button>
|
|
|
+ </Grid>
|
|
|
+ </Grid>
|
|
|
+ </> :
|
|
|
+ result?.payload?._id ?
|
|
|
+ <>
|
|
|
+ <Box display='flex' alignItems='center' flexDirection='column'>
|
|
|
+ <Typography variant='h5' letterSpacing='2px' textAlign='center' color='#616161' marginBottom='20px'>Product successfully created!</Typography>
|
|
|
+ <CheckCircleOutlineIcon sx={{marginBottom: '20px'}}/>
|
|
|
+ <Link to={`/good/${result?.payload?._id}`} style={{color:'#616161', marginBottom:'20px'}}>
|
|
|
+ <Typography variant='h5' letterSpacing='2px' textAlign='center' color='#616161'>View results</Typography>
|
|
|
+ </Link>
|
|
|
+ <Button variant='outlined' onClick={handleFullClear}>Add more</Button>
|
|
|
+ </Box>
|
|
|
+ </> :
|
|
|
+ result?.error ?
|
|
|
+ <Box display='flex' alignItems='center' flexDirection='column'>
|
|
|
+ <Typography variant='h5' letterSpacing='2px' textAlign='center' color='#f00' marginBottom='20px'>Fatal error, try again!</Typography>
|
|
|
+ <Button variant='outlined' onClick={handleFullClear}>Add more</Button>
|
|
|
+ </Box>
|
|
|
+ :
|
|
|
+ <Box sx={{ display: 'flex' }}>
|
|
|
+ <CircularProgress />
|
|
|
+ </Box>
|
|
|
+ }
|
|
|
+ </>
|
|
|
+ )
|
|
|
+}
|
|
|
+export const CGoodEdit = connect(state => ({fileStatus: state.promise['uploadFile'], categoryState: state.category, goods: state.promise['goodCount'], result: state.promise['goodUpsert']}), {actionRootCat: actionFullRootCats, onSave: actionGoodUpsert, goodCount: actionGoodCount, onFileDrop: actionUploadFile, actionClear: actionClearPromise})(GoodEdit)
|
|
|
+
|
|
|
+const ItemFound = ({item:{_id, name, price, images, description, categories}}) => {
|
|
|
+ let [state, setState] = useState(false)
|
|
|
+
|
|
|
+ return (
|
|
|
+ !state ?
|
|
|
+ <Button style={{textDecoration: 'none', display: 'flex', alignItems: 'center', marginBottom: '30px'}} onClick={() => setState(true)}>
|
|
|
+ <Box width='60px' height='60px' borderRadius='10px' overflow='hidden' marginRight='60px'
|
|
|
+ position='relative'>
|
|
|
+ <img style={{
|
|
|
+ position: 'absolute',
|
|
|
+ top: '0',
|
|
|
+ left: '0',
|
|
|
+ width: '100%',
|
|
|
+ height: '100%',
|
|
|
+ objectFit: 'cover'
|
|
|
+ }} src={images && Array.isArray(images) && images[0]?.url ? backURL + '/' + images[0].url : imgNotFound}
|
|
|
+ alt={name}/>
|
|
|
+ </Box>
|
|
|
+ <Box sx={{
|
|
|
+ display: 'flex',
|
|
|
+ flexDirection: 'column',
|
|
|
+ justifyContent: 'space-between',
|
|
|
+ alignItems: 'flex-start'
|
|
|
+ }}>
|
|
|
+ <Typography
|
|
|
+ color='#000'
|
|
|
+ letterSpacing='1px'
|
|
|
+ fontFamily='sarif'
|
|
|
+ fontWeight='600'
|
|
|
+ variant='h6'
|
|
|
+ >
|
|
|
+ {name}
|
|
|
+ </Typography>
|
|
|
+ <Typography
|
|
|
+ letterSpacing='1px'
|
|
|
+ variant='body1'
|
|
|
+ fontWeight='300'
|
|
|
+ color='#616161'
|
|
|
+ margin='10px 0'
|
|
|
+ >
|
|
|
+ {description?.length > 60 ? 'Lorem ipsum dolor sit amet, consectetur adipisicing elit.' : description}
|
|
|
+ </Typography>
|
|
|
+ <Typography
|
|
|
+ color='#000'
|
|
|
+ letterSpacing='1px'
|
|
|
+ variant='body1'
|
|
|
+ fontWeight='600'
|
|
|
+ >
|
|
|
+ ${parseFloat(price).toFixed(2)}
|
|
|
+ </Typography>
|
|
|
+ </Box>
|
|
|
+ </Button>
|
|
|
+ :
|
|
|
+ <CGoodEdit entity={{_id, name, price, images, description, categories}}/>
|
|
|
+ )
|
|
|
+}
|
|
|
+const NotFound = () => {
|
|
|
+ return (
|
|
|
+ <Typography
|
|
|
+ textAlign='center'
|
|
|
+ color='#000'
|
|
|
+ letterSpacing='1px'
|
|
|
+ variant='body1'
|
|
|
+ >
|
|
|
+ No results found
|
|
|
+ </Typography>
|
|
|
+ )
|
|
|
+}
|
|
|
+const FindGoodEdit = ({searchResult, onSearch, onSearchRemove}) => {
|
|
|
+ const [value, setValue] = useState('')
|
|
|
+ const [click, setClick] = useState(false)
|
|
|
+
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <Container maxWidth="sm">
|
|
|
+ <Typography
|
|
|
+ variant='h5'
|
|
|
+ fontFamily='sarif'
|
|
|
+ letterSpacing='3px'
|
|
|
+ marginBottom='30px'
|
|
|
+ marginTop='30px'
|
|
|
+ textAlign='center'
|
|
|
+ >
|
|
|
+ WHICH ITEM TO EDIT?
|
|
|
+ </Typography>
|
|
|
+ <TextField
|
|
|
+ color={'primary'}
|
|
|
+ fullWidth
|
|
|
+ variant="standard"
|
|
|
+ value={value}
|
|
|
+ placeholder="Start typing..."
|
|
|
+ onChange={(event) => {setClick(false); setValue(event.target.value); onSearchRemove()}}
|
|
|
+ InputProps={{
|
|
|
+ sx: {padding: '10px', outline:'none', color: '#616161', fontWeight: '300', letterSpacing: '1px', marginBottom: '50px'},
|
|
|
+ endAdornment: (
|
|
|
+ <InputAdornment position="end">
|
|
|
+ <IconButton onClick={() => {setClick(true); onSearchRemove(); onSearch(value)}}>
|
|
|
+ <SearchIcon />
|
|
|
+ </IconButton>
|
|
|
+ </InputAdornment>
|
|
|
+ )
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ {(value !== '' && click) && (searchResult?.searchResult ?
|
|
|
+ Object.values(searchResult.searchResult).length > 0 ?
|
|
|
+ Object.values(searchResult.searchResult).map(item => <ItemFound item={item}/>) : <NotFound/> :
|
|
|
+ <CircularProgress color="inherit"/>
|
|
|
+ )}
|
|
|
+ </Container>
|
|
|
+ </>
|
|
|
+ )
|
|
|
+}
|
|
|
+export const CFindGoodEdit = connect(state=>({searchResult: state.search}), {onSearch: actionFullGoodFind, onSearchRemove: actionSearchRemove})(FindGoodEdit)
|
|
|
+
|