diff --git a/public/manifest.json b/public/manifest.json index 080d6c7..1f2f141 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -6,16 +6,6 @@ "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" } ], "start_url": ".", diff --git a/src/api/discounts.ts b/src/api/discounts.ts new file mode 100644 index 0000000..9c7be92 --- /dev/null +++ b/src/api/discounts.ts @@ -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; + +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({ + url: "https://admin.pena.digital/price/discount", + method: "post", + useToken: true, + bearer: true, + body: discount, + }); +}; diff --git a/src/kitUI/Cart/Cart.tsx b/src/kitUI/Cart/Cart.tsx index 07aa2bc..b833b31 100644 --- a/src/kitUI/Cart/Cart.tsx +++ b/src/kitUI/Cart/Cart.tsx @@ -19,11 +19,12 @@ import { useCartStore } from "@root/stores/cart"; import { useEffect, useState } from "react"; import { GridSelectionModel } from "@mui/x-data-grid"; 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 { useTariffStore } from "@root/stores/tariffs"; import { AnyDiscount, CartItemTotal } from "@root/model/cart"; import { findPrivilegeById } from "@root/stores/privileges"; +import { Privilege } from "@root/model/tariff"; interface Props { selectedTariffs: GridSelectionModel; @@ -46,7 +47,7 @@ interface MergedTariff { } 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 setCartTotal = useCartStore((store) => store.setCartTotal); const [couponField, setCouponField] = useState(""); diff --git a/src/kitUI/Cart/calc.ts b/src/kitUI/Cart/calc.ts index b14edcc..19852cf 100644 --- a/src/kitUI/Cart/calc.ts +++ b/src/kitUI/Cart/calc.ts @@ -10,9 +10,9 @@ import { ServiceDiscount, UserDiscount, } from "@root/model/cart"; -import { Tariff_BACKEND } from "@root/model/tariff"; import { User } from "../../model/user"; import { findPrivilegeById } from "@root/stores/privileges"; +import { SERVICE_LIST, ServiceType, Tariff } from "@root/model/tariff"; export function calcCartData({ user, diff --git a/src/model/discount.ts b/src/model/discount.ts index 49f98bc..6019599 100644 --- a/src/model/discount.ts +++ b/src/model/discount.ts @@ -1,44 +1,91 @@ +import { ServiceType } from "./tariff"; + + export type Discount = { - ID: string; - Name: string; - Layer: number; - Description: string; - Condition: { - Period?: { - From: string; - To: string; + 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?: string; }; - User?: string; - UserType?: string; - Coupon?: string; - PurchasesAmount?: number; - CartPurchasesAmount?: number; - Product?: string; - Term?: string; - Usage?: string; - PriceFrom?: number; - Group?: string; - }; - Target: { - Products?: { - ID: string; - Factor: number; - Overhelm: boolean; - }[]; - Factor?: number; - TargetScope?: string; - TargetGroup?: string; - Overhelm?: boolean; - }; - Audit: { - UpdatedAt: string; - CreatedAt: string; - DeletedAt: string; - Deleted: boolean; - }; - Deprecated: boolean; + Target: { + Products?: { + ID: string; + Factor: number; + Overhelm: boolean; + }[]; + Factor?: number; + TargetScope?: string; + TargetGroup?: string; + Overhelm?: boolean; + }; + Audit: { + UpdatedAt: string; + CreatedAt: string; + DeletedAt: string; + Deleted: boolean; + }; + Deprecated: boolean; }; -export type DiscountData = { - Discounts: Discount[]; +export type GetDiscountResponse = { + 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; + }]; + }; +}; \ No newline at end of file diff --git a/src/model/privilege.ts b/src/model/privilege.ts new file mode 100644 index 0000000..31e0cb2 --- /dev/null +++ b/src/model/privilege.ts @@ -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; + +export type PrivilegeValueType = "шаблон" | "день" | "МБ"; + +export type PrivilegeWithAmount = Omit & { amount: number; }; + +export type PrivilegeWithoutPrice = Omit; diff --git a/src/model/tariff.ts b/src/model/tariff.ts index b77640d..ce082f9 100644 --- a/src/model/tariff.ts +++ b/src/model/tariff.ts @@ -1,16 +1,16 @@ export const SERVICE_LIST = [ - { - serviceKey: "templategen", - displayName: "Шаблонизатор документов", - }, - { - serviceKey: "squiz", - displayName: "Опросник", - }, - { - serviceKey: "dwarfener", - displayName: "Аналитика сокращателя", - }, + { + serviceKey: "templategen", + displayName: "Шаблонизатор документов", + }, + { + serviceKey: "squiz", + displayName: "Опросник", + }, + { + serviceKey: "dwarfener", + displayName: "Аналитика сокращателя", + }, ] as const; 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 interface Privilege_BACKEND { - name: string; - privilegeId: string; - serviceKey: string; - amount: number; - description: string; - price: number; - type: string; - value: string; - updatedAt: string; - _id: string; + name: string; + privilegeId: string; + serviceKey: string; + amount: number; + description: string; + price: number; + type: string; + value: string; + updatedAt: string; + _id: string; } export type Tariff_BACKEND = { - _id: string, - name: string, - price: number, - isCustom: boolean, - isFront: boolean, - privilegies: Privilege_BACKEND[], - isDeleted: boolean, - createdAt: string, - updatedAt: string -} + _id: string, + name: string, + price: number, + isCustom: boolean, + isFront: boolean, + privilegies: Privilege_BACKEND[], + isDeleted: boolean, + createdAt: string, + updatedAt: string; +}; export type Tariff_FRONTEND = { - id: string, - name: string, - amount:number, - isFront: boolean, - privilegeId: string, - customPricePerUnit: number -} \ No newline at end of file + id: string, + name: string, + amount: number, + privilegeId: string, + 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; +} diff --git a/src/pages/dashboard/Content/DiscountManagement/CreateDiscount.tsx b/src/pages/dashboard/Content/DiscountManagement/CreateDiscount.tsx index 4b5dc56..2c3f15e 100644 --- a/src/pages/dashboard/Content/DiscountManagement/CreateDiscount.tsx +++ b/src/pages/dashboard/Content/DiscountManagement/CreateDiscount.tsx @@ -4,34 +4,29 @@ import Select, { SelectChangeEvent } from "@mui/material/Select"; import { useState } from "react"; import { SERVICE_LIST, ServiceType } from "@root/model/tariff"; import { CustomTextField } from "@root/kitUI/CustomTextField"; -import { usePrivilegeStore } from "@root/stores/privileges"; -import { AnyDiscount } from "@root/model/cart"; -import { nanoid } from "nanoid"; +import { addRealPrivileges, useRealPrivilegeStore } from "@root/stores/privileges"; import { addDiscounts } from "@root/stores/discounts"; 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() { const theme = useTheme(); - const privileges = usePrivilegeStore(state => state.privileges); + const privileges = useRealPrivilegeStore(state => state.privileges); const [serviceType, setServiceType] = useState("templategen"); - const [discountType, setDiscountType] = useState("Лояльность"); + const [discountType, setDiscountType] = useState("purchasesAmount"); const [discountNameField, setDiscountNameField] = useState(""); - const [discountDescription, setDiscountDescription] = useState(""); + const [discountDescriptionField, setDiscountDescriptionField] = useState(""); const [privilegeIdField, setPrivilegeIdField] = useState(""); const [discountFactorField, setDiscountFactorField] = useState("0"); const [purchasesAmountField, setPurchasesAmountField] = useState("0"); const [cartPurchasesAmountField, setCartPurchasesAmountField] = useState("0"); const [discountMinValueField, setDiscountMinValueField] = useState("0"); + usePrivileges({ onNewPrivileges: addRealPrivileges }); + const handleDiscountTypeChange = (event: React.ChangeEvent) => { setDiscountType(event.target.value as DiscountType); }; @@ -40,9 +35,9 @@ export default function CreateDiscount() { setServiceType(event.target.value as ServiceType); }; - function handleCreateDiscount() { + async function handleCreateDiscount() { 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 discountMinValue = parseFloat(discountMinValueField.replace(",", ".")); @@ -50,82 +45,28 @@ export default function CreateDiscount() { if (!isFinite(discountFactor)) return enqueueSnackbar("Поле discountFactor не число"); if (!isFinite(cartPurchasesAmount)) return enqueueSnackbar("Поле cartPurchasesAmount не число"); if (!isFinite(discountMinValue)) return enqueueSnackbar("Поле discountMinValue не число"); + if (discountType === "privilege" && !privilegeIdField) return enqueueSnackbar("Привилегия не выбрана"); - let discount: AnyDiscount; - switch (discountType) { - case "Лояльность": { - discount = { - _id: nanoid(6), - name: discountNameField, - description: discountDescription, - disabled: false, - conditionType: "purchasesAmount", - condition: { - purchasesAmount: purchasesAmount, - }, - factor: discountFactor, - }; - break; - } - case "Корзина": { - discount = { - _id: nanoid(6), - name: discountNameField, - description: discountDescription, - disabled: false, - conditionType: "cartPurchasesAmount", - condition: { - cartPurchasesAmount: cartPurchasesAmount, - }, - factor: discountFactor, - }; - break; - } - case "Сервис": { - discount = { - _id: nanoid(6), - name: discountNameField, - description: discountDescription, - disabled: false, - conditionType: "service", - condition: { - service: { - id: serviceType, - value: discountMinValue, - }, - }, - target: { - service: serviceType, - factor: discountFactor, - }, - }; - break; - } - case "Товар": { - discount = { - _id: nanoid(6), - name: discountNameField, - description: discountDescription, - disabled: false, - conditionType: "privilege", - condition: { - privilege: { - id: privilegeIdField, - value: discountMinValue, - }, - }, - target: { - products: [{ - privilegeId: privilegeIdField, - factor: discountFactor, - }], - }, - }; - break; - } + try { + const createdDiscount = await createDiscountJSON({ + cartPurchasesAmount, + discountFactor, + discountMinValue, + purchasesAmount, + discountDescription: discountDescriptionField, + discountName: discountNameField, + startDate: new Date().toISOString(), + endDate: new Date(Date.now() + 1000 * 3600 * 24 * 30).toISOString(), + serviceType, + discountType, + privilegeId: privilegeIdField, + }); + + addDiscounts([createdDiscount]); + } catch (error) { + console.log("Error creating discount", error); + enqueueSnackbar("Ошибка при создании скидки"); } - - addDiscounts([discount]); } return ( @@ -149,8 +90,8 @@ export default function CreateDiscount() { setDiscountDescription(e.target.value)} + value={discountDescriptionField} + onChange={e => setDiscountDescriptionField(e.target.value)} /> {Object.keys(discountTypes).map(type => - } label={type} /> + } + label={discountTypes[type as DiscountType]} + /> )} - {discountType === "Лояльность" && + {discountType === "purchasesAmount" && setPurchasesAmountField(e.target.value)} /> } - {discountType === "Корзина" && + {discountType === "cartPurchasesAmount" && setCartPurchasesAmountField(e.target.value)} /> } - {discountType === "Сервис" && + {discountType === "service" && <>