добавлена работа со списками продуктов, попытка оптимизировать запрос по поиску коктейлей

This commit is contained in:
Kayashov.SM
2025-08-20 12:53:41 +04:00
parent 9809a19762
commit 34295bc045
15 changed files with 118 additions and 26 deletions

0
front/.env Normal file
View File

View File

@@ -1,5 +1,3 @@
FROM nginx:1.16.0-alpine FROM nginx:1.16.0-alpine
COPY default.conf /etc/nginx/conf.d
COPY build /usr/share/nginx/html
EXPOSE 80 EXPOSE 80
CMD ["nginx", "-g", "daemon off;"] CMD ["nginx", "-g", "daemon off;"]

View File

@@ -41,7 +41,7 @@
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
"build": "CI=false && react-scripts build", "build": "react-scripts build",
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },

View File

@@ -41,9 +41,6 @@ export function PublicLayout({ children }) {
Добро пожаловать в бар Добро пожаловать в бар
</Box> </Box>
</Typography> </Typography>
<Typography align="center" variant="subtitle1">
Самый большой выбор честно спизженных коктейлей
</Typography>
<Box <Box
component="img" component="img"
alt="Under development" alt="Under development"

View File

@@ -2,7 +2,7 @@ import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Toolbar from "@mui/material/Toolbar"; import Toolbar from "@mui/material/Toolbar";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
import {Fab, FormControl, FormControlLabel, InputAdornment, InputLabel, OutlinedInput, Tabs} from "@mui/material"; import {Fab, FormControl, InputAdornment, InputLabel, OutlinedInput, Tabs} from "@mui/material";
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
import SearchIcon from "@mui/icons-material/Search"; import SearchIcon from "@mui/icons-material/Search";
import * as React from "react"; import * as React from "react";
@@ -18,7 +18,6 @@ import {CustomTabPanel} from "../../../components/core/TabPanel";
import {IngredientList} from "../../../components/Ingredients/IngredientList"; import {IngredientList} from "../../../components/Ingredients/IngredientList";
import {blue} from "@mui/material/colors"; import {blue} from "@mui/material/colors";
import UpIcon from "@mui/icons-material/KeyboardArrowUp"; import UpIcon from "@mui/icons-material/KeyboardArrowUp";
import Switch from "@mui/material/Switch";
import {useSelect} from "../../../hooks/useSelect"; import {useSelect} from "../../../hooks/useSelect";
export function IngredientsPage() { export function IngredientsPage() {
@@ -28,7 +27,7 @@ export function IngredientsPage() {
const [findString, setFindString] = useState(""); const [findString, setFindString] = useState("");
const [ingredients, setIngredients] = useState([]); const [ingredients, setIngredients] = useState([]);
const {getIngredient, selectIngredient} = useSelect(); const {getIngredient, selectIngredient} = useSelect();
const {createError} = useAlert(); const {createError, createSuccess} = useAlert();
useEffect(() => { useEffect(() => {
api().get(requests.bar.ingredientList) api().get(requests.bar.ingredientList)
@@ -77,6 +76,14 @@ export function IngredientsPage() {
const handleOpenModal = (i) => { const handleOpenModal = (i) => {
selectIngredient(i) selectIngredient(i)
} }
const handleDelete = (id) => {
const newState = ingredients.filter((ingredient) => ingredient.id !== id);
setIngredients(newState)
api().delete(`${requests.bar.ingredient}/${id}`)
.then((r) => createSuccess("Ингредиент удален"))
.catch(() => createError("Ошибка удаления ингредиента. Перезагрузите страницу"))
}
return ( return (
<Box> <Box>
@@ -136,7 +143,7 @@ export function IngredientsPage() {
{/*Загрузчик*/} {/*Загрузчик*/}
<Loading loading={loading}/> <Loading loading={loading}/>
{/*Модальное окно информации об ингредиенте*/} {/*Модальное окно информации об ингредиенте*/}
<IngredientInfoModal ingredient={getIngredient()}/> <IngredientInfoModal ingredient={getIngredient()} handleDelete={handleDelete}/>
</Box> </Box>
) )
} }

View File

@@ -0,0 +1,39 @@
import * as React from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
export function IngredientAlert({open, handleClose, handleDelete, id, handleCloseParent}) {
return (
<React.Fragment>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="Предупреждение об удалении"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"Вы готовы удалить ингредиент?"}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
После удаления ингредиента, удаляться все рецепты и коктейли связанные с ним!
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Отмена</Button>
<Button color='error' onClick={() => {
handleClose();
handleCloseParent();
handleDelete(id)
}} autoFocus>
Удалить
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}

View File

@@ -13,11 +13,19 @@ import {requests} from "../../requests";
import {useAlert} from "../../hooks/useAlert"; import {useAlert} from "../../hooks/useAlert";
import ListItem from "@mui/material/ListItem"; import ListItem from "@mui/material/ListItem";
import {useSelect} from "../../hooks/useSelect"; import {useSelect} from "../../hooks/useSelect";
import {IngredientAlert} from "./IngredientAlert";
import {useUser} from "../../hooks/useUser";
export function IngredientInfoModal({ingredient}) { export function IngredientInfoModal({ingredient, handleDelete}) {
const {user} = useUser();
const [cocktails, setCocktails] = useState([]); const [cocktails, setCocktails] = useState([]);
const {closeIngredient, getOpenIngredient, selectCocktail} = useSelect(); const {closeIngredient, getOpenIngredient, selectCocktail} = useSelect();
const {createError} = useAlert(); const {createError} = useAlert();
const [open, setOpen] = React.useState(false);
const handleClose = () => {
setOpen(false);
};
useEffect(() => { useEffect(() => {
if(!ingredient) { if(!ingredient) {
@@ -72,8 +80,10 @@ export function IngredientInfoModal({ingredient}) {
)} )}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={closeIngredient}>Close</Button> {user.role !== 'USER' && <Button onClick={() => setOpen(true)}>Удалить</Button>}
<Button onClick={closeIngredient}>Закрыть</Button>
</DialogActions> </DialogActions>
<IngredientAlert handleDelete={handleDelete} handleClose={handleClose} open={open} id={ingredient.id} handleCloseParent={closeIngredient}/>
</Dialog> </Dialog>
); );
} }

View File

@@ -12,6 +12,7 @@ import DeleteIcon from '@mui/icons-material/Delete';
import LocalBarIcon from '@mui/icons-material/LocalBar'; import LocalBarIcon from '@mui/icons-material/LocalBar';
import {paths} from "../../path"; import {paths} from "../../path";
import {useAlert} from "../../hooks/useAlert"; import {useAlert} from "../../hooks/useAlert";
import {useUser} from "../../hooks/useUser";
function renderFavouriteBadge(handleFavourite, row) { function renderFavouriteBadge(handleFavourite, row) {
const childIcon = row.rating.favourite ? <FavoriteIcon/> : <FavoriteBorderIcon/>; const childIcon = row.rating.favourite ? <FavoriteIcon/> : <FavoriteBorderIcon/>;
@@ -35,6 +36,7 @@ function renderRating(handleChangeRating, row) {
export function Cocktail({row, handleFavourite, handleChangeRating, handleSelect, deleteHandler}) { export function Cocktail({row, handleFavourite, handleChangeRating, handleSelect, deleteHandler}) {
const {notImplement} = useAlert(); const {notImplement} = useAlert();
const {user} = useUser();
return ( return (
<Grid item sx={{pr: 2}}> <Grid item sx={{pr: 2}}>
<CocktailItemStyled> <CocktailItemStyled>
@@ -63,12 +65,17 @@ export function Cocktail({row, handleFavourite, handleChangeRating, handleSelect
{renderFavouriteBadge(handleFavourite, row)} {renderFavouriteBadge(handleFavourite, row)}
{renderRating(handleChangeRating, row)} {renderRating(handleChangeRating, row)}
<IconButton size='small' href={`${paths.bar.cocktailEdit}?id=${row.id}`}> {user.role !== 'USER' &&
<EditIcon fontSize='small'/> <>
</IconButton> <IconButton size='small' href={`${paths.bar.cocktailEdit}?id=${row.id}`}>
<IconButton size='small' onClick={() => deleteHandler(row)}> <EditIcon fontSize='small'/>
<DeleteIcon fontSize='small'/> </IconButton>
</IconButton> <IconButton size='small' onClick={() => deleteHandler(row)}>
<DeleteIcon fontSize='small'/>
</IconButton>
</>
}
</CardActions> </CardActions>
<CardContent sx={{pb: 0, pl: 2, pt: 0}}> <CardContent sx={{pb: 0, pl: 2, pt: 0}}>
<Typography variant="h5" minHeight={'50px'} mt={2}>{row.name} </Typography> <Typography variant="h5" minHeight={'50px'} mt={2}>{row.name} </Typography>

View File

@@ -86,7 +86,6 @@ function userDescriptor(user, session) {
<> <>
<Typography variant="subtitle1">{user.name + " " + user.lastName}</Typography> <Typography variant="subtitle1">{user.name + " " + user.lastName}</Typography>
<Typography color="text.secondary" variant="body2">{user.id}</Typography> <Typography color="text.secondary" variant="body2">{user.id}</Typography>
<Typography color="text.secondary" variant="body2">{`Бар ${open}`}</Typography>
</> </>
); );
} }

View File

@@ -1,9 +1,9 @@
import axios from "axios"; import axios from "axios";
import {tokenUtil} from "../TokenUtil"; import {tokenUtil} from "../TokenUtil";
// const host = "localhost:8080"; //дебаг вместе с беком const host = "localhost:8080"; //дебаг вместе с беком
// const host = "192.168.1.100:8091"; //дебаг фронта // const host = "192.168.1.100:8091"; //дебаг фронта
const host = "bar.kayashov.keenetic.pro"; //прод // const host = "bar.kayashov.keenetic.pro"; //прод
export const api = () => { export const api = () => {
const result = axios; const result = axios;
result.defaults.baseURL = `${window.location.protocol}//${host}/`; result.defaults.baseURL = `${window.location.protocol}//${host}/`;

View File

@@ -79,9 +79,7 @@ public class BarController {
@GetMapping("/getMe") @GetMapping("/getMe")
public VisitorResponseDto getMe() { public VisitorResponseDto getMe() {
Visitor visitor = visitorService.getCurrentVisitor(); Visitor visitor = visitorService.getCurrentVisitor();
String role; VisitorResponseDto dto = VisitorResponseDto.mapToDto(visitor, true, visitor.getRole().toString(), true);
role = UserRole.ADMIN.toString();
VisitorResponseDto dto = VisitorResponseDto.mapToDto(visitor, true, role, true);
log.info("Запрос информации о пользователе: {}-{} {}, вошел в бар", log.info("Запрос информации о пользователе: {}-{} {}, вошел в бар",
dto.getId(), dto.getId(),
dto.getName().strip(), dto.getName().strip(),

View File

@@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@@ -64,6 +65,11 @@ public class IngredientController {
ingredientService.changeBarIngredient(id, false); ingredientService.changeBarIngredient(id, false);
} }
@DeleteMapping("/{id}")
public void removeIngredient(@PathVariable Long id) {
ingredientService.removeIngredient(id);
}
@PutMapping @PutMapping
public void putIngredient(@RequestParam Long id) { public void putIngredient(@RequestParam Long id) {
ingredientService.changeBarIngredient(id, true); ingredientService.changeBarIngredient(id, true);

View File

@@ -6,6 +6,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import javax.persistence.CascadeType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Id; import javax.persistence.Id;
@@ -43,7 +44,7 @@ public class IngredientEntity {
@JoinColumn(name = "type") @JoinColumn(name = "type")
private TypeEntity type; private TypeEntity type;
@ManyToMany @ManyToMany(cascade = CascadeType.REMOVE)
@JoinTable( @JoinTable(
name = "bar_ingredient", name = "bar_ingredient",
joinColumns = @JoinColumn(name = "ingredient"), joinColumns = @JoinColumn(name = "ingredient"),

View File

@@ -7,6 +7,8 @@ import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -21,6 +23,8 @@ public class Visitor implements UserDetails {
private Long id; private Long id;
private String name; private String name;
private String lastName; private String lastName;
@Enumerated(EnumType.STRING)
private UserRole role;
private Integer code; private Integer code;
private String login; private String login;
private String password; private String password;
@@ -28,7 +32,8 @@ public class Visitor implements UserDetails {
@Override @Override
public Collection<? extends GrantedAuthority> getAuthorities() { public Collection<? extends GrantedAuthority> getAuthorities() {
return UserRole.ADMIN.getAuthorities(); return role.getAuthorities();
// return UserRole.ADMIN.getAuthorities();
// return residents.stream() // return residents.stream()
// .filter(BarResident::getActive) // .filter(BarResident::getActive)
// .map(BarResident::getRole) // .map(BarResident::getRole)

View File

@@ -3,14 +3,20 @@ package ru.kayashov.bar.service;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.kayashov.bar.controller.dto.ingredient.IngredientResponseDto; import ru.kayashov.bar.controller.dto.ingredient.IngredientResponseDto;
import ru.kayashov.bar.controller.dto.ingredient.IngredientSimpleResponseDto; import ru.kayashov.bar.controller.dto.ingredient.IngredientSimpleResponseDto;
import ru.kayashov.bar.mapper.IngredientMapper; import ru.kayashov.bar.mapper.IngredientMapper;
import ru.kayashov.bar.model.Ingredient;
import ru.kayashov.bar.model.entity.BarEntity; import ru.kayashov.bar.model.entity.BarEntity;
import ru.kayashov.bar.model.entity.CocktailEntity;
import ru.kayashov.bar.model.entity.IngredientEntity; import ru.kayashov.bar.model.entity.IngredientEntity;
import ru.kayashov.bar.model.entity.ReceiptEntity;
import ru.kayashov.bar.model.entity.TypeEntity; import ru.kayashov.bar.model.entity.TypeEntity;
import ru.kayashov.bar.repository.BarEntityRepository; import ru.kayashov.bar.repository.BarEntityRepository;
import ru.kayashov.bar.repository.CocktailRepository;
import ru.kayashov.bar.repository.IngredientRepository; import ru.kayashov.bar.repository.IngredientRepository;
import ru.kayashov.bar.repository.ReceiptRepository;
import ru.kayashov.bar.repository.TypeRepository; import ru.kayashov.bar.repository.TypeRepository;
import java.util.ArrayList; import java.util.ArrayList;
@@ -26,6 +32,8 @@ public class IngredientService {
private final IngredientMapper mapper; private final IngredientMapper mapper;
private final BarEntityRepository barEntityRepository; private final BarEntityRepository barEntityRepository;
private final IngredientRepository ingredientRepository; private final IngredientRepository ingredientRepository;
private final ReceiptRepository receiptRepository;
private final CocktailRepository cocktailRepository;
private TypeEntity findTypeByName(String name) { private TypeEntity findTypeByName(String name) {
return typeRepository.findByName(name) return typeRepository.findByName(name)
@@ -86,4 +94,21 @@ public class IngredientService {
repository.save(entity); repository.save(entity);
return true; return true;
} }
@Transactional
public void removeIngredient(Long id) {
IngredientEntity ingredient = ingredientRepository.findById(id).orElseThrow();
List<CocktailEntity> cocktails = ingredient.getReceipts()
.stream()
.map(ReceiptEntity::getCocktail)
.toList();
receiptRepository.deleteAll(ingredient.getReceipts());
cocktailRepository.deleteAll(cocktails);
log.info("Удалены коктейли {}", cocktails.stream().map(c -> c.getId() + "-" + c.getName()).toList());
repository.delete(ingredient);
log.info("Удален ингредиент {}-{}", id, ingredient.getName());
}
} }