Изменение таблицы, дополнение привелегий

This commit is contained in:
ArtChaos189 2023-06-02 17:10:26 +03:00
parent 035e3f0655
commit 724d17fe75
12 changed files with 1559 additions and 1533 deletions

@ -0,0 +1,30 @@
import { usePrivilegeStore } from "@root/stores/privileges";
import { usePrivilegies } from "./privilege.hook";
export type mergedPrivilege = {
createdAt?: string;
description: string;
isDeleted?: boolean;
name: string;
price: string | number;
privilegeId: string;
serviceKey: string;
type: "count" | "day" | "mb";
updatedAt?: string;
value?: string;
_id?: string;
};
export const useCombinedPrivileges = () => {
const { privilegies, isError, errorMessage } = usePrivilegies();
const examplePrivileges = usePrivilegeStore((state) => state.privileges);
const mergedPrivileges: mergedPrivilege[] = [];
if (privilegies) {
mergedPrivileges.push(...privilegies.Шаблонизатор, ...examplePrivileges);
}
return { mergedPrivileges, isError, errorMessage };
};

@ -102,8 +102,8 @@ root.render(
</PrivateRoute>
}
/>
{componentsArray.map((e: any, i) => (
<Route key={e} path={e[0]} element={e[1]} />
{componentsArray.map((element: any) => (
<Route key={element} path={element[0]} element={element[1]} />
))}
</Route>

@ -1,263 +1,291 @@
import { CartItem, AnyDiscount, CartTotal, CartItemTotal, PrivilegeDiscount, CartPurchasesAmountDiscount, PurchasesAmountDiscount, ServiceToPriceMap, ServiceDiscount, UserDiscount } from "@root/model/cart";
import {
CartItem,
AnyDiscount,
CartTotal,
CartItemTotal,
PrivilegeDiscount,
CartPurchasesAmountDiscount,
PurchasesAmountDiscount,
ServiceToPriceMap,
ServiceDiscount,
UserDiscount,
} from "@root/model/cart";
import { ServiceType, SERVICE_LIST, Tariff } from "../../model/tariff";
import { User } from "../../model/user";
import { findPrivilegeById } from "@root/stores/privileges";
export function calcCartData({ user, purchasesAmount, cartItems, discounts, isNonCommercial = false, coupon }: {
user: User;
purchasesAmount: number;
cartItems: CartItem[];
discounts: AnyDiscount[];
isNonCommercial?: boolean;
coupon?: string;
export function calcCartData({
user,
purchasesAmount,
cartItems,
discounts,
isNonCommercial = false,
coupon,
}: {
user: User;
purchasesAmount: number;
cartItems: CartItem[];
discounts: AnyDiscount[];
isNonCommercial?: boolean;
coupon?: string;
}): CartTotal | Error | null {
let isIncompatibleTariffs = false;
let isIncompatibleTariffs = false;
const defaultTariffTypePresent: { [Key in ServiceType]: boolean } = {
dwarfener: false,
squiz: false,
templategen: false,
};
const defaultTariffTypePresent: { [Key in ServiceType]: boolean } = {
dwarfener: false,
squiz: false,
templategen: false,
};
cartItems.forEach(cartItem => {
const privilege = findPrivilegeById(cartItem.tariff.privilegeId);
if (!privilege) throw new Error(`Привилегия с id ${cartItem.tariff} не найдена в тарифе ${cartItem.tariff.name} с id ${cartItem.tariff.id}`);
cartItems.forEach((cartItem) => {
const privilege = findPrivilegeById(cartItem.tariff.privilegeId);
if (!privilege)
throw new Error(
`Привилегия с id ${cartItem.tariff} не найдена в тарифе ${cartItem.tariff.name} с id ${cartItem.tariff.id}`
);
if (cartItem.tariff.customPricePerUnit === undefined) return defaultTariffTypePresent[privilege.serviceKey] = true;
if (cartItem.tariff.customPricePerUnit === undefined)
return (defaultTariffTypePresent[privilege.serviceKey] = true);
if (
defaultTariffTypePresent[privilege.serviceKey] &&
cartItem.tariff.customPricePerUnit !== undefined
) isIncompatibleTariffs = true;
if (defaultTariffTypePresent[privilege.serviceKey] && cartItem.tariff.customPricePerUnit !== undefined)
isIncompatibleTariffs = true;
});
if (isIncompatibleTariffs)
return new Error("Если взят готовый тариф, то кастомный на этот сервис сделать уже нельзя");
if (!cartItems.length) return null;
const cartTotal: CartTotal = {
items: [],
totalPrice: 0,
priceByService: {
templategen: 0,
squiz: 0,
dwarfener: 0,
},
discountsByService: {
templategen: null,
squiz: null,
dwarfener: null,
},
envolvedCartDiscounts: [],
couponState: coupon ? "not found" : null,
};
// layer 0
for (const discount of discounts) {
if (discount.conditionType !== "userType" || !isNonCommercial) continue;
cartItems.forEach((cartItem) => {
cartTotal.items.push({
envolvedDiscounts: [],
tariff: cartItem.tariff,
totalPrice: cartItem.price,
});
const privilege = findPrivilegeById(cartItem.tariff.privilegeId);
if (!privilege)
throw new Error(`Привилегия не найдена в тарифе ${cartItem.tariff.name} с id ${cartItem.tariff.id}`);
cartTotal.priceByService[privilege.serviceKey] += cartItem.price;
cartTotal.totalPrice += cartItem.price;
});
if (isIncompatibleTariffs) return new Error("Если взят готовый тариф, то кастомный на этот сервис сделать уже нельзя");
if (!cartItems.length) return null;
const cartTotal: CartTotal = {
items: [],
totalPrice: 0,
priceByService: {
templategen: 0,
squiz: 0,
dwarfener: 0,
},
discountsByService: {
templategen: null,
squiz: null,
dwarfener: null,
},
envolvedCartDiscounts: [],
couponState: coupon ? "not found" : null,
};
// layer 0
for (const discount of discounts) {
if (discount.conditionType !== "userType" || !isNonCommercial) continue;
cartItems.forEach(cartItem => {
cartTotal.items.push({
envolvedDiscounts: [],
tariff: cartItem.tariff,
totalPrice: cartItem.price,
});
const privilege = findPrivilegeById(cartItem.tariff.privilegeId);
if (!privilege) throw new Error(`Привилегия не найдена в тарифе ${cartItem.tariff.name} с id ${cartItem.tariff.id}`);
cartTotal.priceByService[privilege.serviceKey] += cartItem.price;
cartTotal.totalPrice += cartItem.price;
});
cartTotal.totalPrice *= discount.target.factor;
cartTotal.envolvedCartDiscounts.push(discount);
return cartTotal;
}
const couponDiscount = coupon ? findUserDiscount(discounts, user, coupon) : null;
// layer 1
for (const cartItem of cartItems) {
const cartItemTotal: CartItemTotal = {
tariff: cartItem.tariff,
envolvedDiscounts: [],
totalPrice: cartItem.price,
};
const tariff = cartItem.tariff;
const privilegesAffectedByCoupon: string[] = [];
couponDiscount?.target.products.forEach(product => {
if (product.privilegeId !== tariff.privilegeId) return;
if (tariff.customPricePerUnit !== undefined && !couponDiscount.overwhelm) return;
cartItemTotal.totalPrice *= product.factor;
cartItemTotal.envolvedDiscounts.push(couponDiscount);
cartTotal.couponState = "applied";
privilegesAffectedByCoupon.push(product.privilegeId);
});
const privilegeDiscount = findMaxApplicablePrivilegeDiscount(discounts, tariff);
privilegeDiscount?.target.products.forEach(product => {
if (product.privilegeId !== tariff.privilegeId) return;
if (tariff.customPricePerUnit !== undefined) return;
if (privilegesAffectedByCoupon.includes(privilegeDiscount.condition.privilege.id)) return;
cartItemTotal.totalPrice *= product.factor;
cartItemTotal.envolvedDiscounts.push(privilegeDiscount);
});
const privilege = findPrivilegeById(cartItem.tariff.privilegeId);
if (!privilege) throw new Error(`Привилегия не найдена в тарифе ${cartItem.tariff.name} с id ${cartItem.tariff.id}`);
cartTotal.items.push(cartItemTotal);
cartTotal.priceByService[privilege.serviceKey] += cartItemTotal.totalPrice;
}
// layer 2
SERVICE_LIST.map(service => service.serviceKey).forEach(service => {
const serviceDiscount = findMaxServiceDiscount(service, discounts, cartTotal.priceByService);
if (serviceDiscount) {
cartTotal.priceByService[service] *= serviceDiscount.target.factor;
cartTotal.discountsByService[service] = serviceDiscount;
}
cartTotal.totalPrice += cartTotal.priceByService[service];
});
// layer 3
const cartPurchasesAmountDiscount = findMaxCartPurchasesAmountDiscount(discounts, cartTotal);
if (cartPurchasesAmountDiscount) {
cartTotal.totalPrice *= cartPurchasesAmountDiscount.factor;
cartTotal.envolvedCartDiscounts.push(cartPurchasesAmountDiscount);
}
// layer 4
const totalPurchasesAmountDiscount = findMaxTotalPurchasesAmountDiscount(discounts, purchasesAmount);
if (totalPurchasesAmountDiscount) {
cartTotal.totalPrice *= totalPurchasesAmountDiscount.factor;
cartTotal.envolvedCartDiscounts.push(totalPurchasesAmountDiscount);
}
cartTotal.totalPrice *= discount.target.factor;
cartTotal.envolvedCartDiscounts.push(discount);
return cartTotal;
}
const couponDiscount = coupon ? findUserDiscount(discounts, user, coupon) : null;
// layer 1
for (const cartItem of cartItems) {
const cartItemTotal: CartItemTotal = {
tariff: cartItem.tariff,
envolvedDiscounts: [],
totalPrice: cartItem.price,
};
const tariff = cartItem.tariff;
const privilegesAffectedByCoupon: string[] = [];
couponDiscount?.target.products.forEach((product) => {
if (product.privilegeId !== tariff.privilegeId) return;
if (tariff.customPricePerUnit !== undefined && !couponDiscount.overwhelm) return;
cartItemTotal.totalPrice *= product.factor;
cartItemTotal.envolvedDiscounts.push(couponDiscount);
cartTotal.couponState = "applied";
privilegesAffectedByCoupon.push(product.privilegeId);
});
const privilegeDiscount = findMaxApplicablePrivilegeDiscount(discounts, tariff);
privilegeDiscount?.target.products.forEach((product) => {
if (product.privilegeId !== tariff.privilegeId) return;
if (tariff.customPricePerUnit !== undefined) return;
if (privilegesAffectedByCoupon.includes(privilegeDiscount.condition.privilege.id)) return;
cartItemTotal.totalPrice *= product.factor;
cartItemTotal.envolvedDiscounts.push(privilegeDiscount);
});
const privilege = findPrivilegeById(cartItem.tariff.privilegeId);
if (!privilege)
throw new Error(`Привилегия не найдена в тарифе ${cartItem.tariff.name} с id ${cartItem.tariff.id}`);
cartTotal.items.push(cartItemTotal);
cartTotal.priceByService[privilege.serviceKey] += cartItemTotal.totalPrice;
}
// layer 2
SERVICE_LIST.map((service) => service.serviceKey).forEach((service) => {
const serviceDiscount = findMaxServiceDiscount(service, discounts, cartTotal.priceByService);
if (serviceDiscount) {
cartTotal.priceByService[service] *= serviceDiscount.target.factor;
cartTotal.discountsByService[service] = serviceDiscount;
}
cartTotal.totalPrice += cartTotal.priceByService[service];
});
// layer 3
const cartPurchasesAmountDiscount = findMaxCartPurchasesAmountDiscount(discounts, cartTotal);
if (cartPurchasesAmountDiscount) {
cartTotal.totalPrice *= cartPurchasesAmountDiscount.factor;
cartTotal.envolvedCartDiscounts.push(cartPurchasesAmountDiscount);
}
// layer 4
const totalPurchasesAmountDiscount = findMaxTotalPurchasesAmountDiscount(discounts, purchasesAmount);
if (totalPurchasesAmountDiscount) {
cartTotal.totalPrice *= totalPurchasesAmountDiscount.factor;
cartTotal.envolvedCartDiscounts.push(totalPurchasesAmountDiscount);
}
return cartTotal;
}
function findMaxApplicablePrivilegeDiscount(discounts: AnyDiscount[], tariff: Tariff): PrivilegeDiscount | null {
const applicableDiscounts = discounts.filter((discount): discount is PrivilegeDiscount => {
return (
discount.conditionType === "privilege" &&
tariff.privilegeId === discount.condition.privilege.id &&
tariff.amount >= discount.condition.privilege.value
);
});
if (!applicableDiscounts.length) return null;
const maxValueDiscount = applicableDiscounts.reduce(
(prev, current) => current.condition.privilege.value > prev.condition.privilege.value ? current : prev
const applicableDiscounts = discounts.filter((discount): discount is PrivilegeDiscount => {
return (
discount.conditionType === "privilege" &&
tariff.privilegeId === discount.condition.privilege.id &&
tariff.amount >= discount.condition.privilege.value
);
});
return maxValueDiscount;
if (!applicableDiscounts.length) return null;
const maxValueDiscount = applicableDiscounts.reduce((prev, current) =>
current.condition.privilege.value > prev.condition.privilege.value ? current : prev
);
return maxValueDiscount;
}
function findMaxCartPurchasesAmountDiscount(discounts: AnyDiscount[], cartTotal: CartTotal): CartPurchasesAmountDiscount | null {
const applicableDiscounts = discounts.filter((discount): discount is CartPurchasesAmountDiscount => {
return discount.conditionType === "cartPurchasesAmount" && cartTotal.totalPrice >= discount.condition.cartPurchasesAmount;
});
if (!applicableDiscounts.length) return null;
const maxValueDiscount = applicableDiscounts.reduce(
(prev, current) => current.condition.cartPurchasesAmount > prev.condition.cartPurchasesAmount ? current : prev
function findMaxCartPurchasesAmountDiscount(
discounts: AnyDiscount[],
cartTotal: CartTotal
): CartPurchasesAmountDiscount | null {
const applicableDiscounts = discounts.filter((discount): discount is CartPurchasesAmountDiscount => {
return (
discount.conditionType === "cartPurchasesAmount" && cartTotal.totalPrice >= discount.condition.cartPurchasesAmount
);
});
return maxValueDiscount;
if (!applicableDiscounts.length) return null;
const maxValueDiscount = applicableDiscounts.reduce((prev, current) =>
current.condition.cartPurchasesAmount > prev.condition.cartPurchasesAmount ? current : prev
);
return maxValueDiscount;
}
function findMaxTotalPurchasesAmountDiscount(discounts: AnyDiscount[], purchasesAmount: number): PurchasesAmountDiscount | null {
const applicableDiscounts = discounts.filter((discount): discount is PurchasesAmountDiscount => {
return discount.conditionType === "purchasesAmount" && purchasesAmount >= discount.condition.purchasesAmount;
});
function findMaxTotalPurchasesAmountDiscount(
discounts: AnyDiscount[],
purchasesAmount: number
): PurchasesAmountDiscount | null {
const applicableDiscounts = discounts.filter((discount): discount is PurchasesAmountDiscount => {
return discount.conditionType === "purchasesAmount" && purchasesAmount >= discount.condition.purchasesAmount;
});
if (!applicableDiscounts.length) return null;
if (!applicableDiscounts.length) return null;
const maxValueDiscount = applicableDiscounts.reduce(
(prev, current) => current.condition.purchasesAmount > prev.condition.purchasesAmount ? current : prev
);
const maxValueDiscount = applicableDiscounts.reduce((prev, current) =>
current.condition.purchasesAmount > prev.condition.purchasesAmount ? current : prev
);
return maxValueDiscount;
return maxValueDiscount;
}
function findMaxServiceDiscount(
service: ServiceType,
discounts: AnyDiscount[],
priceByService: ServiceToPriceMap,
service: ServiceType,
discounts: AnyDiscount[],
priceByService: ServiceToPriceMap
): ServiceDiscount | null {
const discountsForTariffService = discounts.filter((discount): discount is ServiceDiscount => {
return (
discount.conditionType === "service" &&
discount.condition.service.id === service &&
priceByService[service] >= discount.condition.service.value
);
});
const discountsForTariffService = discounts.filter((discount): discount is ServiceDiscount => {
return (
discount.conditionType === "service" &&
discount.condition.service.id === service &&
priceByService[service] >= discount.condition.service.value
);
});
if (!discountsForTariffService.length) return null;
if (!discountsForTariffService.length) return null;
const maxValueDiscount = discountsForTariffService.reduce((prev, current) => {
return current.condition.service.value > prev.condition.service.value ? current : prev;
});
const maxValueDiscount = discountsForTariffService.reduce((prev, current) => {
return current.condition.service.value > prev.condition.service.value ? current : prev;
});
return maxValueDiscount;
return maxValueDiscount;
}
function findUserDiscount(discounts: AnyDiscount[], user: User, coupon: string,): UserDiscount | null {
const userDiscount = discounts.find((discount): discount is UserDiscount => {
return (
discount.conditionType === "user" &&
discount.condition.user === user.ID &&
discount.condition.coupon === coupon
);
});
function findUserDiscount(discounts: AnyDiscount[], user: User, coupon: string): UserDiscount | null {
const userDiscount = discounts.find((discount): discount is UserDiscount => {
return (
discount.conditionType === "user" && discount.condition.user === user.ID && discount.condition.coupon === coupon
);
});
return userDiscount ?? null;
return userDiscount ?? null;
}
export function createCartItem(tariff: Tariff): CartItem {
const pricePerUnit = tariff.customPricePerUnit ?? findPrivilegeById(tariff.privilegeId)?.pricePerUnit ?? 0;
const price = pricePerUnit * tariff.amount;
const pricePerUnit = tariff.customPricePerUnit ?? findPrivilegeById(tariff.privilegeId)?.price ?? 0;
const price = pricePerUnit * tariff.amount;
return { tariff, price, id: "someId" };
return { tariff, price, id: "someId" };
}
export function findDiscountFactor(discount: AnyDiscount): number {
switch (discount.conditionType) {
case "cartPurchasesAmount":
return discount.factor;
case "purchasesAmount":
return discount.factor;
case "privilege": {
const product = discount.target.products[0];
if (!product) throw new Error("Discount target product not found");
switch (discount.conditionType) {
case "cartPurchasesAmount":
return discount.factor;
case "purchasesAmount":
return discount.factor;
case "privilege": {
const product = discount.target.products[0];
if (!product) throw new Error("Discount target product not found");
return product.factor;
}
case "user": {
const product = discount.target.products[0];
if (!product) throw new Error("Discount target product not found");
return product.factor;
}
case "service":
return discount.target.factor;
case "userType":
return discount.target.factor;
return product.factor;
}
case "user": {
const product = discount.target.products[0];
if (!product) throw new Error("Discount target product not found");
return product.factor;
}
case "service":
return discount.target.factor;
case "userType":
return discount.target.factor;
}
}
export function formatDiscountFactor(factor: number): string {
return `${((1 - factor) * 100).toFixed(1)}%`;
return `${((1 - factor) * 100).toFixed(1)}%`;
}

@ -25,7 +25,7 @@ export interface Privilege {
/** Единица измерения привелегии: время в днях/кол-во */
type: "day" | "count";
/** Стоимость одной единицы привелегии */
pricePerUnit: number;
price: number;
}
export interface Tariff {

@ -7,9 +7,9 @@ import ModeEditOutlineOutlinedIcon from "@mui/icons-material/ModeEditOutlineOutl
interface CardPrivilegie {
name: string;
type: "count" | "day" | "mb";
price: string;
price: string | number;
description: string;
value: string;
value?: string;
privilegeId: string;
serviceKey: string;
}

@ -1,16 +1,16 @@
import { Typography } from "@mui/material";
import { СardPrivilegie } from "./CardPrivilegie";
import { usePrivilegies } from "@root/hooks/privilege.hook";
import { useCombinedPrivileges } from "@root/hooks/useCombinedPrivileges.hook";
export default function ListPrivilegie() {
const { privilegies, isError, isLoading, errorMessage } = usePrivilegies();
const { mergedPrivileges, isError, errorMessage } = useCombinedPrivileges();
return (
<>
{isError ? (
<Typography>{errorMessage}</Typography>
) : (
privilegies?.Шаблонизатор.map(({ name, type, price, description, value, privilegeId, serviceKey, _id }) => (
mergedPrivileges.map(({ name, type, price, description, value, privilegeId, serviceKey, _id }) => (
<СardPrivilegie
key={_id}
name={name}

@ -1,131 +1,158 @@
import { Typography, Container, Button, Select, MenuItem, FormControl, InputLabel, useTheme, Box } from "@mui/material";
import { useCombinedPrivileges } from "@root/hooks/useCombinedPrivileges.hook";
import { CustomTextField } from "@root/kitUI/CustomTextField";
import { Tariff } from "@root/model/tariff";
import { findPrivilegeById, usePrivilegeStore } from "@root/stores/privileges";
import { addTariffs } from "@root/stores/tariffs";
import { nanoid } from "nanoid";
import { useState } from "react";
export default function CreateTariff() {
const theme = useTheme();
const privileges = usePrivilegeStore(store => store.privileges);
const [nameField, setNameField] = useState<string>("");
const [amountField, setAmountField] = useState<string>("");
const [customPriceField, setCustomPriceField] = useState<string>("");
const [privilegeIdField, setPrivilegeIdField] = useState<string | "">("");
const theme = useTheme();
const [nameField, setNameField] = useState<string>("");
const [amountField, setAmountField] = useState<string>("");
const [customPriceField, setCustomPriceField] = useState<string>("");
const [privilegeIdField, setPrivilegeIdField] = useState<string>("");
const { mergedPrivileges, isError, errorMessage } = useCombinedPrivileges();
const privilege = findPrivilegeById(privilegeIdField);
const findPrivilegeById = (privilegeId: string) => {
return mergedPrivileges.find((privilege) => privilege.privilegeId === privilegeId) ?? null;
};
function handleCreateTariffClick() {
const amount = Number(amountField);
const customPrice = Number(customPriceField);
const privilege = findPrivilegeById(privilegeIdField);
if (isNaN(amount) || !privilege) return;
function handleCreateTariffClick() {
const amount = Number(amountField);
const customPrice = Number(customPriceField);
const newTariff: Tariff = {
id: nanoid(5),
name: nameField,
amount,
privilegeId: privilege.privilegeId,
customPricePerUnit: customPrice ? customPrice / amount : undefined,
};
if (isNaN(amount) || !privilege) return;
addTariffs([newTariff]);
}
const newTariff: Tariff = {
id: nanoid(5),
name: nameField,
amount,
privilegeId: privilege.privilegeId,
customPricePerUnit: customPrice ? customPrice / amount : undefined,
};
return (
<Container sx={{
p: "20px",
border: "1px solid rgba(224, 224, 224, 1)",
borderRadius: "4px",
addTariffs([newTariff]);
}
console.log(mergedPrivileges);
return (
<Container
sx={{
p: "20px",
border: "1px solid rgba(224, 224, 224, 1)",
borderRadius: "4px",
display: "flex",
flexDirection: "column",
gap: "12px",
}}
>
<Typography variant="h6" sx={{ textAlign: "center", mb: "16px" }}>
Создание тарифа
</Typography>
<FormControl
fullWidth
sx={{
height: "52px",
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>
{isError ? (
<Typography>{errorMessage}</Typography>
) : (
<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,
border: "1px solid",
},
".MuiSvgIcon-root ": {
fill: theme.palette.secondary.main,
},
}}
inputProps={{ sx: { pt: "12px" } }}
>
{mergedPrivileges.map((privilege) => (
<MenuItem key={privilege.description} value={privilege.privilegeId}>
{privilege.description}
</MenuItem>
))}
</Select>
)}
</FormControl>
{privilege && (
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "12px",
}}>
<Typography variant="h6" sx={{ textAlign: "center", mb: "16px" }}>Создание тарифа</Typography>
<FormControl
fullWidth
sx={{
height: "52px",
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,
border: "1px solid",
},
".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>
{privilege &&
<Box sx={{
display: "flex",
flexDirection: "column",
}}>
<Typography>Имя: <span>{privilege.name}</span></Typography>
<Typography>Сервис: <span>{privilege.serviceKey}</span></Typography>
<Typography>Единица: <span>{privilege.type}</span></Typography>
<Typography>Стандартная цена за единицу: <span>{privilege.pricePerUnit}</span></Typography>
</Box>
}
<CustomTextField
id="tariff-name"
label="Название тарифа"
value={nameField}
onChange={e => setNameField(e.target.value)}
/>
<CustomTextField
id="tariff-amount"
label="Кол-во единиц привилегии"
value={amountField}
onChange={e => setAmountField(e.target.value)}
type="number"
/>
<CustomTextField
id="tariff-custom-price"
label="Кастомная цена (не обязательно)"
value={customPriceField}
onChange={e => setCustomPriceField(e.target.value)}
type="number"
/>
<Button
onClick={handleCreateTariffClick}
disabled={privilegeIdField === "" || amountField === "" || nameField === ""}
>Создать</Button>
</Container>
);
}
}}
>
<Typography>
Имя: <span>{privilege.name}</span>
</Typography>
<Typography>
Сервис: <span>{privilege.serviceKey}</span>
</Typography>
<Typography>
Единица: <span>{privilege.type}</span>
</Typography>
<Typography>
Стандартная цена за единицу: <span>{privilege.price}</span>
</Typography>
</Box>
)}
<CustomTextField
id="tariff-name"
label="Название тарифа"
value={nameField}
onChange={(e) => setNameField(e.target.value)}
/>
<CustomTextField
id="tariff-amount"
label="Кол-во единиц привилегии"
value={amountField}
onChange={(e) => setAmountField(e.target.value)}
type="number"
/>
<CustomTextField
id="tariff-custom-price"
label="Кастомная цена (не обязательно)"
value={customPriceField}
onChange={(e) => setCustomPriceField(e.target.value)}
type="number"
/>
<Button
onClick={handleCreateTariffClick}
disabled={privilegeIdField === "" || amountField === "" || nameField === ""}
>
Создать
</Button>
</Container>
);
}

@ -1,42 +1,43 @@
import { Box, Button, Dialog, useTheme } from "@mui/material";
import { CustomTextField } from "@root/kitUI/CustomTextField";
import { changeModalPriceField, changePrivilegePrice, closePrivilegePriceModal, usePrivilegeStore } from "@root/stores/privileges";
import {
changeModalPriceField,
changePrivilegePrice,
closePrivilegePriceModal,
usePrivilegeStore,
} from "@root/stores/privileges";
import { enqueueSnackbar } from "notistack";
export default function ChangePriceModal() {
const theme = useTheme();
const isModalOpen = usePrivilegeStore(state => state.isModalOpen);
const modalPriceField = usePrivilegeStore(state => state.modalPriceField);
const theme = useTheme();
const isModalOpen = usePrivilegeStore((state) => state.isModalOpen);
const modalPriceField = usePrivilegeStore((state) => state.modalPriceField);
function handleSaveChange() {
const errorMessage = changePrivilegePrice();
if (errorMessage) enqueueSnackbar(errorMessage);
}
function handleSaveChange() {
const errorMessage = changePrivilegePrice();
if (errorMessage) enqueueSnackbar(errorMessage);
}
return (
<Dialog
open={isModalOpen}
onClose={closePrivilegePriceModal}
>
<Box sx={{
p: "20px",
backgroundColor: theme.palette.grayLight.main,
display: "flex",
flexDirection: "column",
gap: "8px",
}}>
<CustomTextField
id="privilege-custom-price"
label="Цена"
value={modalPriceField}
onChange={e => changeModalPriceField(e.target.value)}
type="number"
/>
<Button
onClick={handleSaveChange}
>Сохранить</Button>
</Box>
</Dialog>
);
}
return (
<Dialog open={isModalOpen} onClose={closePrivilegePriceModal}>
<Box
sx={{
p: "20px",
backgroundColor: theme.palette.grayLight.main,
display: "flex",
flexDirection: "column",
gap: "8px",
}}
>
<CustomTextField
id="privilege-custom-price"
label="Цена"
value={modalPriceField}
onChange={(e) => changeModalPriceField(e.target.value)}
type="number"
/>
<Button onClick={handleSaveChange}>Сохранить</Button>
</Box>
</Dialog>
);
}

@ -1,51 +1,38 @@
import { GridColDef } from "@mui/x-data-grid";
import DataGrid from "@kitUI/datagrid";
import { openPrivilegePriceModal, usePrivilegeStore } from "@stores/privileges";
import { IconButton } from "@mui/material";
import { MouseEventHandler } from "react";
import EditIcon from '@mui/icons-material/Edit';
import { useCombinedPrivileges } from "@root/hooks/useCombinedPrivileges.hook";
import { Typography } from "@mui/material";
const columns: GridColDef[] = [
{ field: 'id', headerName: 'id', width: 40 },
{ field: 'name', headerName: 'Привелегия', width: 150 },
{ field: 'description', headerName: 'Описание', width: 550 },//инфо из гитлаба.
{ field: 'type', headerName: 'Тип', width: 150 },
{ field: 'price', headerName: 'Стоимость', width: 50 },
{
field: "changeValue",
headerName: "Изменить",
sortable: false,
renderCell: (params) => {
const onClick: MouseEventHandler<HTMLButtonElement> = () => {
openPrivilegePriceModal(params.row.id, params.row.price);
};
return (
<IconButton onClick={onClick}>
<EditIcon />
</IconButton>
);
},
},
{ field: "id", headerName: "id", width: 40 },
{ field: "name", headerName: "Привелегия", width: 150 },
{ field: "description", headerName: "Описание", width: 550 }, //инфо из гитлаба.
{ field: "type", headerName: "Тип", width: 150 },
{ field: "price", headerName: "Стоимость", width: 100 },
];
export default function Privileges() {
const privileges = usePrivilegeStore(state => state.privileges);
const { mergedPrivileges, isError, errorMessage } = useCombinedPrivileges();
const privilegesGridData = privileges.map(privilege => ({
id: privilege.privilegeId,
name: privilege.name,
description: privilege.description,
type: privilege.type,
price: privilege.pricePerUnit,
}));
const privilegesGridData = mergedPrivileges.map((privilege) => ({
id: privilege.privilegeId,
name: privilege.name,
description: privilege.description,
type: privilege.type,
price: privilege.price,
}));
return (
return (
<>
{isError ? (
<Typography>{errorMessage}</Typography>
) : (
<DataGrid
// checkboxSelection={true}
rows={privilegesGridData}
columns={columns}
// checkboxSelection={true}
rows={privilegesGridData}
columns={columns}
/>
);
)}
</>
);
}

@ -29,13 +29,17 @@ export default function TariffsDG({ handleSelectionChange }: Props) {
const gridData = tariffs.map((tariff) => ({
id: tariff.id,
name: tariff.name,
serviceName: SERVICE_LIST.find((service) => service.serviceKey === findPrivilegeById(tariff.privilegeId)?.serviceKey)?.displayName,
privilege: `(${tariff.privilegeId}) ${findPrivilegeById(tariff.privilegeId)?.description ?? "Привилегия не найдена"}`,
serviceName: SERVICE_LIST.find(
(service) => service.serviceKey === findPrivilegeById(tariff.privilegeId)?.serviceKey
)?.displayName,
privilege: `(${tariff.privilegeId}) ${
findPrivilegeById(tariff.privilegeId)?.description ?? "Привилегия не найдена"
}`,
amount: tariff.amount,
type: findPrivilegeById(tariff.privilegeId)?.type === "count" ? "день" : "шт.",
pricePerUnit: tariff.customPricePerUnit ?? findPrivilegeById(tariff.privilegeId)?.pricePerUnit,
pricePerUnit: tariff.customPricePerUnit ?? findPrivilegeById(tariff.privilegeId)?.price,
isCustomPrice: tariff.customPricePerUnit === undefined ? "Нет" : "Да",
total: tariff.amount * (tariff.customPricePerUnit ?? findPrivilegeById(tariff.privilegeId)?.pricePerUnit ?? 0),
total: tariff.amount * (tariff.customPricePerUnit ?? findPrivilegeById(tariff.privilegeId)?.price ?? 0),
}));
return (

File diff suppressed because it is too large Load Diff

@ -3,66 +3,62 @@ import { create } from "zustand";
import { devtools } from "zustand/middleware";
import { exampleCartValues } from "./mocks/exampleCartValues";
interface PrivilegeStore {
privileges: Privilege[];
isModalOpen: boolean;
modalPrivilegeId: string | null;
modalPriceField: string;
addPrivileges: (newPrivileges: Privilege[]) => void;
privileges: Privilege[];
isModalOpen: boolean;
modalPrivilegeId: string | null;
modalPriceField: string;
addPrivileges: (newPrivileges: Privilege[]) => void;
}
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: "Privilege store"
}
)
devtools(
(set, get) => ({
privileges: exampleCartValues.privileges,
isModalOpen: false,
modalPrivilegeId: null,
modalPriceField: "",
addPrivileges: (newPrivileges) => set((state) => ({ privileges: [...state.privileges, ...newPrivileges] })),
}),
{
name: "Privilege store",
}
)
);
export const closePrivilegePriceModal = () => usePrivilegeStore.setState({ isModalOpen: false });
export const openPrivilegePriceModal = (modalPrivilegeId: string | null, defaultPrice: number) => usePrivilegeStore.setState(
{
isModalOpen: true,
modalPriceField: defaultPrice.toString(),
modalPrivilegeId,
}
);
export const openPrivilegePriceModal = (modalPrivilegeId: string | null, defaultPrice: number) =>
usePrivilegeStore.setState({
isModalOpen: true,
modalPriceField: defaultPrice.toString(),
modalPrivilegeId,
});
export const changeModalPriceField = (modalPriceField: string) => usePrivilegeStore.setState({ modalPriceField });
export const changePrivilegePrice = () => {
const { privileges, modalPrivilegeId, modalPriceField } = usePrivilegeStore.getState();
const { privileges, modalPrivilegeId, modalPriceField } = usePrivilegeStore.getState();
const privilegeIndex = privileges.findIndex(privilege => privilege.privilegeId === modalPrivilegeId);
if (privilegeIndex === -1) throw new Error("Privilege not found by id");
const privilegeIndex = privileges.findIndex((privilege) => privilege.privilegeId === modalPrivilegeId);
if (privilegeIndex === -1) throw new Error("Privilege not found by id");
const price = parseFloat(modalPriceField.replace(",", "."));
if (!isFinite(price)) return "Error parsing price";
const price = parseFloat(modalPriceField.replace(",", "."));
if (!isFinite(price)) return "Error parsing price";
const newPrivilege: Privilege = {
...privileges[privilegeIndex],
pricePerUnit: price,
};
const newPrivilege: Privilege = {
...privileges[privilegeIndex],
price: price,
};
const newPrivileges = [...privileges];
newPrivileges.splice(privilegeIndex, 1, newPrivilege);
const newPrivileges = [...privileges];
newPrivileges.splice(privilegeIndex, 1, newPrivilege);
usePrivilegeStore.setState({
privileges: newPrivileges,
isModalOpen: false,
});
usePrivilegeStore.setState({
privileges: newPrivileges,
isModalOpen: false,
});
};
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;
};