Initial commit

This commit is contained in:
Kayashov.SM
2025-03-12 17:54:16 +04:00
commit b6d8a3cebd
254 changed files with 29963 additions and 0 deletions

51
front/src/app/App.js Normal file
View File

@@ -0,0 +1,51 @@
import {CssBaseline, GlobalStyles} from "@mui/material";
import {LocalizationProvider} from "../components/core/LocalizationProvider";
import {AuthProvider} from "../context/AuthContext";
import {createTTheme} from "../styles/theme/create-theme";
import {Experimental_CssVarsProvider as CssVarsProvider} from '@mui/material/styles';
import {BrowserRouter as Router} from "react-router-dom";
import {NavigationRoutes} from "./NavigationRoutes";
import {SnackbarProvider} from 'notistack';
import {UserProvider} from "../context/UserContext";
function App() {
const theme = createTTheme();
return (
// Провайдер времени
<LocalizationProvider>
{/*Провайдер уведомлений*/}
<SnackbarProvider maxSnack={6} anchorOrigin={{vertical: 'bottom', horizontal: 'right'}}
style={{borderRadius: '10px'}}>
{/*Провайдер авторизации*/}
<AuthProvider>
{/*Провайдер пользователя*/}
<UserProvider>
{/*Провайдер темы*/}
<CssVarsProvider theme={theme}>
<CssBaseline/>
<GlobalStyles
styles={{
body: {
'--MainNav-height': '56px',
'--MainNav-zIndex': 1000,
'--SideNav-width': '280px',
'--SideNav-zIndex': 1200,
'--MobileNav-width': '320px',
'--MobileNav-zIndex': 1200,
},
}}
/>
{/*Маршрутизация*/}
<Router>
<NavigationRoutes/>
</Router>
</CssVarsProvider>
</UserProvider>
</AuthProvider>
</SnackbarProvider>
</LocalizationProvider>
);
}
export default App;

View File

@@ -0,0 +1,10 @@
import {paths} from "../path";
import {Loading} from "../components/core/Loading";
export function HomeRedirect({auth}) {
const redirectPath = auth ? paths.dashboard.overview : paths.auth.signIn;
window.location.replace(redirectPath);
return (
<Loading loading={true}/>
)
}

View File

@@ -0,0 +1,151 @@
import {Route, Routes} from "react-router-dom";
import {paths} from "../path";
import {useAuth} from "../hooks/useAuth";
import NotFoundPage from "./pages/notFound/NotFoundPage";
import {UserLayout} from "./layout/UserLayout";
import {HomeRedirect} from "./HomeRedirect";
import {PublicLayout} from "./layout/PublicLayout";
import QueuePage from "./pages/queue/QueuePage";
import LoginPage from "./pages/auth/sign-in/loginPage";
import {TelegramCode} from "./pages/auth/sign-in/telegram-code";
import {IngredientsPage} from "./pages/ingredients/IngredientsPage";
import {MenuPage} from "./pages/cocktails/MenuPage";
import {AllCocktailsPage} from "./pages/cocktails/AllCocktailsPage";
import {EditIngredientPage} from "./pages/ingredients/EditIngredientPage";
import {EditCocktailPage} from "./pages/cocktails/EditCocktailPage";
import {MyQueuePage} from "./pages/queue/MyQueuePage";
import {VisitorPage} from "./pages/VisitorPage";
import {CocktailMenuBarPage} from "./pages/cocktails/CocktailMenuBarPage";
import {MyBarPage} from "./pages/MyBarPage";
import {useEffect, useState} from "react";
export function NavigationRoutes() {
const {auth} = useAuth();
const [loadedRoutes, setLoadedRoutes] = useState(undefined);
useEffect(() => {
setLoadedRoutes(auth ? authPages : guestPages)
}, [auth]);
if (!loadedRoutes) {
return null
}
return (
<Routes>
{loadedRoutes.map((page) => {
return (
<Route
key={page.path + page.isPrivate + page.exact}
path={page.path}
exact={page.exact}
element={<ElementProvider isPrivate={page.isPrivate}>
{page.children}
</ElementProvider>}/>
)
})}
</Routes>
)
}
function ElementProvider({isPrivate, children}) {
if (isPrivate) {
return (<UserLayout>{children}</UserLayout>);
} else {
return (<PublicLayout>{children}</PublicLayout>);
}
}
const authPages = [
{
children: (<HomeRedirect auth={true}/>),
isPrivate: false,
path: paths.home,
},
{
path: paths.auth.signIn,
children: (<LoginPage/>),
isPrivate: false,
},
{
path: paths.dashboard.overview,
isPrivate: true,
children: (<MenuPage/>),
exact: true,
},
{
path: paths.bar.cocktails,
isPrivate: true,
children: (<AllCocktailsPage/>)
},
{
path: paths.bar.list,
isPrivate: true,
children: (<MyBarPage/>)
},
{
path: paths.orders.my,
isPrivate: true,
children: (<MyQueuePage/>)
},
{
path: paths.bar.ingredients,
isPrivate: true,
children: (<IngredientsPage/>)
},
{
path: paths.bar.ordersQueue,
isPrivate: true,
children: (<QueuePage/>),
},
{
path: paths.visitor.inBar,
isPrivate: true,
children: (<VisitorPage/>)
},
{
path: paths.bar.ingredientEdit,
isPrivate: true,
forAdmin: true,
children: (<EditIngredientPage/>)
},
{
path: paths.bar.menu,
isPrivate: true,
children: (<CocktailMenuBarPage/>)
},
{
path: paths.bar.cocktailEdit,
isPrivate: true,
forAdmin: true,
children: (<EditCocktailPage/>)
},
{
path: paths.notFound,
isPrivate: false,
children: (<NotFoundPage/>)
},
]
const guestPages = [
{
path: paths.home,
isPrivate: false,
children: (<HomeRedirect auth={false}/>),
exact: true,
},
{
path: paths.auth.tg,
isPrivate: false,
children: (<TelegramCode/>),
exact: false
},
{
path: paths.auth.signIn,
isPrivate:
false,
children: (<LoginPage/>),
},
{
path: paths.notFound,
isPrivate: false,
children: (<NotFoundPage/>),
},
]

