Merge branch 'discounts' into 'dev'

add discount creating

See merge request frontend/admin!23
This commit is contained in:
Mikhail 2023-06-30 19:41:33 +00:00
commit a815229da0
16 changed files with 653 additions and 423 deletions

@ -6,16 +6,6 @@
"src": "favicon.ico", "src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16", "sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon" "type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
} }
], ],
"start_url": ".", "start_url": ".",

106
src/api/discounts.ts Normal file

@ -0,0 +1,106 @@
import { CreateDiscountBody, Discount, DiscountType } from "@root/model/discount";
import { ServiceType } from "@root/model/tariff";
import { authStore } from "@root/stores/auth";
const makeRequest = authStore.getState().makeRequest;
type CreateDiscount = (props: {
purchasesAmount: number;
cartPurchasesAmount: number;
discountMinValue: number;
discountFactor: number;
discountDescription: string;
discountName: string;
/** ISO string */
startDate: string;
/** ISO string */
endDate: string;
serviceType: ServiceType;
discountType: DiscountType;
privilegeId: string;
}) => Promise<Discount>;
export const createDiscountJSON: CreateDiscount = ({
endDate,
startDate,
discountName,
cartPurchasesAmount,
discountDescription,
discountFactor,
discountMinValue,
purchasesAmount,
serviceType,
discountType,
privilegeId,
}) => {
const discount: CreateDiscountBody = {
Name: discountName,
Layer: 1,
Description: discountDescription,
Condition: {
Period: {
From: startDate,
To: endDate,
},
User: "",
UserType: "",
Coupon: "",
Usage: 0,
PurchasesAmount: 0,
CartPurchasesAmount: 0,
Product: "",
Term: 0,
PriceFrom: 0,
Group: "",
},
Target: {
Factor: discountFactor,
TargetScope: "Sum",
Overhelm: false,
TargetGroup: "",
Products: [{
ID: "",
Factor: 0,
Overhelm: false,
}],
},
};
switch (discountType) {
case "privilege":
discount.Layer = 1;
discount.Condition.Product = privilegeId;
discount.Condition.Term = discountMinValue;
discount.Target.Products = [{
Factor: discountFactor,
ID: privilegeId,
Overhelm: false,
}];
break;
case "service":
discount.Layer = 2;
discount.Condition.PriceFrom = discountMinValue;
discount.Condition.Group = serviceType;
discount.Target.TargetGroup = serviceType;
break;
case "cartPurchasesAmount":
discount.Layer = 3;
discount.Condition.CartPurchasesAmount = cartPurchasesAmount;
break;
case "purchasesAmount":
discount.Layer = 4;
discount.Condition.PurchasesAmount = purchasesAmount;
break;
}
console.log("Constructed discount", discount);
return makeRequest<CreateDiscountBody, Discount>({
url: "https://admin.pena.digital/price/discount",
method: "post",
useToken: true,
bearer: true,
body: discount,
});
};

