diff --git a/front/src/app/NavigationRoutes.js b/front/src/app/NavigationRoutes.js index 8ab17ec..495cfc2 100644 --- a/front/src/app/NavigationRoutes.js +++ b/front/src/app/NavigationRoutes.js @@ -12,6 +12,7 @@ import {MenuPage} from "./pages/cocktails/MenuPage"; import {EditIngredientPage} from "./pages/ingredients/EditIngredientPage"; import {EditCocktailPage} from "./pages/cocktails/EditCocktailPage"; import {useEffect, useState} from "react"; +import {BarChangePage} from "./pages/BarChangePage"; export function NavigationRoutes() { const {auth} = useAuth(); @@ -64,6 +65,11 @@ const authPages = [ children: (), exact: true, }, + { + path: paths.bar.list, + isPrivate: true, + children: (), + }, { path: paths.bar.ingredients, isPrivate: true, diff --git a/front/src/app/pages/BarChangePage.js b/front/src/app/pages/BarChangePage.js new file mode 100644 index 0000000..1ad3828 --- /dev/null +++ b/front/src/app/pages/BarChangePage.js @@ -0,0 +1,111 @@ +import Paper from "@mui/material/Paper"; +import React, {useEffect, useState} from "react"; +import {api} from "../../lib/clients/api"; +import {requests} from "../../requests"; +import {useAlert} from "../../hooks/useAlert"; +import {Card} from "@mui/material"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import IconButton from "@mui/material/IconButton"; +import Box from "@mui/material/Box"; +import DeleteIcon from '@mui/icons-material/Delete'; +import ElectricalServicesIcon from '@mui/icons-material/ElectricalServices'; +import {getComparator} from "../../components/core/getComparator"; +import Toolbar from "@mui/material/Toolbar"; +import AddCircleIcon from '@mui/icons-material/AddCircle'; +import {BarCreateModal} from "../../components/BarCreateModal"; +import PowerIcon from '@mui/icons-material/Power'; + +export function BarChangePage() { + const [bars, setBars] = useState([]) + const [open, setOpen] = useState(false) + const {createError, createSuccess, createWarning} = useAlert(); + + useEffect(() => { + api().get(requests.bar.list) + .then((r) => { + setBars(r.data.sort(getComparator("name"))) + }) + .catch(() => { + createError("Ошибка получения списков") + }) + }, []); + const changeHandler = (bar) => { + createWarning("Дождитесь окончания операции") + api().post(`${requests.bar.change}/${bar.id}`) + .then(() => createSuccess("Список изменен")) + .catch(() => createError("Ошибка изменения активного списка")) + + const newState = bars.map((b) => { + if (b.active) { + return { + ...b, active: false + } + } + if (b.id === bar.id) { + return { + ...b, active: true + } + } + return b; + }) + setBars(newState); + } + const deleteHandler = (bar) => { + if (bar.active) { + createError("Нельзя удалить активный бар!") + return; + } + api().delete(requests.bar.crud + bar.id) + .then(() => createSuccess("Список удален")) + .catch(() => createError("Ошибка удаления. Обновите страницу")) + + setBars(bars.filter((b) => b.id !== bar.id)); + } + const createHandler = (name) => { + api().post(requests.bar.crud + name) + .then((r) => { + createSuccess("Cписок создан"); + let state = bars; + state.push(r.data); + setBars(state) + setOpen(false) + }).catch(() => createError("Ошибка создания списка")) + } + + function closeHandler() { + setOpen(false) + } + + return (<> + + + + Списки ингредиентов (бары) + setOpen(true)}> + + + + {bars.map((b) => { + return + + {b.name} + {b.active && + + } + {!b.active && + deleteHandler(b)}> + + + changeHandler(b)}> + + + } + + + })} + + + + ) +} \ No newline at end of file diff --git a/front/src/components/BarCreateModal.js b/front/src/components/BarCreateModal.js new file mode 100644 index 0000000..e6ab06b --- /dev/null +++ b/front/src/components/BarCreateModal.js @@ -0,0 +1,39 @@ +import Dialog from "@mui/material/Dialog"; +import DialogTitle from "@mui/material/DialogTitle"; +import * as React from "react"; +import Typography from "@mui/material/Typography"; +import DialogContent from "@mui/material/DialogContent"; +import DialogActions from "@mui/material/DialogActions"; +import Button from "@mui/material/Button"; +import {useState} from "react"; +import TextField from "@mui/material/TextField"; + +export function BarCreateModal({open, close, create}) { + const [value, setValue] = useState(""); + return ( + + + Создать список + + + setValue(e.target.value)} + /> + + + + + + ) +} \ No newline at end of file diff --git a/front/src/components/cocktails/Cocktail.js b/front/src/components/cocktails/Cocktail.js index 5fcd76c..ec82a54 100644 --- a/front/src/components/cocktails/Cocktail.js +++ b/front/src/components/cocktails/Cocktail.js @@ -11,6 +11,7 @@ import EditIcon from '@mui/icons-material/Edit'; import DeleteIcon from '@mui/icons-material/Delete'; import LocalBarIcon from '@mui/icons-material/LocalBar'; import {paths} from "../../path"; +import {useAlert} from "../../hooks/useAlert"; function renderFavouriteBadge(handleFavourite, row) { const childIcon = row.rating.favourite ? : ; @@ -33,6 +34,7 @@ function renderRating(handleChangeRating, row) { } export function Cocktail({row, handleFavourite, handleChangeRating, handleSelect, deleteHandler}) { + const {notImplement} = useAlert(); return ( @@ -55,24 +57,23 @@ export function Cocktail({row, handleFavourite, handleChangeRating, handleSelect image={row.image.includes("thecocktaildb") ? (row.image + "/preview") : row.image} /> - + notImplement()}> {renderFavouriteBadge(handleFavourite, row)} {renderRating(handleChangeRating, row)} - - + + - deleteHandler(row)}> - + deleteHandler(row)}> + {row.name} - diff --git a/front/src/hooks/useAlert.js b/front/src/hooks/useAlert.js index 7cfdaf8..0599ea3 100644 --- a/front/src/hooks/useAlert.js +++ b/front/src/hooks/useAlert.js @@ -16,6 +16,10 @@ export function useAlert() { createAlert("Данный функционал пока не реализован", {variant: 'warning'}); } + function createWarning(message) { + createAlert(message, {variant: 'warning'}) + } + function createError(message) { createAlert(message, {variant: "error"}); } @@ -28,5 +32,5 @@ export function useAlert() { createAlert(message, {variant: "success"}); } - return {createAlert, notImplement, createError, getError, createSuccess} + return {createAlert, notImplement, createError, getError, createSuccess, createWarning} } \ No newline at end of file diff --git a/front/src/navItems.js b/front/src/navItems.js index 9515d23..d0e96a9 100644 --- a/front/src/navItems.js +++ b/front/src/navItems.js @@ -2,6 +2,7 @@ import {paths} from "./path"; export const navItems = [ {key: 'menu', title: 'Меню', href: paths.dashboard.overview, icon: 'menu'}, + {key: 'barList', title: 'Список баров', href: paths.bar.list, icon: 'basket', forBarmen: true}, {key: 'ingredients', title: 'Список ингредиентов', href: paths.bar.ingredients, icon: 'basket', forBarmen: true}, {key: 'ingredientEdit', title: 'Ингредиенты', href: paths.bar.ingredientEdit, icon: 'ingredients', forAdmin: true}, {key: 'cocktailEdit', title: 'Коктейли', href: paths.bar.cocktailEdit, icon: 'cocktail', forAdmin: true} diff --git a/front/src/requests.js b/front/src/requests.js index c895fd9..810b4b9 100644 --- a/front/src/requests.js +++ b/front/src/requests.js @@ -36,7 +36,9 @@ export const requests = { invite: routes.visitor + "/invite?" }, bar: { - list: routes.bar + "list", + list: routes.bar + "all", + change: routes.bar + "change", + crud: routes.bar, addToMyList: routes.bar + "addToMyList", enter: routes.bar + "enter?id=", pay: routes.order + "?", diff --git a/src/main/java/ru/kayashov/bar/controller/BarController.java b/src/main/java/ru/kayashov/bar/controller/BarController.java index d9d02ad..15a091c 100644 --- a/src/main/java/ru/kayashov/bar/controller/BarController.java +++ b/src/main/java/ru/kayashov/bar/controller/BarController.java @@ -3,11 +3,15 @@ package ru.kayashov.bar.controller; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import ru.kayashov.bar.controller.dto.VisitorResponseDto; +import ru.kayashov.bar.controller.dto.bar.BarResponseDto; import ru.kayashov.bar.controller.dto.cocktail.ReceiptResponseDto; import ru.kayashov.bar.model.entity.Category; import ru.kayashov.bar.model.entity.Glass; @@ -52,6 +56,26 @@ public class BarController { return sessionService.getReceiptList(id); } + @PostMapping("/change/{id}") + public void changeActiveBar(@PathVariable Long id) { + sessionService.changeActiveBar(id); + } + + @DeleteMapping("/{id}") + public void deleteBar(@PathVariable Long id) { + sessionService.deleteBar(id); + } + + @PostMapping("/{name}") + public BarResponseDto createBar(@PathVariable String name) { + return sessionService.createBar(name); + } + + @GetMapping("/all") + public List getAll() { + return sessionService.findAllBar(); + } + @GetMapping("/getMe") public VisitorResponseDto getMe() { Visitor visitor = visitorService.getCurrentVisitor(); diff --git a/src/main/java/ru/kayashov/bar/controller/dto/bar/BarResponseDto.java b/src/main/java/ru/kayashov/bar/controller/dto/bar/BarResponseDto.java index c0d9a52..584657c 100644 --- a/src/main/java/ru/kayashov/bar/controller/dto/bar/BarResponseDto.java +++ b/src/main/java/ru/kayashov/bar/controller/dto/bar/BarResponseDto.java @@ -2,13 +2,20 @@ package ru.kayashov.bar.controller.dto.bar; import lombok.Getter; import lombok.Setter; +import ru.kayashov.bar.model.entity.BarEntity; @Getter @Setter public class BarResponseDto { private Long id; private String name; - private Boolean open; - private Boolean enter; - private String myRole; + private Boolean active; + + public static BarResponseDto mapToDto(BarEntity barEntity) { + BarResponseDto barResponseDto = new BarResponseDto(); + barResponseDto.setId(barEntity.getId()); + barResponseDto.setName(barEntity.getName()); + barResponseDto.setActive(barEntity.getActive()); + return barResponseDto; + } } diff --git a/src/main/java/ru/kayashov/bar/model/entity/IngredientEntity.java b/src/main/java/ru/kayashov/bar/model/entity/IngredientEntity.java index 67d8a2d..9e46e77 100644 --- a/src/main/java/ru/kayashov/bar/model/entity/IngredientEntity.java +++ b/src/main/java/ru/kayashov/bar/model/entity/IngredientEntity.java @@ -31,6 +31,7 @@ public class IngredientEntity { private String enName; private Boolean alcohol; private Integer abv; + private Boolean isHave; @Column(columnDefinition = "text") private String description; diff --git a/src/main/java/ru/kayashov/bar/service/CocktailService.java b/src/main/java/ru/kayashov/bar/service/CocktailService.java index c870942..563cc5c 100644 --- a/src/main/java/ru/kayashov/bar/service/CocktailService.java +++ b/src/main/java/ru/kayashov/bar/service/CocktailService.java @@ -15,14 +15,12 @@ import ru.kayashov.bar.controller.dto.cocktail.CocktailModalDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailSimpleResponseDto; import ru.kayashov.bar.controller.dto.cocktail.ReceiptResponseDto; import ru.kayashov.bar.mapper.CocktailMapper; -import ru.kayashov.bar.model.Ingredient; import ru.kayashov.bar.model.entity.Alcoholic; import ru.kayashov.bar.model.entity.Category; import ru.kayashov.bar.model.entity.CocktailEntity; import ru.kayashov.bar.model.entity.Glass; import ru.kayashov.bar.model.entity.IngredientEntity; import ru.kayashov.bar.model.entity.ReceiptEntity; -import ru.kayashov.bar.model.entity.Visitor; import ru.kayashov.bar.repository.CocktailRepository; import ru.kayashov.bar.repository.IngredientRepository; import ru.kayashov.bar.repository.ReceiptRepository; @@ -37,7 +35,6 @@ import javax.persistence.criteria.Order; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.persistence.criteria.Subquery; -import javax.print.attribute.standard.MediaSize; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -82,7 +79,8 @@ public class CocktailService { criteriaQuery.distinct(true); if (!dto.getAll()) { - List cocktailIds = findICountCocktailIds(0, new ArrayList<>()); +// List cocktailIds = findICountCocktailIds(0, new ArrayList<>()); + List cocktailIds = findCocktailByCountNotHaveIngredient(); Predicate pr = root.get("id").in(cocktailIds); predicates.add(pr); } @@ -225,35 +223,18 @@ public class CocktailService { group by r.cocktail_id) as cifc where false_count = 0 */ - private List findCocktailByCountNotHaveIngredient(Integer notHaveCount, List currentIngredient) { - CriteriaBuilder cb = entityManager.getCriteriaBuilder(); - List predicates = new ArrayList<>(); - CriteriaQuery mainQuery = cb.createQuery(Long.class); + //todo: так и не придумал я нормальный запрос + private List findCocktailByCountNotHaveIngredient() { + String sql = "SELECT cifc.cocktail_id" + + " FROM (SELECT r.cocktail_id," + + " COUNT(CASE WHEN i.is_have = false THEN 1 END) AS false_count" + + " FROM receipt r" + + " LEFT JOIN public.ingredient i ON i.id = r.ingredient_id" + + " GROUP BY r.cocktail_id) AS cifc" + + " WHERE false_count = 0"; - // Создаем корневые сущности - Subquery sq = mainQuery.subquery(Long.class); - Root rsq = sq.from(ReceiptEntity.class); - Join ingredientJoin = rsq.join("ingredient", JoinType.LEFT); - // Создаем подзапрос - sq.select(cb.count(cb.selectCase(cb.isFalse(ingredientJoin.get("isHave"))))) - .groupBy(rsq.get("cocktail")) - .having(cb.equal(sq.getSelection(), notHaveCount)); - - // Создаем внешний запрос - mainQuery.select(rsq.get("cocktail")); - Predicate predicate = cb.exists(sq); - predicates.add(predicate); -// mainQuery.where(cb.exists(sq)); - - if(!currentIngredient.isEmpty()) { - predicates.add(rsq.get("ingredient").in(currentIngredient)); - } - - mainQuery.where(predicates.toArray(new Predicate[0])); - - // Выполняем запрос - return entityManager.createQuery(mainQuery) - .getResultList(); + javax.persistence.Query query = entityManager.createNativeQuery(sql); + return query.getResultList(); } public CocktailForListResponseDto findById(Long id) { diff --git a/src/main/java/ru/kayashov/bar/service/IngredientService.java b/src/main/java/ru/kayashov/bar/service/IngredientService.java index 17f69c6..9a9263e 100644 --- a/src/main/java/ru/kayashov/bar/service/IngredientService.java +++ b/src/main/java/ru/kayashov/bar/service/IngredientService.java @@ -25,6 +25,7 @@ public class IngredientService { private final IngredientRepository repository; private final IngredientMapper mapper; private final BarEntityRepository barEntityRepository; + private final IngredientRepository ingredientRepository; private TypeEntity findTypeByName(String name) { return typeRepository.findByName(name) @@ -57,6 +58,8 @@ public class IngredientService { } else { bar.getIngredients().remove(ingredientEntity); } + ingredientEntity.setIsHave(isHave); + ingredientRepository.save(ingredientEntity); barEntityRepository.save(bar); } diff --git a/src/main/java/ru/kayashov/bar/service/SessionService.java b/src/main/java/ru/kayashov/bar/service/SessionService.java index ce1d8a3..2b63bb6 100644 --- a/src/main/java/ru/kayashov/bar/service/SessionService.java +++ b/src/main/java/ru/kayashov/bar/service/SessionService.java @@ -4,14 +4,20 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import ru.kayashov.bar.controller.dto.bar.BarResponseDto; import ru.kayashov.bar.controller.dto.cocktail.ReceiptResponseDto; import ru.kayashov.bar.controller.dto.ingredient.IngredientSimpleResponseDto; +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.Visitor; +import ru.kayashov.bar.repository.BarEntityRepository; import ru.kayashov.bar.repository.CocktailRepository; +import ru.kayashov.bar.repository.IngredientRepository; import ru.kayashov.bar.repository.VisitorsRepository; import java.util.List; +import java.util.concurrent.ExecutorService; @Slf4j @Service @@ -20,6 +26,8 @@ public class SessionService { private final VisitorsRepository visitorsRepository; private final CocktailRepository cocktailRepository; + private final BarEntityRepository barEntityRepository; + private final IngredientRepository ingredientRepository; public Visitor getVisitor() { Long id = ((Visitor) SecurityContextHolder.getContext() @@ -40,4 +48,42 @@ public class SessionService { .build()) .toList(); } + + public void changeActiveBar(Long id) { + BarEntity lastBar = barEntityRepository.findByActiveTrue().orElseThrow(); + lastBar.setActive(false); + barEntityRepository.save(lastBar); + + lastBar.getIngredients().stream() + .peek(i -> i.setIsHave(false)) + .forEach(ingredientRepository::save); + + BarEntity barEntity = barEntityRepository.findById(id).orElseThrow(); + barEntity.setActive(true); + + barEntity.getIngredients().stream() + .peek(i -> i.setIsHave(true)) + .forEach(ingredientRepository::save); + + barEntityRepository.save(barEntity); + } + + public List findAllBar() { + return barEntityRepository.findAll().stream() + .map(BarResponseDto::mapToDto) + .toList(); + } + + public void deleteBar(Long id) { + barEntityRepository.deleteById(id); + } + + public BarResponseDto createBar(String name) { + BarEntity bar = new BarEntity(); + bar.setName(name); + bar.setActive(false); + bar = barEntityRepository.save(bar); + + return BarResponseDto.mapToDto(bar); + } }