|
@@ -1,354 +0,0 @@
|
|
|
-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,
|
|
|
- Grid, IconButton,
|
|
|
- InputAdornment,
|
|
|
- TextField
|
|
|
-} from "@mui/material";
|
|
|
-import {connect} from "react-redux";
|
|
|
-import {actionAllCategory} 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,
|
|
|
- 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 (
|
|
|
- <Box key={value?._id} sx={{display: 'flex', justifyContent: 'center', borderRadius: 2, border: '1px solid #eaeaea', marginBottom: 2, width: 200, height: 200, padding: '5px', boxSizing: 'border-box'}}>
|
|
|
- <Box sx={{display: 'flex', justifyContent: 'center', minWidth: 0, overflow: 'hidden', position: 'relative'}}>
|
|
|
- {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', justifyContent: 'center', alignItems: 'center'}}>
|
|
|
- <CircularProgress />
|
|
|
- </Box>
|
|
|
- }
|
|
|
- </Box>
|
|
|
- </Box>
|
|
|
- )
|
|
|
- });
|
|
|
- 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) actionRootCat()
|
|
|
- if(!goods) goodCount()
|
|
|
- if(fileStatus?.status === 'RESOLVED'){
|
|
|
- state.images?.length > 0 ?
|
|
|
- setState({...state, images: [...state.images, fileStatus?.payload]}) :
|
|
|
- setState({...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={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 && categoryState?.payload && categoryState.payload?.length > 0 &&
|
|
|
- <>
|
|
|
- {state.categories?.length > 0 ?
|
|
|
- <Autocomplete
|
|
|
- multiple
|
|
|
- id="tags-standard"
|
|
|
- options={Object.values(categoryState.payload)}
|
|
|
- defaultValue={state.categories}
|
|
|
- onChange={(event, newValue) => {
|
|
|
- setState({...state, categories: [...newValue]})
|
|
|
- }}
|
|
|
- getOptionLabel={(option) => option?.name || 'no name'}
|
|
|
- key={option => option?.id}
|
|
|
- renderInput={(params) => (
|
|
|
- <TextField
|
|
|
- {...params}
|
|
|
- variant="standard"
|
|
|
- label="Select categories"
|
|
|
- placeholder="categories"
|
|
|
- />
|
|
|
- )}
|
|
|
- /> :
|
|
|
- <Autocomplete
|
|
|
- multiple
|
|
|
- id="tags-standard"
|
|
|
- options={Object.values(categoryState.payload)}
|
|
|
- onChange={(event, newValue) => {
|
|
|
- setState({...state, categories: [...newValue]})
|
|
|
- }}
|
|
|
- getOptionLabel={(option) => 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.promise['allCategory'], goods: state.promise['goodCount'], result: state.promise['goodUpsert']}),
|
|
|
- {actionRootCat: actionAllCategory, 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 fullWidth sx={{display: 'flex', justifyContent:'flex-start'}} onClick={() => setState(true)}>
|
|
|
- <Box style={{display: 'flex', alignItems: 'center', marginBottom: '30px'}}>
|
|
|
- <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 || 'no name'}
|
|
|
- </Typography>
|
|
|
- <Typography
|
|
|
- letterSpacing='1px'
|
|
|
- variant='body1'
|
|
|
- fontWeight='300'
|
|
|
- color='#616161'
|
|
|
- margin='10px 0'
|
|
|
- sx={{textTransform: 'capitalize'}}
|
|
|
- >
|
|
|
- {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>
|
|
|
- </Box>
|
|
|
- </Button>
|
|
|
- :
|
|
|
- <Box sx={{marginBottom: '30px', border: '1px solid #616161', borderRadius: '10px', padding: '30px 20px'}}>
|
|
|
- <CGoodEdit entity={{_id, name, price, images, description, categories}}/>
|
|
|
- <Button variant='outlined' sx={{marginTop: '30px'}} fullWidth onClick={() => setState(false)}>Cansel</Button>
|
|
|
- </Box>
|
|
|
- )
|
|
|
-}
|
|
|
-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="md">
|
|
|
- <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 key={item?._id} item={item}/>) : <NotFound/> :
|
|
|
- <CircularProgress color="inherit"/>
|
|
|
- )}
|
|
|
- </Container>
|
|
|
- </>
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
-export const CFindGoodEdit = connect(state=>({searchResult: state.search}),
|
|
|
- {onSearch: actionFullGoodFind, onSearchRemove: actionSearchRemove})(FindGoodEdit)
|