@ -19,11 +19,12 @@ import { useCartStore } from "@root/stores/cart";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { GridSelectionModel } from "@mui/x-data-grid"; import { GridSelectionModel } from "@mui/x-data-grid";
import { testUser } from "@root/stores/mocks/user"; import { testUser } from "@root/stores/mocks/user";
import { useDiscountStore } from "@root/stores/discounts"; import { useMockDiscountStore } from "@root/stores/discounts";
import { calcCartData, createCartItem, findDiscountFactor, formatDiscountFactor } from "./calc"; import { calcCartData, createCartItem, findDiscountFactor, formatDiscountFactor } from "./calc";
import { useTariffStore } from "@root/stores/tariffs"; import { useTariffStore } from "@root/stores/tariffs";
import { AnyDiscount, CartItemTotal } from "@root/model/cart"; import { AnyDiscount, CartItemTotal } from "@root/model/cart";
import { findPrivilegeById } from "@root/stores/privileges"; import { findPrivilegeById } from "@root/stores/privileges";
import { Privilege } from "@root/model/tariff";
interface Props { interface Props {
selectedTariffs: GridSelectionModel; selectedTariffs: GridSelectionModel;
@ -46,7 +47,7 @@ interface MergedTariff {
} }
export default function Cart({ selectedTariffs }: Props) { export default function Cart({ selectedTariffs }: Props) {
const discounts = useDiscountStore((store) => store.discounts); const discounts = useMockDiscountStore((store) => store.discounts);
const cartTotal = useCartStore((state) => state.cartTotal); const cartTotal = useCartStore((state) => state.cartTotal);
const setCartTotal = useCartStore((store) => store.setCartTotal); const setCartTotal = useCartStore((store) => store.setCartTotal);
const [couponField, setCouponField] = useState<string>(""); const [couponField, setCouponField] = useState<string>("");

@ -10,9 +10,9 @@ import {
ServiceDiscount, ServiceDiscount,
UserDiscount, UserDiscount,
} from "@root/model/cart"; } from "@root/model/cart";
import { Tariff_BACKEND } from "@root/model/tariff";
import { User } from "../../model/user"; import { User } from "../../model/user";
import { findPrivilegeById } from "@root/stores/privileges"; import { findPrivilegeById } from "@root/stores/privileges";
import { SERVICE_LIST, ServiceType, Tariff } from "@root/model/tariff";
export function calcCartData({ export function calcCartData({
user, user,

@ -1,44 +1,91 @@
import { ServiceType } from "./tariff";
export type Discount = { export type Discount = {
ID: string; ID: string;
Name: string; Name: string;
Layer: number; Layer: number;
Description: string; Description: string;
Condition: { Condition: {
Period?: { Period?: {
From: string; From: string;
To: string; To: string;
};
User?: string;
UserType?: string;
Coupon?: string;
PurchasesAmount?: number;
CartPurchasesAmount?: number;
Product?: string;
Term?: string;
Usage?: string;
PriceFrom?: number;
Group?: string;
}; };
User?: string; Target: {
UserType?: string; Products?: {
Coupon?: string; ID: string;
PurchasesAmount?: number; Factor: number;
CartPurchasesAmount?: number; Overhelm: boolean;
Product?: string; }[];
Term?: string; Factor?: number;
Usage?: string; TargetScope?: string;
PriceFrom?: number; TargetGroup?: string;
Group?: string; Overhelm?: boolean;
}; };
Target: { Audit: {
Products?: { UpdatedAt: string;
ID: string; CreatedAt: string;
Factor: number; DeletedAt: string;
Overhelm: boolean; Deleted: boolean;
}[]; };
Factor?: number; Deprecated: boolean;
TargetScope?: string;
TargetGroup?: string;
Overhelm?: boolean;
};
Audit: {
UpdatedAt: string;
CreatedAt: string;
DeletedAt: string;
Deleted: boolean;
};
Deprecated: boolean;
}; };
export type DiscountData = { export type GetDiscountResponse = {
Discounts: Discount[]; Discounts: Discount[];
}; };
export const discountTypes = {
"purchasesAmount": "Лояльность",
"cartPurchasesAmount": "Корзина",
"service": "Сервис",
"privilege": "Товар",
} as const;
export type DiscountType = keyof typeof discountTypes;
export type CreateDiscountBody = {
Name: string;
Layer: 1 | 2 | 3 | 4;
Description: string;
Condition: {
Period: {
/** ISO string */
From: string;
/** ISO string */
To: string;
};
User: string;
UserType: string;
Coupon: string;
PurchasesAmount: number;
CartPurchasesAmount: number;
Product: string;
Term: number;
Usage: number;
PriceFrom: number;
Group: ServiceType | "";
};
Target: {
Factor: number;
TargetScope: "Sum" | "Group" | "Each";
Overhelm: boolean;
TargetGroup: ServiceType | "";
Products: [{
ID: string;
Factor: number;
Overhelm: false;
}];
};
};

21
src/model/privilege.ts Normal file

@ -0,0 +1,21 @@
export interface RealPrivilege {
_id: string;
name: string;
privilegeId: string;
serviceKey: string;
description: string;
type: "day" | "count";
value: PrivilegeValueType;
price: number;
updatedAt?: string;
isDeleted?: boolean;
createdAt?: string;
};
export type PrivilegeMap = Record<string, RealPrivilege[]>;
export type PrivilegeValueType = "шаблон" | "день" | "МБ";
export type PrivilegeWithAmount = Omit<RealPrivilege, "_id"> & { amount: number; };
export type PrivilegeWithoutPrice = Omit<PrivilegeWithAmount, "price">;

@ -1,16 +1,16 @@
export const SERVICE_LIST = [ export const SERVICE_LIST = [
{ {
serviceKey: "templategen", serviceKey: "templategen",
displayName: "Шаблонизатор документов", displayName: "Шаблонизатор документов",
}, },
{ {
serviceKey: "squiz", serviceKey: "squiz",
displayName: "Опросник", displayName: "Опросник",
}, },
{ {
serviceKey: "dwarfener", serviceKey: "dwarfener",
displayName: "Аналитика сокращателя", displayName: "Аналитика сокращателя",
}, },
] as const; ] as const;
export type ServiceType = (typeof SERVICE_LIST)[number]["serviceKey"]; export type ServiceType = (typeof SERVICE_LIST)[number]["serviceKey"];
@ -18,33 +18,56 @@ export type ServiceType = (typeof SERVICE_LIST)[number]["serviceKey"];
export type PrivilegeType = "unlim" | "gencount" | "activequiz" | "abcount" | "extended"; export type PrivilegeType = "unlim" | "gencount" | "activequiz" | "abcount" | "extended";
export interface Privilege_BACKEND { export interface Privilege_BACKEND {
name: string; name: string;
privilegeId: string; privilegeId: string;
serviceKey: string; serviceKey: string;
amount: number; amount: number;
description: string; description: string;
price: number; price: number;
type: string; type: string;
value: string; value: string;
updatedAt: string; updatedAt: string;
_id: string; _id: string;
} }
export type Tariff_BACKEND = { export type Tariff_BACKEND = {
_id: string, _id: string,
name: string, name: string,
price: number, price: number,
isCustom: boolean, isCustom: boolean,
isFront: boolean, isFront: boolean,
privilegies: Privilege_BACKEND[], privilegies: Privilege_BACKEND[],
isDeleted: boolean, isDeleted: boolean,
createdAt: string, createdAt: string,
updatedAt: string updatedAt: string;
} };
export type Tariff_FRONTEND = { export type Tariff_FRONTEND = {
id: string, id: string,
name: string, name: string,
amount:number, amount: number,
isFront: boolean, privilegeId: string,
privilegeId: string, customPricePerUnit?: number;
customPricePerUnit: number };
}
/** @deprecated */
export interface Privilege {
serviceKey: ServiceType;
name: PrivilegeType;
privilegeId: string;
description: string;
/** Единица измерения привелегии: время в днях/кол-во */
type: "day" | "count";
/** Стоимость одной единицы привелегии */
price: number;
}
/** @deprecated */
export interface Tariff {
id: string;
name: string;
privilegeId: string;
/** Количество единиц привелегии */
amount: number;
/** Кастомная цена, если есть, то используется вместо privilege.price */
customPricePerUnit?: number;
isFront?: boolean;
}

@ -4,34 +4,29 @@ import Select, { SelectChangeEvent } from "@mui/material/Select";
import { useState } from "react"; import { useState } from "react";
import { SERVICE_LIST, ServiceType } from "@root/model/tariff"; import { SERVICE_LIST, ServiceType } from "@root/model/tariff";
import { CustomTextField } from "@root/kitUI/CustomTextField"; import { CustomTextField } from "@root/kitUI/CustomTextField";
import { usePrivilegeStore } from "@root/stores/privileges"; import { addRealPrivileges, useRealPrivilegeStore } from "@root/stores/privileges";
import { AnyDiscount } from "@root/model/cart";
import { nanoid } from "nanoid";
import { addDiscounts } from "@root/stores/discounts"; import { addDiscounts } from "@root/stores/discounts";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import { DiscountType, discountTypes } from "@root/model/discount";
import { createDiscountJSON } from "@root/api/discounts";
import usePrivileges from "@root/utils/hooks/usePrivileges";
const discountTypes = {
"Лояльность": "purchasesAmount",
"Корзина": "cartPurchasesAmount",
"Сервис": "service",
"Товар": "privilege",
} as const;
type DiscountType = keyof typeof discountTypes;
export default function CreateDiscount() { export default function CreateDiscount() {
const theme = useTheme(); const theme = useTheme();
const privileges = usePrivilegeStore(state => state.privileges); const privileges = useRealPrivilegeStore(state => state.privileges);
const [serviceType, setServiceType] = useState<ServiceType>("templategen"); const [serviceType, setServiceType] = useState<ServiceType>("templategen");
const [discountType, setDiscountType] = useState<DiscountType>("Лояльность"); const [discountType, setDiscountType] = useState<DiscountType>("purchasesAmount");
const [discountNameField, setDiscountNameField] = useState<string>(""); const [discountNameField, setDiscountNameField] = useState<string>("");
const [discountDescription, setDiscountDescription] = useState<string>(""); const [discountDescriptionField, setDiscountDescriptionField] = useState<string>("");
const [privilegeIdField, setPrivilegeIdField] = useState<string | "">(""); const [privilegeIdField, setPrivilegeIdField] = useState<string | "">("");
const [discountFactorField, setDiscountFactorField] = useState<string>("0"); const [discountFactorField, setDiscountFactorField] = useState<string>("0");
const [purchasesAmountField, setPurchasesAmountField] = useState<string>("0"); const [purchasesAmountField, setPurchasesAmountField] = useState<string>("0");
const [cartPurchasesAmountField, setCartPurchasesAmountField] = useState<string>("0"); const [cartPurchasesAmountField, setCartPurchasesAmountField] = useState<string>("0");
const [discountMinValueField, setDiscountMinValueField] = useState<string>("0"); const [discountMinValueField, setDiscountMinValueField] = useState<string>("0");
usePrivileges({ onNewPrivileges: addRealPrivileges });
const handleDiscountTypeChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleDiscountTypeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setDiscountType(event.target.value as DiscountType); setDiscountType(event.target.value as DiscountType);
}; };
@ -40,9 +35,9 @@ export default function CreateDiscount() {
setServiceType(event.target.value as ServiceType); setServiceType(event.target.value as ServiceType);
}; };
function handleCreateDiscount() { async function handleCreateDiscount() {
const purchasesAmount = parseFloat(purchasesAmountField.replace(",", ".")); const purchasesAmount = parseFloat(purchasesAmountField.replace(",", "."));
const discountFactor = 1 - parseFloat(discountFactorField.replace(",", ".")) / 100; const discountFactor = (100 - parseFloat(discountFactorField.replace(",", "."))) / 100;
const cartPurchasesAmount = parseFloat(cartPurchasesAmountField.replace(",", ".")); const cartPurchasesAmount = parseFloat(cartPurchasesAmountField.replace(",", "."));
const discountMinValue = parseFloat(discountMinValueField.replace(",", ".")); const discountMinValue = parseFloat(discountMinValueField.replace(",", "."));
@ -50,82 +45,28 @@ export default function CreateDiscount() {
if (!isFinite(discountFactor)) return enqueueSnackbar("Поле discountFactor не число"); if (!isFinite(discountFactor)) return enqueueSnackbar("Поле discountFactor не число");
if (!isFinite(cartPurchasesAmount)) return enqueueSnackbar("Поле cartPurchasesAmount не число"); if (!isFinite(cartPurchasesAmount)) return enqueueSnackbar("Поле cartPurchasesAmount не число");
if (!isFinite(discountMinValue)) return enqueueSnackbar("Поле discountMinValue не число"); if (!isFinite(discountMinValue)) return enqueueSnackbar("Поле discountMinValue не число");
if (discountType === "privilege" && !privilegeIdField) return enqueueSnackbar("Привилегия не выбрана");
let discount: AnyDiscount; try {
switch (discountType) { const createdDiscount = await createDiscountJSON({
case "Лояльность": { cartPurchasesAmount,
discount = { discountFactor,
_id: nanoid(6), discountMinValue,
name: discountNameField, purchasesAmount,
description: discountDescription, discountDescription: discountDescriptionField,
disabled: false, discountName: discountNameField,
conditionType: "purchasesAmount", startDate: new Date().toISOString(),
condition: { endDate: new Date(Date.now() + 1000 * 3600 * 24 * 30).toISOString(),
purchasesAmount: purchasesAmount, serviceType,
}, discountType,
factor: discountFactor, privilegeId: privilegeIdField,
}; });
break;
} addDiscounts([createdDiscount]);
case "Корзина": { } catch (error) {
discount = { console.log("Error creating discount", error);
_id: nanoid(6), enqueueSnackbar("Ошибка при создании скидки");
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 ( return (
@ -149,8 +90,8 @@ export default function CreateDiscount() {
<CustomTextField <CustomTextField
id="discount-desc" id="discount-desc"
label="Описание" label="Описание"
value={discountDescription} value={discountDescriptionField}
onChange={e => setDiscountDescription(e.target.value)} onChange={e => setDiscountDescriptionField(e.target.value)}
/> />
<Typography <Typography
variant="h4" variant="h4"
@ -187,11 +128,16 @@ export default function CreateDiscount() {
onChange={handleDiscountTypeChange} onChange={handleDiscountTypeChange}
> >
{Object.keys(discountTypes).map(type => {Object.keys(discountTypes).map(type =>
<FormControlLabel key={type} value={type} control={<Radio color="secondary" />} label={type} /> <FormControlLabel
key={type}
value={type}
control={<Radio color="secondary" />}
label={discountTypes[type as DiscountType]}
/>
)} )}
</RadioGroup> </RadioGroup>
</FormControl> </FormControl>
{discountType === "Лояльность" && {discountType === "purchasesAmount" &&
<CustomTextField <CustomTextField
id="discount-purchases" id="discount-purchases"
label="Внесено больше" label="Внесено больше"
@ -203,7 +149,7 @@ export default function CreateDiscount() {
onChange={e => setPurchasesAmountField(e.target.value)} onChange={e => setPurchasesAmountField(e.target.value)}
/> />
} }
{discountType === "Корзина" && {discountType === "cartPurchasesAmount" &&
<CustomTextField <CustomTextField
id="discount-cart-purchases" id="discount-cart-purchases"
label="Объем в корзине" label="Объем в корзине"
@ -215,7 +161,7 @@ export default function CreateDiscount() {
onChange={e => setCartPurchasesAmountField(e.target.value)} onChange={e => setCartPurchasesAmountField(e.target.value)}
/> />
} }
{discountType === "Сервис" && {discountType === "service" &&
<> <>
<Select <Select
labelId="discount-service-label" labelId="discount-service-label"
@ -249,7 +195,7 @@ export default function CreateDiscount() {
/> />
</> </>
} }
{discountType === "Товар" && {discountType === "privilege" &&
<> <>
<FormControl <FormControl
fullWidth fullWidth
@ -330,6 +276,6 @@ export default function CreateDiscount() {
onClick={handleCreateDiscount} onClick={handleCreateDiscount}
>Cоздать</Button> >Cоздать</Button>
</Box> </Box>
</Box > </Box>
); );
} }

@ -1,203 +1,175 @@
import { useEffect, useState } from "react";
import { enqueueSnackbar } from "notistack";
import { Box, Button, styled, useTheme } from "@mui/material"; import { Box, Button, styled, useTheme } from "@mui/material";
import { DataGrid, GridColDef, GridRowsProp, GridToolbar } from "@mui/x-data-grid"; import { DataGrid, GridColDef, GridRowsProp, GridToolbar } from "@mui/x-data-grid";
import { findDiscountFactor, formatDiscountFactor } from "@root/kitUI/Cart/calc"; import { findDiscountFactor, formatDiscountFactor } from "@root/kitUI/Cart/calc";
import { import { activateMockDiscounts, addDiscounts, deactivateMockDiscounts, setMockSelectedDiscountIds, useDiscountStore, useMockDiscountStore, } from "@root/stores/discounts";
activateDiscounts, import useDiscounts from "@root/utils/hooks/useDiscounts";
deactivateDiscounts,
setSelectedDiscountIds,
useDiscountStore,
} from "@root/stores/discounts";
import axios from "axios";
import type { DiscountData } from "@root/model/discount";
const BoxButton = styled("div")(({ theme }) => ({ const BoxButton = styled("div")(({ theme }) => ({
[theme.breakpoints.down(400)]: { [theme.breakpoints.down(400)]: {
justifyContent: "center", justifyContent: "center",
}, },
})); }));
const columns: GridColDef[] = [ const columns: GridColDef[] = [
{ {
field: "id", field: "id",
headerName: "ID", headerName: "ID",
width: 70, width: 70,
sortable: false, sortable: false,
}, },
{ {
field: "name", field: "name",
headerName: "Название скидки", headerName: "Название скидки",
width: 150, width: 150,
sortable: false, sortable: false,
}, },
{ {
field: "description", field: "description",
headerName: "Описание", headerName: "Описание",
width: 120, width: 120,
sortable: false, sortable: false,
}, },
{ {
field: "conditionType", field: "conditionType",
headerName: "Тип условия", headerName: "Тип условия",
width: 120, width: 120,
sortable: false, sortable: false,
}, },
{ {
field: "factor", field: "factor",
headerName: "Процент скидки", headerName: "Процент скидки",
width: 120, width: 120,
sortable: false, sortable: false,
}, },
{ {
field: "value", field: "value",
headerName: "Значение", headerName: "Значение",
width: 120, width: 120,
sortable: false, sortable: false,
}, },
{ {
field: "active", field: "active",
headerName: "Активна", headerName: "Активна",
width: 120, width: 120,
sortable: false, sortable: false,
}, },
]; ];
export default function DiscountDataGrid() { export default function DiscountDataGrid() {
const theme = useTheme(); const theme = useTheme();
const exampleDiscounts = useDiscountStore((state) => state.discounts); const exampleDiscounts = useMockDiscountStore((state) => state.discounts);
const selectedDiscountIds = useDiscountStore((state) => state.selectedDiscountIds); const selectedDiscountIds = useMockDiscountStore((state) => state.selectedDiscountIds);
const realDiscounts = useDiscountStore(state => state.discounts);
const [discount, setDiscount] = useState<DiscountData>(); useDiscounts({ onNewDiscounts: addDiscounts });
const mergeDiscount = [...(discount?.Discounts ?? []), ...exampleDiscounts]; const discountsGridData: GridRowsProp = exampleDiscounts.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 ??
"-";
console.log(mergeDiscount); return {
id: discount._id,
name: discount.name,
description: discount.description,
conditionType: discount.conditionType,
factor: formatDiscountFactor(findDiscountFactor(discount)),
active: discount.disabled ? "🚫" : "✅",
value,
};
});
useEffect(() => { return (
const axiosDiscount = async () => { <Box sx={{ width: "100%", marginTop: "55px", p: "16px", maxWidth: "1000px" }}>
try { <Box sx={{ height: 600 }}>
const { data } = await axios({ <DataGrid
method: "get", checkboxSelection={true}
url: "https://admin.pena.digital/price/discounts", rows={discountsGridData}
}); columns={columns}
setDiscount(data); selectionModel={selectedDiscountIds}
console.log(data); onSelectionModelChange={setMockSelectedDiscountIds}
} catch (error) { sx={{
enqueueSnackbar("Ошибка получения скидок"); color: theme.palette.secondary.main,
} "& .MuiDataGrid-iconSeparator": {
}; display: "none",
},
axiosDiscount(); "& .css-levciy-MuiTablePagination-displayedRows": {
}, []); color: theme.palette.secondary.main,
},
const discountsGridData: GridRowsProp = exampleDiscounts.map((discount) => { "& .MuiSvgIcon-root": {
const value = color: theme.palette.secondary.main,
(discount.condition as any).purchasesAmount ?? },
(discount.condition as any).cartPurchasesAmount ?? "& .MuiTablePagination-selectLabel": {
(discount.condition as any).service?.value ?? color: theme.palette.secondary.main,
(discount.condition as any).privilege?.value ?? },
"-"; "& .MuiInputBase-root": {
color: theme.palette.secondary.main,
return { },
id: discount._id, "& .MuiButton-text": {
name: discount.name, color: theme.palette.secondary.main,
description: discount.description, },
conditionType: discount.conditionType, }}
factor: formatDiscountFactor(findDiscountFactor(discount)), components={{ Toolbar: GridToolbar }}
active: discount.disabled ? "🚫" : "✅", />
value, </Box>
}; <Box
}); sx={{
display: "flex",
return ( flexDirection: "column",
<Box sx={{ width: "100%", marginTop: "55px", p: "16px", maxWidth: "1000px" }}> justifyContent: "center",
<Box sx={{ height: 600 }}> alignItems: "center",
<DataGrid width: "100%",
checkboxSelection={true} marginTop: "45px",
rows={discountsGridData} }}
columns={columns} >
selectionModel={selectedDiscountIds} <BoxButton
onSelectionModelChange={setSelectedDiscountIds} sx={{
sx={{ maxWidth: "420px",
color: theme.palette.secondary.main, width: "100%",
"& .MuiDataGrid-iconSeparator": { display: "flex",
display: "none", justifyContent: "space-between",
}, flexWrap: "wrap",
"& .css-levciy-MuiTablePagination-displayedRows": { }}
color: theme.palette.secondary.main, >
}, <Button
"& .MuiSvgIcon-root": { variant="contained"
color: theme.palette.secondary.main, onClick={deactivateMockDiscounts}
}, sx={{
"& .MuiTablePagination-selectLabel": { backgroundColor: theme.palette.menu.main,
color: theme.palette.secondary.main, width: "200px",
}, height: "48px",
"& .MuiInputBase-root": { fontWeight: "normal",
color: theme.palette.secondary.main, fontSize: "17px",
}, marginBottom: "10px",
"& .MuiButton-text": { "&:hover": {
color: theme.palette.secondary.main, backgroundColor: theme.palette.grayMedium.main,
}, },
}} }}
components={{ Toolbar: GridToolbar }} >
/> Деактивировать
</Box> </Button>
<Box <Button
sx={{ variant="contained"
display: "flex", onClick={activateMockDiscounts}
flexDirection: "column", sx={{
justifyContent: "center", backgroundColor: theme.palette.menu.main,
alignItems: "center", width: "200px",
width: "100%", height: "48px",
marginTop: "45px", fontWeight: "normal",
}} fontSize: "17px",
> "&:hover": {
<BoxButton backgroundColor: theme.palette.grayMedium.main,
sx={{ },
maxWidth: "420px", }}
width: "100%", >
display: "flex", Активировать
justifyContent: "space-between", </Button>
flexWrap: "wrap", </BoxButton>
}} </Box>
> </Box>
<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>
);
} }

@ -1,3 +1,4 @@
// @ts-nocheck
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
@ -48,6 +49,7 @@ interface Props {
getTariffs: () => void getTariffs: () => void
} }
/** @deprecated */
export default function EditModal({tariff, getTariffs}:Props) { export default function EditModal({tariff, getTariffs}:Props) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);

@ -1,3 +1,4 @@
// @ts-nocheck
import React from "react"; import React from "react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { GridColDef, GridSelectionModel, GridToolbar } from "@mui/x-data-grid"; import { GridColDef, GridSelectionModel, GridToolbar } from "@mui/x-data-grid";
@ -24,6 +25,7 @@ interface Props {
getTariffs: () => void; getTariffs: () => void;
} }
/** @deprecated */
export default function TariffsDG({ selectedTariffs, handleSelectionChange, getTariffs }: Props) { export default function TariffsDG({ selectedTariffs, handleSelectionChange, getTariffs }: Props) {
const { token } = authStore(); const { token } = authStore();

1
src/react-app-env.d.ts vendored Normal file

@ -0,0 +1 @@
/// <reference types="react-scripts" />

@ -3,14 +3,40 @@ import { AnyDiscount } from "@root/model/cart";
import { create } from "zustand"; import { create } from "zustand";
import { devtools } from "zustand/middleware"; import { devtools } from "zustand/middleware";
import { exampleCartValues } from "./mocks/exampleCartValues"; import { exampleCartValues } from "./mocks/exampleCartValues";
import { Discount } from "@root/model/discount";
interface DiscountStore { interface DiscountStore {
discounts: AnyDiscount[]; discounts: Discount[];
selectedDiscountIds: GridSelectionModel, selectedDiscountIds: GridSelectionModel,
} }
export const useDiscountStore = create<DiscountStore>()( export const useDiscountStore = create<DiscountStore>()(
devtools(
(set, get) => ({
discounts: [],
selectedDiscountIds: [],
}),
{
name: "Real discount store",
enabled: process.env.NODE_ENV === "development"
}
)
);
export const addDiscounts = (newDiscounts: DiscountStore["discounts"]) => useDiscountStore.setState(state => ({ discounts: [...state.discounts, ...newDiscounts] }));
export const setSelectedDiscountIds = (selectedDiscountIds: DiscountStore["selectedDiscountIds"]) => useDiscountStore.setState({ selectedDiscountIds });
/** @deprecated */
interface MockDiscountStore {
discounts: AnyDiscount[];
selectedDiscountIds: GridSelectionModel,
}
/** @deprecated */
export const useMockDiscountStore = create<MockDiscountStore>()(
devtools( devtools(
(set, get) => ({ (set, get) => ({
discounts: exampleCartValues.discounts, discounts: exampleCartValues.discounts,
@ -22,11 +48,14 @@ export const useDiscountStore = create<DiscountStore>()(
) )
); );
export const addDiscounts = (newDiscounts: AnyDiscount[]) => useDiscountStore.setState(state => ({ discounts: [...state.discounts, ...newDiscounts] })); /** @deprecated */
export const addMockDiscounts = (newDiscounts: AnyDiscount[]) => useMockDiscountStore.setState(state => ({ discounts: [...state.discounts, ...newDiscounts] }));
export const setSelectedDiscountIds = (selectedDiscountIds: DiscountStore["selectedDiscountIds"]) => useDiscountStore.setState({ selectedDiscountIds }); /** @deprecated */
export const setMockSelectedDiscountIds = (selectedDiscountIds: MockDiscountStore["selectedDiscountIds"]) => useMockDiscountStore.setState({ selectedDiscountIds });
export const activateDiscounts = () => useDiscountStore.setState(state => { /** @deprecated */
export const activateMockDiscounts = () => useMockDiscountStore.setState(state => {
const discounts: AnyDiscount[] = []; const discounts: AnyDiscount[] = [];
state.discounts.forEach(discount => { state.discounts.forEach(discount => {
if (!state.selectedDiscountIds.includes(discount._id)) return discounts.push(discount); if (!state.selectedDiscountIds.includes(discount._id)) return discounts.push(discount);
@ -40,7 +69,8 @@ export const activateDiscounts = () => useDiscountStore.setState(state => {
return { discounts }; return { discounts };
}); });
export const deactivateDiscounts = () => useDiscountStore.setState(state => { /** @deprecated */
export const deactivateMockDiscounts = () => useMockDiscountStore.setState(state => {
const discounts: AnyDiscount[] = []; const discounts: AnyDiscount[] = [];
state.discounts.forEach(discount => { state.discounts.forEach(discount => {
if (!state.selectedDiscountIds.includes(discount._id)) return discounts.push(discount); if (!state.selectedDiscountIds.includes(discount._id)) return discounts.push(discount);
@ -52,4 +82,4 @@ export const deactivateDiscounts = () => useDiscountStore.setState(state => {
}); });
return { discounts }; return { discounts };
}); });

@ -2,63 +2,90 @@ import { Privilege } from "@root/model/tariff";
import { create } from "zustand"; import { create } from "zustand";
import { devtools } from "zustand/middleware"; import { devtools } from "zustand/middleware";
import { exampleCartValues } from "./mocks/exampleCartValues"; import { exampleCartValues } from "./mocks/exampleCartValues";
import { RealPrivilege } from "@root/model/privilege";
interface PrivilegeStore { interface RealPrivilegeStore {
privileges: Privilege[]; privileges: RealPrivilege[];
isModalOpen: boolean;
modalPrivilegeId: string | null;
modalPriceField: string;
addPrivileges: (newPrivileges: Privilege[]) => void;
} }
export const usePrivilegeStore = create<PrivilegeStore>()( export const useRealPrivilegeStore = create<RealPrivilegeStore>()(
devtools( devtools(
(set, get) => ({ (set, get) => ({
privileges: exampleCartValues.privileges, privileges: [],
isModalOpen: false, }),
modalPrivilegeId: null, {
modalPriceField: "", name: "Privilege store",
addPrivileges: (newPrivileges) => set((state) => ({ privileges: [...state.privileges, ...newPrivileges] })), }
}), )
{
name: "Privilege store",
}
)
); );
export const closePrivilegePriceModal = () => usePrivilegeStore.setState({ isModalOpen: false }); export const addRealPrivileges = (privileges: RealPrivilegeStore["privileges"]) => useRealPrivilegeStore.setState({ privileges });
export const openPrivilegePriceModal = (modalPrivilegeId: string | null, defaultPrice: number) =>
usePrivilegeStore.setState({
isModalOpen: true,
modalPriceField: defaultPrice.toString(),
modalPrivilegeId,
});
/** @deprecated */
interface PrivilegeStore {
privileges: Privilege[];
isModalOpen: boolean;
modalPrivilegeId: string | null;
modalPriceField: string;
addPrivileges: (newPrivileges: Privilege[]) => void;
}
/** @deprecated */
export const usePrivilegeStore = create<PrivilegeStore>()(
devtools(
(set, get) => ({
privileges: exampleCartValues.privileges,
isModalOpen: false,
modalPrivilegeId: null,
modalPriceField: "",
addPrivileges: (newPrivileges) => set((state) => ({ privileges: [...state.privileges, ...newPrivileges] })),
}),
{
name: "Mock Privilege store",
}
)
);
/** @deprecated */
export const closePrivilegePriceModal = () => usePrivilegeStore.setState({ isModalOpen: false });
/** @deprecated */
export const openPrivilegePriceModal = (modalPrivilegeId: string | null, defaultPrice: number) =>
usePrivilegeStore.setState({
isModalOpen: true,
modalPriceField: defaultPrice.toString(),
modalPrivilegeId,
});
/** @deprecated */
export const changeModalPriceField = (modalPriceField: string) => usePrivilegeStore.setState({ modalPriceField }); export const changeModalPriceField = (modalPriceField: string) => usePrivilegeStore.setState({ modalPriceField });
/** @deprecated */
export const changePrivilegePrice = () => { export const changePrivilegePrice = () => {
const { privileges, modalPrivilegeId, modalPriceField } = usePrivilegeStore.getState(); const { privileges, modalPrivilegeId, modalPriceField } = usePrivilegeStore.getState();
const privilegeIndex = privileges.findIndex((privilege) => privilege.privilegeId === modalPrivilegeId); const privilegeIndex = privileges.findIndex((privilege) => privilege.privilegeId === modalPrivilegeId);
if (privilegeIndex === -1) throw new Error("Privilege not found by id"); if (privilegeIndex === -1) throw new Error("Privilege not found by id");
const price = parseFloat(modalPriceField.replace(",", ".")); const price = parseFloat(modalPriceField.replace(",", "."));
if (!isFinite(price)) return "Error parsing price"; if (!isFinite(price)) return "Error parsing price";
const newPrivilege: Privilege = { const newPrivilege: Privilege = {
...privileges[privilegeIndex], ...privileges[privilegeIndex],
price: price, price: price,
}; };
const newPrivileges = [...privileges]; const newPrivileges = [...privileges];
newPrivileges.splice(privilegeIndex, 1, newPrivilege); newPrivileges.splice(privilegeIndex, 1, newPrivilege);
usePrivilegeStore.setState({ usePrivilegeStore.setState({
privileges: newPrivileges, privileges: newPrivileges,
isModalOpen: false, isModalOpen: false,
}); });
}; };
/** @deprecated */
export const findPrivilegeById = (privilegeId: string) => { export const findPrivilegeById = (privilegeId: string) => {
return usePrivilegeStore.getState().privileges.find((privilege) => privilege.privilegeId === privilegeId) ?? null; return usePrivilegeStore.getState().privileges.find((privilege) => privilege.privilegeId === privilegeId) ?? null;
}; };

@ -0,0 +1,31 @@
import { Discount, GetDiscountResponse } from "@root/model/discount";
import { authStore } from "@root/stores/auth";
import { useEffect } from "react";
const makeRequest = authStore.getState().makeRequest;
export default function useDiscounts({ onError, onNewDiscounts }: {
onNewDiscounts: (response: Discount[]) => void;
onError?: (error: any) => void;
}) {
useEffect(() => {
const controller = new AbortController();
makeRequest<never, GetDiscountResponse>({
url: "https://admin.pena.digital/price/discounts",
method: "get",
useToken: true,
bearer: true,
signal: controller.signal,
}).then(result => {
console.log("New discounts", result);
onNewDiscounts(result.Discounts);
}).catch(error => {
console.log("Error fetching discounts", error);
onError?.(error);
});
return () => controller.abort();
}, [onError, onNewDiscounts]);
}

@ -0,0 +1,31 @@
import { RealPrivilege } from "@root/model/privilege";
import { authStore } from "@root/stores/auth";
import { useEffect } from "react";
const makeRequest = authStore.getState().makeRequest;
export default function usePrivileges({ onError, onNewPrivileges }: {
onNewPrivileges: (response: RealPrivilege[]) => void;
onError?: (error: any) => void;
}) {
useEffect(() => {
const controller = new AbortController();
makeRequest<never, RealPrivilege[]>({
url: "https://admin.pena.digital/strator/privilege",
method: "get",
useToken: true,
bearer: true,
signal: controller.signal,
}).then(result => {
console.log("New privileges", result);
onNewPrivileges(result);
}).catch(error => {
console.log("Error fetching privileges", error);
onError?.(error);
});
return () => controller.abort();
}, [onError, onNewPrivileges]);
}