Merge branch '#3' into 'back_release'
#3 - исправление NPE для загрузки фото See merge request kayashov/my-little-bar!4
This commit is contained in:
@@ -7,7 +7,7 @@ stages:
|
||||
- deploy front
|
||||
- deploy back
|
||||
|
||||
stop-job:
|
||||
deploy-back-job:
|
||||
stage: deploy back
|
||||
only:
|
||||
- back_release
|
||||
@@ -18,5 +18,17 @@ stop-job:
|
||||
- echo "Сборка образа докер"
|
||||
- docker build -t my-bar .
|
||||
- echo "Запуск докер контейнера"
|
||||
- docker run --name my-bar --restart=always -p 8091:8080 -d my-bar
|
||||
- docker run --name my-bar --restart=always -p 8091:8080 -d -e COCKTAIL_PHOTO_PATH=/front/assets/cocktails -v /mnt/sdb1/my-bar-front/build:/front my-bar
|
||||
- echo "Деплой завершен"
|
||||
|
||||
deploy-front-job:
|
||||
stage: deploy front
|
||||
only:
|
||||
- release_front
|
||||
image: node:22.12
|
||||
script:
|
||||
- cd front
|
||||
- npm install
|
||||
- npm run build
|
||||
- cp -r build /app
|
||||
- echo "Деплой завершен"
|
||||
@@ -41,7 +41,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"build": "CI=false && react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ import {BrowserRouter as Router} from "react-router-dom";
|
||||
import {NavigationRoutes} from "./NavigationRoutes";
|
||||
import {SnackbarProvider} from 'notistack';
|
||||
import {UserProvider} from "../context/UserContext";
|
||||
import {SelectProvider} from "../context/SelectContext";
|
||||
|
||||
function App() {
|
||||
const theme = createTTheme();
|
||||
@@ -36,10 +37,13 @@ function App() {
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{/*Провайдер выбора*/}
|
||||
<SelectProvider>
|
||||
{/*Маршрутизация*/}
|
||||
<Router>
|
||||
<NavigationRoutes/>
|
||||
</Router>
|
||||
</SelectProvider>
|
||||
</CssVarsProvider>
|
||||
</UserProvider>
|
||||
</AuthProvider>
|
||||
|
||||
@@ -16,6 +16,7 @@ import {requests} from "../../../requests";
|
||||
import {api} from "../../../lib/clients/api";
|
||||
import {useAlert} from "../../../hooks/useAlert";
|
||||
import {CocktailInfoModal} from "../../../components/cocktails/CocktailInfoModal";
|
||||
import {useSelect} from "../../../hooks/useSelect";
|
||||
|
||||
export function CocktailMenuBarPage() {
|
||||
const {createError} = useAlert();
|
||||
@@ -23,8 +24,7 @@ export function CocktailMenuBarPage() {
|
||||
const [findString, setFindString] = useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [cocktails, setCocktails] = useState([]);
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [selected, setSelected] = useState(null);
|
||||
const {setCocktail, state} = useSelect();
|
||||
|
||||
useEffect(() => {
|
||||
api().get(`${requests.cocktails.menu}?all=true`)
|
||||
@@ -38,8 +38,7 @@ export function CocktailMenuBarPage() {
|
||||
|
||||
|
||||
const handleOpenModal = (row) => {
|
||||
setSelected(row)
|
||||
setOpenModal(true);
|
||||
setCocktail(row);
|
||||
}
|
||||
const changeHandler = (row, value) => {
|
||||
const newState = cocktails.map((r) => {
|
||||
@@ -117,11 +116,7 @@ export function CocktailMenuBarPage() {
|
||||
{/*Загрузчик*/}
|
||||
<Loading loading={loading}/>
|
||||
{/*Модальное окно информации об ингредиенте*/}
|
||||
<CocktailInfoModal open={openModal} row={selected}
|
||||
closeHandler={() => {
|
||||
setSelected(null);
|
||||
setOpenModal(false);
|
||||
}}/>
|
||||
<CocktailInfoModal row={state.cocktail}/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -14,57 +14,8 @@ import {useUser} from "../../../hooks/useUser";
|
||||
import {blue} from "@mui/material/colors";
|
||||
import UpIcon from "@mui/icons-material/KeyboardArrowUp";
|
||||
import {sortList} from "../../../components/cocktails/sortingList";
|
||||
import {getComparator} from "../../../components/core/getComparator";
|
||||
import Button from "@mui/material/Button";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import CheckMarks from "../../../components/cocktails/CheckMarks";
|
||||
|
||||
const filterList = (rows, filter, allowIngredients) => {
|
||||
let regExp = new RegExp("(.*?)" + filter.search + "(.*?)", "i");
|
||||
const sortingObj = sortList.find((s) => s.name === filter.sorting);
|
||||
const sortingValues = sortingObj.id.split("|");
|
||||
return rows
|
||||
.filter((row) => {
|
||||
const nameReg = row.name.split(" ").map((n) => n.match(regExp) !== null).includes(true);
|
||||
const ingredientReg = row.components
|
||||
.split(", ")
|
||||
.map((r) => r.match(regExp) !== null)
|
||||
.includes(true);
|
||||
return nameReg || ingredientReg;
|
||||
})
|
||||
.filter((row) => filter.onlyFavourite ? row.rating.favourite : true)
|
||||
.filter((row) => filter.glass.length === 0 || filter.glass.includes(row.glass))
|
||||
.filter((row) => filter.category.length === 0 || filter.category.includes(row.category))
|
||||
.filter((row) => filter.alcohol.length === 0 || filter.alcohol.includes(row.alcoholic))
|
||||
.filter((row) => {
|
||||
if (filter.tags.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (row.tags.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return row.tags.split(",").find((tag) => filter.tags.includes(tag))
|
||||
})
|
||||
.filter((row) => {
|
||||
if (filter.iCount.length === 0) {
|
||||
return true;
|
||||
}
|
||||
const arr = row.components.split(", ");
|
||||
const count = arr.filter((n) => !allowIngredients.includes(n)).length;
|
||||
const filt = filter.ingredient.length === 0 || arr.filter((n) => filter.ingredient.includes(n)).length > 0;
|
||||
|
||||
return filter.iCount === count && filt;
|
||||
})
|
||||
.filter((row) => {
|
||||
if (filter.inMenu === "") {
|
||||
return row;
|
||||
}
|
||||
const filterValue = filter.inMenu === "Есть в меню";
|
||||
return filterValue === row.inMenu;
|
||||
})
|
||||
.sort(getComparator(sortingValues[1], sortingValues[0], "name"))
|
||||
}
|
||||
import {useSelect} from "../../../hooks/useSelect";
|
||||
|
||||
const emptyFilter = {
|
||||
search: "",
|
||||
@@ -86,14 +37,14 @@ const CocktailsPageContent = ({all}) => {
|
||||
const [allowIngredients, setAllowIngredients] = useState([])
|
||||
const [rows, setRows] = useState([]);
|
||||
const [filter, setFilter] = useState(emptyFilter)
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedCocktail, setSelectedCocktail] = useState(null)
|
||||
const [chips, setChips] = useState([])
|
||||
// const [chips, setChips] = useState([])
|
||||
const [page, setPage] = useState(-1);
|
||||
const [load, setLoad] = useState(false);
|
||||
const [isEnd, setIsEnd] = useState(false);
|
||||
const [isNew, setIsNew] = useState(true);
|
||||
|
||||
const {selectCocktail, getCocktail, getOpenCocktail} = useSelect();
|
||||
|
||||
const loading = useCallback(() => {
|
||||
const size = Math.floor((window.innerWidth) / 350) * 5;
|
||||
if (load || (!isNew && isEnd)) {
|
||||
@@ -139,6 +90,7 @@ const CocktailsPageContent = ({all}) => {
|
||||
}
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
// eslint-disable-next-line
|
||||
}, [loading]);
|
||||
useEffect(() => {
|
||||
api().get(requests.bar.ingredientSimple)
|
||||
@@ -152,6 +104,7 @@ const CocktailsPageContent = ({all}) => {
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
loading();
|
||||
// eslint-disable-next-line
|
||||
}, [filter])
|
||||
useEffect(() => {
|
||||
if (!all) {
|
||||
@@ -164,7 +117,8 @@ const CocktailsPageContent = ({all}) => {
|
||||
.filter((nhc) => nhc.length === 1)
|
||||
.map((fc) => fc[0])
|
||||
.forEach((i) => ingredients.add(i))
|
||||
setChips(Array.from(ingredients).sort(getComparator()));
|
||||
// setChips(Array.from(ingredients).sort(getComparator()));
|
||||
// eslint-disable-next-line
|
||||
}, [rows, allowIngredients])
|
||||
|
||||
const renderSkeleton = () => {
|
||||
@@ -230,12 +184,9 @@ const CocktailsPageContent = ({all}) => {
|
||||
setFilter(emptyFilter);
|
||||
}
|
||||
const handleSelectCocktail = (row) => {
|
||||
setSelectedCocktail(row.id)
|
||||
setOpen(true)
|
||||
}
|
||||
const handleCloseCocktailModal = () => {
|
||||
setOpen(false);
|
||||
setSelectedCocktail(null);
|
||||
selectCocktail(row.id)
|
||||
// setSelectedCocktail(row.id)
|
||||
// setOpen(true)
|
||||
}
|
||||
const handleEditMenu = (row, value) => {
|
||||
const newState = rows.map((r) => {
|
||||
@@ -271,8 +222,7 @@ const CocktailsPageContent = ({all}) => {
|
||||
<Box>
|
||||
{/*<Loading loading={load}/>*/}
|
||||
{/*Модальное окно информации о коктейле*/}
|
||||
<CocktailInfoModal row={selectedCocktail} open={open}
|
||||
closeHandler={handleCloseCocktailModal}/>
|
||||
<CocktailInfoModal row={getCocktail()} open={getOpenCocktail()}/>
|
||||
{/*Блок фильтров*/}
|
||||
<FilterBlock
|
||||
filter={filter}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {IngredientList} from "../../../components/Ingredients/IngredientList";
|
||||
import {blue} from "@mui/material/colors";
|
||||
import UpIcon from "@mui/icons-material/KeyboardArrowUp";
|
||||
import Switch from "@mui/material/Switch";
|
||||
import {useSelect} from "../../../hooks/useSelect";
|
||||
|
||||
export function IngredientsPage() {
|
||||
const [value, setValue] = React.useState(0);
|
||||
@@ -27,8 +28,7 @@ export function IngredientsPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [findString, setFindString] = useState("");
|
||||
const [ingredients, setIngredients] = useState([]);
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [selectedInfo, setSelectedInfo] = useState(null);
|
||||
const {getIngredient, selectIngredient} = useSelect();
|
||||
const {createError} = useAlert();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -76,12 +76,7 @@ export function IngredientsPage() {
|
||||
});
|
||||
}
|
||||
const handleOpenModal = (i) => {
|
||||
setOpenModal(true);
|
||||
setSelectedInfo(i);
|
||||
}
|
||||
const handleCloseModal = () => {
|
||||
setSelectedInfo(null);
|
||||
setOpenModal(false);
|
||||
selectIngredient(i)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -147,7 +142,7 @@ export function IngredientsPage() {
|
||||
{/*Загрузчик*/}
|
||||
<Loading loading={loading}/>
|
||||
{/*Модальное окно информации об ингредиенте*/}
|
||||
<IngredientInfoModal ingredient={selectedInfo} open={openModal} closeHandler={handleCloseModal}/>
|
||||
<IngredientInfoModal ingredient={getIngredient()}/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -6,13 +6,34 @@ import Button from "@mui/material/Button";
|
||||
import * as React from "react";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import List from "@mui/material/List";
|
||||
import {useEffect, useState} from "react";
|
||||
import {api} from "../../lib/clients/api";
|
||||
import {requests} from "../../requests";
|
||||
import {useAlert} from "../../hooks/useAlert";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import {useSelect} from "../../hooks/useSelect";
|
||||
|
||||
export function IngredientInfoModal({ingredient}) {
|
||||
const [cocktails, setCocktails] = useState([]);
|
||||
const {closeIngredient, getOpenIngredient, selectCocktail} = useSelect();
|
||||
const {createError} = useAlert();
|
||||
|
||||
useEffect(() => {
|
||||
if(!ingredient) {
|
||||
return
|
||||
}
|
||||
api().get(requests.cocktails.byIngredient + ingredient.id)
|
||||
.then((r) => setCocktails(r.data))
|
||||
.catch(() => createError())
|
||||
// eslint-disable-next-line
|
||||
}, [ingredient]);
|
||||
|
||||
export function IngredientInfoModal({ingredient, open, closeHandler}) {
|
||||
if (!ingredient) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Dialog fullWidth={true} maxWidth="350px" open={open} onClose={closeHandler}
|
||||
<Dialog fullWidth={true} maxWidth="350px" open={getOpenIngredient()} onClose={closeIngredient}
|
||||
sx={{
|
||||
'& .MuiDialog-paper': {
|
||||
margin: '8px',
|
||||
@@ -28,9 +49,30 @@ export function IngredientInfoModal({ingredient, open, closeHandler}) {
|
||||
{ingredient.alcohol && (<Typography>{`Крепость ${ingredient.abv}`}</Typography>)}
|
||||
<Typography>{ingredient.description}</Typography>
|
||||
</Stack>
|
||||
{cocktails.length > 0 && (
|
||||
<>
|
||||
<Typography sx={{ mt:2}}>Коктейли:</Typography>
|
||||
<List>
|
||||
{cocktails.map((c) => {
|
||||
return (
|
||||
<ListItem key={c.id} onClick={() => {
|
||||
selectCocktail(c.id)
|
||||
closeIngredient();
|
||||
}}>
|
||||
<Stack direction={'row'}>
|
||||
<img src={c.image} alt={c.name} loading={"eager"} width={"50"}/>
|
||||
<Typography sx={{mx:1}}>{c.name}</Typography>
|
||||
{c.rating.rating > 0 && <Typography> {`${c.rating.rating}/5`}</Typography>}
|
||||
</Stack>
|
||||
</ListItem>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={closeHandler}>Close</Button>
|
||||
<Button onClick={closeIngredient}>Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
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";
|
||||
@@ -14,6 +11,7 @@ import FavoriteIcon from '@mui/icons-material/Favorite';
|
||||
import {api} from "../../lib/clients/api";
|
||||
import Box from "@mui/material/Box";
|
||||
import {useUser} from "../../hooks/useUser";
|
||||
import {CocktailDescription} from "./CocktailDescription";
|
||||
|
||||
function renderFavouriteBadge(handleFavourite, row) {
|
||||
const childIcon = row.rating.favourite ? <FavoriteIcon color='error'/> : <FavoriteBorderIcon color={'warning'}/>;
|
||||
@@ -65,40 +63,13 @@ export function Cocktail({row, handleFavourite, handleChangeRating, handleSelect
|
||||
alt={row.name}
|
||||
height="300"
|
||||
|
||||
image={`${row.image}/preview`}
|
||||
image={row.image.includes("thecocktaildb") ? (row.image + "/preview") : row.image}
|
||||
/>
|
||||
{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>
|
||||
<CocktailDescription row={row}/>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
{(row.isAllowed && session.isActive && user.invited) &&
|
||||
|
||||
36
front/src/components/cocktails/CocktailDescription.js
Normal file
36
front/src/components/cocktails/CocktailDescription.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import List from "@mui/material/List";
|
||||
|
||||
export function CocktailDescription({row}) {
|
||||
return (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
@@ -21,24 +21,20 @@ import {useAlert} from "../../hooks/useAlert";
|
||||
import {paths} from "../../path";
|
||||
import {Loading} from "../core/Loading";
|
||||
import {useUser} from "../../hooks/useUser";
|
||||
import {useSelect} from "../../hooks/useSelect";
|
||||
|
||||
export function CocktailInfoModal({open, row, closeHandler}) {
|
||||
export function CocktailInfoModal({row}) {
|
||||
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 {closeCocktail, selectIngredient, getIngredient, getOpenCocktail} = useSelect();
|
||||
const openIngredientModalHandler = (id) => {
|
||||
api().get(`${requests.bar.ingredient}?id=${id}`)
|
||||
.then((r) => {
|
||||
setSelectedIngredient(r.data)
|
||||
setOpenIngredientModal(true);
|
||||
}).catch(() => createError("Ошибка получения информации об ингредиенте"))
|
||||
selectIngredient(r.data)
|
||||
})
|
||||
.catch(() => createError("Ошибка получения информации об ингредиенте"))
|
||||
}
|
||||
const selectIngredientHandler = (ingredient) => {
|
||||
const url = `${requests.bar.ingredient}?id=${ingredient.id}`;
|
||||
@@ -79,18 +75,17 @@ export function CocktailInfoModal({open, row, closeHandler}) {
|
||||
.catch(() => {
|
||||
getError();
|
||||
setLoading(false)
|
||||
closeHandler();
|
||||
closeCocktail();
|
||||
})
|
||||
// eslint-disable-next-line
|
||||
}, [row]);
|
||||
|
||||
if (!row || !cocktail) {
|
||||
return null;
|
||||
}
|
||||
let alko = 0;
|
||||
let volume = 0;
|
||||
return (
|
||||
<Dialog fullWidth={true}
|
||||
open={open} onClose={closeHandler}
|
||||
open={getOpenCocktail()} onClose={closeCocktail}
|
||||
sx={{
|
||||
'& .MuiDialog-paper': {
|
||||
margin: '8px',
|
||||
@@ -99,8 +94,7 @@ export function CocktailInfoModal({open, row, closeHandler}) {
|
||||
width: 'calc(100% - 16px)',
|
||||
}
|
||||
}}>
|
||||
<IngredientInfoModal ingredient={selectedIngredient} open={openIngredientModal}
|
||||
closeHandler={closeIngredientHandler}/>
|
||||
<IngredientInfoModal ingredient={getIngredient()}/>
|
||||
<Loading loading={loading}/>
|
||||
<DialogTitle>
|
||||
<Stack direction='row' justifyContent={'space-between'}>
|
||||
@@ -118,7 +112,7 @@ export function CocktailInfoModal({open, row, closeHandler}) {
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<CardMedia
|
||||
image={`${cocktail.image}/preview`}
|
||||
image={cocktail.image.includes("thecocktaildb") ? (cocktail.image + "/preview") : cocktail.image}
|
||||
sx={{
|
||||
loading: "eager",
|
||||
borderRadius: 2
|
||||
@@ -134,9 +128,6 @@ export function CocktailInfoModal({open, row, closeHandler}) {
|
||||
{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}>
|
||||
@@ -173,7 +164,7 @@ export function CocktailInfoModal({open, row, closeHandler}) {
|
||||
{user.role.includes("ADMIN") && (
|
||||
<Button href={`${paths.bar.cocktailEdit}?id=${cocktail.id}`}>Редактировать</Button>
|
||||
)}
|
||||
<Button onClick={closeHandler}>Закрыть</Button>
|
||||
<Button onClick={closeCocktail}>Закрыть</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
|
||||
@@ -37,6 +37,7 @@ function renderButtons(row, my, handleChange) {
|
||||
|
||||
export default function OrderModal({row, handleClose, open, handleChange, my}) {
|
||||
const [receipt, setReceipt] = useState([]);
|
||||
const [instructions, setInstructions] = useState();
|
||||
const {createError} = useAlert();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -46,6 +47,10 @@ export default function OrderModal({row, handleClose, open, handleChange, my}) {
|
||||
api().get(requests.bar.receipts + row.cocktail.id)
|
||||
.then((r) => setReceipt(r.data))
|
||||
.catch(() => createError("Ошибка получения рецепта"))
|
||||
|
||||
api().get(requests.cocktails.instructions + row.cocktail.id)
|
||||
.then((r) => setInstructions(r.data))
|
||||
.catch(() => createError("Ошибка получения инструкции"))
|
||||
// eslint-disable-next-line
|
||||
}, [row]);
|
||||
|
||||
@@ -62,7 +67,6 @@ export default function OrderModal({row, handleClose, open, handleChange, my}) {
|
||||
<DialogTitle>{"Заказ №" + row.id}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>{row.cocktail.name}</DialogContentText>
|
||||
<DialogContentText>{row.cocktail.alcoholic + " " + row.cocktail.category}</DialogContentText>
|
||||
<DialogContentText>{"для: " + row.visitor.name + " " + row.visitor.lastName}</DialogContentText>
|
||||
<Box noValidate component="form"
|
||||
sx={{display: 'flex', flexDirection: 'column', m: 'auto', width: 'fit-content',}}>
|
||||
@@ -74,8 +78,14 @@ export default function OrderModal({row, handleClose, open, handleChange, my}) {
|
||||
return (<Typography key={r.id}>{`${r.ingredient.name} - ${r.measure}`}</Typography>)
|
||||
})}
|
||||
</Stack>
|
||||
{instructions &&
|
||||
(
|
||||
<>
|
||||
<Typography>Инструкция:</Typography>
|
||||
<Typography pl={1}>{row.cocktail.instructions}</Typography>
|
||||
<Typography pl={1}>{instructions}</Typography>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
{row.cocktail.video && (<iframe width="350" /*height="315"*/
|
||||
src={row.cocktail.video}
|
||||
|
||||
61
front/src/context/SelectContext.js
Normal file
61
front/src/context/SelectContext.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import * as React from "react";
|
||||
|
||||
export const SelectContext = React.createContext(undefined);
|
||||
|
||||
export function SelectProvider({children}) {
|
||||
const [selected, setSelected] = React.useState({
|
||||
cocktail: null,
|
||||
ingredient: null
|
||||
});
|
||||
|
||||
const selectCocktail = (row) => {
|
||||
setSelected((prev) => ({
|
||||
...prev,
|
||||
cocktail: row
|
||||
}))
|
||||
}
|
||||
const getCocktail = () => {
|
||||
return selected.cocktail
|
||||
}
|
||||
const getOpenCocktail = () => {
|
||||
return selected.cocktail !== null;
|
||||
}
|
||||
const closeCocktail = () => {
|
||||
setSelected((prevState) => ({
|
||||
...prevState,
|
||||
cocktail: null,
|
||||
}))
|
||||
}
|
||||
|
||||
const selectIngredient = (row) => {
|
||||
setSelected((prev) => ({
|
||||
...prev,
|
||||
ingredient: row
|
||||
}))
|
||||
}
|
||||
const closeIngredient = () => {
|
||||
setSelected((prevState) => ({
|
||||
...prevState,
|
||||
ingredient: null
|
||||
}))
|
||||
}
|
||||
const getIngredient = () => {
|
||||
return selected.ingredient
|
||||
}
|
||||
const getOpenIngredient = () => {
|
||||
return selected.ingredient !== null
|
||||
}
|
||||
|
||||
return <SelectContext.Provider value={{...selected,
|
||||
selectCocktail,
|
||||
getCocktail,
|
||||
getOpenCocktail,
|
||||
closeCocktail,
|
||||
selectIngredient,
|
||||
closeIngredient,
|
||||
getIngredient,
|
||||
getOpenIngredient
|
||||
}}>{children}</SelectContext.Provider>;
|
||||
}
|
||||
|
||||
export const SelectConsumer = SelectContext.Consumer;
|
||||
14
front/src/hooks/useSelect.js
Normal file
14
front/src/hooks/useSelect.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import * as React from "react";
|
||||
import {SelectContext} from "../context/SelectContext";
|
||||
|
||||
export function useSelect() {
|
||||
const context = React.useContext(SelectContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useSelect must be used within a SelectProvider');
|
||||
}
|
||||
|
||||
window.select = context;
|
||||
|
||||
return context;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ export const navItems = [
|
||||
{key: 'queue', title: 'Очередь заказов', href: paths.bar.ordersQueue, icon: 'orders', forBarmen: true},
|
||||
{key: 'ingredients', title: 'Ингредиенты в баре', href: paths.bar.ingredients, icon: 'basket', forBarmen: true},
|
||||
{key: 'visitors', title: "Посетители", href: paths.visitor.inBar, icon: 'visitors', forBarmen: true},
|
||||
{key: 'editMenu', title: "Редактировать меню", href: paths.bar.menu, icon: 'menu', forBarmen: true},
|
||||
// {key: 'editMenu', title: "Редактировать меню", href: paths.bar.menu, icon: 'menu', forBarmen: true},
|
||||
{key: 'ingredientEdit', title: 'Ингредиенты', href: paths.bar.ingredientEdit, icon: 'ingredients', forAdmin: true},
|
||||
{key: 'cocktailEdit', title: 'Коктейли', href: paths.bar.cocktailEdit, icon: 'cocktail', forAdmin: true}
|
||||
];
|
||||
|
||||
@@ -27,7 +27,9 @@ export const requests = {
|
||||
savePhoto: routes.cocktails + "/photo",
|
||||
favourite: routes.cocktails + "/favourite?id=",
|
||||
rating: routes.cocktails + "/rating?id=",
|
||||
receipts: routes.cocktails + "/receipts?id="
|
||||
receipts: routes.cocktails + "/receipts?id=",
|
||||
byIngredient: routes.cocktails + "/byIngredient?id=",
|
||||
instructions: routes.cocktails + "/instructions?id="
|
||||
},
|
||||
visitors: {
|
||||
all: routes.visitor,
|
||||
|
||||
@@ -44,6 +44,7 @@ public class AuthController {
|
||||
Visitor visitor = visitorsRepository.findById(Long.valueOf(decodeArr[0]))
|
||||
.orElseThrow();
|
||||
|
||||
log.info("Попытка авторизации пользователя {}", visitor.getId());
|
||||
Integer visitorCode = visitor.getCode();
|
||||
if (visitorCode == null) {
|
||||
return new AuthResponseDto(null, "Повторите запрос кода из бота");
|
||||
@@ -66,6 +67,7 @@ public class AuthController {
|
||||
return new AuthResponseDto(null, "Не найдет пользователь " + login);
|
||||
}
|
||||
Visitor visitor = visitorOpt.get();
|
||||
log.info("Попытка авторизации пользователя {}", visitor.getId());
|
||||
if (passwordEncoder.matches(password, visitor.getPassword())) {
|
||||
return new AuthResponseDto(jwtTokenProvider.generateToken(visitor), null);
|
||||
} else {
|
||||
@@ -77,6 +79,7 @@ public class AuthController {
|
||||
public AuthResponseDto refreshToken(@RequestHeader("Authorization") String token) {
|
||||
Claims claims = jwtTokenProvider.extractAllClaims(token);
|
||||
Long visitorId = claims.get("id", Long.class);
|
||||
log.info("обновление токена для пользователя {}", visitorId);
|
||||
Visitor visitor = visitorsRepository.findById(visitorId).orElseThrow();
|
||||
return new AuthResponseDto(jwtTokenProvider.generateToken(visitor), null);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package ru.kayashov.bar.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.hibernate.Session;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
@@ -10,14 +11,13 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
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.SessionResponseDto;
|
||||
import ru.kayashov.bar.controller.dto.VisitorResponseDto;
|
||||
import ru.kayashov.bar.controller.dto.bar.BarResponseDto;
|
||||
import ru.kayashov.bar.controller.dto.bar.CategoryResponseDto;
|
||||
import ru.kayashov.bar.controller.dto.bar.GlassResponseDto;
|
||||
import ru.kayashov.bar.controller.dto.cocktail.ReceiptResponseDto;
|
||||
import ru.kayashov.bar.controller.dto.SessionResponseDto;
|
||||
import ru.kayashov.bar.controller.dto.bar.TagResponseDto;
|
||||
import ru.kayashov.bar.controller.dto.VisitorResponseDto;
|
||||
import ru.kayashov.bar.model.entity.BarEntity;
|
||||
import ru.kayashov.bar.controller.dto.cocktail.ReceiptResponseDto;
|
||||
import ru.kayashov.bar.model.entity.BarResident;
|
||||
import ru.kayashov.bar.model.entity.SessionEntity;
|
||||
import ru.kayashov.bar.model.entity.Unit;
|
||||
@@ -105,12 +105,14 @@ public class BarController {
|
||||
@PostMapping("session")
|
||||
public void changeSessionStatus(@RequestParam Boolean value) {
|
||||
if (value) {
|
||||
sessionService.createEmptySession();
|
||||
SessionEntity entity = sessionService.createEmptySession();
|
||||
log.info("Открыта смена {}", entity.getId());
|
||||
return;
|
||||
}
|
||||
SessionEntity session = sessionService.findActiveSession();
|
||||
session.setIsActive(false);
|
||||
sessionRepository.save(session);
|
||||
log.info("Закрыта смена {}", session.getId());
|
||||
}
|
||||
|
||||
@GetMapping("/getMe")
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import ru.kayashov.bar.controller.dto.cocktail.CocktailFilterRequestDto;
|
||||
import ru.kayashov.bar.controller.dto.cocktail.CocktailForIngredientModalDto;
|
||||
import ru.kayashov.bar.controller.dto.cocktail.CocktailForListResponseDto;
|
||||
import ru.kayashov.bar.controller.dto.ErrorDto;
|
||||
import ru.kayashov.bar.controller.dto.cocktail.CocktailModalDto;
|
||||
@@ -43,6 +44,16 @@ public class CocktailController {
|
||||
return cocktailService.getMenu(dto);
|
||||
}
|
||||
|
||||
@GetMapping("/instructions")
|
||||
public String getInstructions(@RequestParam Long id) {
|
||||
return cocktailService.findInstructions(id);
|
||||
}
|
||||
|
||||
@GetMapping("/byIngredient")
|
||||
public List<CocktailForIngredientModalDto> getByIngredient(@RequestParam Long id) {
|
||||
return cocktailService.findByIngredient(id);
|
||||
}
|
||||
|
||||
@PostMapping("/photo")
|
||||
public String savePhoto(@RequestBody MultipartFile file) throws IOException {
|
||||
if(file == null) {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package ru.kayashov.bar.controller.dto.cocktail;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CocktailForIngredientModalDto {
|
||||
private Long id;
|
||||
private String name;
|
||||
private RatingResponseDto rating;
|
||||
private String image;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import ru.kayashov.bar.controller.dto.cocktail.CocktailForIngredientModalDto;
|
||||
import ru.kayashov.bar.controller.dto.cocktail.CocktailModalDto;
|
||||
import ru.kayashov.bar.controller.dto.cocktail.CocktailForListResponseDto;
|
||||
import ru.kayashov.bar.controller.dto.ingredient.IngredientSimpleResponseDto;
|
||||
@@ -224,4 +225,13 @@ public class CocktailMapper {
|
||||
}
|
||||
|
||||
|
||||
public CocktailForIngredientModalDto cocktailToIngredientDtoList(CocktailEntity e) {
|
||||
Visitor visitor = getCurrentVisitor();
|
||||
return CocktailForIngredientModalDto.builder()
|
||||
.id(e.getId())
|
||||
.name(e.getName())
|
||||
.image(e.getImage())
|
||||
.rating(createRatingDto(e.getRating(), visitor))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import ru.kayashov.bar.controller.IngredientController;
|
||||
import ru.kayashov.bar.controller.dto.cocktail.CocktailFilterRequestDto;
|
||||
import ru.kayashov.bar.controller.dto.cocktail.CocktailForIngredientModalDto;
|
||||
import ru.kayashov.bar.controller.dto.cocktail.CocktailForListResponseDto;
|
||||
import ru.kayashov.bar.controller.dto.cocktail.CocktailModalDto;
|
||||
import ru.kayashov.bar.controller.dto.cocktail.CocktailSimpleResponseDto;
|
||||
@@ -473,4 +475,19 @@ public class CocktailService {
|
||||
log.info("Удален коктейль {}", cocktail);
|
||||
repository.delete(cocktail);
|
||||
}
|
||||
|
||||
public String findInstructions(Long id) {
|
||||
CocktailEntity cocktail = repository.findById(id).orElseThrow(RuntimeException::new);
|
||||
return cocktail.getInstructions();
|
||||
}
|
||||
|
||||
public List<CocktailForIngredientModalDto> findByIngredient(Long id) {
|
||||
return ingredientRepository.findById(id).orElseThrow(RuntimeException::new)
|
||||
.getReceipts()
|
||||
.stream()
|
||||
.map(ReceiptEntity::getCocktail)
|
||||
.distinct()
|
||||
.map(mapper::cocktailToIngredientDtoList)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
spring.datasource.url=${DB_URL:jdbc:postgresql://192.168.1.100:5432/drinks}
|
||||
spring.datasource.username=${DB_NAME:nextcloud}
|
||||
spring.datasource.password=${DB_PASSWORD:kayash73}
|
||||
spring.datasource.hikari.minimum-idle=1
|
||||
spring.datasource.hikari.minimum-idle=10
|
||||
spring.datasource.hikari.maximum-pool-size=50
|
||||
spring.datasource.hikari.idle-timeout=30000
|
||||
spring.datasource.hikari.max-lifetime=600000
|
||||
|
||||
|
||||
Reference in New Issue
Block a user