Merge branch 'dev' of penahub.gitlab.yandexcloud.net:frontend/admin into dev
This commit is contained in:
commit
0026081093
@ -19,7 +19,7 @@ import Error404 from "@pages/Error404";
|
||||
import Users from "@pages/dashboard/Content/Users";
|
||||
import Entities from "@pages/dashboard/Content/Entities";
|
||||
import Tariffs from "@pages/dashboard/Content/Tariffs";
|
||||
import DiscountManagement from "@pages/dashboard/Content/DiscountManagement";
|
||||
import DiscountManagement from "@root/pages/dashboard/Content/DiscountManagement/DiscountManagement";
|
||||
import PromocodeManagement from "@pages/dashboard/Content/PromocodeManagement";
|
||||
import { SettingRoles } from "@pages/Setting/SettingRoles";
|
||||
import Support from "@pages/dashboard/Content/Support/Support";
|
||||
|
@ -328,9 +328,6 @@ function DiscountTooltip({ discount, cartItemTotal }: {
|
||||
<>
|
||||
<Typography>Скидка: {discount?.name}</Typography>
|
||||
<Typography>{discount?.description}</Typography>
|
||||
<Typography>-------</Typography>
|
||||
<Typography>layer: {discount?.layer}</Typography>
|
||||
<Typography>id: {discount?._id}</Typography>
|
||||
</>
|
||||
}>
|
||||
<span>{discountText}</span>
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { TextField, useTheme } from "@mui/material";
|
||||
import { SxProps, TextField, Theme, useTheme } from "@mui/material";
|
||||
import { HTMLInputTypeAttribute, ChangeEvent } from "react";
|
||||
|
||||
|
||||
export function CustomTextField({ id, label, value, type, setValue }: {
|
||||
export function CustomTextField({ id, label, value, type, sx, onChange: setValue }: {
|
||||
id: string;
|
||||
label: string;
|
||||
value: number | string | null;
|
||||
type?: HTMLInputTypeAttribute;
|
||||
setValue: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
sx?: SxProps<Theme>;
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
|
||||
@ -19,6 +20,7 @@ export function CustomTextField({ id, label, value, type, setValue }: {
|
||||
variant="filled"
|
||||
color="secondary"
|
||||
type={type}
|
||||
sx={sx}
|
||||
InputProps={{
|
||||
style: {
|
||||
backgroundColor: theme.palette.content.main,
|
||||
|
@ -6,7 +6,7 @@ interface DiscountBase {
|
||||
name: string;
|
||||
description: string;
|
||||
/** Этап применения скидки */
|
||||
layer: number;
|
||||
layer?: number;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ export const SERVICE_LIST = [
|
||||
},
|
||||
{
|
||||
serviceKey: "dwarfener",
|
||||
displayName: "Сокращатель ссылок",
|
||||
displayName: "Аналитика сокращателя",
|
||||
},
|
||||
] as const;
|
||||
|
||||
|
@ -1,562 +0,0 @@
|
||||
import { Box, Typography, TextField, Checkbox, Button } from "@mui/material";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
||||
import { DesktopDatePicker } from "@mui/x-date-pickers/DesktopDatePicker";
|
||||
import Table from "@mui/material/Table";
|
||||
import TableBody from "@mui/material/TableBody";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import TableContainer from "@mui/material/TableContainer";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import { DataGrid, GridColDef, GridRowsProp, GridToolbar } from "@mui/x-data-grid";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Select, { SelectChangeEvent } from "@mui/material/Select";
|
||||
import theme from "../../../theme";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import { activateDiscounts, deactivateDiscounts, setSelectedDiscountIds, useDiscountStore } from "../../../stores/discounts";
|
||||
import { useState } from "react";
|
||||
import { DiscountConditionType } from "@root/model/cart";
|
||||
import { ServiceType } from "@root/model/tariff";
|
||||
import { findDiscountFactor, formatDiscountFactor } from "@root/kitUI/Cart/calc";
|
||||
|
||||
|
||||
const BoxButton = styled('div')(({ theme }) => ({
|
||||
[theme.breakpoints.down(400)]: {
|
||||
justifyContent: 'center'
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{
|
||||
field: "id",
|
||||
headerName: "ID",
|
||||
width: 30,
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
field: "name",
|
||||
headerName: "Название скидки",
|
||||
width: 200,
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
field: "description",
|
||||
headerName: "Описание",
|
||||
width: 120,
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
field: "conditionType",
|
||||
headerName: "Тип условия",
|
||||
width: 120,
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
field: "factor",
|
||||
headerName: "Процент скидки",
|
||||
width: 120,
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
field: "active",
|
||||
headerName: "Активна",
|
||||
width: 120,
|
||||
sortable: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
const DiscountManagement: React.FC = () => {
|
||||
const discounts = useDiscountStore(state => state.discounts);
|
||||
const selectedDiscountIds = useDiscountStore(state => state.selectedDiscountIds);
|
||||
const [isInfinite, setIsInfinite] = useState<boolean>(false);
|
||||
const [serviceType, setServiceType] = useState<ServiceType>("templategen");
|
||||
const [startDate, setStartDate] = useState<Date>(new Date());
|
||||
const [endDate, setEndDate] = useState<Date>(new Date());
|
||||
const [discountTypeField, setDiscountTypeField] = useState<string>("");
|
||||
const [discountNameField, setDiscountNameField] = useState<string>("");
|
||||
const [discountDescriptionField, setDiscountDescriptionField] = useState<string>("");
|
||||
const [discountConditionType, setDiscountConditionType] = useState<DiscountConditionType | null>(null);
|
||||
const [discountFactor, setDiscountFactor] = useState<number>(1);
|
||||
const [purchasesAmountThreshold, setPurchasesAmountThreshold] = useState<number>(0);
|
||||
const [cartPurchasesAmountThreshold, setCartPurchasesAmountThreshold] = useState<number>(0);
|
||||
const [discountMinValue, setDiscountMinValue] = useState<number>(0);
|
||||
|
||||
const handleServiceTypeChange = (event: SelectChangeEvent) => {
|
||||
setServiceType(event.target.value as ServiceType);
|
||||
};
|
||||
|
||||
function createDiscount() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// const discountsArrayConverted = discounts.map((item) => {
|
||||
// const basketMorePerc = Math.round(Number(item.basketMore) * 100) + "%";
|
||||
// const toTimePerc = Math.round(Number(item.toTime) * 100) + "%";
|
||||
// const toCapacityPerc = Math.round(Number(item.toCapacity) * 100) + "%";
|
||||
|
||||
// const dateFrom = item.from ? new Date(Number(item.from)) : "";
|
||||
// const dateDueTo = item.from ? new Date(Number(item.dueTo)) : "";
|
||||
|
||||
// const strFrom = dateFrom
|
||||
// ? `${dateFrom.getDate()}.${dateFrom.getMonth()}.${dateFrom.getFullYear()}`
|
||||
// : "-";
|
||||
|
||||
// const strDueTo = dateDueTo
|
||||
// ? `${dateDueTo.getDate()}.${dateDueTo.getMonth()}.${dateDueTo.getFullYear()}`
|
||||
// : "-";
|
||||
|
||||
// if (item.privileges.length) {
|
||||
// const result = item.privileges.reduce((acc, privilege) => {
|
||||
// acc = acc
|
||||
// ? `${acc}, ${privilege.good} - ${privilege.discount}%`
|
||||
// : `${privilege.good} - ${Math.round(privilege.discount * 100)}%`;
|
||||
|
||||
// return acc;
|
||||
// }, "");
|
||||
|
||||
// return {
|
||||
// ...item, privileges: result, from: strFrom, dueTo: strDueTo,
|
||||
// basketMore: basketMorePerc, toTime: toTimePerc, toCapacity: toCapacityPerc
|
||||
// };
|
||||
// } else {
|
||||
// return {
|
||||
// ...item, from: strFrom, dueTo: strDueTo,
|
||||
// basketMore: basketMorePerc, toTime: toTimePerc, toCapacity: toCapacityPerc
|
||||
// };
|
||||
// }
|
||||
// });
|
||||
|
||||
const discountsGridData: GridRowsProp = discounts.map(discount => {
|
||||
return {
|
||||
id: discount._id,
|
||||
name: discount.name,
|
||||
description: discount.description,
|
||||
conditionType: discount.conditionType,
|
||||
factor: formatDiscountFactor(findDiscountFactor(discount)),
|
||||
active: discount.disabled ? "🚫" : "✅",
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
sx={{
|
||||
width: "90%",
|
||||
height: "60px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
color: theme.palette.secondary.main
|
||||
}}>
|
||||
СКИДКИ
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "left",
|
||||
alignItems: "left",
|
||||
marginTop: "15px",
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
id="standard-basic"
|
||||
label={"Название"}
|
||||
variant="filled"
|
||||
color="secondary"
|
||||
sx={{
|
||||
height: "30px",
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
backgroundColor: theme.palette.content.main,
|
||||
color: theme.palette.secondary.main,
|
||||
}
|
||||
}}
|
||||
InputLabelProps={{
|
||||
style: {
|
||||
color: theme.palette.secondary.main
|
||||
}
|
||||
}}
|
||||
value={discountNameField}
|
||||
onChange={e => setDiscountNameField(e.target.value)}
|
||||
/>
|
||||
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
width: "90%",
|
||||
height: "40px",
|
||||
fontWeight: "normal",
|
||||
color: theme.palette.grayDisabled.main,
|
||||
marginTop: "75px",
|
||||
paddingLeft: '10px',
|
||||
}}>
|
||||
Условия:
|
||||
</Typography>
|
||||
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
value={serviceType}
|
||||
label="Age"
|
||||
onChange={handleServiceTypeChange}
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
border: "1px solid",
|
||||
borderColor: theme.palette.secondary.main,
|
||||
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: theme.palette.secondary.main
|
||||
},
|
||||
".MuiSvgIcon-root ": {
|
||||
fill: theme.palette.secondary.main,
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MenuItem value={"templategen"}>Шаблонизатор</MenuItem>
|
||||
<MenuItem value={"squiz"}>Опросник</MenuItem>
|
||||
<MenuItem value={"dwarfener"}>Аналитика сокращателя</MenuItem>
|
||||
</Select>
|
||||
|
||||
<TextField
|
||||
id="standard-basic"
|
||||
label={"Процент скидки"}
|
||||
variant="filled"
|
||||
color="secondary"
|
||||
sx={{
|
||||
marginTop: "15px"
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
backgroundColor: theme.palette.content.main,
|
||||
color: theme.palette.secondary.main,
|
||||
}
|
||||
}}
|
||||
InputLabelProps={{
|
||||
style: {
|
||||
color: theme.palette.secondary.main
|
||||
}
|
||||
}}
|
||||
value={discountFactor}
|
||||
onChange={e => setDiscountFactor(Number(e.target.value) || 1)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
id="standard-basic"
|
||||
label={"Внесено больше"}
|
||||
variant="filled"
|
||||
color="secondary"
|
||||
type="number"
|
||||
sx={{
|
||||
marginTop: "15px"
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
backgroundColor: theme.palette.content.main,
|
||||
color: theme.palette.secondary.main,
|
||||
}
|
||||
}}
|
||||
InputLabelProps={{
|
||||
style: {
|
||||
color: theme.palette.secondary.main
|
||||
}
|
||||
}}
|
||||
value={purchasesAmountThreshold}
|
||||
onChange={e => setPurchasesAmountThreshold(Number(e.target.value) || 0)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
id="standard-basic"
|
||||
label={"Объем в корзине"}
|
||||
variant="filled"
|
||||
color="secondary"
|
||||
type="number"
|
||||
sx={{
|
||||
marginTop: "15px"
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
backgroundColor: theme.palette.content.main,
|
||||
color: theme.palette.secondary.main,
|
||||
}
|
||||
}}
|
||||
InputLabelProps={{
|
||||
style: {
|
||||
color: theme.palette.secondary.main
|
||||
}
|
||||
}}
|
||||
value={cartPurchasesAmountThreshold}
|
||||
onChange={e => setCartPurchasesAmountThreshold(Number(e.target.value) || 0)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
id="standard-basic"
|
||||
label={"Минимальное значение"}
|
||||
variant="filled"
|
||||
color="secondary"
|
||||
type="number"
|
||||
sx={{
|
||||
marginTop: "15px"
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
backgroundColor: theme.palette.content.main,
|
||||
color: theme.palette.secondary.main,
|
||||
}
|
||||
}}
|
||||
InputLabelProps={{
|
||||
style: {
|
||||
color: theme.palette.secondary.main
|
||||
}
|
||||
}}
|
||||
value={discountMinValue}
|
||||
onChange={e => setDiscountMinValue(Number(e.target.value) || 0)}
|
||||
/>
|
||||
|
||||
<TableContainer component={Paper} sx={{
|
||||
width: "100%",
|
||||
marginTop: "35px",
|
||||
backgroundColor: theme.palette.content.main
|
||||
}}>
|
||||
<Table aria-label="simple table">
|
||||
<TableBody>
|
||||
<TableRow sx={{ border: "1px solid white" }} >
|
||||
<TableCell component="th" scope="row" sx={{ color: theme.palette.secondary.main }}>
|
||||
Работает, если заплатите 100500 денег
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow sx={{ border: "1px solid white" }} >
|
||||
<TableCell component="th" scope="row" sx={{ color: theme.palette.secondary.main }}>
|
||||
Вы должны будете продать душу дьяволу
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
width: "90%",
|
||||
height: "40px",
|
||||
fontWeight: "normal",
|
||||
color: theme.palette.grayDisabled.main,
|
||||
marginTop: "55px"
|
||||
}}>
|
||||
Дата действия:
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexWrap: 'wrap'
|
||||
}}
|
||||
>
|
||||
<Typography sx={{
|
||||
width: "35px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "left",
|
||||
}}>С</Typography>
|
||||
|
||||
<DesktopDatePicker
|
||||
inputFormat="DD/MM/YYYY"
|
||||
value={startDate}
|
||||
onChange={(e) => { if (e) { setStartDate(e); } }}
|
||||
renderInput={(params) => <TextField {...params} />}
|
||||
InputProps={{
|
||||
sx: {
|
||||
height: "40px",
|
||||
color: theme.palette.secondary.main,
|
||||
border: "1px solid",
|
||||
borderColor: theme.palette.secondary.main,
|
||||
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main }
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<Typography sx={{
|
||||
width: "65px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}>по</Typography>
|
||||
|
||||
<DesktopDatePicker
|
||||
inputFormat="DD/MM/YYYY"
|
||||
value={endDate}
|
||||
onChange={(e) => { if (e) { setEndDate(e); } }}
|
||||
renderInput={(params) => <TextField {...params} />}
|
||||
InputProps={{
|
||||
sx: {
|
||||
height: "40px",
|
||||
color: theme.palette.secondary.main,
|
||||
border: "1px solid",
|
||||
borderColor: theme.palette.secondary.main,
|
||||
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main }
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
width: "90%",
|
||||
marginTop: theme.spacing(2),
|
||||
}}>
|
||||
<Box sx={{
|
||||
width: "20px",
|
||||
height: "42px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "left",
|
||||
alignItems: "left",
|
||||
marginRight: theme.spacing(1)
|
||||
}}>
|
||||
<Checkbox
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
"&.Mui-checked": {
|
||||
color: theme.palette.secondary.main,
|
||||
},
|
||||
}}
|
||||
checked={isInfinite}
|
||||
onClick={() => setIsInfinite(p => !p)}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}>
|
||||
Бессрочно
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{
|
||||
width: "90%",
|
||||
marginTop: "55px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.menu.main,
|
||||
height: "52px",
|
||||
fontWeight: "normal",
|
||||
fontSize: "17px",
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.grayMedium.main
|
||||
}
|
||||
}}
|
||||
onClick={createDiscount}
|
||||
>Cоздать</Button>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
|
||||
<Box style={{ width: "80%", marginTop: "55px" }}>
|
||||
<Box style={{ height: 600 }}>
|
||||
<DataGrid
|
||||
checkboxSelection={true}
|
||||
rows={discountsGridData}
|
||||
columns={columns}
|
||||
selectionModel={selectedDiscountIds}
|
||||
onSelectionModelChange={setSelectedDiscountIds}
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
"& .MuiDataGrid-iconSeparator": {
|
||||
display: "none"
|
||||
},
|
||||
"& .css-levciy-MuiTablePagination-displayedRows": {
|
||||
color: theme.palette.secondary.main
|
||||
},
|
||||
"& .MuiSvgIcon-root": {
|
||||
color: theme.palette.secondary.main
|
||||
},
|
||||
"& .MuiTablePagination-selectLabel": {
|
||||
color: theme.palette.secondary.main
|
||||
},
|
||||
"& .MuiInputBase-root": {
|
||||
color: theme.palette.secondary.main
|
||||
},
|
||||
"& .MuiButton-text": {
|
||||
color: theme.palette.secondary.main
|
||||
},
|
||||
}}
|
||||
components={{ Toolbar: GridToolbar }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: '100%',
|
||||
marginTop: "45px"
|
||||
}}>
|
||||
<BoxButton sx={{
|
||||
maxWidth: "420px",
|
||||
width: '100%',
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexWrap: 'wrap',
|
||||
}}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={deactivateDiscounts}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.menu.main,
|
||||
width: "200px",
|
||||
height: "48px",
|
||||
fontWeight: "normal",
|
||||
fontSize: "17px",
|
||||
marginBottom: '10px',
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.grayMedium.main
|
||||
}
|
||||
}}>
|
||||
Деактивировать
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={activateDiscounts}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.menu.main,
|
||||
width: "200px",
|
||||
height: "48px",
|
||||
fontWeight: "normal",
|
||||
fontSize: "17px",
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.grayMedium.main
|
||||
}
|
||||
}}>
|
||||
Активировать
|
||||
</Button>
|
||||
</BoxButton>
|
||||
</Box>
|
||||
|
||||
</LocalizationProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default DiscountManagement;
|
@ -0,0 +1,335 @@
|
||||
import { Box, Typography, Button, useTheme, FormControl, FormLabel, RadioGroup, FormControlLabel, Radio, InputLabel } from "@mui/material";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Select, { SelectChangeEvent } from "@mui/material/Select";
|
||||
import { useState } from "react";
|
||||
import { SERVICE_LIST, ServiceType } from "@root/model/tariff";
|
||||
import { CustomTextField } from "@root/kitUI/CustomTextField";
|
||||
import { usePrivilegeStore } from "@root/stores/privileges";
|
||||
import { AnyDiscount } from "@root/model/cart";
|
||||
import { nanoid } from "nanoid";
|
||||
import { addDiscounts } from "@root/stores/discounts";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
|
||||
|
||||
const discountTypes = {
|
||||
"Лояльность": "purchasesAmount",
|
||||
"Корзина": "cartPurchasesAmount",
|
||||
"Сервис": "service",
|
||||
"Товар": "privilege",
|
||||
} as const;
|
||||
type DiscountType = keyof typeof discountTypes;
|
||||
|
||||
export default function CreateDiscount() {
|
||||
const theme = useTheme();
|
||||
const privileges = usePrivilegeStore(state => state.privileges);
|
||||
const [serviceType, setServiceType] = useState<ServiceType>("templategen");
|
||||
const [discountType, setDiscountType] = useState<DiscountType>("Лояльность");
|
||||
const [discountNameField, setDiscountNameField] = useState<string>("");
|
||||
const [discountDescription, setDiscountDescription] = useState<string>("");
|
||||
const [privilegeIdField, setPrivilegeIdField] = useState<string | "">("");
|
||||
const [discountFactorField, setDiscountFactorField] = useState<string>("0");
|
||||
const [purchasesAmountField, setPurchasesAmountField] = useState<string>("0");
|
||||
const [cartPurchasesAmountField, setCartPurchasesAmountField] = useState<string>("0");
|
||||
const [discountMinValueField, setDiscountMinValueField] = useState<string>("0");
|
||||
|
||||
const handleDiscountTypeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDiscountType(event.target.value as DiscountType);
|
||||
};
|
||||
|
||||
const handleServiceTypeChange = (event: SelectChangeEvent) => {
|
||||
setServiceType(event.target.value as ServiceType);
|
||||
};
|
||||
|
||||
function handleCreateDiscount() {
|
||||
const purchasesAmount = parseFloat(purchasesAmountField.replace(",", "."));
|
||||
const discountFactor = 1 - parseFloat(discountFactorField.replace(",", ".")) / 100;
|
||||
const cartPurchasesAmount = parseFloat(cartPurchasesAmountField.replace(",", "."));
|
||||
const discountMinValue = parseFloat(discountMinValueField.replace(",", "."));
|
||||
|
||||
if (!isFinite(purchasesAmount)) return enqueueSnackbar("Поле purchasesAmount не число");
|
||||
if (!isFinite(discountFactor)) return enqueueSnackbar("Поле discountFactor не число");
|
||||
if (!isFinite(cartPurchasesAmount)) return enqueueSnackbar("Поле cartPurchasesAmount не число");
|
||||
if (!isFinite(discountMinValue)) return enqueueSnackbar("Поле discountMinValue не число");
|
||||
|
||||
let discount: AnyDiscount;
|
||||
switch (discountType) {
|
||||
case "Лояльность": {
|
||||
discount = {
|
||||
_id: nanoid(6),
|
||||
name: discountNameField,
|
||||
description: discountDescription,
|
||||
disabled: false,
|
||||
conditionType: "purchasesAmount",
|
||||
condition: {
|
||||
purchasesAmount: purchasesAmount,
|
||||
},
|
||||
factor: discountFactor,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "Корзина": {
|
||||
discount = {
|
||||
_id: nanoid(6),
|
||||
name: discountNameField,
|
||||
description: discountDescription,
|
||||
disabled: false,
|
||||
conditionType: "cartPurchasesAmount",
|
||||
condition: {
|
||||
cartPurchasesAmount: cartPurchasesAmount,
|
||||
},
|
||||
factor: discountFactor,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "Сервис": {
|
||||
discount = {
|
||||
_id: nanoid(6),
|
||||
name: discountNameField,
|
||||
description: discountDescription,
|
||||
disabled: false,
|
||||
conditionType: "service",
|
||||
condition: {
|
||||
service: {
|
||||
id: serviceType,
|
||||
value: discountMinValue,
|
||||
},
|
||||
},
|
||||
target: {
|
||||
service: serviceType,
|
||||
factor: discountFactor,
|
||||
},
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "Товар": {
|
||||
discount = {
|
||||
_id: nanoid(6),
|
||||
name: discountNameField,
|
||||
description: discountDescription,
|
||||
disabled: false,
|
||||
conditionType: "privilege",
|
||||
condition: {
|
||||
privilege: {
|
||||
id: privilegeIdField,
|
||||
value: discountMinValue,
|
||||
},
|
||||
},
|
||||
target: {
|
||||
products: [{
|
||||
privilegeId: privilegeIdField,
|
||||
factor: discountFactor,
|
||||
}],
|
||||
},
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
addDiscounts([discount]);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "left",
|
||||
alignItems: "left",
|
||||
marginTop: "15px",
|
||||
width: "100%",
|
||||
padding: "16px",
|
||||
maxWidth: "600px",
|
||||
gap: "1em",
|
||||
}}>
|
||||
<CustomTextField
|
||||
id="discount-name"
|
||||
label="Название"
|
||||
value={discountNameField}
|
||||
onChange={e => setDiscountNameField(e.target.value)}
|
||||
/>
|
||||
<CustomTextField
|
||||
id="discount-desc"
|
||||
label="Описание"
|
||||
value={discountDescription}
|
||||
onChange={e => setDiscountDescription(e.target.value)}
|
||||
/>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
width: "90%",
|
||||
fontWeight: "normal",
|
||||
color: theme.palette.grayDisabled.main,
|
||||
paddingLeft: "10px",
|
||||
}}>
|
||||
Условия:
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
id="discount-factor"
|
||||
label="Процент скидки"
|
||||
value={discountFactorField}
|
||||
type="number"
|
||||
onChange={e => setDiscountFactorField(e.target.value)}
|
||||
/>
|
||||
<FormControl>
|
||||
<FormLabel
|
||||
id="discount-type"
|
||||
sx={{
|
||||
color: "white",
|
||||
"&.Mui-focused": {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
>Тип скидки</FormLabel>
|
||||
<RadioGroup
|
||||
row
|
||||
aria-labelledby="discount-type"
|
||||
name="discount-type"
|
||||
value={discountType}
|
||||
onChange={handleDiscountTypeChange}
|
||||
>
|
||||
{Object.keys(discountTypes).map(type =>
|
||||
<FormControlLabel key={type} value={type} control={<Radio color="secondary" />} label={type} />
|
||||
)}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
{discountType === "Лояльность" &&
|
||||
<CustomTextField
|
||||
id="discount-purchases"
|
||||
label="Внесено больше"
|
||||
type="number"
|
||||
sx={{
|
||||
marginTop: "15px"
|
||||
}}
|
||||
value={purchasesAmountField}
|
||||
onChange={e => setPurchasesAmountField(e.target.value)}
|
||||
/>
|
||||
}
|
||||
{discountType === "Корзина" &&
|
||||
<CustomTextField
|
||||
id="discount-cart-purchases"
|
||||
label="Объем в корзине"
|
||||
type="number"
|
||||
sx={{
|
||||
marginTop: "15px"
|
||||
}}
|
||||
value={cartPurchasesAmountField}
|
||||
onChange={e => setCartPurchasesAmountField(e.target.value)}
|
||||
/>
|
||||
}
|
||||
{discountType === "Сервис" &&
|
||||
<>
|
||||
<Select
|
||||
labelId="discount-service-label"
|
||||
id="discount-service"
|
||||
value={serviceType}
|
||||
onChange={handleServiceTypeChange}
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
borderColor: theme.palette.secondary.main,
|
||||
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: theme.palette.secondary.main
|
||||
},
|
||||
".MuiSvgIcon-root ": {
|
||||
fill: theme.palette.secondary.main,
|
||||
}
|
||||
}}
|
||||
>
|
||||
{SERVICE_LIST.map(service =>
|
||||
<MenuItem key={service.serviceKey} value={service.serviceKey}>{service.displayName}</MenuItem>
|
||||
)}
|
||||
</Select>
|
||||
<CustomTextField
|
||||
id="discount-min-value"
|
||||
label="Минимальное значение"
|
||||
type="number"
|
||||
sx={{
|
||||
marginTop: "15px"
|
||||
}}
|
||||
value={discountMinValueField}
|
||||
onChange={e => setDiscountMinValueField(e.target.value)}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
{discountType === "Товар" &&
|
||||
<>
|
||||
<FormControl
|
||||
fullWidth
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
"& .MuiInputLabel-outlined": {
|
||||
color: theme.palette.secondary.main,
|
||||
},
|
||||
"& .MuiInputLabel-outlined.MuiInputLabel-shrink": {
|
||||
color: theme.palette.secondary.main,
|
||||
}
|
||||
}}
|
||||
>
|
||||
<InputLabel
|
||||
id="privilege-select-label"
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
fontSize: "16px",
|
||||
lineHeight: "19px",
|
||||
}}
|
||||
>Привелегия</InputLabel>
|
||||
<Select
|
||||
labelId="privilege-select-label"
|
||||
id="privilege-select"
|
||||
value={privilegeIdField}
|
||||
label="Привелегия"
|
||||
onChange={e => setPrivilegeIdField(e.target.value)}
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
borderColor: theme.palette.secondary.main,
|
||||
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: theme.palette.secondary.main,
|
||||
},
|
||||
".MuiSvgIcon-root ": {
|
||||
fill: theme.palette.secondary.main,
|
||||
}
|
||||
}}
|
||||
inputProps={{ sx: { pt: "12px" } }}
|
||||
>
|
||||
{privileges.map((privilege, index) => (
|
||||
<MenuItem key={index} value={privilege.privilegeId}>
|
||||
{privilege.description}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<CustomTextField
|
||||
id="discount-min-value"
|
||||
label="Минимальное значение"
|
||||
type="number"
|
||||
sx={{
|
||||
marginTop: "15px"
|
||||
}}
|
||||
value={discountMinValueField}
|
||||
onChange={e => setDiscountMinValueField(e.target.value)}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
<Box sx={{
|
||||
width: "90%",
|
||||
marginTop: "55px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.menu.main,
|
||||
height: "52px",
|
||||
fontWeight: "normal",
|
||||
fontSize: "17px",
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.grayMedium.main
|
||||
}
|
||||
}}
|
||||
onClick={handleCreateDiscount}
|
||||
>Cоздать</Button>
|
||||
</Box>
|
||||
</Box >
|
||||
);
|
||||
}
|
112
src/pages/dashboard/Content/DiscountManagement/DatePickers.tsx
Normal file
112
src/pages/dashboard/Content/DiscountManagement/DatePickers.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import { Box, Checkbox, TextField, Typography, useTheme } from "@mui/material";
|
||||
import { DesktopDatePicker } from "@mui/x-date-pickers/DesktopDatePicker";
|
||||
import { useState } from "react";
|
||||
|
||||
|
||||
export default function DatePickers() {
|
||||
const theme = useTheme();
|
||||
const [isInfinite, setIsInfinite] = useState<boolean>(false);
|
||||
const [startDate, setStartDate] = useState<Date>(new Date());
|
||||
const [endDate, setEndDate] = useState<Date>(new Date());
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
width: "90%",
|
||||
height: "40px",
|
||||
fontWeight: "normal",
|
||||
color: theme.palette.grayDisabled.main,
|
||||
marginTop: "55px"
|
||||
}}>
|
||||
Дата действия:
|
||||
</Typography>
|
||||
<Box sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexWrap: 'wrap'
|
||||
}}>
|
||||
<Typography sx={{
|
||||
width: "35px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "left",
|
||||
}}>С</Typography>
|
||||
<DesktopDatePicker
|
||||
inputFormat="DD/MM/YYYY"
|
||||
value={startDate}
|
||||
onChange={(e) => { if (e) { setStartDate(e); } }}
|
||||
renderInput={(params) => <TextField {...params} />}
|
||||
InputProps={{
|
||||
sx: {
|
||||
height: "40px",
|
||||
color: theme.palette.secondary.main,
|
||||
border: "1px solid",
|
||||
borderColor: theme.palette.secondary.main,
|
||||
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main }
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Typography sx={{
|
||||
width: "65px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}>по</Typography>
|
||||
<DesktopDatePicker
|
||||
inputFormat="DD/MM/YYYY"
|
||||
value={endDate}
|
||||
onChange={(e) => { if (e) { setEndDate(e); } }}
|
||||
renderInput={(params) => <TextField {...params} />}
|
||||
InputProps={{
|
||||
sx: {
|
||||
height: "40px",
|
||||
color: theme.palette.secondary.main,
|
||||
border: "1px solid",
|
||||
borderColor: theme.palette.secondary.main,
|
||||
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main }
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
width: "90%",
|
||||
marginTop: theme.spacing(2),
|
||||
}}>
|
||||
<Box sx={{
|
||||
width: "20px",
|
||||
height: "42px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "left",
|
||||
alignItems: "left",
|
||||
marginRight: theme.spacing(1)
|
||||
}}>
|
||||
<Checkbox
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
"&.Mui-checked": {
|
||||
color: theme.palette.secondary.main,
|
||||
},
|
||||
}}
|
||||
checked={isInfinite}
|
||||
onClick={() => setIsInfinite(p => !p)}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}>
|
||||
Бессрочно
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
@ -0,0 +1,165 @@
|
||||
import { Box, Button, styled, useTheme } from "@mui/material";
|
||||
import { DataGrid, GridColDef, GridRowsProp, GridToolbar } from "@mui/x-data-grid";
|
||||
import { findDiscountFactor, formatDiscountFactor } from "@root/kitUI/Cart/calc";
|
||||
import { activateDiscounts, deactivateDiscounts, setSelectedDiscountIds, useDiscountStore } from "@root/stores/discounts";
|
||||
|
||||
|
||||
const BoxButton = styled('div')(({ theme }) => ({
|
||||
[theme.breakpoints.down(400)]: {
|
||||
justifyContent: 'center'
|
||||
},
|
||||
}));
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{
|
||||
field: "id",
|
||||
headerName: "ID",
|
||||
width: 70,
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
field: "name",
|
||||
headerName: "Название скидки",
|
||||
width: 150,
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
field: "description",
|
||||
headerName: "Описание",
|
||||
width: 120,
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
field: "conditionType",
|
||||
headerName: "Тип условия",
|
||||
width: 120,
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
field: "factor",
|
||||
headerName: "Процент скидки",
|
||||
width: 120,
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
field: "value",
|
||||
headerName: "Значение",
|
||||
width: 120,
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
field: "active",
|
||||
headerName: "Активна",
|
||||
width: 120,
|
||||
sortable: false,
|
||||
},
|
||||
];
|
||||
|
||||
export default function DiscountDataGrid() {
|
||||
const theme = useTheme();
|
||||
const discounts = useDiscountStore(state => state.discounts);
|
||||
const selectedDiscountIds = useDiscountStore(state => state.selectedDiscountIds);
|
||||
|
||||
const discountsGridData: GridRowsProp = discounts.map(discount => {
|
||||
const value =
|
||||
(discount.condition as any).purchasesAmount ??
|
||||
(discount.condition as any).cartPurchasesAmount ??
|
||||
(discount.condition as any).service?.value ??
|
||||
(discount.condition as any).privilege?.value ??
|
||||
"-";
|
||||
|
||||
return {
|
||||
id: discount._id,
|
||||
name: discount.name,
|
||||
description: discount.description,
|
||||
conditionType: discount.conditionType,
|
||||
factor: formatDiscountFactor(findDiscountFactor(discount)),
|
||||
active: discount.disabled ? "🚫" : "✅",
|
||||
value,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<Box sx={{ width: "100%", marginTop: "55px", p: "16px", maxWidth: "1000px" }}>
|
||||
<Box sx={{ height: 600 }}>
|
||||
<DataGrid
|
||||
checkboxSelection={true}
|
||||
rows={discountsGridData}
|
||||
columns={columns}
|
||||
selectionModel={selectedDiscountIds}
|
||||
onSelectionModelChange={setSelectedDiscountIds}
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
"& .MuiDataGrid-iconSeparator": {
|
||||
display: "none"
|
||||
},
|
||||
"& .css-levciy-MuiTablePagination-displayedRows": {
|
||||
color: theme.palette.secondary.main
|
||||
},
|
||||
"& .MuiSvgIcon-root": {
|
||||
color: theme.palette.secondary.main
|
||||
},
|
||||
"& .MuiTablePagination-selectLabel": {
|
||||
color: theme.palette.secondary.main
|
||||
},
|
||||
"& .MuiInputBase-root": {
|
||||
color: theme.palette.secondary.main
|
||||
},
|
||||
"& .MuiButton-text": {
|
||||
color: theme.palette.secondary.main
|
||||
},
|
||||
}}
|
||||
components={{ Toolbar: GridToolbar }}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: '100%',
|
||||
marginTop: "45px"
|
||||
}}>
|
||||
<BoxButton sx={{
|
||||
maxWidth: "420px",
|
||||
width: '100%',
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexWrap: 'wrap',
|
||||
}}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={deactivateDiscounts}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.menu.main,
|
||||
width: "200px",
|
||||
height: "48px",
|
||||
fontWeight: "normal",
|
||||
fontSize: "17px",
|
||||
marginBottom: '10px',
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.grayMedium.main
|
||||
}
|
||||
}}>
|
||||
Деактивировать
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={activateDiscounts}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.menu.main,
|
||||
width: "200px",
|
||||
height: "48px",
|
||||
fontWeight: "normal",
|
||||
fontSize: "17px",
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.grayMedium.main
|
||||
}
|
||||
}}>
|
||||
Активировать
|
||||
</Button>
|
||||
</BoxButton>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import { Typography, useTheme } from "@mui/material";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
||||
import DiscountDataGrid from "./DiscountDataGrid";
|
||||
import CreateDiscount from "./CreateDiscount";
|
||||
|
||||
|
||||
const DiscountManagement: React.FC = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
sx={{
|
||||
width: "90%",
|
||||
height: "60px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
color: theme.palette.secondary.main
|
||||
}}>
|
||||
СКИДКИ
|
||||
</Typography>
|
||||
<CreateDiscount />
|
||||
<DiscountDataGrid />
|
||||
</LocalizationProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default DiscountManagement;
|
@ -106,20 +106,20 @@ export default function CreateTariff() {
|
||||
id="tariff-name"
|
||||
label="Название тарифа"
|
||||
value={nameField}
|
||||
setValue={e => setNameField(e.target.value)}
|
||||
onChange={e => setNameField(e.target.value)}
|
||||
/>
|
||||
<CustomTextField
|
||||
id="tariff-amount"
|
||||
label="Кол-во единиц привилегии"
|
||||
value={amountField}
|
||||
setValue={e => setAmountField(e.target.value)}
|
||||
onChange={e => setAmountField(e.target.value)}
|
||||
type="number"
|
||||
/>
|
||||
<CustomTextField
|
||||
id="tariff-custom-price"
|
||||
label="Кастомная цена (не обязательно)"
|
||||
value={customPriceField}
|
||||
setValue={e => setCustomPriceField(e.target.value)}
|
||||
onChange={e => setCustomPriceField(e.target.value)}
|
||||
type="number"
|
||||
/>
|
||||
<Button
|
||||
|
@ -30,7 +30,7 @@ export default function ChangePriceModal() {
|
||||
id="privilege-custom-price"
|
||||
label="Цена"
|
||||
value={modalPriceField}
|
||||
setValue={e => changeModalPriceField(e.target.value)}
|
||||
onChange={e => changeModalPriceField(e.target.value)}
|
||||
type="number"
|
||||
/>
|
||||
<Button
|
||||
|
Loading…
Reference in New Issue
Block a user