#3 - исправление NPE для загрузки фото

This commit is contained in:
Administrator
2025-04-27 10:04:48 +00:00
parent 8843b6d6c8
commit 2c9beac0ca
22 changed files with 304 additions and 158 deletions

View File

@@ -7,7 +7,7 @@ stages:
- deploy front - deploy front
- deploy back - deploy back
stop-job: deploy-back-job:
stage: deploy back stage: deploy back
only: only:
- back_release - back_release
@@ -18,5 +18,17 @@ stop-job:
- echo "Сборка образа докер" - echo "Сборка образа докер"
- docker build -t my-bar . - docker build -t my-bar .
- echo "Запуск докер контейнера" - 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 "Деплой завершен" - echo "Деплой завершен"

View File

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

View File

@@ -7,6 +7,7 @@ import {BrowserRouter as Router} from "react-router-dom";
import {NavigationRoutes} from "./NavigationRoutes"; import {NavigationRoutes} from "./NavigationRoutes";
import {SnackbarProvider} from 'notistack'; import {SnackbarProvider} from 'notistack';
import {UserProvider} from "../context/UserContext"; import {UserProvider} from "../context/UserContext";
import {SelectProvider} from "../context/SelectContext";
function App() { function App() {
const theme = createTTheme(); const theme = createTTheme();
@@ -36,10 +37,13 @@ function App() {
}, },
}} }}
/> />
{/*Провайдер выбора*/}
<SelectProvider>
{/*Маршрутизация*/} {/*Маршрутизация*/}
<Router> <Router>
<NavigationRoutes/> <NavigationRoutes/>
</Router> </Router>
</SelectProvider>
</CssVarsProvider> </CssVarsProvider>
</UserProvider> </UserProvider>
</AuthProvider> </AuthProvider>

View File

