Initial commit
This commit is contained in:
68
front/src/components/cocktails/CheckMarks.js
Normal file
68
front/src/components/cocktails/CheckMarks.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import * as React from 'react';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import Select from '@mui/material/Select';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
|
||||
const ITEM_HEIGHT = 48;
|
||||
const ITEM_PADDING_TOP = 8;
|
||||
const MenuProps = {
|
||||
PaperProps: {
|
||||
style: {
|
||||
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
|
||||
width: 250,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default function CheckMarks({rows, name, filterValue, handleChange, filterName, width, nonMulti, nullValue, identity}) {
|
||||
const realValue = !nonMulti ? filterValue.filter((v) => v.length > 0) : filterValue;
|
||||
return (
|
||||
<div>
|
||||
<FormControl sx={{m: 1, width: !width ? 300 : width}}>
|
||||
<InputLabel>{name}</InputLabel>
|
||||
<Select
|
||||
multiple={!nonMulti}
|
||||
value={realValue}
|
||||
onChange={(e) => handleChange(filterName, e.target.value)}
|
||||
input={<OutlinedInput label={name}/>}
|
||||
renderValue={(selected) => !nonMulti ? selected.join(", ") : selected}
|
||||
MenuProps={MenuProps}
|
||||
defaultChecked={nonMulti && rows[0]}
|
||||
variant="filled">
|
||||
{(nonMulti && nullValue) && (
|
||||
<MenuItem value={""}>
|
||||
<em>Не выбрано</em>
|
||||
</MenuItem>
|
||||
)}
|
||||
{rows.map((value) => {
|
||||
if(identity) {
|
||||
return (
|
||||
<MenuItem key={"menuItemIn" + value} value={value}>
|
||||
{!nonMulti && (
|
||||
<Checkbox
|
||||
checked={realValue.includes(value)}/>
|
||||
)}
|
||||
<ListItemText primary={value}/>
|
||||
</MenuItem>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<MenuItem key={value.id} value={value.name}>
|
||||
{!nonMulti && (
|
||||
<Checkbox
|
||||
checked={realValue.includes(value.name)}/>
|
||||
)}
|
||||
<ListItemText primary={value.name}/>
|
||||
</MenuItem>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
113
front/src/components/cocktails/Cocktail.js
Normal file
113
front/src/components/cocktails/Cocktail.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import {CardActions, CardContent, CardMedia, Rating} from "@mui/material";
|
||||
import {useAlert} from "../../hooks/useAlert";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import List from "@mui/material/List";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import Button from "@mui/material/Button";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import {requests} from "../../requests";
|
||||
import {CocktailItemStyled} from "./CocktailItemStyled";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import FavoriteBorderIcon from '@mui/icons-material/FavoriteTwoTone';
|
||||
import FavoriteIcon from '@mui/icons-material/Favorite';
|
||||
import {api} from "../../lib/clients/api";
|
||||
import Box from "@mui/material/Box";
|
||||
import {useUser} from "../../hooks/useUser";
|
||||
|
||||
function renderFavouriteBadge(handleFavourite, row) {
|
||||
const childIcon = row.rating.favourite ? <FavoriteIcon color='error'/> : <FavoriteBorderIcon color={'warning'}/>;
|
||||
return (
|
||||
<IconButton sx={{position: 'absolute', top: "15px", right: "15px"}} onClick={() => handleFavourite(row)}>
|
||||
{childIcon}
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
|
||||
function renderRating(handleChangeRating, row) {
|
||||
return (
|
||||
<Rating
|
||||
sx={{position: 'absolute', top: '310px', right: '85px'}}
|
||||
name="simple-controlled"
|
||||
size="large"
|
||||
value={row.rating.rating}
|
||||
onChange={(event, newValue) => handleChangeRating(row, newValue)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function Cocktail({row, handleFavourite, handleChangeRating, handleSelect, editMenuBlock}) {
|
||||
const {createAlert, createError} = useAlert();
|
||||
const {session, user} = useUser();
|
||||
|
||||
function pay(cocktailId) {
|
||||
api().post(`${requests.bar.pay}cocktail=${cocktailId}`)
|
||||
.then(() => createAlert("Ожидайте свой заказ", "success"))
|
||||
.catch(() => createError("Ошибка во время создания заказа"))
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid item sx={{pr: 2}}>
|
||||
<CocktailItemStyled>
|
||||
<Box sx={{
|
||||
p: '4px 4px',
|
||||
m: 1,
|
||||
width: '320px',
|
||||
position: 'relative',
|
||||
}}>
|
||||
<CardMedia
|
||||
sx={{
|
||||
loading: "eager",
|
||||
borderRadius: 2
|
||||
}}
|
||||
onClick={() => handleSelect(row)}
|
||||
component="img"
|
||||
alt={row.name}
|
||||
height="300"
|
||||
|
||||
image={`${row.image}/preview`}
|
||||
/>
|
||||
{renderFavouriteBadge(handleFavourite, row)}
|
||||
{renderRating(handleChangeRating, row)}
|
||||
<CardContent sx={{pb: '4px', pl: 2}}>
|
||||
<Typography variant="h5" minHeight={'50px'} mt={2}>{row.name} </Typography>
|
||||
<List sx={{py: '0px'}}>
|
||||
{row.hasError && (
|
||||
<ListItem sx={{p: '2px 12px 0px 0px', m: '0px'}}>
|
||||
<ListItemText color={'red'}>Имеет ошибку в рецепте или ингредиентах</ListItemText>
|
||||
</ListItem>
|
||||
)}
|
||||
<ListItem sx={{p: '2px 12px 0px 0px', m: '0px'}}>
|
||||
<ListItemText>{"Категория: " + row.category}</ListItemText>
|
||||
</ListItem>
|
||||
<ListItem sx={{p: '2px 12px 0px 0px', m: '0px'}}>
|
||||
<ListItemText>{"Алкоголь: " + row.alcoholic}</ListItemText>
|
||||
</ListItem>
|
||||
{row.volume !== null && (
|
||||
<ListItem sx={{p: '2px 12px 0px 0px', m: '0px'}}>
|
||||
<ListItemText>{"Крепость: ≈" + row.volume}</ListItemText>
|
||||
</ListItem>
|
||||
)}
|
||||
<ListItem sx={{p: '2px 12px 0px 0px', m: '0px'}}>
|
||||
<ListItemText>{"Подача: " + row.glass}</ListItemText>
|
||||
</ListItem>
|
||||
<ListItem sx={{p: '2px 12px 0px 0px', m: '0px'}}>
|
||||
<ListItemText>{"Состав: " + row.components}</ListItemText>
|
||||
</ListItem>
|
||||
{(row.tags && row.tags.length > 0) && (
|
||||
<ListItem sx={{p: '2px 12px 0px 0px', m: '0px'}}>
|
||||
<ListItemText>{"Теги: " + row.tags.replaceAll(',', ', ')}</ListItemText>
|
||||
</ListItem>)}
|
||||
</List>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
{(row.isAllowed && session.isActive && user.invited) &&
|
||||
<Button variant="contained" onClick={() => pay(row.id)}>Заказать</Button>
|
||||
}
|
||||
{editMenuBlock(row)}
|
||||
</CardActions>
|
||||
</Box>
|
||||
</CocktailItemStyled>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
180
front/src/components/cocktails/CocktailInfoModal.js
Normal file
180
front/src/components/cocktails/CocktailInfoModal.js
Normal file
@@ -0,0 +1,180 @@
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import * as React from "react";
|
||||
import {useEffect, useState} from "react";
|
||||
import {CardMedia} from "@mui/material";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Box from "@mui/material/Box";
|
||||
import StarBorderIcon from '@mui/icons-material/StarBorder';
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import {IngredientInfoModal} from "../Ingredients/IngredientInfoModal";
|
||||
import {api} from "../../lib/clients/api";
|
||||
import {requests} from "../../requests";
|
||||
import {useAlert} from "../../hooks/useAlert";
|
||||
import {paths} from "../../path";
|
||||
import {Loading} from "../core/Loading";
|
||||
import {useUser} from "../../hooks/useUser";
|
||||
|
||||
export function CocktailInfoModal({open, row, closeHandler}) {
|
||||
const {user} = useUser();
|
||||
const {getError, createError, createSuccess} = useAlert();
|
||||
const [cocktail, setCocktail] = useState(null)
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedIngredient, setSelectedIngredient] = useState(null);
|
||||
const [openIngredientModal, setOpenIngredientModal] = useState(false)
|
||||
const closeIngredientHandler = () => {
|
||||
setOpenIngredientModal(false);
|
||||
setSelectedIngredient(null);
|
||||
}
|
||||
const openIngredientModalHandler = (id) => {
|
||||
api().get(`${requests.bar.ingredient}?id=${id}`)
|
||||
.then((r) => {
|
||||
setSelectedIngredient(r.data)
|
||||
setOpenIngredientModal(true);
|
||||
}).catch(() => createError("Ошибка получения информации об ингредиенте"))
|
||||
}
|
||||
const selectIngredientHandler = (ingredient) => {
|
||||
const url = `${requests.bar.ingredient}?id=${ingredient.id}`;
|
||||
const request = ingredient.isHave ? api().delete(url) : api().put(url);
|
||||
const value = !ingredient.isHave;
|
||||
request.then(() => {
|
||||
const newReceipts = cocktail.receipt.map((r) => {
|
||||
if (r.ingredient.id !== ingredient.id) {
|
||||
return r;
|
||||
}
|
||||
return {
|
||||
...r,
|
||||
ingredient: {
|
||||
...ingredient,
|
||||
isHave: value
|
||||
}
|
||||
}
|
||||
})
|
||||
setCocktail({
|
||||
...cocktail,
|
||||
receipt: newReceipts
|
||||
})
|
||||
createSuccess("Сохранено")
|
||||
}).catch(() => createError("Ошибка сохранения"))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
if (!row) {
|
||||
setLoading(false)
|
||||
return;
|
||||
}
|
||||
api().get(requests.cocktails.modal + row)
|
||||
.then((r) => {
|
||||
setCocktail(r.data)
|
||||
setLoading(false)
|
||||
})
|
||||
.catch(() => {
|
||||
getError();
|
||||
setLoading(false)
|
||||
closeHandler();
|
||||
})
|
||||
}, [row]);
|
||||
|
||||
if (!row || !cocktail) {
|
||||
return null;
|
||||
}
|
||||
let alko = 0;
|
||||
let volume = 0;
|
||||
return (
|
||||
<Dialog fullWidth={true}
|
||||
open={open} onClose={closeHandler}
|
||||
sx={{
|
||||
'& .MuiDialog-paper': {
|
||||
margin: '8px',
|
||||
},
|
||||
'& .MuiPaper-root': {
|
||||
width: 'calc(100% - 16px)',
|
||||
}
|
||||
}}>
|
||||
<IngredientInfoModal ingredient={selectedIngredient} open={openIngredientModal}
|
||||
closeHandler={closeIngredientHandler}/>
|
||||
<Loading loading={loading}/>
|
||||
<DialogTitle>
|
||||
<Stack direction='row' justifyContent={'space-between'}>
|
||||
<Box>{cocktail.name}</Box>
|
||||
|
||||
{cocktail.rating.rating > 0 &&
|
||||
(
|
||||
<Stack ml={3} direction='row'>
|
||||
{`${cocktail.rating.rating}/5`}
|
||||
<StarBorderIcon sx={{pb: "2px"}}/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
</Stack>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<CardMedia
|
||||
image={`${cocktail.image}/preview`}
|
||||
sx={{
|
||||
loading: "eager",
|
||||
borderRadius: 2
|
||||
}}
|
||||
component="img"
|
||||
alt={cocktail.name}
|
||||
height="300"
|
||||
/>
|
||||
<Box mt={1}>
|
||||
<Typography>Ингредиенты:</Typography>
|
||||
<Paper sx={{p: 1}} elevation={3}>
|
||||
<Stack>
|
||||
{cocktail.receipt.map((r) => {
|
||||
const hasError = r.count === null || r.unit === null;
|
||||
const measure = hasError ? r.measure : (r.count + " " + r.unit.name)
|
||||
if(alko !== null && volume !== null) {
|
||||
console.log(r)
|
||||
}
|
||||
return (
|
||||
<Stack key={r.ingredient.id} direction='row' justifyContent={'space-between'}
|
||||
mt={1}>
|
||||
<Stack direction='row'>
|
||||
{user.role !== "USER" && (
|
||||
<IconButton size="small" sx={{pb: "2px"}}
|
||||
onClick={() => selectIngredientHandler(r.ingredient)}>
|
||||
{r.ingredient.isHave
|
||||
? (<DeleteIcon fontSize="small"/>)
|
||||
: (<ShoppingCartIcon fontSize="small"/>)
|
||||
}
|
||||
</IconButton>
|
||||
)}
|
||||
<Typography
|
||||
onClick={() => openIngredientModalHandler(r.ingredient.id)}>{r.ingredient.name}</Typography>
|
||||
</Stack>
|
||||
<Typography color={hasError && 'red'}>{measure}</Typography>
|
||||
</Stack>
|
||||
)
|
||||
})}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography mt={2}>Инструкция:</Typography>
|
||||
<Paper sx={{p: 1}} elevation={3}>
|
||||
<Box>
|
||||
{cocktail.instructions}
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{user.role.includes("ADMIN") && (
|
||||
<Button href={`${paths.bar.cocktailEdit}?id=${cocktail.id}`}>Редактировать</Button>
|
||||
)}
|
||||
<Button onClick={closeHandler}>Закрыть</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
12
front/src/components/cocktails/CocktailItemStyled.js
Normal file
12
front/src/components/cocktails/CocktailItemStyled.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import {styled} from "@mui/material/styles";
|
||||
import {Card} from "@mui/material";
|
||||
|
||||
export const CocktailItemStyled = styled(Card)(({theme}) => ({
|
||||
backgroundColor: '#fff',
|
||||
...theme.typography.body2,
|
||||
padding: theme.spacing(1),
|
||||
color: theme.palette.text.secondary,
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: '#1A2027',
|
||||
})
|
||||
}));
|
||||
44
front/src/components/cocktails/CocktailListCard.js
Normal file
44
front/src/components/cocktails/CocktailListCard.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import {Card, FormControlLabel} from "@mui/material";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Box from "@mui/material/Box";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import InfoRoundedIcon from "@mui/icons-material/InfoRounded";
|
||||
import {paths} from "../../path";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import React from "react";
|
||||
import Switch from "@mui/material/Switch";
|
||||
|
||||
export function CocktailListCard({row, changeHandler, infoHandler}) {
|
||||
return (
|
||||
<Card sx={{mb: 1, height: '130px', display: 'relative', pt: 1, borderRadius: '15px'}}>
|
||||
<Stack direction='row' justifyContent='start' alignItems='start'>
|
||||
<Box sx={{width: '100px', height: '100px', ml: 1}}>
|
||||
<img src={row.image} loading='eager' height={'100px'} width={'100px'} alt={row.id}
|
||||
style={{borderRadius: '20%'}}/>
|
||||
</Box>
|
||||
<Stack sx={{width: 'calc(95% - 100px)', pr: 2, ml: 1}}>
|
||||
<Box>{row.name}</Box>
|
||||
<FormControlLabel sx={{mt: 5, pr: 2}}
|
||||
onClick={() => changeHandler(row, !row.inMenu)}
|
||||
value="bottom"
|
||||
control={
|
||||
<Switch color="primary" checked={row.inMenu}/>
|
||||
}
|
||||
label="В меню"
|
||||
labelPlacement="start"
|
||||
/>
|
||||
</Stack>
|
||||
<Stack direction='row'>
|
||||
<Stack sx={{width: '5%', mt: 2}} spacing={1} justifyContent='flex-start'>
|
||||
<IconButton size='small' onClick={() => infoHandler(row)}>
|
||||
<InfoRoundedIcon/>
|
||||
</IconButton>
|
||||
<IconButton size='small' href={`${paths.bar.cocktailEdit}?id=${row.id}`}>
|
||||
<EditIcon/>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
76
front/src/components/cocktails/CocktailsList.js
Normal file
76
front/src/components/cocktails/CocktailsList.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import {useMemo, useState} from "react";
|
||||
import {getComparator} from "../core/getComparator";
|
||||
import {Accordion, AccordionDetails, AccordionSummary} from "@mui/material";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Box from "@mui/material/Box";
|
||||
import {CocktailListCard} from "./CocktailListCard";
|
||||
import {groupByForLoop} from "../core/groupByForLoop";
|
||||
|
||||
export function CocktailsList({rows, grouping, changeHandler, infoHandler}) {
|
||||
const [size, setSize] = useState(20);
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
if (window.innerHeight + window.scrollY >= (document.documentElement.scrollHeight - 100)) {
|
||||
if (!grouping) {
|
||||
setSize(size + 10)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const visibleRows = useMemo(() => {
|
||||
let res = [];
|
||||
if (rows.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (!grouping) {
|
||||
return rows
|
||||
.sort(getComparator("asc", "name"))
|
||||
.slice(0, size)
|
||||
.map((row) => {
|
||||
return (
|
||||
<CocktailListCard row={row} key={row.id}
|
||||
changeHandler={changeHandler} infoHandler={infoHandler}/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const group = groupByForLoop(rows, "category")
|
||||
if (!group || group.size === 0) {
|
||||
return null;
|
||||
}
|
||||
Array.from(group.keys())
|
||||
.sort(getComparator())
|
||||
.map((key) => {
|
||||
const list = group.get(key);
|
||||
res.push(
|
||||
<Accordion key={key} sx={{borderRadius: '5px', mb: 1}}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon/>}
|
||||
aria-controls="panel1-content"
|
||||
id="panel1-header"
|
||||
>
|
||||
<Typography component="span">{key}</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{p: 1}}>
|
||||
{list.sort(getComparator("asc", "name"))
|
||||
.map((row) => {
|
||||
return (
|
||||
<CocktailListCard row={row} key={row.id}
|
||||
changeHandler={changeHandler} infoHandler={infoHandler}/>
|
||||
)
|
||||
})}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)
|
||||
})
|
||||
return res;
|
||||
// eslint-disable-next-line
|
||||
}, [size, rows])
|
||||
|
||||
return (
|
||||
<Box mt={2}>
|
||||
{visibleRows}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
167
front/src/components/cocktails/EditCocktailReceipt.js
Normal file
167
front/src/components/cocktails/EditCocktailReceipt.js
Normal file
@@ -0,0 +1,167 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import * as React from "react";
|
||||
import {useEffect, useState} from "react";
|
||||
import {useAlert} from "../../hooks/useAlert";
|
||||
import {api} from "../../lib/clients/api";
|
||||
import {requests} from "../../requests";
|
||||
import {getComparator} from "../core/getComparator";
|
||||
import {Card} from "@mui/material";
|
||||
import {SelectEdit} from "./SelectEdit";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
||||
|
||||
export function EditCocktailReceipt({receipt, handler}) {
|
||||
const {createError} = useAlert()
|
||||
const [ingredients, setIngredients] = useState([]);
|
||||
const [units, setUnits] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
api().get(requests.bar.ingredientList)
|
||||
.then((r) => setIngredients(r.data.sort(getComparator("asc", "name"))))
|
||||
.catch(() => createError("Ошибка получения списка ингредиентов"))
|
||||
|
||||
api().get(requests.bar.unit)
|
||||
.then((r) => setUnits(r.data.sort(getComparator("asc", "name"))))
|
||||
.catch(() => createError("Ошибка получения единиц измерения"))
|
||||
}, []);
|
||||
|
||||
const selectHandler = (name, value) => {
|
||||
const ing = ingredients.find((i) => i.name === value)
|
||||
const newState = receipt.map((r, i) => {
|
||||
if (i !== name) {
|
||||
return r;
|
||||
}
|
||||
return {
|
||||
id: r.id,
|
||||
ingredient: {
|
||||
id: ing.id,
|
||||
isHave: ing.have,
|
||||
name: ing.name
|
||||
},
|
||||
measure: r.measure
|
||||
}
|
||||
})
|
||||
handler("receipt", newState);
|
||||
checkAllowed(newState);
|
||||
}
|
||||
const unitHandler = (name, value) => {
|
||||
const ing = units.find((i) => i.name === value)
|
||||
const newState = receipt.map((r, i) => {
|
||||
if (i !== name) {
|
||||
return r;
|
||||
}
|
||||
return {
|
||||
id: r.id,
|
||||
ingredient: r.ingredient,
|
||||
unit: ing,
|
||||
count: r.count,
|
||||
measure: r.measure
|
||||
}
|
||||
})
|
||||
handler("receipt", newState);
|
||||
checkAllowed(newState);
|
||||
}
|
||||
const removeHandler = (index) => {
|
||||
const arr = receipt.filter((r, i) => i !== index)
|
||||
handler("receipt", arr)
|
||||
checkAllowed(arr)
|
||||
}
|
||||
const addHandler = () => {
|
||||
const oldState = receipt;
|
||||
oldState.push({
|
||||
id: null,
|
||||
ingredient: {
|
||||
id: null,
|
||||
isHave: false,
|
||||
name: ""
|
||||
},
|
||||
measure: ""
|
||||
});
|
||||
handler("receipt", oldState);
|
||||
checkAllowed(oldState);
|
||||
}
|
||||
const checkAllowed = (state) => {
|
||||
handler("isAllowed", !state.map((r) => r.ingredient.isHave).includes(false))
|
||||
}
|
||||
const measureHandler = (index, value) => {
|
||||
const newState = receipt.map((r, i) => {
|
||||
if (index !== i) {
|
||||
return r
|
||||
}
|
||||
return {
|
||||
...r,
|
||||
measure: value
|
||||
}
|
||||
})
|
||||
handler("receipt", newState)
|
||||
}
|
||||
const countHandler = (index, value) => {
|
||||
const newState = receipt.map((r, i) => {
|
||||
if (index !== i) {
|
||||
return r
|
||||
}
|
||||
return {
|
||||
...r,
|
||||
count: value
|
||||
}
|
||||
})
|
||||
handler("receipt", newState)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box mb={2}>
|
||||
{/*Заголовок*/}
|
||||
<Stack direction='row' justifyContent={'space-between'} sx={{mr: 1}}>
|
||||
<Typography ml={1} mt={1}>Рецепт</Typography>
|
||||
<IconButton onClick={() => addHandler()}>
|
||||
<AddIcon/>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
{/*Рецепт*/}
|
||||
<Stack sx={{mr: 1}}>
|
||||
{receipt.map((r, i) => {
|
||||
return (
|
||||
<Card key={i} sx={{ml: 0, mb: 1}}>
|
||||
<Stack>
|
||||
<Stack direction='row'>
|
||||
<SelectEdit width={'calc(65% - 28px)'} array={ingredients} value={r.ingredient.name}
|
||||
handler={selectHandler} label={"Ингредиент"}
|
||||
margin={1} attributeName={i}
|
||||
/>
|
||||
<TextField sx={{width: 'calc(35% - 28px)', mt: 1}}
|
||||
label={"Кол-во"}
|
||||
variant="outlined"
|
||||
disabled
|
||||
value={r.measure}
|
||||
onChange={(e) => measureHandler(i, e.target.value)}
|
||||
/>
|
||||
<IconButton sx={{mt: 2}}
|
||||
onClick={() => removeHandler(i)}
|
||||
>
|
||||
<DeleteForeverIcon/>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
<Stack direction='row' ml={1} mb={1}>
|
||||
<TextField sx={{width: 'calc(35% - 28px)', mt: 1}}
|
||||
label={"Кол-во"}
|
||||
variant="outlined"
|
||||
value={r.count}
|
||||
onChange={(e) => countHandler(i, e.target.value)}
|
||||
/>
|
||||
<SelectEdit width={'calc(65% - 28px)'} array={units} value={!r.unit ? null : r.unit.name}
|
||||
handler={unitHandler} label={"Ед."}
|
||||
margin={1} attributeName={i}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
)
|
||||
})}
|
||||
</Stack>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
160
front/src/components/cocktails/FilterBlock.js
Normal file
160
front/src/components/cocktails/FilterBlock.js
Normal file
@@ -0,0 +1,160 @@
|
||||
import {Card, FormControl, FormControlLabel, InputAdornment, InputLabel, OutlinedInput} from "@mui/material";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||
import Box from "@mui/material/Box";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Switch from "@mui/material/Switch";
|
||||
import CheckMarks from "./CheckMarks";
|
||||
import Button from "@mui/material/Button";
|
||||
import * as React from "react";
|
||||
import {useEffect, useState} from "react";
|
||||
import {requests} from "../../requests";
|
||||
import {useAlert} from "../../hooks/useAlert";
|
||||
import {api} from "../../lib/clients/api";
|
||||
import {sortList} from "./sortingList";
|
||||
|
||||
const inMenuFilter = [
|
||||
{
|
||||
id: true,
|
||||
name: "Есть в меню"
|
||||
},
|
||||
{
|
||||
id: false,
|
||||
name: "Нет в меню"
|
||||
}
|
||||
]
|
||||
|
||||
export function FilterBlock({filter, handleFilterChange, handleClearFilter, barmen, all}) {
|
||||
const {createError} = useAlert();
|
||||
const [glass, setGlass] = useState([]);
|
||||
const [category, setCategory] = useState([]);
|
||||
const [tags, setTags] = useState([])
|
||||
const alcohol = [
|
||||
{
|
||||
name: "Алкогольный",
|
||||
id: "alcohol1"
|
||||
},
|
||||
{
|
||||
name: "Безалкогольный",
|
||||
id: "alcohol2"
|
||||
},
|
||||
{
|
||||
name: "Опционально",
|
||||
id: "alcohol3"
|
||||
}];
|
||||
const ingredientCount = [
|
||||
{
|
||||
id: "1IngredientCount",
|
||||
name: 1
|
||||
},
|
||||
{
|
||||
id: "2IngredientCount",
|
||||
name: 2
|
||||
},
|
||||
{
|
||||
id: "3IngredientCount",
|
||||
name: 3
|
||||
},
|
||||
{
|
||||
id: "4IngredientCount",
|
||||
name: 4
|
||||
},
|
||||
{
|
||||
id: "5IngredientCount",
|
||||
name: 5
|
||||
}]
|
||||
|
||||
useEffect(() => {
|
||||
api().get(requests.bar.category)
|
||||
.then((r) => setCategory(r.data))
|
||||
.catch(() => createError("Ошибка получения категорий"))
|
||||
|
||||
api().get(requests.bar.glass)
|
||||
.then((r) => setGlass(r.data))
|
||||
.catch(() => createError("Ошибка получения посуды"))
|
||||
|
||||
api().get(requests.bar.tags)
|
||||
.then((r) => setTags(r.data))
|
||||
.catch(() => createError("Ошибка получения тегов"))
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
{/*Строка поиска*/}
|
||||
<FormControl sx={{m: 1, width: '300px'}}>
|
||||
<InputLabel htmlFor="outlined-adornment-amount">Поиск</InputLabel>
|
||||
<OutlinedInput
|
||||
onChange={(e) => handleFilterChange("search", e.target.value)}
|
||||
label="With normal TextField"
|
||||
startAdornment={
|
||||
<InputAdornment position="start">
|
||||
<IconButton edge="end">
|
||||
<SearchIcon/>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
{/*Кнопка открытия фильтров*/}
|
||||
<Tooltip title="Filter list">
|
||||
<IconButton onClick={() => handleFilterChange("hidden", !filter.hidden)}>
|
||||
<FilterListIcon/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{/*Блок сортировки*/}
|
||||
<Box hidden={filter.hidden}>
|
||||
<Grid container>
|
||||
{/*Фильтр по алкогольности*/}
|
||||
<CheckMarks rows={sortList} name={"Сортировать по..."} handleChange={handleFilterChange}
|
||||
filterValue={filter.sorting} filterName={"sorting"} nonMulti/>
|
||||
</Grid>
|
||||
</Box>
|
||||
{/*Блок фильтров*/}
|
||||
<Box hidden={filter.hidden}>
|
||||
<Grid container>
|
||||
{/*Фильтр по меню*/}
|
||||
{(barmen && all) && (
|
||||
<CheckMarks rows={inMenuFilter} name={"Есть в меню"} filterName={"inMenu"}
|
||||
filterValue={filter.inMenu}
|
||||
handleChange={handleFilterChange}
|
||||
nonMulti nullValue
|
||||
/>
|
||||
)}
|
||||
{/*Фильтр по избранным*/}
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch inputProps={{'aria-label': 'controlled'}}
|
||||
onChange={() => handleFilterChange("onlyFavourite", !filter.onlyFavourite)}
|
||||
/>
|
||||
}
|
||||
label="Только избранные"
|
||||
sx={{ml: 1}}
|
||||
/>
|
||||
{/*Фильтр по алкогольности*/}
|
||||
<CheckMarks rows={alcohol} name={"Алкогольность"} handleChange={handleFilterChange}
|
||||
filterValue={filter.alcohol} filterName={"alcohol"}/>
|
||||
{/*Фильтр по категории*/}
|
||||
{category.length > 0 && (
|
||||
<CheckMarks rows={category} name={"Категории"} filterValue={filter.category}
|
||||
filterName={"category"} handleChange={handleFilterChange}/>)}
|
||||
{/*Фильтр по посуде*/}
|
||||
{glass.length > 0 && (<CheckMarks rows={glass} name={"Подача"} handleChange={handleFilterChange}
|
||||
filterValue={filter.glass} filterName={"glass"}/>)}
|
||||
{/*Фильтр по тегам*/}
|
||||
{tags.length > 0 && (<CheckMarks rows={tags} name={"Теги"} handleChange={handleFilterChange}
|
||||
filterValue={filter.tags} filterName={"tags"}/>)}
|
||||
{/*Фильтр по нехватке ингредиентов*/}
|
||||
{/*todo: доделать эти фильтры в беке*/}
|
||||
{/*{(barmen && all) && (<CheckMarks rows={ingredientCount} name={"Не хватает ингредиентов"}*/}
|
||||
{/* handleChange={handleFilterChange}*/}
|
||||
{/* nonMulti nullValue*/}
|
||||
{/* filterValue={filter.iCount} filterName={"iCount"}/>)}*/}
|
||||
<Button onClick={() => handleClearFilter()}>Сбросить</Button>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
17
front/src/components/cocktails/NoResult.js
Normal file
17
front/src/components/cocktails/NoResult.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import Grid from "@mui/material/Grid";
|
||||
import {Stack} from "@mui/material";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import {CocktailItemStyled} from "./CocktailItemStyled";
|
||||
|
||||
export function NoResult({load}) {
|
||||
return (
|
||||
<Grid item lg={4} md={6} sm={12} xl={3} hidden={!load}>
|
||||
<CocktailItemStyled>
|
||||
<Stack align="center" sx={{width: "350px"}}>
|
||||
<Typography variant="h5" minHeight={'50px'} mt={2}>Нет результатов</Typography>
|
||||
<Typography>Попробуйте заглянуть позднее</Typography>
|
||||
</Stack>
|
||||
</CocktailItemStyled>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
25
front/src/components/cocktails/SelectEdit.js
Normal file
25
front/src/components/cocktails/SelectEdit.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import {FormControl, InputLabel} from "@mui/material";
|
||||
import Select from "@mui/material/Select";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import * as React from "react";
|
||||
|
||||
export function SelectEdit({label, value, array, handler, attributeName, width, margin}) {
|
||||
return (
|
||||
<FormControl sx={{width: width, m: margin}}>
|
||||
<InputLabel>{label}</InputLabel>
|
||||
<Select
|
||||
autoWidth
|
||||
label={label}
|
||||
value={!value ? "" : value}
|
||||
onChange={(e) => handler(attributeName, e.target.value)}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>None</em>
|
||||
</MenuItem>
|
||||
{array.map((c) => {
|
||||
return (<MenuItem key={c.id} value={c.name}>{c.name}</MenuItem>)
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
27
front/src/components/cocktails/sortingList.js
Normal file
27
front/src/components/cocktails/sortingList.js
Normal file
@@ -0,0 +1,27 @@
|
||||
export const sortList = [
|
||||
{
|
||||
id: "name|asc",
|
||||
name: "Название по возрастанию"
|
||||
},
|
||||
{
|
||||
id: "name|desc",
|
||||
name: "Название по убыванию"
|
||||
},
|
||||
// todo: добавить сортировки в беке
|
||||
// {
|
||||
// id: "rating.rating|desc",
|
||||
// name: "Сначала с оценкой"
|
||||
// },
|
||||
// {
|
||||
// id: "rating.rating|asc",
|
||||
// name: "Сначала без оценки"
|
||||
// },
|
||||
// {
|
||||
// id: "rating.favourite|desc",
|
||||
// name: "Сначала избранные"
|
||||
// },
|
||||
// {
|
||||
// id: "rating.favourite|asc",
|
||||
// name: "Сначала не избранные"
|
||||
// }
|
||||
]
|
||||
Reference in New Issue
Block a user