diff --git a/src/index.tsx b/src/index.tsx index cfeaa9e..e1348c6 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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"; diff --git a/src/kitUI/Cart/Cart.tsx b/src/kitUI/Cart/Cart.tsx index f35f33b..3cbd5fa 100644 --- a/src/kitUI/Cart/Cart.tsx +++ b/src/kitUI/Cart/Cart.tsx @@ -328,9 +328,6 @@ function DiscountTooltip({ discount, cartItemTotal }: { <> Скидка: {discount?.name} {discount?.description} - ------- - layer: {discount?.layer} - id: {discount?._id} }> {discountText} diff --git a/src/kitUI/CustomTextField.tsx b/src/kitUI/CustomTextField.tsx index d06729a..1d632fc 100644 --- a/src/kitUI/CustomTextField.tsx +++ b/src/kitUI/CustomTextField.tsx @@ -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) => void; + sx?: SxProps; + onChange: (e: ChangeEvent) => 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, diff --git a/src/model/cart.ts b/src/model/cart.ts index c809037..a751a1f 100644 --- a/src/model/cart.ts +++ b/src/model/cart.ts @@ -6,7 +6,7 @@ interface DiscountBase { name: string; description: string; /** Этап применения скидки */ - layer: number; + layer?: number; disabled?: boolean; } diff --git a/src/model/tariff.ts b/src/model/tariff.ts index 8ed9c58..25c18ec 100644 --- a/src/model/tariff.ts +++ b/src/model/tariff.ts @@ -9,7 +9,7 @@ export const SERVICE_LIST = [ }, { serviceKey: "dwarfener", - displayName: "Сокращатель ссылок", + displayName: "Аналитика сокращателя", }, ] as const; diff --git a/src/pages/dashboard/Content/DiscountManagement.tsx b/src/pages/dashboard/Content/DiscountManagement.tsx deleted file mode 100644 index 4a5d7be..0000000 --- a/src/pages/dashboard/Content/DiscountManagement.tsx +++ /dev/null @@ -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(false); - const [serviceType, setServiceType] = useState("templategen"); - const [startDate, setStartDate] = useState(new Date()); - const [endDate, setEndDate] = useState(new Date()); - const [discountTypeField, setDiscountTypeField] = useState(""); - const [discountNameField, setDiscountNameField] = useState(""); - const [discountDescriptionField, setDiscountDescriptionField] = useState(""); - const [discountConditionType, setDiscountConditionType] = useState(null); - const [discountFactor, setDiscountFactor] = useState(1); - const [purchasesAmountThreshold, setPurchasesAmountThreshold] = useState(0); - const [cartPurchasesAmountThreshold, setCartPurchasesAmountThreshold] = useState(0); - const [discountMinValue, setDiscountMinValue] = useState(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 ( - <> - - - СКИДКИ - - - - setDiscountNameField(e.target.value)} - /> - - - Условия: - - - - - setDiscountFactor(Number(e.target.value) || 1)} - /> - - setPurchasesAmountThreshold(Number(e.target.value) || 0)} - /> - - setCartPurchasesAmountThreshold(Number(e.target.value) || 0)} - /> - - setDiscountMinValue(Number(e.target.value) || 0)} - /> - - - - - - - Работает, если заплатите 100500 денег - - - - - Вы должны будете продать душу дьяволу - - - -
-
- - - Дата действия: - - - - С - - { if (e) { setStartDate(e); } }} - renderInput={(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 } - } - }} - /> - - по - - { if (e) { setEndDate(e); } }} - renderInput={(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 } - } - }} - /> - - - - - - setIsInfinite(p => !p)} - /> - - - Бессрочно - - - - - - - -
- - - - - - - - - - - - - - - -
- - ); -}; - - -export default DiscountManagement; diff --git a/src/pages/dashboard/Content/DiscountManagement/CreateDiscount.tsx b/src/pages/dashboard/Content/DiscountManagement/CreateDiscount.tsx new file mode 100644 index 0000000..4b5dc56 --- /dev/null +++ b/src/pages/dashboard/Content/DiscountManagement/CreateDiscount.tsx @@ -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("templategen"); + const [discountType, setDiscountType] = useState("Лояльность"); + const [discountNameField, setDiscountNameField] = useState(""); + const [discountDescription, setDiscountDescription] = useState(""); + const [privilegeIdField, setPrivilegeIdField] = useState(""); + const [discountFactorField, setDiscountFactorField] = useState("0"); + const [purchasesAmountField, setPurchasesAmountField] = useState("0"); + const [cartPurchasesAmountField, setCartPurchasesAmountField] = useState("0"); + const [discountMinValueField, setDiscountMinValueField] = useState("0"); + + const handleDiscountTypeChange = (event: React.ChangeEvent) => { + 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 ( + + setDiscountNameField(e.target.value)} + /> + setDiscountDescription(e.target.value)} + /> + + Условия: + + setDiscountFactorField(e.target.value)} + /> + + Тип скидки + + {Object.keys(discountTypes).map(type => + } label={type} /> + )} + + + {discountType === "Лояльность" && + setPurchasesAmountField(e.target.value)} + /> + } + {discountType === "Корзина" && + setCartPurchasesAmountField(e.target.value)} + /> + } + {discountType === "Сервис" && + <> + + setDiscountMinValueField(e.target.value)} + /> + + } + {discountType === "Товар" && + <> + + Привелегия + + + setDiscountMinValueField(e.target.value)} + /> + + } + + + + + ); +} \ No newline at end of file diff --git a/src/pages/dashboard/Content/DiscountManagement/DatePickers.tsx b/src/pages/dashboard/Content/DiscountManagement/DatePickers.tsx new file mode 100644 index 0000000..2749c1f --- /dev/null +++ b/src/pages/dashboard/Content/DiscountManagement/DatePickers.tsx @@ -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(false); + const [startDate, setStartDate] = useState(new Date()); + const [endDate, setEndDate] = useState(new Date()); + + + return ( + <> + + Дата действия: + + + С + { if (e) { setStartDate(e); } }} + renderInput={(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 } + } + }} + /> + по + { if (e) { setEndDate(e); } }} + renderInput={(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 } + } + }} + /> + + + + setIsInfinite(p => !p)} + /> + + + Бессрочно + + + + ); +}; \ No newline at end of file diff --git a/src/pages/dashboard/Content/DiscountManagement/DiscountDataGrid.tsx b/src/pages/dashboard/Content/DiscountManagement/DiscountDataGrid.tsx new file mode 100644 index 0000000..a5f7394 --- /dev/null +++ b/src/pages/dashboard/Content/DiscountManagement/DiscountDataGrid.tsx @@ -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 ( + + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/pages/dashboard/Content/DiscountManagement/DiscountManagement.tsx b/src/pages/dashboard/Content/DiscountManagement/DiscountManagement.tsx new file mode 100644 index 0000000..00ec49d --- /dev/null +++ b/src/pages/dashboard/Content/DiscountManagement/DiscountManagement.tsx @@ -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 ( + <> + + + СКИДКИ + + + + + + ); +}; + + +export default DiscountManagement; diff --git a/src/pages/dashboard/Content/Tariffs/CreateTariff.tsx b/src/pages/dashboard/Content/Tariffs/CreateTariff.tsx index 3f3e7b1..2b450fd 100644 --- a/src/pages/dashboard/Content/Tariffs/CreateTariff.tsx +++ b/src/pages/dashboard/Content/Tariffs/CreateTariff.tsx @@ -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)} /> setAmountField(e.target.value)} + onChange={e => setAmountField(e.target.value)} type="number" /> setCustomPriceField(e.target.value)} + onChange={e => setCustomPriceField(e.target.value)} type="number" />