@@ -16,6 +16,7 @@ import {requests} from "../../../requests";
import {api} from "../../../lib/clients/api"; import {api} from "../../../lib/clients/api";
import {useAlert} from "../../../hooks/useAlert"; import {useAlert} from "../../../hooks/useAlert";
import {CocktailInfoModal} from "../../../components/cocktails/CocktailInfoModal"; import {CocktailInfoModal} from "../../../components/cocktails/CocktailInfoModal";
import {useSelect} from "../../../hooks/useSelect";
export function CocktailMenuBarPage() { export function CocktailMenuBarPage() {
const {createError} = useAlert(); const {createError} = useAlert();
@@ -23,8 +24,7 @@ export function CocktailMenuBarPage() {
const [findString, setFindString] = useState(""); const [findString, setFindString] = useState("");
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [cocktails, setCocktails] = useState([]); const [cocktails, setCocktails] = useState([]);
const [openModal, setOpenModal] = useState(false); const {setCocktail, state} = useSelect();
const [selected, setSelected] = useState(null);
useEffect(() => { useEffect(() => {
api().get(`${requests.cocktails.menu}?all=true`) api().get(`${requests.cocktails.menu}?all=true`)
@@ -38,12 +38,11 @@ export function CocktailMenuBarPage() {
const handleOpenModal = (row) => { const handleOpenModal = (row) => {
setSelected(row) setCocktail(row);
setOpenModal(true);
} }
const changeHandler = (row, value) => { const changeHandler = (row, value) => {
const newState = cocktails.map((r) => { const newState = cocktails.map((r) => {
if(r.id !== row.id) { if (r.id !== row.id) {
return r; return r;
} }
return { return {
@@ -117,11 +116,7 @@ export function CocktailMenuBarPage() {
{/*Загрузчик*/} {/*Загрузчик*/}
<Loading loading={loading}/> <Loading loading={loading}/>
{/*Модальное окно информации об ингредиенте*/} {/*Модальное окно информации об ингредиенте*/}
<CocktailInfoModal open={openModal} row={selected} <CocktailInfoModal row={state.cocktail}/>
closeHandler={() => {
setSelected(null);
setOpenModal(false);
}}/>
</Box> </Box>
) )
} }

View File

@@ -14,57 +14,8 @@ import {useUser} from "../../../hooks/useUser";
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 {sortList} from "../../../components/cocktails/sortingList"; import {sortList} from "../../../components/cocktails/sortingList";
import {getComparator} from "../../../components/core/getComparator";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Paper from "@mui/material/Paper"; import {useSelect} from "../../../hooks/useSelect";
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"))
}
const emptyFilter = { const emptyFilter = {
search: "", search: "",
@@ -86,14 +37,14 @@ const CocktailsPageContent = ({all}) => {
const [allowIngredients, setAllowIngredients] = useState([]) const [allowIngredients, setAllowIngredients] = useState([])
const [rows, setRows] = useState([]); const [rows, setRows] = useState([]);
const [filter, setFilter] = useState(emptyFilter) const [filter, setFilter] = useState(emptyFilter)
const [open, setOpen] = useState(false); // const [chips, setChips] = useState([])
const [selectedCocktail, setSelectedCocktail] = useState(null)
const [chips, setChips] = useState([])
const [page, setPage] = useState(-1); const [page, setPage] = useState(-1);
const [load, setLoad] = useState(false); const [load, setLoad] = useState(false);
const [isEnd, setIsEnd] = useState(false); const [isEnd, setIsEnd] = useState(false);
const [isNew, setIsNew] = useState(true); const [isNew, setIsNew] = useState(true);
const {selectCocktail, getCocktail, getOpenCocktail} = useSelect();
const loading = useCallback(() => { const loading = useCallback(() => {
const size = Math.floor((window.innerWidth) / 350) * 5; const size = Math.floor((window.innerWidth) / 350) * 5;
if (load || (!isNew && isEnd)) { if (load || (!isNew && isEnd)) {
@@ -111,7 +62,7 @@ const CocktailsPageContent = ({all}) => {
api().post(requests.cocktails.menu, request) api().post(requests.cocktails.menu, request)
.then((r) => { .then((r) => {
if (r.data.length === 0) { if (r.data.length === 0) {
if(isNew) { if (isNew) {
setRows([]); setRows([]);
} }
setIsEnd(true); setIsEnd(true);
@@ -139,6 +90,7 @@ const CocktailsPageContent = ({all}) => {
} }
window.addEventListener('scroll', handleScroll); window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll);
// eslint-disable-next-line
}, [loading]); }, [loading]);
useEffect(() => { useEffect(() => {
api().get(requests.bar.ingredientSimple) api().get(requests.bar.ingredientSimple)
@@ -152,6 +104,7 @@ const CocktailsPageContent = ({all}) => {
}, []) }, [])
useEffect(() => { useEffect(() => {
loading(); loading();
// eslint-disable-next-line
}, [filter]) }, [filter])
useEffect(() => { useEffect(() => {
if (!all) { if (!all) {
@@ -164,7 +117,8 @@ const CocktailsPageContent = ({all}) => {
.filter((nhc) => nhc.length === 1) .filter((nhc) => nhc.length === 1)
.map((fc) => fc[0]) .map((fc) => fc[0])
.forEach((i) => ingredients.add(i)) .forEach((i) => ingredients.add(i))
setChips(Array.from(ingredients).sort(getComparator())); // setChips(Array.from(ingredients).sort(getComparator()));
// eslint-disable-next-line
}, [rows, allowIngredients]) }, [rows, allowIngredients])
const renderSkeleton = () => { const renderSkeleton = () => {
@@ -230,12 +184,9 @@ const CocktailsPageContent = ({all}) => {
setFilter(emptyFilter); setFilter(emptyFilter);
} }
const handleSelectCocktail = (row) => { const handleSelectCocktail = (row) => {
setSelectedCocktail(row.id) selectCocktail(row.id)
setOpen(true) // setSelectedCocktail(row.id)
} // setOpen(true)
const handleCloseCocktailModal = () => {
setOpen(false);
setSelectedCocktail(null);
} }
const handleEditMenu = (row, value) => { const handleEditMenu = (row, value) => {
const newState = rows.map((r) => { const newState = rows.map((r) => {
@@ -271,8 +222,7 @@ const CocktailsPageContent = ({all}) => {
<Box> <Box>
{/*<Loading loading={load}/>*/} {/*<Loading loading={load}/>*/}
{/*Модальное окно информации о коктейле*/} {/*Модальное окно информации о коктейле*/}
<CocktailInfoModal row={selectedCocktail} open={open} <CocktailInfoModal row={getCocktail()} open={getOpenCocktail()}/>
closeHandler={handleCloseCocktailModal}/>
{/*Блок фильтров*/} {/*Блок фильтров*/}
<FilterBlock <FilterBlock
filter={filter} filter={filter}

View File

@@ -19,6 +19,7 @@ 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 Switch from "@mui/material/Switch";
import {useSelect} from "../../../hooks/useSelect";
export function IngredientsPage() { export function IngredientsPage() {
const [value, setValue] = React.useState(0); const [value, setValue] = React.useState(0);
@@ -27,8 +28,7 @@ export function IngredientsPage() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [findString, setFindString] = useState(""); const [findString, setFindString] = useState("");
const [ingredients, setIngredients] = useState([]); const [ingredients, setIngredients] = useState([]);
const [openModal, setOpenModal] = useState(false); const {getIngredient, selectIngredient} = useSelect();
const [selectedInfo, setSelectedInfo] = useState(null);
const {createError} = useAlert(); const {createError} = useAlert();
useEffect(() => { useEffect(() => {
@@ -76,12 +76,7 @@ export function IngredientsPage() {
}); });
} }
const handleOpenModal = (i) => { const handleOpenModal = (i) => {
setOpenModal(true); selectIngredient(i)
setSelectedInfo(i);
}
const handleCloseModal = () => {
setSelectedInfo(null);
setOpenModal(false);
} }
return ( return (
@@ -147,7 +142,7 @@ export function IngredientsPage() {
{/*Загрузчик*/} {/*Загрузчик*/}
<Loading loading={loading}/> <Loading loading={loading}/>
{/*Модальное окно информации об ингредиенте*/} {/*Модальное окно информации об ингредиенте*/}
<IngredientInfoModal ingredient={selectedInfo} open={openModal} closeHandler={handleCloseModal}/> <IngredientInfoModal ingredient={getIngredient()}/>
</Box> </Box>
) )
} }

View File

@@ -6,13 +6,34 @@ import Button from "@mui/material/Button";
import * as React from "react"; import * as React from "react";
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography"; 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) { if (!ingredient) {
return null; return null;
} }
return ( return (
<Dialog fullWidth={true} maxWidth="350px" open={open} onClose={closeHandler} <Dialog fullWidth={true} maxWidth="350px" open={getOpenIngredient()} onClose={closeIngredient}
sx={{ sx={{
'& .MuiDialog-paper': { '& .MuiDialog-paper': {
margin: '8px', margin: '8px',
@@ -28,9 +49,30 @@ export function IngredientInfoModal({ingredient, open, closeHandler}) {
{ingredient.alcohol && (<Typography>{`Крепость ${ingredient.abv}`}</Typography>)} {ingredient.alcohol && (<Typography>{`Крепость ${ingredient.abv}`}</Typography>)}
<Typography>{ingredient.description}</Typography> <Typography>{ingredient.description}</Typography>
</Stack> </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> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={closeHandler}>Close</Button> <Button onClick={closeIngredient}>Close</Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
); );

View File

@@ -1,9 +1,6 @@
import {CardActions, CardContent, CardMedia, Rating} from "@mui/material"; import {CardActions, CardContent, CardMedia, Rating} from "@mui/material";
import {useAlert} from "../../hooks/useAlert"; import {useAlert} from "../../hooks/useAlert";
import Typography from "@mui/material/Typography"; 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 Button from "@mui/material/Button";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import {requests} from "../../requests"; import {requests} from "../../requests";
@@ -14,6 +11,7 @@ import FavoriteIcon from '@mui/icons-material/Favorite';
import {api} from "../../lib/clients/api"; import {api} from "../../lib/clients/api";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import {useUser} from "../../hooks/useUser"; import {useUser} from "../../hooks/useUser";
import {CocktailDescription} from "./CocktailDescription";
function renderFavouriteBadge(handleFavourite, row) { function renderFavouriteBadge(handleFavourite, row) {
const childIcon = row.rating.favourite ? <FavoriteIcon color='error'/> : <FavoriteBorderIcon color={'warning'}/>; 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} alt={row.name}
height="300" height="300"
image={`${row.image}/preview`} image={row.image.includes("thecocktaildb") ? (row.image + "/preview") : row.image}
/> />
{renderFavouriteBadge(handleFavourite, row)} {renderFavouriteBadge(handleFavourite, row)}
{renderRating(handleChangeRating, row)} {renderRating(handleChangeRating, row)}
<CardContent sx={{pb: '4px', pl: 2}}> <CardContent sx={{pb: '4px', pl: 2}}>
<Typography variant="h5" minHeight={'50px'} mt={2}>{row.name} </Typography> <Typography variant="h5" minHeight={'50px'} mt={2}>{row.name} </Typography>
<List sx={{py: '0px'}}> <CocktailDescription row={row}/>
{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> </CardContent>
<CardActions> <CardActions>
{(row.isAllowed && session.isActive && user.invited) && {(row.isAllowed && session.isActive && user.invited) &&

View 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>
)
}

View File

@@ -21,24 +21,20 @@ import {useAlert} from "../../hooks/useAlert";
import {paths} from "../../path"; import {paths} from "../../path";
import {Loading} from "../core/Loading"; import {Loading} from "../core/Loading";
import {useUser} from "../../hooks/useUser"; import {useUser} from "../../hooks/useUser";
import {useSelect} from "../../hooks/useSelect";
export function CocktailInfoModal({open, row, closeHandler}) { export function CocktailInfoModal({row}) {
const {user} = useUser(); const {user} = useUser();
const {getError, createError, createSuccess} = useAlert(); const {getError, createError, createSuccess} = useAlert();
const [cocktail, setCocktail] = useState(null) const [cocktail, setCocktail] = useState(null)
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [selectedIngredient, setSelectedIngredient] = useState(null); const {closeCocktail, selectIngredient, getIngredient, getOpenCocktail} = useSelect();
const [openIngredientModal, setOpenIngredientModal] = useState(false)
const closeIngredientHandler = () => {
setOpenIngredientModal(false);
setSelectedIngredient(null);
}
const openIngredientModalHandler = (id) => { const openIngredientModalHandler = (id) => {
api().get(`${requests.bar.ingredient}?id=${id}`) api().get(`${requests.bar.ingredient}?id=${id}`)
.then((r) => { .then((r) => {
setSelectedIngredient(r.data) selectIngredient(r.data)
setOpenIngredientModal(true); })
}).catch(() => createError("Ошибка получения информации об ингредиенте")) .catch(() => createError("Ошибка получения информации об ингредиенте"))
} }
const selectIngredientHandler = (ingredient) => { const selectIngredientHandler = (ingredient) => {
const url = `${requests.bar.ingredient}?id=${ingredient.id}`; const url = `${requests.bar.ingredient}?id=${ingredient.id}`;
@@ -79,18 +75,17 @@ export function CocktailInfoModal({open, row, closeHandler}) {
.catch(() => { .catch(() => {
getError(); getError();
setLoading(false) setLoading(false)
closeHandler(); closeCocktail();
}) })
// eslint-disable-next-line
}, [row]); }, [row]);
if (!row || !cocktail) { if (!row || !cocktail) {
return null; return null;
} }
let alko = 0;
let volume = 0;
return ( return (
<Dialog fullWidth={true} <Dialog fullWidth={true}
open={open} onClose={closeHandler} open={getOpenCocktail()} onClose={closeCocktail}
sx={{ sx={{
'& .MuiDialog-paper': { '& .MuiDialog-paper': {
margin: '8px', margin: '8px',
@@ -99,8 +94,7 @@ export function CocktailInfoModal({open, row, closeHandler}) {
width: 'calc(100% - 16px)', width: 'calc(100% - 16px)',
} }
}}> }}>
<IngredientInfoModal ingredient={selectedIngredient} open={openIngredientModal} <IngredientInfoModal ingredient={getIngredient()}/>
closeHandler={closeIngredientHandler}/>
<Loading loading={loading}/> <Loading loading={loading}/>
<DialogTitle> <DialogTitle>
<Stack direction='row' justifyContent={'space-between'}> <Stack direction='row' justifyContent={'space-between'}>
@@ -118,7 +112,7 @@ export function CocktailInfoModal({open, row, closeHandler}) {
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<CardMedia <CardMedia
image={`${cocktail.image}/preview`} image={cocktail.image.includes("thecocktaildb") ? (cocktail.image + "/preview") : cocktail.image}
sx={{ sx={{
loading: "eager", loading: "eager",
borderRadius: 2 borderRadius: 2
@@ -134,9 +128,6 @@ export function CocktailInfoModal({open, row, closeHandler}) {
{cocktail.receipt.map((r) => { {cocktail.receipt.map((r) => {
const hasError = r.count === null || r.unit === null; const hasError = r.count === null || r.unit === null;
const measure = hasError ? r.measure : (r.count + " " + r.unit.name) const measure = hasError ? r.measure : (r.count + " " + r.unit.name)
if(alko !== null && volume !== null) {
console.log(r)
}
return ( return (
<Stack key={r.ingredient.id} direction='row' justifyContent={'space-between'} <Stack key={r.ingredient.id} direction='row' justifyContent={'space-between'}
mt={1}> mt={1}>
@@ -173,7 +164,7 @@ export function CocktailInfoModal({open, row, closeHandler}) {
{user.role.includes("ADMIN") && ( {user.role.includes("ADMIN") && (
<Button href={`${paths.bar.cocktailEdit}?id=${cocktail.id}`}>Редактировать</Button> <Button href={`${paths.bar.cocktailEdit}?id=${cocktail.id}`}>Редактировать</Button>
)} )}
<Button onClick={closeHandler}>Закрыть</Button> <Button onClick={closeCocktail}>Закрыть</Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
) )

View File

@@ -37,6 +37,7 @@ function renderButtons(row, my, handleChange) {
export default function OrderModal({row, handleClose, open, handleChange, my}) { export default function OrderModal({row, handleClose, open, handleChange, my}) {
const [receipt, setReceipt] = useState([]); const [receipt, setReceipt] = useState([]);
const [instructions, setInstructions] = useState();
const {createError} = useAlert(); const {createError} = useAlert();
useEffect(() => { useEffect(() => {
@@ -46,6 +47,10 @@ export default function OrderModal({row, handleClose, open, handleChange, my}) {
api().get(requests.bar.receipts + row.cocktail.id) api().get(requests.bar.receipts + row.cocktail.id)
.then((r) => setReceipt(r.data)) .then((r) => setReceipt(r.data))
.catch(() => createError("Ошибка получения рецепта")) .catch(() => createError("Ошибка получения рецепта"))
api().get(requests.cocktails.instructions + row.cocktail.id)
.then((r) => setInstructions(r.data))
.catch(() => createError("Ошибка получения инструкции"))
// eslint-disable-next-line // eslint-disable-next-line
}, [row]); }, [row]);
@@ -62,7 +67,6 @@ export default function OrderModal({row, handleClose, open, handleChange, my}) {
<DialogTitle>{"Заказ №" + row.id}</DialogTitle> <DialogTitle>{"Заказ №" + row.id}</DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText>{row.cocktail.name}</DialogContentText> <DialogContentText>{row.cocktail.name}</DialogContentText>
<DialogContentText>{row.cocktail.alcoholic + " " + row.cocktail.category}</DialogContentText>
<DialogContentText>{"для: " + row.visitor.name + " " + row.visitor.lastName}</DialogContentText> <DialogContentText>{"для: " + row.visitor.name + " " + row.visitor.lastName}</DialogContentText>
<Box noValidate component="form" <Box noValidate component="form"
sx={{display: 'flex', flexDirection: 'column', m: 'auto', width: 'fit-content',}}> 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>) return (<Typography key={r.id}>{`${r.ingredient.name} - ${r.measure}`}</Typography>)
})} })}
</Stack> </Stack>
{instructions &&
(
<>
<Typography>Инструкция:</Typography> <Typography>Инструкция:</Typography>
<Typography pl={1}>{row.cocktail.instructions}</Typography> <Typography pl={1}>{instructions}</Typography>
</>
)
}
{row.cocktail.video && (<iframe width="350" /*height="315"*/ {row.cocktail.video && (<iframe width="350" /*height="315"*/
src={row.cocktail.video} src={row.cocktail.video}

View 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;

View 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;
}

View File

@@ -8,7 +8,7 @@ export const navItems = [
{key: 'queue', title: 'Очередь заказов', href: paths.bar.ordersQueue, icon: 'orders', forBarmen: true}, {key: 'queue', title: 'Очередь заказов', href: paths.bar.ordersQueue, icon: 'orders', forBarmen: true},
{key: 'ingredients', title: 'Ингредиенты в баре', href: paths.bar.ingredients, icon: 'basket', 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: '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: 'ingredientEdit', title: 'Ингредиенты', href: paths.bar.ingredientEdit, icon: 'ingredients', forAdmin: true},
{key: 'cocktailEdit', title: 'Коктейли', href: paths.bar.cocktailEdit, icon: 'cocktail', forAdmin: true} {key: 'cocktailEdit', title: 'Коктейли', href: paths.bar.cocktailEdit, icon: 'cocktail', forAdmin: true}
]; ];

View File

@@ -27,7 +27,9 @@ export const requests = {
savePhoto: routes.cocktails + "/photo", savePhoto: routes.cocktails + "/photo",
favourite: routes.cocktails + "/favourite?id=", favourite: routes.cocktails + "/favourite?id=",
rating: routes.cocktails + "/rating?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: { visitors: {
all: routes.visitor, all: routes.visitor,

View File

@@ -44,6 +44,7 @@ public class AuthController {
Visitor visitor = visitorsRepository.findById(Long.valueOf(decodeArr[0])) Visitor visitor = visitorsRepository.findById(Long.valueOf(decodeArr[0]))
.orElseThrow(); .orElseThrow();
log.info("Попытка авторизации пользователя {}", visitor.getId());
Integer visitorCode = visitor.getCode(); Integer visitorCode = visitor.getCode();
if (visitorCode == null) { if (visitorCode == null) {
return new AuthResponseDto(null, "Повторите запрос кода из бота"); return new AuthResponseDto(null, "Повторите запрос кода из бота");
@@ -66,6 +67,7 @@ public class AuthController {
return new AuthResponseDto(null, "Не найдет пользователь " + login); return new AuthResponseDto(null, "Не найдет пользователь " + login);
} }
Visitor visitor = visitorOpt.get(); Visitor visitor = visitorOpt.get();
log.info("Попытка авторизации пользователя {}", visitor.getId());
if (passwordEncoder.matches(password, visitor.getPassword())) { if (passwordEncoder.matches(password, visitor.getPassword())) {
return new AuthResponseDto(jwtTokenProvider.generateToken(visitor), null); return new AuthResponseDto(jwtTokenProvider.generateToken(visitor), null);
} else { } else {
@@ -77,6 +79,7 @@ public class AuthController {
public AuthResponseDto refreshToken(@RequestHeader("Authorization") String token) { public AuthResponseDto refreshToken(@RequestHeader("Authorization") String token) {
Claims claims = jwtTokenProvider.extractAllClaims(token); Claims claims = jwtTokenProvider.extractAllClaims(token);
Long visitorId = claims.get("id", Long.class); Long visitorId = claims.get("id", Long.class);
log.info("обновление токена для пользователя {}", visitorId);
Visitor visitor = visitorsRepository.findById(visitorId).orElseThrow(); Visitor visitor = visitorsRepository.findById(visitorId).orElseThrow();
return new AuthResponseDto(jwtTokenProvider.generateToken(visitor), null); return new AuthResponseDto(jwtTokenProvider.generateToken(visitor), null);
} }

View File

@@ -2,6 +2,7 @@ package ru.kayashov.bar.controller;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.hibernate.Session;
import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.CrossOrigin;
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;
@@ -10,14 +11,13 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; 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.BarResponseDto;
import ru.kayashov.bar.controller.dto.bar.CategoryResponseDto; import ru.kayashov.bar.controller.dto.bar.CategoryResponseDto;
import ru.kayashov.bar.controller.dto.bar.GlassResponseDto; 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.bar.TagResponseDto;
import ru.kayashov.bar.controller.dto.VisitorResponseDto; import ru.kayashov.bar.controller.dto.cocktail.ReceiptResponseDto;
import ru.kayashov.bar.model.entity.BarEntity;
import ru.kayashov.bar.model.entity.BarResident; import ru.kayashov.bar.model.entity.BarResident;
import ru.kayashov.bar.model.entity.SessionEntity; import ru.kayashov.bar.model.entity.SessionEntity;
import ru.kayashov.bar.model.entity.Unit; import ru.kayashov.bar.model.entity.Unit;
@@ -105,12 +105,14 @@ public class BarController {
@PostMapping("session") @PostMapping("session")
public void changeSessionStatus(@RequestParam Boolean value) { public void changeSessionStatus(@RequestParam Boolean value) {
if (value) { if (value) {
sessionService.createEmptySession(); SessionEntity entity = sessionService.createEmptySession();
log.info("Открыта смена {}", entity.getId());
return; return;
} }
SessionEntity session = sessionService.findActiveSession(); SessionEntity session = sessionService.findActiveSession();
session.setIsActive(false); session.setIsActive(false);
sessionRepository.save(session); sessionRepository.save(session);
log.info("Закрыта смена {}", session.getId());
} }
@GetMapping("/getMe") @GetMapping("/getMe")

View File

@@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import ru.kayashov.bar.controller.dto.cocktail.CocktailFilterRequestDto; 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.CocktailForListResponseDto;
import ru.kayashov.bar.controller.dto.ErrorDto; import ru.kayashov.bar.controller.dto.ErrorDto;
import ru.kayashov.bar.controller.dto.cocktail.CocktailModalDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailModalDto;
@@ -43,6 +44,16 @@ public class CocktailController {
return cocktailService.getMenu(dto); 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") @PostMapping("/photo")
public String savePhoto(@RequestBody MultipartFile file) throws IOException { public String savePhoto(@RequestBody MultipartFile file) throws IOException {
if(file == null) { if(file == null) {

View File

@@ -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;
}

View File

@@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; 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.CocktailModalDto;
import ru.kayashov.bar.controller.dto.cocktail.CocktailForListResponseDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailForListResponseDto;
import ru.kayashov.bar.controller.dto.ingredient.IngredientSimpleResponseDto; 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();
}
} }

View File

@@ -9,7 +9,9 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile; 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.CocktailFilterRequestDto;
import ru.kayashov.bar.controller.dto.cocktail.CocktailForIngredientModalDto;
import ru.kayashov.bar.controller.dto.cocktail.CocktailForListResponseDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailForListResponseDto;
import ru.kayashov.bar.controller.dto.cocktail.CocktailModalDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailModalDto;
import ru.kayashov.bar.controller.dto.cocktail.CocktailSimpleResponseDto; import ru.kayashov.bar.controller.dto.cocktail.CocktailSimpleResponseDto;
@@ -161,7 +163,7 @@ public class CocktailService {
} }
} }
if(dto.getICount() != null) { if (dto.getICount() != null) {
log.info("iii"); log.info("iii");
} }
// //
@@ -473,4 +475,19 @@ public class CocktailService {
log.info("Удален коктейль {}", cocktail); log.info("Удален коктейль {}", cocktail);
repository.delete(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();
}
} }

View File

@@ -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.url=${DB_URL:jdbc:postgresql://192.168.1.100:5432/drinks}
spring.datasource.username=${DB_NAME:nextcloud} spring.datasource.username=${DB_NAME:nextcloud}
spring.datasource.password=${DB_PASSWORD:kayash73} 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.idle-timeout=30000
spring.datasource.hikari.max-lifetime=600000 spring.datasource.hikari.max-lifetime=600000