View File

@@ -0,0 +1,58 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import {DynamicLogo} from "../../components/core/Logo";
import {paths} from "../../path";
export function PublicLayout({ children }) {
return (
<Box
sx={{
display: { xs: 'flex', lg: 'grid' },
flexDirection: 'column',
gridTemplateColumns: '1fr 1fr',
}}
>
<Box sx={{ display: 'flex', flex: '1 1 auto', flexDirection: 'column' }}>
<Box sx={{ p: 3 }}>
<Box component={'a'} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
</Box>
</Box>
<Box sx={{ alignItems: 'center', display: 'flex', flex: '1 1 auto', justifyContent: 'center', p: 3 }}>
<Box sx={{ maxWidth: '450px', width: '100%' }}>{children}</Box>
</Box>
</Box>
<Box
sx={{
alignItems: 'center',
background: 'radial-gradient(50% 50% at 50% 50%, #122647 0%, #090E23 100%)',
color: 'var(--mui-palette-common-white)',
display: { xs: 'none', lg: 'flex' },
justifyContent: 'center',
p: 3,
}}
>
<Stack spacing={3}>
<Stack spacing={1}>
<Typography color="inherit" sx={{ fontSize: '24px', lineHeight: '32px', textAlign: 'center' }} variant="h1">
<Box component="span" sx={{ color: '#15b79e' }}>
Добро пожаловать в бар
</Box>
</Typography>
<Typography align="center" variant="subtitle1">
Самый большой выбор честно спизженных коктейлей
</Typography>
<Box
component="img"
alt="Under development"
src="/assets/qr.png"
sx={{ display: 'inline-block', height: 'auto', maxWidth: '100%', width: '400px' }}
/>
</Stack>
</Stack>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,31 @@
import {SideNav} from "../../components/navigation/SideNav";
import Box from "@mui/material/Box";
import {MainNav} from "../../components/navigation/MainNav";
import Container from "@mui/material/Container";
export function UserLayout({children}) {
return (
<Box
sx={{
bgcolor: 'var(--mui-palette-background-default)',
display: 'flex',
flexDirection: 'column',
position: 'relative',
minHeight: '100%',
}}
>
<SideNav/>
<Box sx={{
display: 'flex',
flex: '1 1 auto',
flexDirection: 'column',
pl: {xl: 'var(--SideNav-width)'}
}}>
<MainNav/>
<Container maxWidth="xl" sx={{py: '16px'}}>
{children}
</Container>
</Box>
</Box>
)
}

View File

@@ -0,0 +1,62 @@
import Box from "@mui/material/Box";
import Toolbar from "@mui/material/Toolbar";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
import {FormControl, InputAdornment, InputLabel, OutlinedInput, Tabs} from "@mui/material";
import IconButton from "@mui/material/IconButton";
import SearchIcon from "@mui/icons-material/Search";
import * as React from "react";
import {useState} from "react";
import Tab from "@mui/material/Tab";
import {a11yProps} from "../../components/core/tabProps";
import {CustomTabPanel} from "../../components/core/TabPanel";
import {BarList} from "../../components/bar/BarList";
export function MyBarPage() {
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => setValue(newValue);
const [findString, setFindString] = useState("");
return (
<Box>
{/*Заголовок*/}
<Toolbar>
<Typography variant="h6" component="div" sx={{flexGrow: 1}}>Мои бары</Typography>
</Toolbar>
{/*Поиск*/}
<Paper elevation={6} sx={{my: 2}}>
<FormControl sx={{m: 1, width: 'calc(100% - 20px'}}>
<InputLabel htmlFor="outlined-adornment-amount">Поиск</InputLabel>
<OutlinedInput
onChange={(e) => setFindString(e.target.value)}
label="With normal TextField"
startAdornment={
<InputAdornment position="start">
<IconButton edge="end">
<SearchIcon/>
</IconButton>
</InputAdornment>
}
/>
</FormControl>
</Paper>
{/*Рабочее поле ингредиентов*/}
<Box>
<Tabs value={value} onChange={handleChange} aria-label="basic tabs example">
<Tab label="Мои бары" {...a11yProps(0)} />
<Tab label="Список" {...a11yProps(1)} />
</Tabs>
</Box>
<Box>
<CustomTabPanel value={value} index={0}>
<BarList all={false} find={findString}/>
</CustomTabPanel>
<CustomTabPanel value={value} index={1}>
<BarList all={true} find={findString}/>
</CustomTabPanel>
</Box>
{/*Модальное окно информации об ингредиенте*/}
{/*<IngredientInfoModal ingredient={selectedInfo} open={openModal} closeHandler={handleCloseModal}/>*/}
</Box>
)
}

View File

@@ -0,0 +1,77 @@
import Box from "@mui/material/Box";
import {useEffect, useState} from "react";
import {api} from "../../lib/clients/api";
import {requests} from "../../requests";
import {useAlert} from "../../hooks/useAlert";
import Typography from "@mui/material/Typography";
import {VisitorItem} from "../../components/visitor/VisitorItem";
import Toolbar from "@mui/material/Toolbar";
import * as React from "react";
import Button from "@mui/material/Button";
import {useUser} from "../../hooks/useUser";
export function VisitorPage() {
const {session, checkSession} = useUser();
const [visitors, setVisitors] = useState([])
const [open, setOpen] = useState(false);
const {createError} = useAlert();
useEffect(() => {
api().get(requests.visitors.all)
.then((r) => {
setVisitors(r.data)
})
.catch(() => createError("Ошибка получения данных"))
// eslint-disable-next-line
}, []);
useEffect(() => {
setOpen(session.isActive);
}, [session, checkSession])
const changeHandler = (visitor) => {
const arr = visitors.map((v) => {
if(v.id === visitor.id) {
return {
...visitor,
invited: !visitor.invited
}
}
return v;
})
api().post(`${requests.visitors.invite}id=${visitor.id}&value=${!visitor.invited}`)
.then(() => setVisitors(arr))
.catch(() => createError("Ошибка запроса"))
}
const changeShift = () => {
api().post(`${requests.bar.session.change}?value=${!open}`)
.then(() => {
checkSession?.();
setOpen(!open)
})
.catch(() => createError("Ошибка закрытия сессии"))
}
return (
<Box>
{/*Заголовок*/}
<Toolbar>
<Typography variant="h6" component="div" sx={{flexGrow: 1}}>Посетители</Typography>
</Toolbar>
<Box ml={0} mb={2}>
{visitors.map((v) => {
return (
<VisitorItem key={v.id} visitor={v} changeHandler={changeHandler} open={open}/>
)
})}
</Box>
<Button
variant='contained'
color={open ? 'error' : 'success'}
onClick={() => changeShift()}
>{`${open ? "Закрыть " : "Открыть "}смену`}</Button>
</Box>
)
}

View File

@@ -0,0 +1,11 @@
import * as React from 'react';
import {GuestGuard} from "../../../../components/auth/guest-guard";
import {SignInForm} from "../../../../components/auth/sign-in-form";
export default function LoginPage() {
return (
<GuestGuard>
<SignInForm/>
</GuestGuard>
);
}

View File

@@ -0,0 +1,30 @@
import * as React from "react";
import {useSearchParams} from "react-router-dom";
import {Loading} from "../../../../components/core/Loading";
import {api} from "../../../../lib/clients/api";
import {requests} from "../../../../requests";
import {useAuth} from "../../../../hooks/useAuth";
export function TelegramCode() {
const [searchParams] = useSearchParams();
const {checkSession} = useAuth();
let code = searchParams.get("code");
const request = {
byLogin: false,
code: code
}
api().post(requests.auth.login, request)
.then(async (response) => {
if (response.data.error) {
return;
}
localStorage.setItem("token", response.data.token);
await checkSession?.();
window.location.reload();
})
return (
<Loading loading={true}/>
)
}

View File

@@ -0,0 +1,7 @@
import CocktailsPageContent from "./CocktailsPageContent";
export function AllCocktailsPage() {
return (
<CocktailsPageContent all={true}/>
)
}

View File

@@ -0,0 +1,127 @@
import Box from "@mui/material/Box";
import Toolbar from "@mui/material/Toolbar";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
import {Fab, FormControl, FormControlLabel, InputAdornment, InputLabel, OutlinedInput} from "@mui/material";
import IconButton from "@mui/material/IconButton";
import SearchIcon from "@mui/icons-material/Search";
import Switch from "@mui/material/Switch";
import {blue} from "@mui/material/colors";
import UpIcon from "@mui/icons-material/KeyboardArrowUp";
import {Loading} from "../../../components/core/Loading";
import * as React from "react";
import {useEffect, useMemo, useState} from "react";
import {CocktailsList} from "../../../components/cocktails/CocktailsList";
import {requests} from "../../../requests";
import {api} from "../../../lib/clients/api";
import {useAlert} from "../../../hooks/useAlert";
import {CocktailInfoModal} from "../../../components/cocktails/CocktailInfoModal";
export function CocktailMenuBarPage() {
const {createError} = useAlert();
const [grouping, setGrouping] = useState(true);
const [findString, setFindString] = useState("");
const [loading, setLoading] = useState(true);
const [cocktails, setCocktails] = useState([]);
const [openModal, setOpenModal] = useState(false);
const [selected, setSelected] = useState(null);
useEffect(() => {
api().get(`${requests.cocktails.menu}?all=true`)
.then((r) => {
setCocktails(r.data);
setLoading(false);
})
.catch(() => createError("Ошибка получения данных"))
// eslint-disable-next-line
}, []);
const handleOpenModal = (row) => {
setSelected(row)
setOpenModal(true);
}
const changeHandler = (row, value) => {
const newState = cocktails.map((r) => {
if(r.id !== row.id) {
return r;
}
return {
...r,
inMenu: value
}
});
api().post(`${requests.cocktails.menu}?id=${row.id}&value=${value}`)
.then(() => {
setCocktails(newState);
}).catch(() => createError("Ошибка сохранения данных"))
}
const visibleRows = useMemo(() => {
if (findString === "") {
return cocktails;
}
let regExp = new RegExp("(.*?)" + findString + "(.*?)", "i");
return cocktails
.filter((row) => row.name.split(" ").map((n) => n.match(regExp) !== null).includes(true))
// eslint-disable-next-line
}, [cocktails, findString])
return (
<Box>
{/*Заголовок*/}
<Toolbar>
<Typography variant="h6" component="div" sx={{flexGrow: 1}}>Меню бара</Typography>
</Toolbar>
{/*Поиск*/}
<Paper elevation={6} sx={{my: 2}}>
<FormControl sx={{m: 1, width: 'calc(100% - 20px'}}>
<InputLabel htmlFor="outlined-adornment-amount">Поиск</InputLabel>
<OutlinedInput
onChange={(e) => setFindString(e.target.value)}
label="With normal TextField"
startAdornment={
<InputAdornment position="start">
<IconButton edge="end">
<SearchIcon/>
</IconButton>
</InputAdornment>
}
/>
</FormControl>
<FormControlLabel sx={{ml: '2px'}}
control={<Switch defaultChecked/>}
onClick={() => setGrouping(!grouping)}
label="Группировать"
labelPlacement="end"/>
</Paper>
{/*Рабочее поле коктейлей*/}
<CocktailsList rows={visibleRows} changeHandler={changeHandler}
infoHandler={handleOpenModal} grouping={grouping}/>
{/*Иконка возврата наверх*/}
<Fab sx={{
alpha: '30%',
position: 'sticky',
bottom: '16px',
color: 'common.white',
bgcolor: blue[600],
'&:hover': {
bgcolor: blue[600],
},
}}
onClick={() => window.window.scrollTo(0, 0)}
aria-label='Expand'
color='inherit'>
<UpIcon/>
</Fab>
{/*Загрузчик*/}
<Loading loading={loading}/>
{/*Модальное окно информации об ингредиенте*/}
<CocktailInfoModal open={openModal} row={selected}
closeHandler={() => {
setSelected(null);
setOpenModal(false);
}}/>
</Box>
)
}

View File

@@ -0,0 +1,333 @@
import Grid from "@mui/material/Grid";
import {useAlert} from "../../../hooks/useAlert";
import * as React from "react";
import {useCallback, useEffect, useState} from "react";
import {Cocktail} from "../../../components/cocktails/Cocktail";
import {Fab, Skeleton} from "@mui/material";
import Box from "@mui/material/Box";
import {requests} from "../../../requests";
import {NoResult} from "../../../components/cocktails/NoResult";
import {FilterBlock} from "../../../components/cocktails/FilterBlock";
import {api} from "../../../lib/clients/api";
import {CocktailInfoModal} from "../../../components/cocktails/CocktailInfoModal";
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"))
}
const emptyFilter = {
search: "",
hidden: true,
onlyFavourite: false,
glass: [],
category: [],
alcohol: [],
tags: [],
iCount: [],
ingredient: [],
inMenu: "",
sorting: "Название по возрастанию"
}
const CocktailsPageContent = ({all}) => {
const {user} = useUser();
const {createError, createSuccess} = useAlert();
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 [page, setPage] = useState(-1);
const [load, setLoad] = useState(false);
const [isEnd, setIsEnd] = useState(false);
const [isNew, setIsNew] = useState(true);
const loading = useCallback(() => {
const size = Math.floor((window.innerWidth) / 350) * 5;
if (load || (!isNew && isEnd)) {
return false;
}
setLoad(true);
const request = {
...filter,
all: all,
sort: sortList.find((s) => s.name === filter.sorting).id,
page: page + 1,
size: size,
iCount: Array.isArray(filter.iCount) ? null : filter.iCount
}
api().post(requests.cocktails.menu, request)
.then((r) => {
if (r.data.length === 0) {
if(isNew) {
setRows([]);
}
setIsEnd(true);
setLoad(false);
return;
}
const cocktails = isNew ? r.data : rows.concat(r.data);
setRows(cocktails);
setIsNew(false);
setPage(page + 1);
setLoad(false);
})
.catch((r) => {
setLoad(false);
createError("Ошибка загрузки данных от сервера Status:" + r.status)
})
// eslint-disable-next-line
}, [load, isEnd, page]);
useEffect(() => {
const handleScroll = () => {
const {scrollTop, scrollHeight, clientHeight} = document.documentElement;
if (scrollTop + clientHeight >= scrollHeight - 100) {
loading();
}
}
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [loading]);
useEffect(() => {
api().get(requests.bar.ingredientSimple)
.then((r) => {
const arr = r.data.filter((i) => i.isHave)
.map((i) => i.name)
setAllowIngredients(arr)
})
.catch(() => createError("Ошибка получения ингредиентов"))
// eslint-disable-next-line
}, [])
useEffect(() => {
loading();
}, [filter])
useEffect(() => {
if (!all) {
return;
}
const ingredients = new Set();
rows.map((c) => c.components)
.map((c) => c.split(", "))
.map((c) => c.filter((i) => !allowIngredients.includes(i)))
.filter((nhc) => nhc.length === 1)
.map((fc) => fc[0])
.forEach((i) => ingredients.add(i))
setChips(Array.from(ingredients).sort(getComparator()));
}, [rows, allowIngredients])
const renderSkeleton = () => {
return Array.from({length: 3}, () => null)
.map((v, index) => <Skeleton sx={{m: 2}}
key={index}
variant="rounded"
width={350}
height={690}/>);
}
const handleChangeRating = (row, value) => {
const newState = rows.map((r) => {
if (row.id === r.id) {
let newRating = r.rating;
newRating.rating = value
return {
...r,
rating: newRating
}
}
return r;
})
api().post(`${requests.cocktails.rating}${row.id}&rating=${value}`)
.then(() => {
setRows(newState);
createSuccess("Спасибо за оценку!")
}).catch(() => createError("Ошибка сохранения"))
}
const handleFilterChange = (filterName, value) => {
const newState = {
...filter,
[filterName]: value
}
setFilter(newState)
setIsNew(true);
setIsEnd(false);
setPage(-1);
}
const handleFavourite = (row) => {
const value = !row.rating.favourite;
const newState = rows.map((r) => {
if (r.id === row.id) {
let newRating = r.rating;
newRating.favourite = value;
return {
...r,
rating: newRating
}
}
return r;
});
let url = `${requests.cocktails.favourite}${row.id}`;
let request = value ? api().put(url) : api().delete(url);
request
.then(() => {
setRows(newState);
createSuccess("Спасибо за оценку!")
}).catch(() => createError("Ошибка сохранения"))
}
const handleFilterClear = () => {
setFilter(emptyFilter);
}
const handleSelectCocktail = (row) => {
setSelectedCocktail(row.id)
setOpen(true)
}
const handleCloseCocktailModal = () => {
setOpen(false);
setSelectedCocktail(null);
}
const handleEditMenu = (row, value) => {
const newState = rows.map((r) => {
if (r.id !== row.id) {
return r;
}
if (all) {
return {
...r,
inMenu: value
}
}
return null
}).filter((r) => r !== null);
api().post(`${requests.cocktails.menu}?id=${row.id}&value=${value}`)
.then(() => setRows(newState))
.catch(() => createError("Ошибка сохранения данных"))
}
const editMenuBlock = (row) => {
if (user.role === "USER" || user.role === "ADMIN_NOT_BARMEN") {
return null;
}
return (
<Button color={row.inMenu ? 'error' : 'success'} variant='contained'
onClick={() => handleEditMenu(row, !row.inMenu)}>
{(row.inMenu ? 'Удалить из' : 'Добавить в') + ' меню'}
</Button>
)
}
return (
<Box>
{/*<Loading loading={load}/>*/}
{/*Модальное окно информации о коктейле*/}
<CocktailInfoModal row={selectedCocktail} open={open}
closeHandler={handleCloseCocktailModal}/>
{/*Блок фильтров*/}
<FilterBlock
filter={filter}
handleFilterChange={handleFilterChange}
handleClearFilter={handleFilterClear}
barmen={user.role !== 'USER'}
all={all}
/>
{/*todo: доделать фильтр по количеству недостающих ингредиентов*/}
{/*{*/}
{/* (all && filter.iCount === 1) && (*/}
{/* <Paper sx={{mt: 1}}>*/}
{/* <CheckMarks rows={chips} name={"Выбор ингредиента"} filterName={"ingredient"}*/}
{/* filterValue={filter.ingredient}*/}
{/* handleChange={handleFilterChange}*/}
{/* identity*/}
{/* />*/}
{/* </Paper>*/}
{/* )*/}
{/*}*/}
<Box>
{/*Основное содержимое*/}
<Grid container rowSpacing={2} columnSpacing={{xs: 1, sm: 1, md: 2}} sx={{m: 1}}>
{rows.length > 0 && rows.map((row) => {
return (
<Cocktail key={row.id} row={row} handleFavourite={handleFavourite}
handleChangeRating={handleChangeRating}
handleSelect={handleSelectCocktail}
editMenuBlock={editMenuBlock}
/>
)
})}
{load && renderSkeleton()}
{rows.length === 0 && (<NoResult/>)}
</Grid>
</Box>
<Fab sx={{
alpha: '30%',
position: 'sticky',
left: 'calc(100% - 16px)',
bottom: '16px',
color: 'common.white',
bgcolor: blue[600],
'&:hover': {
bgcolor: blue[600],
},
}}
onClick={() => window.window.scrollTo(0, 0)}
aria-label='Expand'
color='inherit'>
<UpIcon/>
</Fab>
</Box>
);
}
export default CocktailsPageContent;

View File

@@ -0,0 +1,258 @@
import Box from "@mui/material/Box";
import Toolbar from "@mui/material/Toolbar";
import Typography from "@mui/material/Typography";
import * as React from "react";
import {useEffect, useState} from "react";
import Paper from "@mui/material/Paper";
import {Autocomplete} from "@mui/material";
import TextField from "@mui/material/TextField";
import {api} from "../../../lib/clients/api";
import {requests} from "../../../requests";
import {useAlert} from "../../../hooks/useAlert";
import Stack from "@mui/material/Stack";
import Button from "@mui/material/Button";
import CheckMarks from "../../../components/cocktails/CheckMarks";
import {EditCocktailReceipt} from "../../../components/cocktails/EditCocktailReceipt";
import {SelectEdit} from "../../../components/cocktails/SelectEdit";
import {getComparator} from "../../../components/core/getComparator";
import {useSearchParams} from "react-router-dom";
import {Loading} from "../../../components/core/Loading";
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
import {styled} from "@mui/material/styles";
const emptyCocktail = {
id: null,
name: "",
alcoholic: "",
category: "",
components: "",
glass: "",
image: "",
instructions: "",
isAllowed: false,
rating: {
rating: 0,
favourite: false
},
receipt: [],
tags: "",
video: ""
};
const alcohol = [
{
id: 1,
name: "Алкогольный"
},
{
id: 2,
name: "Безалкогольный",
},
{
id: 3,
name: "Опционально"
}
]
const VisuallyHiddenInput = styled('input')({
clip: 'rect(0 0 0 0)',
clipPath: 'inset(50%)',
height: 1,
overflow: 'hidden',
position: 'absolute',
bottom: 0,
left: 0,
whiteSpace: 'nowrap',
width: 1,
});
export function EditCocktailPage() {
const [searchParams] = useSearchParams();
const [loading, setLoading] = useState(true);
const {createError, createSuccess, getError} = useAlert();
const [cocktails, setCocktails] = useState([]);
const [selected, setSelected] = useState(null);
const [cocktail, setCocktail] = useState(emptyCocktail);
const [glass, setGlass] = useState([]);
const [category, setCategory] = useState([]);
const [tags, setTags] = useState([])
useEffect(() => {
api().get(requests.cocktails.simple)
.then((r) => {
const arr = r.data.sort(getComparator("asc", "name"));
setCocktails(arr)
const currentId = searchParams.get("id");
if (!currentId) {
setLoading(false);
return;
}
const currentCocktail = arr.find((r) => r.id === (currentId * 1));
if (!currentCocktail) {
setLoading(false);
return;
}
setSelected(currentCocktail.id);
setLoading(false);
})
.catch(() => createError("Ошибка получения данных"))
api().get(requests.bar.category)
.then((r) => setCategory(r.data.sort(getComparator("asc", "name"))))
.catch(() => createError("Ошибка получения категорий"))
api().get(requests.bar.glass)
.then((r) => setGlass(r.data.sort(getComparator("asc", "name"))))
.catch(() => createError("Ошибка получения посуды"))
api().get(requests.bar.tags)
.then((r) => setTags(r.data.sort(getComparator("asc", "name"))))
.catch(() => createError("Ошибка получения тегов"))
// eslint-disable-next-line
}, []);
useEffect(() => {
if (!selected) {
setCocktail(emptyCocktail);
return;
}
api().get(requests.cocktails.cocktail + selected)
.then((r) => {
setCocktail(r.data)
})
.catch(() => getError());
// eslint-disable-next-line
}, [selected])
const changeCocktailValue = (name, value) => {
if (name === "tags") {
value = value.join(",");
}
setCocktail((prev) => ({
...prev,
[name]: value
}))
}
const saveHandler = () => {
api().patch(requests.cocktails.edit, cocktail)
.then((r) => {
if (!r.data.error) {
createSuccess("Сохранено")
return;
}
createError("Ошибка на сервере: " + r.data.error)
})
.catch(() => createError("Неизвестная ошибка"))
}
const deleteHandle = () => {
api().delete(requests.cocktails.cocktail + cocktail.id)
.then(() => {
setCocktails(cocktails.filter((r) => r.id !== cocktail.id))
setCocktail(emptyCocktail);
})
.catch(() => createError("Ошибка удаления коктейля"))
}
return (
<Box>
{/*Загрузка*/}
<Loading loading={loading}/>
{/*Заголовок*/}
<Toolbar>
<Typography variant="h6" component="div" sx={{flexGrow: 1}}>Коктейли</Typography>
</Toolbar>
{/*Поиск*/}
<Paper elevation={6} sx={{my: 2, display: 'grid', p: 2}}>
<Autocomplete
disablePortal
options={cocktails}
onChange={(e, v) => {
if (!v) {
setCocktail(emptyCocktail);
setSelected(null)
} else {
setSelected(v.id)
}
}}
isOptionEqualToValue={(selected, value) => selected.id === value.id}
getOptionKey={(selected) => selected.id}
getOptionLabel={(selected) => selected.name + (selected.hasError ? " (есть ошибка)" : "")}
renderInput={(params) => <TextField {...params} label="Поиск"/>}
/>
</Paper>
{/*Рабочая область*/}
<Paper elevation={6} sx={{my: 2, display: 'grid', p: 1, pb: 2}}>
<Stack>
<Box hidden={cocktail.id === null} ml={1} mb={1}>
<Button color='error' onClick={() => deleteHandle()}>Удалить коктейль</Button>
</Box>
{/*Фото*/}
<Box ml={1}>
<img src={cocktail.image} alt={""} width={300} height={300} loading={'eager'}/>
</Box>
{/*Редактирование ссылки на фото*/}
<Stack direction='row' pr={2} m={1} display='relative'>
<TextField sx={{width: '75%'}}
label={"Ссылка на фото"} variant='outlined' multiline
value={!cocktail.image ? "" : cocktail.image}
onChange={(e) => changeCocktailValue("image", e.target.value)}
/>
<Button
component="label"
role={undefined}
variant="contained"
tabIndex={-1}
startIcon={<CloudUploadIcon/>}
sx={{width: '10%', fontSize: 40, ml: 1, pr: 1}}
>
<VisuallyHiddenInput
type="file"
accept=".jpg,.jpeg,.png"
onChange={(event) => {
const file = event.target.files[0];
let formData = new FormData();
formData.append('file', file);
api().post(requests.cocktails.savePhoto, formData)
.then((r) => changeCocktailValue("image", r.data))
.catch(() => getError())
}}
/>
</Button>
</Stack>
{/*Название*/}
<Box m={1}>
<TextField sx={{mr: 1, mb: 2, minWidth: 300}}
variant="outlined" label={"Название"}
value={cocktail.name}
onChange={(e) => changeCocktailValue("name", e.target.value)}/>
</Box>
{/*Категория, посуда, алкогольность, теги*/}
<Box mb={2}>
<SelectEdit value={cocktail.category} label={"Категория"} width={300} margin={1}
array={category}
attributeName={"category"} handler={changeCocktailValue}/>
<SelectEdit value={cocktail.glass} label={"Посуда"} width={300} margin={1} array={glass}
attributeName={"glass"} handler={changeCocktailValue}/>
<SelectEdit value={cocktail.alcoholic} label={"Алкогольность"} width={300} margin={1}
array={alcohol}
attributeName={"alcoholic"} handler={changeCocktailValue}/>
<CheckMarks rows={tags} width={300} name={"Теги"} handleChange={changeCocktailValue}
filterValue={cocktail.tags.split(",")} filterName={"tags"}/>
</Box>
{/*Рецепт*/}
<EditCocktailReceipt receipt={cocktail.receipt} handler={changeCocktailValue}/>
<Box pr={2} ml={1}>
<TextField sx={{width: '100%'}}
label={"Инструкция"} variant='outlined' multiline
value={!cocktail.instructions ? "" : cocktail.instructions}
onChange={(e) => changeCocktailValue("instructions", e.target.value)}
/>
</Box>
</Stack>
</Paper>
<Box display={'flex'} justifyContent={'flex-end'}>
<Button variant='contained' onClick={() => saveHandler()}>Сохранить</Button>
</Box>
</Box>
)
}

View File

@@ -0,0 +1,7 @@
import CocktailsPageContent from "./CocktailsPageContent";
export function MenuPage() {
return (
<CocktailsPageContent all={false}/>
)
}

View File

@@ -0,0 +1,169 @@
import Box from "@mui/material/Box";
import Toolbar from "@mui/material/Toolbar";
import Typography from "@mui/material/Typography";
import * as React from "react";
import {useEffect, useState} from "react";
import Paper from "@mui/material/Paper";
import {Autocomplete, FormControl, FormControlLabel, InputLabel} from "@mui/material";
import {api} from "../../../lib/clients/api";
import {requests} from "../../../requests";
import {useAlert} from "../../../hooks/useAlert";
import {useSearchParams} from "react-router-dom";
import TextField from "@mui/material/TextField";
import Switch from "@mui/material/Switch";
import Stack from "@mui/material/Stack";
import Button from "@mui/material/Button";
import Select from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import {getComparator} from "../../../components/core/getComparator";
const emptyIngredient = {
id: null,
name: "",
enName: "",
have: false,
image: null,
type: "",
alcohol: false,
abv: null,
description: null
}
export function EditIngredientPage() {
const [searchParams] = useSearchParams();
const [ingredients, setIngredients] = useState([]);
const [types, setTypes] = useState([]);
const [ingredient, setIngredient] = useState(emptyIngredient)
const {createError, createSuccess} = useAlert();
useEffect(() => {
api().get(requests.bar.ingredientList)
.then((r) => {
const arr = r.data.sort(getComparator("asc", "name"));
setIngredients(arr)
const currentId = searchParams.get("id");
if (!currentId) {
return;
}
const currentIngredient = arr.find((r) => r.id === (currentId * 1));
if (!currentIngredient) {
return;
}
setIngredient(currentIngredient);
})
.catch(() => createError("Ошибка получения данных"))
api().get(requests.bar.type)
.then((r) => setTypes(r.data.sort(getComparator("asc", "name"))))
// eslint-disable-next-line
}, []);
const changeIngredientValue = (name, value) => {
setIngredient((prev) => ({
...prev,
[name]: value
}))
}
const saveIngredientHandler = () => {
api().patch(requests.bar.ingredient, ingredient)
.then(() => createSuccess("Ингредиент сохранен"))
.catch(() => createError("Ошибка сохранения"))
}
return (
<Box>
{/*Заголовок*/}
<Toolbar>
<Typography variant="h6" component="div" sx={{flexGrow: 1}}>Ингредиенты</Typography>
</Toolbar>
{/*Поиск*/}
<Paper elevation={6} sx={{my: 2, display: 'grid', p: 2}}>
<Autocomplete
disablePortal
options={ingredients}
defaultChecked={emptyIngredient}
onChange={(e, v) => {
console.log(v);
return !v ? setIngredient(emptyIngredient) : setIngredient(v)
}}
isOptionEqualToValue={(selected, value) => selected.id === value.id}
getOptionKey={(selected) => selected.id}
getOptionLabel={(selected) => selected.name}
renderInput={(params) => <TextField {...params} label="Ингредиенты"/>}
/>
</Paper>
{/*Форма ингредиента*/}
<Paper elevation={6} sx={{my: 2, display: 'grid', p: 1, pb: 2}}>
<Stack>
<Box display={'flex'} justifyContent={'flex-end'} pr={2}>
<FormControlLabel control={
<Switch
checked={ingredient.have}
onChange={() => changeIngredientValue("have", !ingredient.have)}
/>}
label={"Наличие"} labelPlacement={'start'}/>
</Box>
<Box>
<img src={ingredient.image} alt={""} loading={'eager'}/>
</Box>
<Box m={1}>
<TextField sx={{mr: 1, mb: 2, minWidth: 330}}
variant="outlined" label={"Название"}
value={ingredient.name}
onChange={(e) => changeIngredientValue("name", e.target.value)}/>
<TextField sx={{mr: 1, mb: 2, minWidth: 330}}
label="Английское название" variant="outlined"
value={ingredient.enName}
onChange={(e) => changeIngredientValue("enName", e.target.value)}/>
</Box>
<Box height={70} mt={1} ml={1}>
<FormControlLabel sx={{pt: 1}}
control={
<Switch
checked={ingredient.alcohol}
onChange={() => changeIngredientValue("alcohol", !ingredient.alcohol)}
/>}
label="Алкогольный"/>
{ingredient.alcohol && (
<TextField sx={{width: 100}}
variant='outlined' label='Градус'
value={!ingredient.abv ? "" : ingredient.abv}
onChange={(e) => changeIngredientValue("abv", e.target.value)}/>
)}
</Box>
<Box mb={2} ml={1}>
<FormControl sx={{width: 330}}>
<InputLabel id="select-label">Категория</InputLabel>
<Select
id="select-label"
autoWidth
label={"Категория"}
value={!ingredient.type ? "" : ingredient.type}
onChange={(e) => changeIngredientValue("type", e.target.value)}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
{types.map((c) => {
return (<MenuItem key={c.id} value={c.name}>{c.name}</MenuItem>)
})}
</Select>
</FormControl>
</Box>
<Box pr={2} ml={1}>
<TextField sx={{width: '100%'}}
label={"Описание"} variant='outlined' multiline
value={!ingredient.description ? "" : ingredient.description}/>
</Box>
</Stack>
</Paper>
<Box display={'flex'} justifyContent={'flex-end'}>
<Button variant='contained' onClick={() => saveIngredientHandler()}>Сохранить</Button>
</Box>
</Box>
)
}

View File

@@ -0,0 +1,153 @@
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import Toolbar from "@mui/material/Toolbar";
import Paper from "@mui/material/Paper";
import {Fab, FormControl, FormControlLabel, InputAdornment, InputLabel, OutlinedInput, Tabs} from "@mui/material";
import IconButton from "@mui/material/IconButton";
import SearchIcon from "@mui/icons-material/Search";
import * as React from "react";
import {useEffect, useMemo, useState} from "react";
import {Loading} from "../../../components/core/Loading";
import {requests} from "../../../requests";
import {useAlert} from "../../../hooks/useAlert";
import {IngredientInfoModal} from "../../../components/Ingredients/IngredientInfoModal";
import {api} from "../../../lib/clients/api";
import Tab from "@mui/material/Tab";
import {a11yProps} from "../../../components/core/tabProps";
import {CustomTabPanel} from "../../../components/core/TabPanel";
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";
export function IngredientsPage() {
const [value, setValue] = React.useState(0);
const [grouping, setGrouping] = useState(true);
const handleChange = (event, newValue) => setValue(newValue);
const [loading, setLoading] = useState(true);
const [findString, setFindString] = useState("");
const [ingredients, setIngredients] = useState([]);
const [openModal, setOpenModal] = useState(false);
const [selectedInfo, setSelectedInfo] = useState(null);
const {createError} = useAlert();
useEffect(() => {
api().get(requests.bar.ingredientList)
.then((r) => {
setIngredients(r.data)
setLoading(false);
})
.catch(() => {
createError("Ошибка получения списка ингредиентов");
setLoading(false);
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const visibleIngredients = useMemo(() => {
if (findString.length === 0) {
return ingredients;
}
const reg = new RegExp("(.*?)" + findString + "(.*?)", "i");
return ingredients.filter((ingredient) => ingredient.name.match(reg) !== null);
}, [findString, ingredients]);
const ingredientsToAdd = visibleIngredients.filter((ingredient) => !ingredient.have);
const ingredientsInBar = visibleIngredients.filter((ingredient) => ingredient.have);
const changeHandler = (row, value) => {
const newState = ingredients.map((ingredient) => {
if (ingredient.id === row.id) {
return {
...ingredient,
have: value
}
} else {
return ingredient;
}
})
const url = `${requests.bar.ingredient}?id=${row.id}`;
const request = value ? api().put(url) : api().delete(url);
request
.then(() => {
setIngredients(newState);
})
.catch(() => {
createError("Ошибка изменения ингредиента");
});
}
const handleOpenModal = (i) => {
setOpenModal(true);
setSelectedInfo(i);
}
const handleCloseModal = () => {
setSelectedInfo(null);
setOpenModal(false);
}
return (
<Box>
{/*Заголовок*/}
<Toolbar>
<Typography variant="h6" component="div" sx={{flexGrow: 1}}>Ингредиенты бара</Typography>
</Toolbar>
{/*Поиск*/}
<Paper elevation={6} sx={{my: 2}}>
<FormControl sx={{m: 1, width: 'calc(100% - 20px'}}>
<InputLabel htmlFor="outlined-adornment-amount">Поиск</InputLabel>
<OutlinedInput
onChange={(e) => setFindString(e.target.value)}
label="With normal TextField"
startAdornment={
<InputAdornment position="start">
<IconButton edge="end">
<SearchIcon/>
</IconButton>
</InputAdornment>
}
/>
</FormControl>
<FormControlLabel sx={{ml: '2px'}}
control={<Switch defaultChecked/>}
onClick={() => setGrouping(!grouping)}
label="Группировать"
labelPlacement="end"/>
</Paper>
{/*Рабочее поле ингредиентов*/}
<Box>
<Tabs value={value} onChange={handleChange} aria-label="basic tabs example">
<Tab label="В баре" {...a11yProps(0)} />
<Tab label="Список" {...a11yProps(1)} />
</Tabs>
</Box>
<Box>
<CustomTabPanel value={value} index={0}>
<IngredientList rows={ingredientsInBar} value={false} changeHandler={changeHandler}
infoHandler={handleOpenModal} grouping={grouping}/>
</CustomTabPanel>
<CustomTabPanel value={value} index={1}>
<IngredientList rows={ingredientsToAdd} value={true} changeHandler={changeHandler}
infoHandler={handleOpenModal} grouping={grouping}/>
</CustomTabPanel>
</Box>
<Fab sx={{
alpha: '30%',
position: 'sticky',
bottom: '16px',
color: 'common.white',
bgcolor: blue[600],
'&:hover': {
bgcolor: blue[600],
},
}}
onClick={() => window.window.scrollTo(0, 0)}
aria-label='Expand'
color='inherit'>
<UpIcon/>
</Fab>
{/*Загрузчик*/}
<Loading loading={loading}/>
{/*Модальное окно информации об ингредиенте*/}
<IngredientInfoModal ingredient={selectedInfo} open={openModal} closeHandler={handleCloseModal}/>
</Box>
)
}

View File

@@ -0,0 +1,38 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import {paths} from "../../../path";
export default function NotFoundPage() {
return (
<Box component="main" sx={{ alignItems: 'center', display: 'flex', justifyContent: 'center', minHeight: '100%' }}>
<Stack spacing={3} sx={{ alignItems: 'center', maxWidth: 'md' }}>
<Box>
<Box
component="img"
alt="Under development"
src="/assets/error-404.png"
sx={{ display: 'inline-block', height: 'auto', maxWidth: '100%', width: '400px' }}
/>
</Box>
<Typography variant="h3" sx={{ textAlign: 'center' }}>
404: Страница не найдена или недоступна
</Typography>
<Typography color="text.secondary" variant="body1" sx={{ textAlign: 'center' }}>
Вы либо выбрали какой-то сомнительный маршрут, либо попали сюда по ошибке. Что бы это ни было, попробуйте воспользоваться навигацией
</Typography>
<Button
// component={'a'}
href={paths.home}
startIcon={<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />}
variant="contained"
>
На домашнюю страницу
</Button>
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,7 @@
import {QueueContent} from "./QueueContent";
export function MyQueuePage() {
return (
<QueueContent my={true}/>
)
}

View File

@@ -0,0 +1,82 @@
import {useEffect, useMemo, useState} from "react";
import {useAlert} from "../../../hooks/useAlert";
import * as React from "react";
import {api} from "../../../lib/clients/api";
import {requests} from "../../../requests";
import {createHeadCell} from "../../../components/orders/createHeadCelll";
import {Loading} from "../../../components/core/Loading";
import OrderModal from "../../../components/orders/OrderModal";
import EnhancedTable from "../../../components/orders/EnhancedTable";
export function QueueContent({my}) {
const [load, setLoad] = useState(false);
const [orders, setOrders] = useState([]);
const {createSuccess, createError} = useAlert();
const [openModal, setOpenModal] = React.useState(false);
const [selected, setSelected] = useState(null);
useEffect(() => {
setLoad(false);
const url = my ? requests.bar.myOrders : requests.bar.order;
api().get(url)
.then(r => {
setOrders(r.data);
setLoad(true);
})
.catch(() => {
createError("Ошибка при получении заказов");
setLoad(true)
})
// eslint-disable-next-line
}, []);
const sliced = useMemo(() => orders.sort((a, b) => b.id - a.id), [orders])
const cells = [
createHeadCell('id', true, true, 'Номер заказа', "20px"),
createHeadCell('cocktail.name', true, false, 'Коктейль', "40px"),
createHeadCell('visitor.name', true, false, 'Клиент', "40px"),
createHeadCell('status', true, true, 'Статус', "30px"),
];
const changeOrderHandle = (row, status) => {
let url = requests.bar.order + "?id=" + row.id;
let isCancel = status === "CANCEL";
let request = isCancel ? api().delete(url) : api().put(url);
request
.then(() => {
createSuccess(isCancel ? "Заказ отменен" : "Заказ готов");
let newArr = orders.filter((order) => {
if (order.id !== row.id) {
row.status = isCancel ? "CANCEL" : "DONE";
return row;
}
return order;
})
setOrders(newArr);
setSelected(null);
setOpenModal(false);
})
.catch(() => createError("Ошибка изменения заказа"))
}
const handleSelect = (row) => {
setSelected(row);
setOpenModal(true);
}
const handleCloseModal = () => {
setOpenModal(false);
setSelected(null);
};
const filterValues = !my ? ["DONE", "CANCEL"] : [];
return (
<>
<Loading loading={!load}/>
<OrderModal row={selected} handleClose={handleCloseModal} open={openModal}
handleChange={changeOrderHandle} my={my}/>
<EnhancedTable name={"Заказы"} rows={sliced} cells={cells} handleSelect={handleSelect} filterEqual={false}
filterField={["status"]} filterValue={filterValues}/>
</>
)
}

View File

@@ -0,0 +1,10 @@
import * as React from "react";
import {QueueContent} from "./QueueContent";
const QueuePage = () => {
return (
<QueueContent my={false}/>
)
}
export default QueuePage;