From a6ac175f22c25c0045bacc56de930c0af8c877ad Mon Sep 17 00:00:00 2001 From: nflnkr Date: Thu, 13 Jul 2023 21:59:23 +0300 Subject: [PATCH] fix cart & custom tariff calculations use discount type from kitui package use functions for finding discounts from kitui package remove old discount types --- package.json | 6 +- src/__mocks__/discounts.ts | 643 ------------------ src/api/discount.ts | 31 - src/components/Drawers.tsx | 13 +- src/components/TotalPrice.tsx | 98 ++- src/index.tsx | 17 +- src/model/customTariffs.ts | 8 +- src/model/discount.ts | 142 +--- src/model/privilege.ts | 4 +- src/pages/Basket/Basket.tsx | 3 +- .../TariffConstructor/CustomTariffCard.tsx | 19 +- src/pages/TariffConstructor/Summary.tsx | 25 +- .../TariffConstructor/TariffConstructor.tsx | 14 +- src/pages/TariffConstructor/TariffItem.tsx | 21 +- src/pages/Tariffs/FreeTariffCard.tsx | 6 - src/pages/Tariffs/TariffCard.tsx | 36 +- src/pages/Tariffs/Tariffs.tsx | 20 +- src/pages/Tariffs/TariffsPage.tsx | 12 +- src/stores/cart.ts | 8 +- src/stores/customTariffs.ts | 71 +- src/stores/discounts.ts | 6 +- src/utils/calcCart.ts | 18 +- src/utils/calcTariffPrices.ts | 109 +-- src/utils/hooks/useCustomTariffs.ts | 8 +- src/utils/hooks/useDiscounts.ts | 36 + src/utils/hooks/useSetDiscountsStore.ts | 89 --- yarn.lock | 25 +- 27 files changed, 288 insertions(+), 1200 deletions(-) delete mode 100644 src/__mocks__/discounts.ts delete mode 100644 src/api/discount.ts create mode 100644 src/utils/hooks/useDiscounts.ts delete mode 100644 src/utils/hooks/useSetDiscountsStore.ts diff --git a/package.json b/package.json index fe07ccb..94b85f8 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,10 @@ "dependencies": { "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", - "@frontend/kitui": "^1.0.8", + "@frontend/kitui": "^1.0.11", "@mui/icons-material": "^5.10.14", "@mui/material": "^5.10.14", - "axios": "^1.3.4", + "axios": "^1.4.0", "formik": "^2.2.9", "immer": "^10.0.2", "isomorphic-fetch": "^3.0.0", @@ -24,7 +24,7 @@ "react-router-dom": "^6.4.3", "web-vitals": "^2.1.0", "yup": "^1.1.1", - "zustand": "^4.3.6" + "zustand": "^4.3.8" }, "devDependencies": { "@craco/craco": "^7.1.0", diff --git a/src/__mocks__/discounts.ts b/src/__mocks__/discounts.ts deleted file mode 100644 index fea93e2..0000000 --- a/src/__mocks__/discounts.ts +++ /dev/null @@ -1,643 +0,0 @@ -import { AnyDiscount } from "../model/discount"; - - -export const mockDiscounts: AnyDiscount[] = [ - { - _id: "id1", - name: "Лояльность 1", - description: - "постоянная скидка для юзеров, внёсших на проект от 10 000 рублей. Применяется на итоговую сумму, после скидок за сумму в корзине", - conditionType: "purchasesAmount", - layer: 4, // "слой", т.е. этап применения скидки - condition: { - purchasesAmount: 10000, - }, - factor: 0.99, // множитель, применяемый к сумме - }, - { - _id: "id2", - name: "Лояльность 2", - description: - "постоянная скидка для юзеров, внёсших на проект от 25 000 рублей. Применяется на итоговую сумму, после скидок за сумму в корзине", - conditionType: "purchasesAmount", - layer: 4, - condition: { - purchasesAmount: 25000, - }, - factor: 0.98, - }, - { - _id: "id3", - name: "Лояльность 3", - description: - "постоянная скидка для юзеров, внёсших на проект от 50 000 рублей. Применяется на итоговую сумму, после скидок за сумму в корзине", - conditionType: "purchasesAmount", - layer: 4, - condition: { - purchasesAmount: 50000, - }, - factor: 0.975, - }, - { - _id: "id4", - name: "Корзина 1", - description: "Скидка на размер корзины от 5 000 р. Применяется на итоговую сумму, после суммирования корзины", - conditionType: "cartPurchasesAmount", - layer: 3, - condition: { - cartPurchasesAmount: 5000, - }, - factor: 0.985, - }, - { - _id: "id5", - name: "Корзина 2", - description: "Скидка на размер корзины от 50 000 р. Применяется на итоговую сумму, после суммирования корзины", - conditionType: "cartPurchasesAmount", - layer: 3, - condition: { - cartPurchasesAmount: 50000, - }, - factor: 0.965, - }, - { - _id: "id6", - name: "Анлим Шабло 1", - description: "Скидка на количество безлимитных дней работы от 30 дней", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - // если в condition условие для поиска, то вот я добавил в него условие для поиска по привилегии - id: "p1", // айди привилегии - value: 30, // скидка работает, если значние больше либо равно этому значению - }, - }, - target: { - products: [ - { - privilegeId: "p1", // не знаю, стоит ли тут оставлять массив products, но на всякий случай оставлю. т.е. скидка, при срабатывании, применяется к этой привилегии в корзине, т.е. умножает её сумму на factor - factor: 0.975, - }, - ], - }, - }, - { - _id: "id7", - name: "Анлим Шабло 2", - description: "Скидка на количество безлимитных дней работы от 90 дней", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p1", - value: 90, - }, - }, - target: { - products: [ - { - privilegeId: "p1", - factor: 0.975, - }, - ], - }, - }, - { - _id: "id8.rev", - name: "Анлим Шабло 3", - description: "Скидка на количество безлимитных дней работы от 180 дней", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p1", - value: 180, - }, - }, - target: { - products: [ - { - privilegeId: "p1", - factor: 0.93, - }, - ], - }, - }, - { - _id: "id8", - name: "Генерации Шабло 1", - description: "Скидка на количество генераций от 100 шт", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "templateCnt", - value: 100, - }, - }, - target: { - products: [ - { - privilegeId: "templateCnt", - factor: 0.995, - }, - ], - }, - }, - { - _id: "id9", - name: "Генерации Шабло 2", - description: "Скидка на количество генераций от 350 шт", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "templateCnt", - value: 350, - }, - }, - target: { - products: [ - { - privilegeId: "templateCnt", - factor: 0.98, - }, - ], - }, - }, - { - _id: "id10", - name: "Генерации Шабло 3", - description: "Скидка на количество генераций от 500 шт", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "templateCnt", - value: 500, - }, - }, - target: { - products: [ - { - privilegeId: "templateCnt", - factor: 0.945, - }, - ], - }, - }, - { - _id: "id11", - name: "Анлим Квиз 1", - description: "Скидка на количество дней безлимитного использования опросника, от 30 дней", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p3", - value: 30, - }, - }, - target: { - products: [ - { - privilegeId: "p3", - factor: 0.97, - }, - ], - }, - }, - { - _id: "id12", - name: "Анлим Квиз 2", - description: "Скидка на количество дней безлимитного использования опросника, от 90 дней", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p3", - value: 90, - }, - }, - target: { - products: [ - { - privilegeId: "p3", - factor: 0.93, - }, - ], - }, - }, - { - _id: "id13", - name: "Анлим Квиз 3", - description: "Скидка на количество дней безлимитного использования опросника, от 180 дней", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p3", - value: 180, - }, - }, - target: { - products: [ - { - privilegeId: "p3", - factor: 0.85, - }, - ], - }, - }, - { - _id: "id14", - name: "Актив квиз 1", - description: "Скидка на количество опросов от 100 шт", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p4", - value: 100, - }, - }, - target: { - products: [ - { - privilegeId: "p4", - factor: 0.98, - }, - ], - }, - }, - { - _id: "id15", - name: "Актив квиз 2", - description: "Скидка на количество опросов от 200 шт", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p4", - value: 200, - }, - }, - target: { - products: [ - { - privilegeId: "p4", - factor: 0.96, - }, - ], - }, - }, - { - _id: "id16", - name: "Актив квиз 3", - description: "Скидка на количество опросов от 350 шт", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p4", - value: 350, - }, - }, - target: { - products: [ - { - privilegeId: "p4", - factor: 0.9, - }, - ], - }, - }, - { - _id: "id17", - name: "Анлим Сокращатель 1", - description: "Скидка на безлимитное использование сокращателя от 30 дней", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p5", - value: 30, - }, - }, - target: { - products: [ - { - privilegeId: "p5", - factor: 0.99, - }, - ], - }, - }, - { - _id: "id18", - name: "Анлим Сокращатель 2", - description: "Скидка на безлимитное использование сокращателя от 60 дней", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p5", - value: 60, - }, - }, - target: { - products: [ - { - privilegeId: "p5", - factor: 0.98, - }, - ], - }, - }, - { - _id: "id19", - name: "Анлим Сокращатель 3", - description: "Скидка на безлимитное использование сокращателя от 90 дней", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p5", - value: 90, - }, - }, - target: { - products: [ - { - privilegeId: "p5", - factor: 0.97, - }, - ], - }, - }, - { - _id: "id20", - name: "АБ Сокращатель 1", - description: "Скидка на количество АБ тестов, от 10 штук", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p6", - value: 10, - }, - }, - target: { - products: [ - { - privilegeId: "p6", - factor: 0.99, - }, - ], - }, - }, - { - _id: "id22", - name: "АБ Сокращатель 2", - description: "Скидка на количество АБ тестов, от 25 штук", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p6", - value: 25, - }, - }, - target: { - products: [ - { - privilegeId: "p6", - factor: 0.965, - }, - ], - }, - }, - { - _id: "id23", - name: "АБ Сокращатель 3", - description: "Скидка на количество АБ тестов, от 50 штук", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p6", - value: 50, - }, - }, - target: { - products: [ - { - privilegeId: "p6", - factor: 0.935, - }, - ], - }, - }, - { - _id: "id24.1", - name: "Стата Сокращатель 1", - description: "Скидка на дни сбора расширенной статистики от 30 дней", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p7", - value: 30, - }, - }, - target: { - products: [ - { - privilegeId: "p7", - factor: 0.935, - }, - ], - }, - }, - { - _id: "id24.2", - name: "Стата Сокращатель 2", - description: "Скидка на дни сбора расширенной статистики от 90 дней", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p7", - value: 90, - }, - }, - target: { - products: [ - { - privilegeId: "p7", - factor: 0.875, - }, - ], - }, - }, - { - _id: "id25", - name: "Стата Сокращатель 3", - description: "Скидка на дни сбора расширенной статистики от 180 дней", - conditionType: "privilege", - layer: 1, - condition: { - privilege: { - id: "p7", - value: 180, - }, - }, - target: { - products: [ - { - privilegeId: "p7", - factor: 0.83, - }, - ], - }, - }, - { - _id: "id26", - name: "Шаблонизатор 1", - description: "Скидка на сумму стоимостей товаров, принадлежащих сервису шаблонизации, от 1000 р", - conditionType: "service", - layer: 2, - condition: { - service: { - id: "templategen", - value: 1000, - }, - }, - target: { - service: "templategen", - factor: 0.996, - }, - }, - { - _id: "id27", - name: "Шаблонизатор 2", - description: "Скидка на сумму стоимостей товаров, принадлежащих сервису шаблонизации, от 5000 р", - conditionType: "service", - layer: 2, - condition: { - service: { - id: "templategen", - value: 5000, - }, - }, - target: { - service: "templategen", - factor: 0.983, - }, - }, - { - _id: "id28", - name: "Опросник 1", - description: "Скидка на сумму стоимостей товаров, принадлежащих сервису опросника, от 2000 р", - conditionType: "service", - layer: 2, - condition: { - service: { - id: "squiz", - value: 2000, - }, - }, - target: { - service: "squiz", - factor: 0.983, - }, - }, - { - _id: "id29", - name: "Опросник 2", - description: "Скидка на сумму стоимостей товаров, принадлежащих сервису опросника, от 6000 р", - conditionType: "service", - layer: 2, - condition: { - service: { - id: "squiz", - value: 6000, - }, - }, - target: { - service: "squiz", - factor: 0.969, - }, - }, - { - _id: "id30", - name: "Сокращатель 1", - description: "Скидка на сумму стоимостей товаров, принадлежащих сервису сокращателя, от 500 р", - conditionType: "service", - layer: 2, - condition: { - service: { - id: "dwarfener", - value: 500, - }, - }, - target: { - service: "dwarfener", - factor: 0.99, - }, - }, - { - _id: "id31", - name: "Сокращатель 2", - description: "Скидка на сумму стоимостей товаров, принадлежащих сервису сокращателя, от 2500 р", - conditionType: "service", - layer: 2, - condition: { - service: { - id: "dwarfener", - value: 2500, - }, - }, - target: { - service: "dwarfener", - factor: 0.96, - }, - }, - { - _id: "id32", - name: "НКО", - description: - "Скидка всем подтвердившим статус НКО. Перекрывает ВСЕ остальные скидки. Если эта скидка срабатывает, остальные можно не вычислять. Т.е. если на уровне 0 находится какая-лидо скидка для выданных условий, просто суммируем корзину и применяем к сумме указанный множитель, после чего прекращаем процесс рассчёта", - conditionType: "userType", - layer: 0, - condition: { - userType: "nko", - }, - target: { - IsAllProducts: true, - factor: 0.2, - }, - overwhelm: true, - }, - { - _id: "id33", - name: "Промокод На АБ тесты", - description: "Скидка, полученная конкретным юзером, после введения промокода. Заменяет собой не промокодовую", - conditionType: "user", - layer: 1, - condition: { - coupon: "ABCD", // на мой вкус, стоит при активации промокода создавать скидку, привязанную к юзеру по айдишнику, и удалять после использования. т.е. кондишн не по coupon, а по - user: "buddy", - }, - target: { - products: [ - { - privilegeId: "p6", - factor: 0.5, - }, - ], - }, - overwhelm: true, - }, -]; \ No newline at end of file diff --git a/src/api/discount.ts b/src/api/discount.ts deleted file mode 100644 index e2fca8d..0000000 --- a/src/api/discount.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useEffect, useState } from "react"; -import { makeRequest } from "@frontend/kitui"; -import {useSetDiscountsStore} from "@utils/hooks/useSetDiscountsStore"; - -const apiUrl = process.env.NODE_ENV === "production" ? "" : "https://hub.pena.digital"; - -export const GetDiscounts = () => { - const [discounts, setDiscounts] = useState([]); - - useEffect(() => { - const get = async () => { - // @ts-ignore - const data = await makeRequest({ - url: apiUrl + "/price/discounts", - method: "get", - useToken: true, - }) - return data; - }; - - get() - .then(r => { - if (r) { - // @ts-ignore - setDiscounts(r.Discounts) - } - }) - }, []); - - return { discounts }; -}; diff --git a/src/components/Drawers.tsx b/src/components/Drawers.tsx index f10d6f5..13bb4b8 100644 --- a/src/components/Drawers.tsx +++ b/src/components/Drawers.tsx @@ -10,6 +10,7 @@ import { useNavigate } from "react-router"; import { useCart } from "@root/utils/hooks/useCart"; import { currencyFormatter } from "@root/utils/currencyFormatter"; import { closeCartDrawer, openCartDrawer, useCartStore } from "@root/stores/cart"; +import { useCustomTariffsStore } from "@root/stores/customTariffs"; export default function Drawers() { @@ -18,6 +19,14 @@ export default function Drawers() { const upMd = useMediaQuery(theme.breakpoints.up("md")); const isDrawerOpen = useCartStore(state => state.isDrawerOpen); const cart = useCart(); + const summaryPriceBeforeDiscountsMap = useCustomTariffsStore(state => state.summaryPriceBeforeDiscountsMap); + const summaryPriceAfterDiscountsMap = useCustomTariffsStore(state => state.summaryPriceAfterDiscountsMap); + + const basePrice = Object.values(summaryPriceBeforeDiscountsMap).reduce((a, e) => a + e, 0); + const discountedPrice = Object.values(summaryPriceAfterDiscountsMap).reduce((a, e) => a + e, 0); + + const totalPriceBeforeDiscounts = cart.priceBeforeDiscounts + basePrice; + const totalPriceAfterDiscounts = cart.priceAfterDiscounts + discountedPrice; return ( @@ -151,7 +160,7 @@ export default function Drawers() { order: upMd ? 1 : 2, }} > - {currencyFormatter.format(cart.priceBeforeDiscounts / 100)} + {currencyFormatter.format(totalPriceBeforeDiscounts / 100)} - {currencyFormatter.format(cart.priceAfterDiscounts / 100)} + {currencyFormatter.format(totalPriceAfterDiscounts / 100)} state.notEnoughMoneyAmount); const navigate = useNavigate(); + const { priceBeforeDiscounts, priceAfterDiscounts } = useCart(); + const summaryPriceBeforeDiscountsMap = useCustomTariffsStore(state => state.summaryPriceBeforeDiscountsMap); + const summaryPriceAfterDiscountsMap = useCustomTariffsStore(state => state.summaryPriceAfterDiscountsMap); + + const basePrice = Object.values(summaryPriceBeforeDiscountsMap).reduce((a, e) => a + e, 0); + const discountedPrice = Object.values(summaryPriceAfterDiscountsMap).reduce((a, e) => a + e, 0); + + const totalPriceBeforeDiscounts = priceBeforeDiscounts + basePrice; + const totalPriceAfterDiscounts = priceAfterDiscounts + discountedPrice; function handlePayClick() { // payCart().then(result => { @@ -42,24 +47,20 @@ export default function TotalPrice({ price, priceWithDiscounts }: Props) { } return ( - + - + flexWrap: "wrap", + flexDirection: "column", + }}> Итоговая цена @@ -68,34 +69,27 @@ export default function TotalPrice({ price, priceWithDiscounts }: Props) { это текст, который имеет Текст-заполнитель — это текст, который имеет Текст-заполнитель - - + + - {currencyFormatter.format(price / 100)} + {currencyFormatter.format(totalPriceBeforeDiscounts / 100)} - {currencyFormatter.format(priceWithDiscounts / 100)} + {currencyFormatter.format(totalPriceAfterDiscounts / 100)} {notEnoughMoneyAmount > 0 && diff --git a/src/index.tsx b/src/index.tsx index ab6a991..a3e857e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -25,16 +25,17 @@ import TariffConstructor from "./pages/TariffConstructor/TariffConstructor"; import { useUser } from "./utils/hooks/useUser"; import { clearAuthToken, getMessageFromFetchError } from "@frontend/kitui"; import { useUserAccount } from "./utils/hooks/useUserAccount"; -import { useSetDiscountsStore } from "./utils/hooks/useSetDiscountsStore"; -import { setCustomTariffs} from "@root/stores/customTariffs"; +import { setCustomTariffs } from "@root/stores/customTariffs"; import { useCustomTariffs } from "@root/utils/hooks/useCustomTariffs"; +import { useDiscounts } from "./utils/hooks/useDiscounts"; +import { setDiscounts } from "./stores/discounts"; const App = () => { const location = useLocation(); const userId = useUserStore(state => state.userId); const navigate = useNavigate(); - + useCustomTariffs({ url: "https://admin.pena.digital/strator/privilege/service", onNewUser: setCustomTariffs, @@ -44,8 +45,6 @@ const App = () => { } }); - useSetDiscountsStore(); - useUser({ url: `https://hub.pena.digital/user/${userId}`, userId, @@ -75,6 +74,14 @@ const App = () => { } }); + useDiscounts({ + onNewDiscounts: setDiscounts, + onError: error => { + const message = getMessageFromFetchError(error); + if (message) enqueueSnackbar(message); + } + }); + if (location.state?.redirectTo) return ; return ( diff --git a/src/model/customTariffs.ts b/src/model/customTariffs.ts index dd3c1b3..19fd7dd 100644 --- a/src/model/customTariffs.ts +++ b/src/model/customTariffs.ts @@ -1,11 +1,13 @@ import { PrivilegeWithAmount, PrivilegeWithoutPrice } from "./privilege"; +type ServiceKey = string; + export type CustomTariffUserValues = Record; -export type CustomTariffUserValuesMap = Record; +export type CustomTariffUserValuesMap = Record; -export type SummaryPriceMap = Record; +export type ServiceKeyToPriceMap = Record; export interface CustomTariff { name: string; @@ -17,4 +19,4 @@ export interface CustomTariff { createdAt?: string; } -export type CreateTariffBody = Omit & { privilegies: PrivilegeWithoutPrice[]; }; \ No newline at end of file +export type CreateTariffBody = Omit & { privilegies: PrivilegeWithoutPrice[]; }; diff --git a/src/model/discount.ts b/src/model/discount.ts index 38bbdfa..d0d7faf 100644 --- a/src/model/discount.ts +++ b/src/model/discount.ts @@ -1,102 +1,6 @@ -export interface DiscountBase { - _id: string; - name: string; - description: string; - /** Этап применения скидки */ - layer?: number; - disabled?: boolean; -} +import { Discount } from "@frontend/kitui"; -export interface PurchasesAmountDiscount extends DiscountBase { - conditionType: "purchasesAmount"; - condition: { - purchasesAmount: number; - }; - /** Множитель, на который умножается сумма при применении скидки */ - factor: number; -} -export interface CartPurchasesAmountDiscount extends DiscountBase { - conditionType: "cartPurchasesAmount"; - condition: { - cartPurchasesAmount: number; - }; - /** Множитель, на который умножается сумма при применении скидки */ - factor: number; -} - -export interface PrivilegeDiscount extends DiscountBase { - conditionType: "privilege"; - condition: { - privilege: { - id: string; - /** Скидка применяется, если значение больше или равно этому значению */ - value: number; - }; - }; - target: { - products: Array<{ - privilegeId: string; - /** Множитель, на который умножается сумма при применении скидки */ - factor: number; - }>; - }; -} - -export interface ServiceDiscount extends DiscountBase { - conditionType: "service"; - condition: { - service: { - id: string; - /** Скидка применяется, если значение больше или равно этому значению */ - value: number; - }; - }; - target: { - service: string; - /** Множитель, на который умножается сумма при применении скидки */ - factor: number; - }; -} - -export interface UserTypeDiscount extends DiscountBase { - conditionType: "userType"; - condition: { - userType: string; - }; - target: { - IsAllProducts: boolean; - /** Множитель, на который умножается сумма при применении скидки */ - factor: number; - }; - overwhelm: boolean; -} - -export interface UserDiscount extends DiscountBase { - conditionType: "user"; - condition: { - coupon: string; - user: string; - }; - target: { - products: Array<{ - privilegeId: string; - /** Множитель, на который умножается сумма при применении скидки */ - factor: number; - }>; - }; - overwhelm: boolean; -} - -export type AnyDiscount = - | PurchasesAmountDiscount - | CartPurchasesAmountDiscount - | PrivilegeDiscount - | ServiceDiscount - | UserTypeDiscount - | UserDiscount; - -export type DiscountConditionType = AnyDiscount["conditionType"]; export const SERVICE_LIST = [ { serviceKey: "templategen", @@ -113,43 +17,7 @@ export const SERVICE_LIST = [ ] as const; export type ServiceType = (typeof SERVICE_LIST)[number]["serviceKey"]; -export interface BACKEND_Discount { - ID: string; - Name: string; - Layer: number; - Description: string; - Condition: { - Period: { - From: string; - To: string; - }; - User: string; - UserType: string; - Coupon: string; - PurchasesAmount: number; - CartPurchasesAmount: number; - Product: string; - Term: string; - Usage: string; - PriceFrom: number; - Group: ServiceType; - }; - Target: { - Products: [{ - ID: string; - Factor: number; - Overhelm: boolean; - }]; - Factor: number; - TargetScope: string; - Group: ServiceType; - Overhelm: boolean; - }; - Audit: { - UpdatedAt: string; - CreatedAt: string; - DeletedAt?: string; - Deleted: boolean; - }; - Deprecated: boolean; -}; + +export interface GetDiscountsResponse { + Discounts: Discount[]; +} diff --git a/src/model/privilege.ts b/src/model/privilege.ts index 49e427e..fdedde3 100644 --- a/src/model/privilege.ts +++ b/src/model/privilege.ts @@ -12,10 +12,10 @@ export interface Privilege { createdAt?: string; }; -export type PrivilegeMap = Record; +export type ServiceKeyToPrivilegesMap = Record; export type PrivilegeValueType = "шаблон" | "день" | "МБ"; export type PrivilegeWithAmount = Omit & { amount: number; }; -export type PrivilegeWithoutPrice = Omit; \ No newline at end of file +export type PrivilegeWithoutPrice = Omit; diff --git a/src/pages/Basket/Basket.tsx b/src/pages/Basket/Basket.tsx index a55b9f8..dd16285 100644 --- a/src/pages/Basket/Basket.tsx +++ b/src/pages/Basket/Basket.tsx @@ -5,7 +5,6 @@ import TotalPrice from "@components/TotalPrice"; import CustomWrapper from "./CustomWrapper"; import ComplexNavText from "@root/components/ComplexNavText"; import { useCart } from "@root/utils/hooks/useCart"; -import {GetDiscounts} from "@root/api/discount"; export default function Basket() { @@ -49,7 +48,7 @@ export default function Basket() { /> )} - + ); } diff --git a/src/pages/TariffConstructor/CustomTariffCard.tsx b/src/pages/TariffConstructor/CustomTariffCard.tsx index bf282c3..de6c94c 100644 --- a/src/pages/TariffConstructor/CustomTariffCard.tsx +++ b/src/pages/TariffConstructor/CustomTariffCard.tsx @@ -7,9 +7,6 @@ import { cardShadow } from "@root/utils/themes/shadow"; import { currencyFormatter } from "@root/utils/currencyFormatter"; import { devlog, getMessageFromFetchError } from "@frontend/kitui"; import { enqueueSnackbar } from "notistack"; -import { findServiceDiscount } from "@root/utils/calcTariffPrices"; -import { useDiscountStore } from "@root/stores/discounts"; -import { useCartStore } from "@root/stores/cart"; interface Props { @@ -20,14 +17,12 @@ interface Props { export default function CustomTariffCard({ serviceKey, tariffs }: Props) { const theme = useTheme(); const upMd = useMediaQuery(theme.breakpoints.up("md")); - const summaryPrice = useCustomTariffsStore(state => state.summaryPrice); + const summaryPriceBeforeDiscounts = useCustomTariffsStore(state => state.summaryPriceBeforeDiscountsMap); + const summaryPriceAfterDiscounts = useCustomTariffsStore(state => state.summaryPriceAfterDiscountsMap); + + const priceBeforeDiscounts = summaryPriceBeforeDiscounts[serviceKey] ?? 0; + const priceAfterDiscounts = summaryPriceAfterDiscounts[serviceKey] ?? 0; - const tariffPrice = summaryPrice[serviceKey] ?? 0; - let discounted = tariffPrice; - const discounts = useDiscountStore.getState().discounts; - const cartservice = useCartStore.getState().cart.services.find(e => e.serviceKey === serviceKey)?.price; - const serviceDiscount = findServiceDiscount(serviceKey, tariffPrice + (cartservice || 0), discounts); - if (serviceDiscount) discounted *= serviceDiscount.target.factor; async function handleConfirmClick() { createAndSendTariff(serviceKey).then(result => { devlog(result); @@ -105,8 +100,8 @@ export default function CustomTariffCard({ serviceKey, tariffs }: Props) { gap: "20px", mb: "30px", }}> - {currencyFormatter.format(discounted / 100)} - {currencyFormatter.format(tariffPrice / 100)} + {currencyFormatter.format(priceAfterDiscounts / 100)} + {currencyFormatter.format(priceBeforeDiscounts / 100)} state.summaryPrice); + const summaryPriceBeforeDiscountsMap = useCustomTariffsStore(state => state.summaryPriceBeforeDiscountsMap); + const summaryPriceAfterDiscountsMap = useCustomTariffsStore(state => state.summaryPriceAfterDiscountsMap); - const basePrice = Object.values(summaryPrice).reduce((a,e) => a+e); - let discountedPrice = basePrice; - const discounts = useDiscountStore.getState().discounts; + const basePrice = Object.values(summaryPriceBeforeDiscountsMap).reduce((a, e) => a + e, 0); + const discountedPrice = Object.values(summaryPriceAfterDiscountsMap).reduce((a, e) => a + e, 0); - const cartDiscount = findCartDiscount(discountedPrice + useCartStore.getState().cart.priceAfterDiscounts, discounts); - if (cartDiscount) discountedPrice *= cartDiscount.factor; - - const acc = useUserStore.getState().userAccount; - if (acc) { - const loyalDiscount = findLoyalDiscount(acc!.wallet.purchasesAmount,discounts); - if (loyalDiscount) discountedPrice *= loyalDiscount.factor; - } return ( - {currencyFormatter.format(basePrice/100)} руб. + {currencyFormatter.format(basePrice / 100)} руб. - {currencyFormatter.format(discountedPrice/100)} руб. + {currencyFormatter.format(discountedPrice / 100)} руб. state.customTariffs); + const customTariffs = useCustomTariffsStore(state => state.customTariffsMap); return ( )} - + ); } diff --git a/src/pages/TariffConstructor/TariffItem.tsx b/src/pages/TariffConstructor/TariffItem.tsx index 80765d8..170491f 100644 --- a/src/pages/TariffConstructor/TariffItem.tsx +++ b/src/pages/TariffConstructor/TariffItem.tsx @@ -4,7 +4,10 @@ import CustomSlider from "@root/components/CustomSlider"; import CalendarIcon from "@root/components/icons/CalendarIcon"; import PieChartIcon from "@root/components/icons/PieChartIcon"; import { Privilege, PrivilegeValueType } from "@root/model/privilege"; +import { useCartStore } from "@root/stores/cart"; import { setCustomTariffsUserValue, useCustomTariffsStore } from "@root/stores/customTariffs"; +import { useDiscountStore } from "@root/stores/discounts"; +import { useUserStore } from "@root/stores/user"; import { formatDateWithDeclention } from "@root/utils/date"; import { getDeclension } from "@root/utils/declension"; import { useEffect, useState } from "react"; @@ -32,7 +35,10 @@ interface Props { export default function TariffPrivilegeSlider({ tariff }: Props) { const theme = useTheme(); const upMd = useMediaQuery(theme.breakpoints.up("md")); - const userValue = useCustomTariffsStore(state => state.userValues[tariff.serviceKey]?.[tariff._id]) ?? 0; + const userValue = useCustomTariffsStore(state => state.userValuesMap[tariff.serviceKey]?.[tariff._id]) ?? 0; + const discounts = useDiscountStore(state => state.discounts); + const currentCartTotal = useCartStore(state => state.cart.priceAfterDiscounts); + const purchasesAmount = useUserStore(state => state.userAccount?.wallet.purchasesAmount) ?? 0; const [value, setValue] = useState(userValue); const throttledValue = useThrottle(value, 200); @@ -57,8 +63,15 @@ export default function TariffPrivilegeSlider({ tariff }: Props) { } useEffect(function setStoreValue() { - setCustomTariffsUserValue(tariff.serviceKey, tariff._id, throttledValue); - }, [tariff._id, tariff.serviceKey, throttledValue]); + setCustomTariffsUserValue( + tariff.serviceKey, + tariff._id, + throttledValue, + discounts, + currentCartTotal, + purchasesAmount + ); + }, [currentCartTotal, discounts, purchasesAmount, tariff._id, tariff.serviceKey, throttledValue]); return ( @@ -96,4 +109,4 @@ export default function TariffPrivilegeSlider({ tariff }: Props) { ); -} \ No newline at end of file +} diff --git a/src/pages/Tariffs/FreeTariffCard.tsx b/src/pages/Tariffs/FreeTariffCard.tsx index 1912ef4..11b1ee1 100644 --- a/src/pages/Tariffs/FreeTariffCard.tsx +++ b/src/pages/Tariffs/FreeTariffCard.tsx @@ -12,19 +12,13 @@ export default function FreeTariffCard() { color="#7E2AEA" backgroundColor="white" />} - buttonText="Выбрать" headerText="бесплатно" text="Текст-заполнитель — это текст, который имеет " - onButtonClick={undefined} price={0 руб.} sx={{ backgroundColor: "#7E2AEA", color: "white", }} - buttonSx={{ - color: "white", - borderColor: "white", - }} /> ); } diff --git a/src/pages/Tariffs/TariffCard.tsx b/src/pages/Tariffs/TariffCard.tsx index 4b9d40b..51e2a00 100644 --- a/src/pages/Tariffs/TariffCard.tsx +++ b/src/pages/Tariffs/TariffCard.tsx @@ -10,13 +10,15 @@ interface Props { headerText: string; text: string | string[]; sx?: SxProps; - buttonSx?: SxProps; - onButtonClick?: MouseEventHandler; - buttonText: string; + buttonProps?: { + sx?: SxProps; + onClick?: MouseEventHandler; + text?: string; + }; price?: ReactNode; } -export default function TariffCard({ icon, headerText, text, sx, buttonText, onButtonClick, price, buttonSx }: Props) { +export default function TariffCard({ icon, headerText, text, sx, price, buttonProps }: Props) { const theme = useTheme(); return ( @@ -66,18 +68,20 @@ export default function TariffCard({ icon, headerText, text, sx, buttonText, onB mb: "10px", }}>{text} } - - {buttonText} - + {buttonProps && + + {buttonProps.text} + + } ); } diff --git a/src/pages/Tariffs/Tariffs.tsx b/src/pages/Tariffs/Tariffs.tsx index e8034ef..237ffdf 100644 --- a/src/pages/Tariffs/Tariffs.tsx +++ b/src/pages/Tariffs/Tariffs.tsx @@ -44,24 +44,30 @@ export default function Tariffs() { icon={} headerText="Тарифы на время" text="безлимит на 1 месяц , 3 , 6 , 12" - buttonText="Подробнее" - onButtonClick={() => navigate("time")} + buttonProps={{ + text: "Подробнее", + onClick: () => navigate("time") + }} sx={{ maxWidth: "360px" }} /> } headerText="Тариф на объем" text="200 шаблонов, 1000 шаблонов, 5000 шаблонов, 10 000 шаблонов" - buttonText="Подробнее" - onButtonClick={() => navigate("volume")} + buttonProps={{ + text: "Подробнее", + onClick: () => navigate("volume") + }} sx={{ maxWidth: "360px" }} /> } headerText="Кастом" text="Текст-заполнитель — это текст, который имеет " - buttonText="Подробнее" - onButtonClick={() => navigate("/tariffconstructor")} + buttonProps={{ + text: "Подробнее", + onClick: () => navigate("/tariffconstructor") + }} sx={{ maxWidth: "360px" }} /> @@ -114,4 +120,4 @@ export default function Tariffs() { {/**/} ); -} \ No newline at end of file +} diff --git a/src/pages/Tariffs/TariffsPage.tsx b/src/pages/Tariffs/TariffsPage.tsx index b44246e..c137d4d 100644 --- a/src/pages/Tariffs/TariffsPage.tsx +++ b/src/pages/Tariffs/TariffsPage.tsx @@ -10,7 +10,7 @@ import { CustomTab } from "@root/components/CustomTab"; import TariffCard from "./TariffCard"; import NumberIcon from "@root/components/NumberIcon"; import { currencyFormatter } from "@root/utils/currencyFormatter"; -import { calcTariffPrices } from "@root/utils/calcTariffPrices"; +import { calcIndividualTariffPrices } from "@root/utils/calcTariffPrices"; import { getMessageFromFetchError } from "@frontend/kitui"; import FreeTariffCard from "./FreeTariffCard"; import { addTariffToCart } from "@root/stores/user"; @@ -25,7 +25,7 @@ export default function TariffPage() { const tariffs = useTariffStore(state => state.tariffs); const [tabIndex, setTabIndex] = useState(0); const discounts = useDiscountStore(state => state.discounts); - const customTariffs = useCustomTariffsStore(state => state.customTariffs); + const customTariffs = useCustomTariffsStore(state => state.customTariffsMap); const unit: string = String(location.pathname).slice(9); @@ -55,7 +55,7 @@ export default function TariffPage() { }); const tariffElements = filteredTariffs.map((tariff, index) => { - const { price, priceWithDiscounts } = calcTariffPrices(tariff,discounts,customTariffs); + const { price, priceWithDiscounts } = calcIndividualTariffPrices(tariff, discounts, customTariffs); return ( } - buttonText="Выбрать" + buttonProps={{ + text: "Выбрать", + onClick: () => handleTariffItemClick(tariff._id), + }} headerText={tariff.name} text={tariff.privilegies.map(p => `${p.name} - ${p.amount}`)} - onButtonClick={() => handleTariffItemClick(tariff._id)} price={<> {price !== undefined && price !== priceWithDiscounts && {currencyFormatter.format(price / 100)} diff --git a/src/stores/cart.ts b/src/stores/cart.ts index 5c4b766..958aebc 100644 --- a/src/stores/cart.ts +++ b/src/stores/cart.ts @@ -53,12 +53,13 @@ export const addCartTariffs = (tariffs: Tariff[]) => useCartStore.setState( state.cartTariffMap[tariff._id] = tariff; }); const cartTariffs = Object.values(state.cartTariffMap).filter((tariff): tariff is Tariff => typeof tariff === "object"); - state.cart = calcCart(cartTariffs); + const cart = calcCart(cartTariffs); + state.cart = cart; }), false, { type: tariffs.length > 0 ? "addCartTariffs" : "rejected", - tariffIds: tariffs.map(tariff => tariff._id), + tariffs, } ); @@ -72,8 +73,7 @@ export const removeMissingCartTariffs = (tariffIds: string[]) => useCartStore.se }), false, { - type: "removeMissingCartTariffs", - tariffIds, + type: "rejected" } ); diff --git a/src/stores/customTariffs.ts b/src/stores/customTariffs.ts index 04f70f2..05eb91a 100644 --- a/src/stores/customTariffs.ts +++ b/src/stores/customTariffs.ts @@ -1,23 +1,24 @@ import { createTariff } from "@root/api/tariff"; -import { CustomTariffUserValuesMap, SummaryPriceMap } from "@root/model/customTariffs"; -import { PrivilegeMap, PrivilegeWithoutPrice, PrivilegeWithAmount } from "@root/model/privilege"; +import { CustomTariffUserValuesMap, ServiceKeyToPriceMap } from "@root/model/customTariffs"; +import { ServiceKeyToPrivilegesMap, PrivilegeWithoutPrice } from "@root/model/privilege"; import { produce } from "immer"; import { create } from "zustand"; import { devtools, persist } from "zustand/middleware"; -import { findPrivilegeDiscount } from "@root/utils/calcTariffPrices"; -import { useDiscountStore } from "@root/stores/discounts"; +import { Discount, findCartDiscount, findLoyaltyDiscount, findPrivilegeDiscount, findServiceDiscount } from "@frontend/kitui"; interface CustomTariffsStore { - customTariffs: PrivilegeMap; - userValues: CustomTariffUserValuesMap; - summaryPrice: SummaryPriceMap; + customTariffsMap: ServiceKeyToPrivilegesMap; + userValuesMap: CustomTariffUserValuesMap; + summaryPriceBeforeDiscountsMap: ServiceKeyToPriceMap; + summaryPriceAfterDiscountsMap: ServiceKeyToPriceMap; } const initialState: CustomTariffsStore = { - customTariffs: {}, - userValues: {}, - summaryPrice: {}, + customTariffsMap: {}, + userValuesMap: {}, + summaryPriceBeforeDiscountsMap: {}, + summaryPriceAfterDiscountsMap: {}, }; export const useCustomTariffsStore = create()( @@ -31,35 +32,51 @@ export const useCustomTariffsStore = create()( { name: "customTariffs", partialize: state => ({ - userValues: state.userValues, - summaryPrice: state.summaryPrice, + userValuesMap: state.userValuesMap, + summaryPriceBeforeDiscountsMap: state.summaryPriceBeforeDiscountsMap, + summaryPriceAfterDiscountsMap: state.summaryPriceAfterDiscountsMap, }), + migrate: () => initialState, } ) ); -export const setCustomTariffs = (customTariffs: PrivilegeMap) => useCustomTariffsStore.setState({ customTariffs }); +export const setCustomTariffs = (customTariffs: ServiceKeyToPrivilegesMap) => useCustomTariffsStore.setState({ customTariffsMap: customTariffs }); export const setCustomTariffsUserValue = ( serviceKey: string, privilegeId: string, value: number, + discounts: Discount[], + currentCartTotal: number, + purchasesAmount: number, ) => useCustomTariffsStore.setState( produce(state => { - state.userValues[serviceKey] ??= {}; - state.userValues[serviceKey][privilegeId] = value; + state.userValuesMap[serviceKey] ??= {}; + state.userValuesMap[serviceKey][privilegeId] = value; - const sum = state.customTariffs[serviceKey].reduce((acc, tariff) => { - const amount = state.userValues[serviceKey]?.[tariff._id] ?? 0; - const discounts = useDiscountStore.getState().discounts; - const discount = findPrivilegeDiscount({privilegeId: tariff.privilegeId, amount: amount} as PrivilegeWithAmount, discounts); - //console.log(discount) - if (discount) - return acc + tariff.price * amount * discount.target.products[0].factor; - return acc + tariff.price * amount; - }, 0); + let priceWithoutDiscounts = 0; + let priceAfterDiscounts = 0; - state.summaryPrice[serviceKey] = sum; + state.customTariffsMap[serviceKey].forEach(tariff => { + const amount = state.userValuesMap[serviceKey]?.[tariff._id] ?? 0; + priceWithoutDiscounts += tariff.price * amount; + + const discount = findPrivilegeDiscount(tariff.privilegeId, tariff.price * amount, discounts); + priceAfterDiscounts += tariff.price * amount * discount.factor; + }); + + const serviceDiscount = findServiceDiscount(serviceKey, priceAfterDiscounts, discounts); + priceAfterDiscounts *= serviceDiscount.factor; + + const cartDiscount = findCartDiscount(currentCartTotal, discounts); + priceAfterDiscounts *= cartDiscount.factor; + + const loyaltyDiscount = findLoyaltyDiscount(purchasesAmount, discounts); + priceAfterDiscounts *= loyaltyDiscount.factor; + + state.summaryPriceBeforeDiscountsMap[serviceKey] = priceWithoutDiscounts; + state.summaryPriceAfterDiscountsMap[serviceKey] = priceAfterDiscounts; }) ); @@ -68,10 +85,10 @@ export const createAndSendTariff = (serviceKey: string) => { const privilegies: PrivilegeWithoutPrice[] = []; - Object.entries(state.userValues[serviceKey]).forEach(([privilegeId, userValue]) => { + Object.entries(state.userValuesMap[serviceKey]).forEach(([privilegeId, userValue]) => { if (userValue === 0) return; - const privilege = state.customTariffs[serviceKey].find(p => p._id === privilegeId); + const privilege = state.customTariffsMap[serviceKey].find(p => p._id === privilegeId); if (!privilege) throw new Error(`Privilege not found: ${privilegeId}`); const p2 = { diff --git a/src/stores/discounts.ts b/src/stores/discounts.ts index 809ca83..0535ad2 100644 --- a/src/stores/discounts.ts +++ b/src/stores/discounts.ts @@ -1,10 +1,10 @@ -import { AnyDiscount } from "@root/model/discount"; +import { Discount } from "@frontend/kitui"; import { create } from "zustand"; import { devtools } from "zustand/middleware"; interface DiscountStore { - discounts: AnyDiscount[]; + discounts: Discount[]; } export const useDiscountStore = create()( @@ -19,4 +19,4 @@ export const useDiscountStore = create()( ) ); -export const setDiscounts = (discounts: AnyDiscount[]) => useDiscountStore.setState({ discounts }); +export const setDiscounts = (discounts: DiscountStore["discounts"]) => useDiscountStore.setState({ discounts }); diff --git a/src/utils/calcCart.ts b/src/utils/calcCart.ts index 5ab45bc..1166269 100644 --- a/src/utils/calcCart.ts +++ b/src/utils/calcCart.ts @@ -1,7 +1,6 @@ +import { findCartDiscount, findLoyaltyDiscount, findPrivilegeDiscount, findServiceDiscount } from "@frontend/kitui"; import { CartData, PrivilegeCartData } from "@root/model/cart"; -import { AnyDiscount } from "@root/model/discount"; import { Tariff } from "@root/model/tariff"; -import { findPrivilegeDiscount, findServiceDiscount, findCartDiscount, findLoyalDiscount } from "./calcTariffPrices"; import { useDiscountStore } from "@root/stores/discounts"; import { useUserStore } from "@root/stores/user"; @@ -33,12 +32,11 @@ export function calcCart(tariffs: Tariff[]): CartData { if (!tariff.price) cartData.priceBeforeDiscounts += privilegePrice; - const privilegeDiscount = findPrivilegeDiscount(privilege, discounts); - if (privilegeDiscount) privilegePrice *= privilegeDiscount.target.products[0].factor; + const privilegeDiscount = findPrivilegeDiscount(privilege.privilegeId, privilege.price * privilege.amount, discounts); + privilegePrice *= privilegeDiscount.factor; const serviceDiscount = findServiceDiscount(privilege.serviceKey, privilegePrice, discounts); - if (serviceDiscount) privilegePrice *= serviceDiscount.target.factor; - + privilegePrice *= serviceDiscount.factor; const privilegeData: PrivilegeCartData = { tariffId: tariff._id, @@ -54,11 +52,11 @@ export function calcCart(tariffs: Tariff[]): CartData { }); }); - const cartDiscount = findCartDiscount(cartData.priceAfterDiscounts, discounts) - if (cartDiscount) cartData.priceAfterDiscounts *= cartDiscount.factor; + const cartDiscount = findCartDiscount(cartData.priceAfterDiscounts, discounts); + cartData.priceAfterDiscounts *= cartDiscount.factor; - const loyalDiscount = findLoyalDiscount(useUserStore.getState().userAccount!.wallet.purchasesAmount, discounts) - if (loyalDiscount) cartData.priceAfterDiscounts *= loyalDiscount.factor; + const loyalDiscount = findLoyaltyDiscount(useUserStore.getState().userAccount!.wallet.purchasesAmount, discounts); + cartData.priceAfterDiscounts *= loyalDiscount.factor; return cartData; } diff --git a/src/utils/calcTariffPrices.ts b/src/utils/calcTariffPrices.ts index 4fe7178..32f35a3 100644 --- a/src/utils/calcTariffPrices.ts +++ b/src/utils/calcTariffPrices.ts @@ -1,124 +1,43 @@ import { Tariff } from "@root/model/tariff"; -import { PrivilegeMap} from "@root/model/privilege"; -import { PrivilegeWithAmount } from "@root/model/privilege"; -import { AnyDiscount, PrivilegeDiscount, ServiceDiscount, CartPurchasesAmountDiscount, PurchasesAmountDiscount } from "../model/discount"; +import { ServiceKeyToPrivilegesMap } from "@root/model/privilege"; import { useCartStore } from "@root/stores/cart"; import { useUserStore } from "@root/stores/user"; +import { Discount, findCartDiscount, findLoyaltyDiscount, findPrivilegeDiscount, findServiceDiscount } from "@frontend/kitui"; -export function calcTariffPrices(tariff: Tariff, discounts: AnyDiscount[], privilegies: PrivilegeMap): { +export function calcIndividualTariffPrices(tariff: Tariff, discounts: Discount[], privilegies: ServiceKeyToPrivilegesMap): { price: number | undefined; priceWithDiscounts: number | undefined; } { let price = tariff.price || tariff.privilegies.reduce((sum, privilege) => sum + privilege.amount * privilege.price, 0); - - console.log(useCartStore.getState().cart) let priceWithDiscounts = tariff.privilegies.reduce((sum, privilege) => { let privilegePrice = privilege.amount * privilege.price; - let realprivilegie = privilegies[privilege.serviceKey].find(e => e._id === privilege.privilegeId) - if (realprivilegie) privilege.privilegeId = realprivilegie.privilegeId - const privilegeDiscount = findPrivilegeDiscount(privilege, discounts); - if (privilegeDiscount) privilegePrice *= privilegeDiscount.target.products[0].factor; + + let realprivilegie = privilegies[privilege.serviceKey].find(e => e._id === privilege.privilegeId); + if (realprivilegie) privilege.privilegeId = realprivilegie.privilegeId; + + const privilegeDiscount = findPrivilegeDiscount(privilege.privilegeId, privilege.price * privilege.amount, discounts); + privilegePrice *= privilegeDiscount.factor; + const serviceCartData = useCartStore.getState().cart.services.find(e => e.serviceKey === privilege.serviceKey); let serviceprice = 0; if (serviceCartData) serviceprice = serviceCartData.price; const serviceDiscount = findServiceDiscount(privilege.serviceKey, privilegePrice + serviceprice, discounts); - if (serviceDiscount) privilegePrice *= serviceDiscount.target.factor; + if (serviceDiscount) privilegePrice *= serviceDiscount.factor; return sum + privilegePrice; }, 0); const cartDiscount = findCartDiscount(priceWithDiscounts + useCartStore.getState().cart.priceAfterDiscounts, discounts); - if (cartDiscount) priceWithDiscounts *= cartDiscount.factor; + priceWithDiscounts *= cartDiscount.factor; const acc = useUserStore.getState(); - const loyalDiscount = findLoyalDiscount(acc!.userAccount!.wallet.purchasesAmount, discounts); - if (loyalDiscount) priceWithDiscounts *= loyalDiscount.factor; - + const loyalDiscount = findLoyaltyDiscount(acc!.userAccount!.wallet.purchasesAmount, discounts); + priceWithDiscounts *= loyalDiscount.factor; return { price, priceWithDiscounts, }; } - -export function findPrivilegeDiscount(privilege: PrivilegeWithAmount, discounts: AnyDiscount[]): PrivilegeDiscount | null { - const applicableDiscounts = discounts.filter((discount): discount is PrivilegeDiscount => { - return ( - discount.conditionType === "privilege" && - privilege.privilegeId === discount.condition.privilege.id && - privilege.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 - ); - - return maxValueDiscount; -} - -export function findServiceDiscount( - serviceKey: string, - currentPrice: number, - discounts: AnyDiscount[], -): ServiceDiscount | null { - const discountsForTariffService = discounts.filter((discount): discount is ServiceDiscount => { - return ( - discount.conditionType === "service" && - discount.condition.service.id === serviceKey && - currentPrice >= discount.condition.service.value - ); - }); - - if (!discountsForTariffService.length) return null; - - const maxValueDiscount = discountsForTariffService.reduce((prev, current) => { - return current.condition.service.value > prev.condition.service.value ? current : prev; - }); - - return maxValueDiscount; -} - -export function findCartDiscount( - currentPrice: number, - discounts: AnyDiscount[], -): CartPurchasesAmountDiscount | null { - const discountsForCart = discounts.filter((discount) : discount is CartPurchasesAmountDiscount => { - return ( - discount.conditionType === "cartPurchasesAmount" && - currentPrice >= discount.condition.cartPurchasesAmount - ) - }) - - if (!discountsForCart.length) return null; - - const maxValueDiscount = discountsForCart.reduce((prev, current) => { - return current.condition.cartPurchasesAmount > prev.condition.cartPurchasesAmount ? current : prev; - }) - - return maxValueDiscount; -} - -export function findLoyalDiscount( - currentPrice: number, - discounts: AnyDiscount[], -): PurchasesAmountDiscount | null { - const discountsForCart = discounts.filter((discount) : discount is PurchasesAmountDiscount => { - return ( - discount.conditionType === "purchasesAmount" && - currentPrice >= discount.condition.purchasesAmount - ) - }) - - if (!discountsForCart.length) return null; - - const maxValueDiscount = discountsForCart.reduce((prev, current) => { - return current.condition.purchasesAmount > prev.condition.purchasesAmount ? current : prev; - }) - - return maxValueDiscount; -} diff --git a/src/utils/hooks/useCustomTariffs.ts b/src/utils/hooks/useCustomTariffs.ts index 542e7af..084740e 100644 --- a/src/utils/hooks/useCustomTariffs.ts +++ b/src/utils/hooks/useCustomTariffs.ts @@ -1,11 +1,11 @@ import { devlog, makeRequest } from "@frontend/kitui"; -import { PrivilegeMap } from "@root/model/privilege"; +import { ServiceKeyToPrivilegesMap } from "@root/model/privilege"; import { useEffect, useLayoutEffect, useRef } from "react"; export function useCustomTariffs({ onError, onNewUser, url }: { url: string; - onNewUser: (response: PrivilegeMap) => void; + onNewUser: (response: ServiceKeyToPrivilegesMap) => void; onError: (error: any) => void; }) { const onNewUserRef = useRef(onNewUser); @@ -19,7 +19,7 @@ export function useCustomTariffs({ onError, onNewUser, url }: { useEffect(() => { const controller = new AbortController(); - makeRequest({ + makeRequest({ url, signal: controller.signal, method: "get", @@ -33,4 +33,4 @@ export function useCustomTariffs({ onError, onNewUser, url }: { return () => controller.abort(); }, [url]); -} \ No newline at end of file +} diff --git a/src/utils/hooks/useDiscounts.ts b/src/utils/hooks/useDiscounts.ts new file mode 100644 index 0000000..e73bbf6 --- /dev/null +++ b/src/utils/hooks/useDiscounts.ts @@ -0,0 +1,36 @@ +import { Discount, devlog, makeRequest } from "@frontend/kitui"; +import { GetDiscountsResponse } from "@root/model/discount"; +import { useEffect, useLayoutEffect, useRef } from "react"; + + +export function useDiscounts({ url = "https://admin.pena.digital/price/discounts", onNewDiscounts, onError }: { + url?: string; + onNewDiscounts: (response: Discount[]) => void; + onError: (error: Error) => void; +}) { + const onNewTariffsRef = useRef(onNewDiscounts); + const onErrorRef = useRef(onError); + + useLayoutEffect(() => { + onNewTariffsRef.current = onNewDiscounts; + onErrorRef.current = onError; + }, [onError, onNewDiscounts]); + + useEffect(() => { + const controller = new AbortController(); + + makeRequest({ + url, + method: "get", + useToken: true, + signal: controller.signal, + }).then((result) => { + onNewTariffsRef.current(result.Discounts); + }).catch(error => { + devlog("Error fetching tariffs", error); + onErrorRef.current(error); + }); + + return () => controller.abort(); + }, [url]); +} diff --git a/src/utils/hooks/useSetDiscountsStore.ts b/src/utils/hooks/useSetDiscountsStore.ts deleted file mode 100644 index 55dd6a2..0000000 --- a/src/utils/hooks/useSetDiscountsStore.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { useEffect } from "react"; -import {GetDiscounts} from "@root/api/discount"; -import {setDiscounts} from "@stores/discounts"; -import {BACKEND_Discount} from "@root/model/discount"; - - -export const useSetDiscountsStore = () => { - const gotten = GetDiscounts() - console.log(gotten) - // const gotten:any = getDiscounts() - - useEffect(() => { - if (gotten.discounts !== undefined) { - let readyArray = gotten.discounts.map((discount:BACKEND_Discount) => { - if (discount.Condition.PurchasesAmount > 0) return { - _id: discount.ID, - name: discount.Name, - description: discount.Description, - conditionType: "purchasesAmount", - layer: 4, - disabled: discount.Deprecated || discount.Audit.Deleted, - condition:{ - purchasesAmount: discount.Condition.PurchasesAmount - }, - factor: discount.Target.Factor - } - if (discount.Condition.CartPurchasesAmount > 0) return { - _id: discount.ID, - name: discount.Name, - description: discount.Description, - conditionType: "cartPurchasesAmount", - layer: 3, - disabled: discount.Deprecated || discount.Audit.Deleted, - condition:{ - cartPurchasesAmount: discount.Condition.CartPurchasesAmount - }, - factor: discount.Target.Factor - } - if (discount.Condition.Product !== "") return { - _id: discount.ID, - name: discount.Name, - description: discount.Description, - conditionType: "privilege", - layer: 1, - disabled: discount.Deprecated || discount.Audit.Deleted, - condition:{ - privilege: { - id: discount.Condition.Product, - value: Number(discount.Condition.Term) - } - }, - target: { - products: [{ - privilegeId: discount.Target.Products[0].ID, - factor: discount.Target.Products[0].Factor - }] - }, - factor: discount.Target.Factor - } - if (discount.Condition.Group as string !== "") return { - _id: discount.ID, - name: discount.Name, - description: discount.Description, - conditionType: "service", - layer: 2, - disabled: discount.Deprecated || discount.Audit.Deleted, - condition:{ - service: { - id: discount.Condition.Group, - value: discount.Condition.PriceFrom - } - }, - target: { - service: discount.Target.Group, - factor: discount.Target.Factor - } - } - }) - console.log(readyArray) - // @ts-ignore - setDiscounts(readyArray) - - } - }, [gotten.discounts]); - - - - return null -}; diff --git a/yarn.lock b/yarn.lock index 88316f8..da1e802 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1450,11 +1450,12 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@frontend/kitui@^1.0.8": - version "1.0.8" - resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/@frontend/kitui/-/@frontend/kitui-1.0.8.tgz#9d0619a558db6b417250dceaee60ec61af18b3fd" - integrity sha1-nQYZpVjba0FyUNzq7mDsYa8Ys/0= +"@frontend/kitui@^1.0.11": + version "1.0.11" + resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/@frontend/kitui/-/@frontend/kitui-1.0.11.tgz#c23c2cb3bcac6ed314e6193de6c56c57132e21a4" + integrity sha1-wjwss7ysbtMU5hk95sVsVxMuIaQ= dependencies: + immer "^10.0.2" reconnecting-eventsource "^1.6.2" "@humanwhocodes/config-array@^0.11.6": @@ -3271,10 +3272,10 @@ axe-core@^4.4.3: resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.5.2.tgz" integrity sha512-u2MVsXfew5HBvjsczCv+xlwdNnB1oQR9HlAcsejZttNjKKSkeDNVwB1vMThIUIFI9GoT57Vtk8iQLwqOfAkboA== -axios@^1.3.4: - version "1.3.4" - resolved "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz" - integrity sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ== +axios@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f" + integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA== dependencies: follow-redirects "^1.15.0" form-data "^4.0.0" @@ -10614,9 +10615,9 @@ yup@^1.1.1: toposort "^2.0.2" type-fest "^2.19.0" -zustand@^4.3.6: - version "4.3.6" - resolved "https://registry.npmjs.org/zustand/-/zustand-4.3.6.tgz" - integrity sha512-6J5zDxjxLE+yukC2XZWf/IyWVKnXT9b9HUv09VJ/bwGCpKNcaTqp7Ws28Xr8jnbvnZcdRaidztAPsXFBIqufiw== +zustand@^4.3.8: + version "4.3.9" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.3.9.tgz#a7d4332bbd75dfd25c6848180b3df1407217f2ad" + integrity sha512-Tat5r8jOMG1Vcsj8uldMyqYKC5IZvQif8zetmLHs9WoZlntTHmIoNM8TpLRY31ExncuUvUOXehd0kvahkuHjDw== dependencies: use-sync-external-store "1.2.0"