From 001eb4ee025ce37bf3028d0604ac3aaff36eddee Mon Sep 17 00:00:00 2001 From: nflnkr Date: Fri, 22 Mar 2024 13:27:15 +0300 Subject: [PATCH 01/17] fix promocode creation error display --- src/api/promocode/requests.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/api/promocode/requests.ts b/src/api/promocode/requests.ts index f28ee26..1e7e114 100644 --- a/src/api/promocode/requests.ts +++ b/src/api/promocode/requests.ts @@ -2,6 +2,7 @@ import { makeRequest } from "@frontend/kitui"; import { CreatePromocodeBody, GetPromocodeListBody, Promocode, PromocodeList } from "@root/model/promocodes"; import { parseAxiosError } from "@root/utils/parse-error"; +import { isAxiosError } from "axios"; const baseUrl = process.env.REACT_APP_DOMAIN + "/codeword/promocode"; @@ -38,6 +39,10 @@ const createPromocode = async (body: CreatePromocodeBody) => { return createPromocodeResponse; } catch (nativeError) { + if (isAxiosError(nativeError) && nativeError.response?.data.error === "Duplicate Codeword") { + throw new Error(`Промокод уже существует`); + } + const [error] = parseAxiosError(nativeError); throw new Error(`Ошибка создания промокода. ${error}`); } From 3ef684fdfef7060850f11646fcdae0d019c3faf5 Mon Sep 17 00:00:00 2001 From: nflnkr Date: Fri, 22 Mar 2024 13:27:36 +0300 Subject: [PATCH 02/17] fix promocode creation date field --- .../Content/PromocodeManagement/CreatePromocodeForm.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/pages/dashboard/Content/PromocodeManagement/CreatePromocodeForm.tsx b/src/pages/dashboard/Content/PromocodeManagement/CreatePromocodeForm.tsx index 9ce1870..8b99a10 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/CreatePromocodeForm.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/CreatePromocodeForm.tsx @@ -10,7 +10,6 @@ import { } from "@mui/material"; import { DesktopDatePicker } from "@mui/x-date-pickers/DesktopDatePicker"; import { Field, Form, Formik } from "formik"; -import moment from "moment"; import { useEffect, useState } from "react"; import { requestPrivileges } from "@root/services/privilegies.service"; @@ -135,10 +134,8 @@ export const CreatePromocodeForm = ({ createPromocode }: Props) => { as={DesktopDatePicker} inputFormat="DD/MM/YYYY" value={values.dueTo ? new Date(Number(values.dueTo) * 1000) : null} - onChange={(date: Date | null) => { - if (date) { - setFieldValue("dueTo", moment(date).unix() || null); - } + onChange={(event: any) => { + setFieldValue("dueTo", event.$d.getTime() / 1000 || null); }} renderInput={(params: TextFieldProps) => } InputProps={{ From e4ecd541ffacb595c2ebfbb9c1ea370738230130 Mon Sep 17 00:00:00 2001 From: nflnkr Date: Fri, 22 Mar 2024 13:27:52 +0300 Subject: [PATCH 03/17] round promocode discount value --- .../Content/PromocodeManagement/usePromocodeGridColDef.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx b/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx index a897d00..c5daedc 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx @@ -25,7 +25,7 @@ export function usePromocodeGridColDef(deletePromocode: (id: string) => void) { headerName: "Коэф. скидки", width: 120, sortable: false, - valueGetter: ({ row }) => row.bonus.discount.factor, + valueGetter: ({ row }) => Math.round(row.bonus.discount.factor * 1000) / 1000, }, { field: "activationCount", From fd80f38b4fbd7ccf54c34f65937f9486421053d2 Mon Sep 17 00:00:00 2001 From: IlyaDoronin Date: Tue, 26 Mar 2024 18:41:22 +0300 Subject: [PATCH 04/17] feat: Promocode statistics modal --- src/api/promocode/requests.ts | 123 +++++++---- src/api/promocode/swr.ts | 176 +++++++++------ src/model/promocodes.ts | 65 +++--- .../PromocodeManagement/StatisticsModal.tsx | 203 ++++++++++++++++++ .../Content/PromocodeManagement/index.tsx | 148 +++++++------ .../usePromocodeGridColDef.tsx | 123 ++++++----- 6 files changed, 577 insertions(+), 261 deletions(-) create mode 100644 src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx diff --git a/src/api/promocode/requests.ts b/src/api/promocode/requests.ts index 1e7e114..5e78e63 100644 --- a/src/api/promocode/requests.ts +++ b/src/api/promocode/requests.ts @@ -1,5 +1,12 @@ import { makeRequest } from "@frontend/kitui"; -import { CreatePromocodeBody, GetPromocodeListBody, Promocode, PromocodeList } from "@root/model/promocodes"; + +import type { + CreatePromocodeBody, + GetPromocodeListBody, + Promocode, + PromocodeList, + PromocodeStatistics, +} from "@root/model/promocodes"; import { parseAxiosError } from "@root/utils/parse-error"; import { isAxiosError } from "axios"; @@ -7,62 +14,84 @@ import { isAxiosError } from "axios"; const baseUrl = process.env.REACT_APP_DOMAIN + "/codeword/promocode"; const getPromocodeList = async (body: GetPromocodeListBody) => { - try { - const promocodeListResponse = await makeRequest< - GetPromocodeListBody, - PromocodeList - >({ - url: baseUrl + "/getList", - method: "POST", - body, - useToken: false, - }); + try { + const promocodeListResponse = await makeRequest< + GetPromocodeListBody, + PromocodeList + >({ + url: baseUrl + "/getList", + method: "POST", + body, + useToken: false, + }); - return promocodeListResponse; - } catch (nativeError) { - const [error] = parseAxiosError(nativeError); - throw new Error(`Ошибка при получении списка промокодов. ${error}`); - } + return promocodeListResponse; + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); + throw new Error(`Ошибка при получении списка промокодов. ${error}`); + } }; const createPromocode = async (body: CreatePromocodeBody) => { - try { - const createPromocodeResponse = await makeRequest< - CreatePromocodeBody, - Promocode - >({ - url: baseUrl + "/create", - method: "POST", - body, - useToken: false, - }); + try { + const createPromocodeResponse = await makeRequest< + CreatePromocodeBody, + Promocode + >({ + url: baseUrl + "/create", + method: "POST", + body, + useToken: false, + }); - return createPromocodeResponse; - } catch (nativeError) { - if (isAxiosError(nativeError) && nativeError.response?.data.error === "Duplicate Codeword") { - throw new Error(`Промокод уже существует`); - } - - const [error] = parseAxiosError(nativeError); - throw new Error(`Ошибка создания промокода. ${error}`); + return createPromocodeResponse; + } catch (nativeError) { + if ( + isAxiosError(nativeError) && + nativeError.response?.data.error === "Duplicate Codeword" + ) { + throw new Error(`Промокод уже существует`); } + + const [error] = parseAxiosError(nativeError); + throw new Error(`Ошибка создания промокода. ${error}`); + } }; const deletePromocode = async (id: string): Promise => { - try { - await makeRequest({ - url: `${baseUrl}/${id}`, - method: "DELETE", - useToken: false, - }); - } catch (nativeError) { - const [error] = parseAxiosError(nativeError); - throw new Error(`Ошибка удаления промокода. ${error}`); - } + try { + await makeRequest({ + url: `${baseUrl}/${id}`, + method: "DELETE", + useToken: false, + }); + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); + throw new Error(`Ошибка удаления промокода. ${error}`); + } +}; + +const getPromocodeStatistics = async (id: string) => { + try { + const promocodeStatisticsResponse = await makeRequest< + unknown, + PromocodeStatistics[] + >({ + url: baseUrl + `/getStatistics/${id}`, + method: "GET", + useToken: false, + }); + + return promocodeStatisticsResponse; + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); + throw new Error(`Ошибка при получении статистики промокода. ${error}`); + } }; export const promocodeApi = { - getPromocodeList, - createPromocode, - deletePromocode, + getPromocodeList, + createPromocode, + deletePromocode, + getPromocodeStatistics, }; diff --git a/src/api/promocode/swr.ts b/src/api/promocode/swr.ts index 064d41a..f66a7ed 100644 --- a/src/api/promocode/swr.ts +++ b/src/api/promocode/swr.ts @@ -1,81 +1,119 @@ -import { CreatePromocodeBody, PromocodeList } from "@root/model/promocodes"; -import { enqueueSnackbar } from "notistack"; import { useCallback, useRef } from "react"; import useSwr, { mutate } from "swr"; +import { enqueueSnackbar } from "notistack"; import { promocodeApi } from "./requests"; +import type { + CreatePromocodeBody, + PromocodeList, +} from "@root/model/promocodes"; -export function usePromocodes(page: number, pageSize: number) { - const promocodesCountRef = useRef(0); - const swrResponse = useSwr( - ["promocodes", page, pageSize], - async (key) => { - const result = await promocodeApi.getPromocodeList({ - limit: key[2], - filter: { - active: true, - }, - page: key[1], - }); - - promocodesCountRef.current = result.count; - return result; +export function usePromocodes( + page: number, + pageSize: number, + promocodeId: string +) { + const promocodesCountRef = useRef(0); + const swrResponse = useSwr( + ["promocodes", page, pageSize], + async (key) => { + const result = await promocodeApi.getPromocodeList({ + limit: key[2], + filter: { + active: true, }, - { - onError(err) { - console.log("Error fetching promocodes", err); - enqueueSnackbar(err.message, { variant: "error" }); + page: key[1], + }); + + promocodesCountRef.current = result.count; + return result; + }, + { + onError(err) { + console.log("Error fetching promocodes", err); + enqueueSnackbar(err.message, { variant: "error" }); + }, + focusThrottleInterval: 60e3, + keepPreviousData: true, + } + ); + + const createPromocode = useCallback( + async function (body: CreatePromocodeBody) { + try { + await promocodeApi.createPromocode(body); + mutate(["promocodes", page, pageSize]); + } catch (error) { + console.log("Error creating promocode", error); + if (error instanceof Error) + enqueueSnackbar(error.message, { variant: "error" }); + } + }, + [page, pageSize] + ); + + const deletePromocode = useCallback( + async function (id: string) { + try { + await mutate( + ["promocodes", page, pageSize], + promocodeApi.deletePromocode(id), + { + optimisticData(currentData, displayedData) { + if (!displayedData) return; + + return { + count: displayedData.count - 1, + items: displayedData.items.filter((item) => item.id !== id), + }; }, - focusThrottleInterval: 60e3, - keepPreviousData: true, - } - ); + rollbackOnError: true, + populateCache(result, currentData) { + if (!currentData) return; - const createPromocode = useCallback(async function (body: CreatePromocodeBody) { - try { - await promocodeApi.createPromocode(body); - mutate(["promocodes", page, pageSize]); - } catch (error) { - console.log("Error creating promocode", error); - if (error instanceof Error) enqueueSnackbar(error.message, { variant: "error" }); - } - }, [page, pageSize]); + return { + count: currentData.count - 1, + items: currentData.items.filter((item) => item.id !== id), + }; + }, + } + ); + } catch (error) { + console.log("Error deleting promocode", error); + if (error instanceof Error) + enqueueSnackbar(error.message, { variant: "error" }); + } + }, + [page, pageSize] + ); - const deletePromocode = useCallback(async function (id: string) { - try { - await mutate( - ["promocodes", page, pageSize], - promocodeApi.deletePromocode(id), - { - optimisticData(currentData, displayedData) { - if (!displayedData) return; + const promocodeStatistics = useSwr( + ["promocodeStatistics", promocodeId], + async ([_, id]) => { + if (!id) { + return null; + } - return { - count: displayedData.count - 1, - items: displayedData.items.filter((item) => item.id !== id), - }; - }, - rollbackOnError: true, - populateCache(result, currentData) { - if (!currentData) return; + const promocodeStatisticsResponse = + await promocodeApi.getPromocodeStatistics(id); - return { - count: currentData.count - 1, - items: currentData.items.filter((item) => item.id !== id), - }; - }, - } - ); - } catch (error) { - console.log("Error deleting promocode", error); - if (error instanceof Error) enqueueSnackbar(error.message, { variant: "error" }); - } - }, [page, pageSize]); + return promocodeStatisticsResponse; + }, + { + onError(err) { + console.log("Error fetching promocode statistics", err); + enqueueSnackbar(err.message, { variant: "error" }); + }, + focusThrottleInterval: 60e3, + keepPreviousData: true, + } + ); - return { - ...swrResponse, - createPromocode, - deletePromocode, - promocodesCount: promocodesCountRef.current, - }; -} + return { + ...swrResponse, + createPromocode, + deletePromocode, + promocodeStatistics: promocodeStatistics.data, + promocodesCount: promocodesCountRef.current, + }; +} diff --git a/src/model/promocodes.ts b/src/model/promocodes.ts index 1a6ff14..3b1b524 100644 --- a/src/model/promocodes.ts +++ b/src/model/promocodes.ts @@ -1,41 +1,48 @@ export type CreatePromocodeBody = { - codeword: string; - description: string; - greetings: string; - dueTo: number; - activationCount: number; - bonus: { - privilege: { - privilegeID: string; - amount: number; - }; - discount: { - layer: number; - factor: number; - target: string; - threshold: number; - }; + codeword: string; + description: string; + greetings: string; + dueTo: number; + activationCount: number; + bonus: { + privilege: { + privilegeID: string; + amount: number; }; + discount: { + layer: number; + factor: number; + target: string; + threshold: number; + }; + }; }; export type GetPromocodeListBody = { - page: number; - limit: number; - filter: { - active: boolean; - text?: string; - }; + page: number; + limit: number; + filter: { + active: boolean; + text?: string; + }; }; export type Promocode = CreatePromocodeBody & { - id: string; - outdated: boolean; - offLimit: boolean; - delete: boolean; - createdAt: string; + id: string; + outdated: boolean; + offLimit: boolean; + delete: boolean; + createdAt: string; }; export type PromocodeList = { - count: number; - items: Promocode[]; + count: number; + items: Promocode[]; +}; + +export type PromocodeStatistics = { + id: string; + link: string; + useCount: number; + purchasesCount: number; }; diff --git a/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx b/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx new file mode 100644 index 0000000..70c08c9 --- /dev/null +++ b/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx @@ -0,0 +1,203 @@ +import { useState } from "react"; +import { + Box, + Button, + Typography, + Modal, + TextField, + useTheme, + useMediaQuery, +} from "@mui/material"; +import { DataGrid, GridLoadingOverlay, GridToolbar } from "@mui/x-data-grid"; +import { DesktopDatePicker } from "@mui/x-date-pickers/DesktopDatePicker"; + +import { fadeIn } from "@root/utils/style/keyframes"; + +import type { GridColDef } from "@mui/x-data-grid"; +import type { PromocodeStatistics } from "@root/model/promocodes"; + +type StatisticsModalProps = { + id: string; + setId: (id: string) => void; + promocodeStatistics: PromocodeStatistics[] | null | undefined; +}; + +const COLUMNS: GridColDef[] = [ + { + field: "link", + headerName: "Ссылка", + width: 320, + sortable: false, + valueGetter: ({ row }) => row.link, + }, + { + field: "useCount", + headerName: "Использований", + width: 120, + sortable: false, + valueGetter: ({ row }) => String(row.useCount), + }, + { + field: "purchasesCount", + headerName: "Покупок", + width: 70, + sortable: false, + valueGetter: ({ row }) => String(row.purchasesCount), + }, +]; + +export const StatisticsModal = ({ + id, + setId, + promocodeStatistics, +}: StatisticsModalProps) => { + const [startDate, setStartDate] = useState(new Date()); + const [endDate, setEndDate] = useState(new Date()); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(550)); + + return ( + setId("")} + sx={{ "& > .MuiBox-root": { outline: "none" } }} + > + + + + + + + + + от + date && setStartDate(date)} + renderInput={(params) => ( + + )} + InputProps={{ + sx: { + height: "40px", + color: theme.palette.secondary.main, + border: "1px solid", + borderColor: theme.palette.secondary.main, + "& .MuiSvgIcon-root": { + color: theme.palette.secondary.main, + }, + }, + }} + /> + + + до + date && setEndDate(date)} + renderInput={(params) => ( + + )} + InputProps={{ + sx: { + height: "40px", + color: theme.palette.secondary.main, + border: "1px solid", + borderColor: theme.palette.secondary.main, + "& .MuiSvgIcon-root": { + color: theme.palette.secondary.main, + }, + }, + }} + /> + + + + + + + ); +}; diff --git a/src/pages/dashboard/Content/PromocodeManagement/index.tsx b/src/pages/dashboard/Content/PromocodeManagement/index.tsx index bdd7be5..4f59943 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/index.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/index.tsx @@ -1,78 +1,96 @@ +import { useState } from "react"; import { Box, Typography, useTheme } from "@mui/material"; import { DataGrid, GridLoadingOverlay, GridToolbar } from "@mui/x-data-grid"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { usePromocodes } from "@root/api/promocode/swr"; import { fadeIn } from "@root/utils/style/keyframes"; -import { useState } from "react"; import { CreatePromocodeForm } from "./CreatePromocodeForm"; import { usePromocodeGridColDef } from "./usePromocodeGridColDef"; - +import { StatisticsModal } from "./StatisticsModal"; export const PromocodeManagement = () => { - const theme = useTheme(); - const [page, setPage] = useState(0); - const [pageSize, setPageSize] = useState(10); - const { data, error, isValidating, promocodesCount, deletePromocode, createPromocode } = usePromocodes(page, pageSize); - const columns = usePromocodeGridColDef(deletePromocode); + const theme = useTheme(); + const [showStatisticsModalId, setShowStatisticsModalId] = + useState(""); + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = useState(10); + const { + data, + error, + isValidating, + promocodesCount, + promocodeStatistics, + deletePromocode, + createPromocode, + } = usePromocodes(page, pageSize, showStatisticsModalId); + const columns = usePromocodeGridColDef( + setShowStatisticsModalId, + deletePromocode + ); - if (error) return Ошибка загрузки промокодов; + if (error) return Ошибка загрузки промокодов; - return ( - - - Создание промокода - - - - - - - ); + return ( + + + Создание промокода + + + + + + + + ); }; diff --git a/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx b/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx index c5daedc..14b1b03 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx @@ -1,59 +1,80 @@ -import DeleteIcon from '@mui/icons-material/Delete'; import { IconButton } from "@mui/material"; import { GridColDef } from "@mui/x-data-grid"; import { Promocode } from "@root/model/promocodes"; import { useMemo } from "react"; -export function usePromocodeGridColDef(deletePromocode: (id: string) => void) { - return useMemo[]>(() => [ - { - field: "id", - headerName: "ID", - width: 30, - sortable: false, - valueGetter: ({ row }) => row.id, +import { BarChart, Delete } from "@mui/icons-material"; + +export function usePromocodeGridColDef( + setStatistics: (id: string) => void, + deletePromocode: (id: string) => void +) { + return useMemo[]>( + () => [ + { + field: "id", + headerName: "ID", + width: 30, + sortable: false, + valueGetter: ({ row }) => row.id, + }, + { + field: "codeword", + headerName: "Кодовое слово", + width: 160, + sortable: false, + valueGetter: ({ row }) => row.codeword, + }, + { + field: "factor", + headerName: "Коэф. скидки", + width: 120, + sortable: false, + valueGetter: ({ row }) => + Math.round(row.bonus.discount.factor * 1000) / 1000, + }, + { + field: "activationCount", + headerName: "Кол-во активаций", + width: 140, + sortable: false, + valueGetter: ({ row }) => row.activationCount, + }, + { + field: "dueTo", + headerName: "Истекает", + width: 160, + sortable: false, + valueGetter: ({ row }) => row.dueTo * 1000, + valueFormatter: ({ value }) => new Date(value).toLocaleString(), + }, + { + field: "settings", + headerName: "", + width: 60, + sortable: false, + renderCell: (params) => { + return ( + setStatistics(params.row.id)}> + + + ); }, - { - field: "codeword", - headerName: "Кодовое слово", - width: 160, - sortable: false, - valueGetter: ({ row }) => row.codeword, + }, + { + field: "delete", + headerName: "", + width: 60, + sortable: false, + renderCell: (params) => { + return ( + deletePromocode(params.row.id)}> + + + ); }, - { - field: "factor", - headerName: "Коэф. скидки", - width: 120, - sortable: false, - valueGetter: ({ row }) => Math.round(row.bonus.discount.factor * 1000) / 1000, - }, - { - field: "activationCount", - headerName: "Кол-во активаций", - width: 140, - sortable: false, - valueGetter: ({ row }) => row.activationCount, - }, - { - field: "dueTo", - headerName: "Истекает", - width: 160, - sortable: false, - valueGetter: ({ row }) => row.dueTo * 1000, - valueFormatter: ({ value }) => new Date(value).toLocaleString(), - }, - { - field: "delete", - headerName: "", - width: 60, - sortable: false, - renderCell: (params) => { - return ( - deletePromocode(params.row.id)}> - - - ); - }, - }, - ], [deletePromocode]); + }, + ], + [deletePromocode, setStatistics] + ); } From 1f133351e5f7b999a66d9deb617bf5daf4e99aeb Mon Sep 17 00:00:00 2001 From: Nastya Date: Wed, 27 Mar 2024 09:31:30 +0300 Subject: [PATCH 05/17] =?UTF-8?q?=D1=80=D0=B0=D0=B7=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B9=20=D0=BF=D0=BE=20=D1=81=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D0=BD=D0=B0=D0=BC=20=D0=BE=D1=82=20=D1=80=D0=B0=D0=B7?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/dashboard/Content/Support/Chat/ChatDocument.tsx | 1 + src/pages/dashboard/Content/Support/Chat/ChatImage.tsx | 1 + src/pages/dashboard/Content/Support/Chat/ChatMessage.tsx | 1 + src/pages/dashboard/Content/Support/Chat/ChatVideo.tsx | 1 + src/pages/dashboard/Content/Support/Chat/Message.tsx | 2 +- 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/dashboard/Content/Support/Chat/ChatDocument.tsx b/src/pages/dashboard/Content/Support/Chat/ChatDocument.tsx index f180a67..c0ba536 100644 --- a/src/pages/dashboard/Content/Support/Chat/ChatDocument.tsx +++ b/src/pages/dashboard/Content/Support/Chat/ChatDocument.tsx @@ -24,6 +24,7 @@ export default function ChatDocument({ display: "flex", gap: "9px", padding: isSelf ? "0 8px 0 0" : "0 0 0 8px", + justifyContent: isSelf ? "end" : "start", }} > ); - +console.log(isSelf) return ( Date: Wed, 27 Mar 2024 10:35:28 +0300 Subject: [PATCH 06/17] fix: styles --- .../Content/PromocodeManagement/StatisticsModal.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx b/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx index 70c08c9..100e5e5 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx @@ -70,7 +70,7 @@ export const StatisticsModal = ({ transform: "translate(-50%, -50%)", width: "95%", maxWidth: "600px", - bgcolor: "background.paper", + background: "#1F2126", border: "2px solid gray", borderRadius: "6px", boxShadow: 24, @@ -109,7 +109,9 @@ export const StatisticsModal = ({ - от + + от + - до + + до + Date: Wed, 27 Mar 2024 17:50:14 +0300 Subject: [PATCH 07/17] upgrade kitui use cart calc functions from kitui fix cart discount list display fix types --- package.json | 3 +- src/api/discounts.ts | 32 + src/api/privilegies.ts | 16 +- src/api/tariffs.ts | 6 +- src/kitUI/Cart/Cart.tsx | 92 +- src/kitUI/Cart/CartItemRow.tsx | 17 +- src/kitUI/Cart/DiscountTooltip.tsx | 55 +- src/model/tariff.ts | 4 +- src/pages/Setting/CardPrivilegie.tsx | 6 +- .../DiscountManagement/ControlPanel.tsx | 4 +- .../DiscountManagement/CreateDiscount.tsx | 2 + .../DiscountManagement/DiscountDataGrid.tsx | 4 +- .../DiscountManagement/EditDiscountDialog.tsx | 12 +- .../Content/Tariffs/CreateTariff.tsx | 8 +- .../dashboard/Content/Tariffs/TariffsInfo.tsx | 40 +- .../dashboard/Content/Tariffs/tariffsDG.tsx | 2 +- src/services/privilegies.service.ts | 6 +- src/stores/privilegesStore.ts | 52 +- src/utils/calcCart/calcCart.test.ts | 1204 ----------------- src/utils/calcCart/calcCart.ts | 82 -- src/utils/formatDiscountFactor.ts | 3 + src/utils/hooks/usePrivileges.ts | 57 +- yarn.lock | 15 +- 23 files changed, 229 insertions(+), 1493 deletions(-) delete mode 100644 src/utils/calcCart/calcCart.test.ts delete mode 100644 src/utils/calcCart/calcCart.ts create mode 100644 src/utils/formatDiscountFactor.ts diff --git a/package.json b/package.json index 38c4ac5..abed30b 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "@date-io/dayjs": "^2.15.0", "@emotion/react": "^11.10.4", "@emotion/styled": "^11.10.4", - "@frontend/kitui": "^1.0.59", + "@frontend/kitui": "^1.0.77", "@material-ui/pickers": "^3.3.10", "@mui/icons-material": "^5.10.3", "@mui/material": "^5.10.5", @@ -36,6 +36,7 @@ "numeral": "^2.0.6", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.13", "react-numeral": "^1.1.1", "react-router-dom": "^6.3.0", "react-scripts": "^5.0.1", diff --git a/src/api/discounts.ts b/src/api/discounts.ts index 068f8f0..93316c4 100644 --- a/src/api/discounts.ts +++ b/src/api/discounts.ts @@ -8,6 +8,8 @@ import type { DiscountType, GetDiscountResponse, } from "@root/model/discount"; +import useSWR from "swr"; +import { enqueueSnackbar } from "notistack"; const baseUrl = process.env.REACT_APP_DOMAIN + "/price" @@ -213,3 +215,33 @@ export const requestDiscounts = async (): Promise< return [null, `Ошибка получения скидок. ${error}`]; } }; + +async function getDiscounts() { + try { + const discountsResponse = await makeRequest({ + url: baseUrl + "/discounts", + method: "get", + useToken: true, + }); + + return discountsResponse.Discounts.filter((discount) => !discount.Deprecated); + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); + + throw new Error(`Ошибка получения списка скидок. ${error}`); + } +} + +export function useDiscounts() { + const { data } = useSWR("discounts", getDiscounts, { + keepPreviousData: true, + suspense: true, + onError: (error) => { + if (!(error instanceof Error)) return; + + enqueueSnackbar(error.message, { variant: "error" }); + } + }); + + return data; +} diff --git a/src/api/privilegies.ts b/src/api/privilegies.ts index cc87faa..65e70a7 100644 --- a/src/api/privilegies.ts +++ b/src/api/privilegies.ts @@ -1,13 +1,13 @@ -import { makeRequest } from "@frontend/kitui"; +import { CustomPrivilege, makeRequest } from "@frontend/kitui"; import { parseAxiosError } from "@root/utils/parse-error"; -import { PrivilegeWithAmount } from "@frontend/kitui"; +import { Privilege } from "@frontend/kitui"; import type { TMockData } from "./roles"; type SeverPrivilegesResponse = { - templategen: PrivilegeWithAmount[]; - squiz: PrivilegeWithAmount[]; + templategen: CustomPrivilege[]; + squiz: CustomPrivilege[]; }; const baseUrl = process.env.REACT_APP_DOMAIN + "/strator" @@ -28,11 +28,11 @@ export const getRoles = async (): Promise<[TMockData | null, string?]> => { }; export const putPrivilege = async ( - body: Omit + body: Omit ): Promise<[unknown, string?]> => { try { const putedPrivilege = await makeRequest< - Omit, + Omit, unknown >({ url: baseUrl + "/privilege", @@ -70,9 +70,9 @@ export const requestServicePrivileges = async (): Promise< export const requestPrivileges = async ( signal: AbortSignal | undefined -): Promise<[PrivilegeWithAmount[], string?]> => { +): Promise<[CustomPrivilege[], string?]> => { try { - const privilegesResponse = await makeRequest( + const privilegesResponse = await makeRequest( { url: baseUrl + "/privilege", method: "get", diff --git a/src/api/tariffs.ts b/src/api/tariffs.ts index 475b783..9ef9a3f 100644 --- a/src/api/tariffs.ts +++ b/src/api/tariffs.ts @@ -2,7 +2,7 @@ import { makeRequest } from "@frontend/kitui"; import { parseAxiosError } from "@root/utils/parse-error"; -import type { PrivilegeWithAmount } from "@frontend/kitui"; +import type { Privilege } from "@frontend/kitui"; import type { Tariff } from "@frontend/kitui"; import type { EditTariffRequestBody } from "@root/model/tariff"; @@ -12,7 +12,7 @@ type CreateTariffBackendRequest = { order: number; price: number; isCustom: boolean; - privileges: Omit[]; + privileges: Omit[]; }; type GetTariffsResponse = { @@ -52,7 +52,7 @@ export const putTariff = async (tariff: Tariff): Promise<[null, string?]> => { price: tariff.price ?? 0, isCustom: false, order: tariff.order || 1, - description: tariff.description, + description: tariff.description ?? "", privileges: tariff.privileges, }, }); diff --git a/src/kitUI/Cart/Cart.tsx b/src/kitUI/Cart/Cart.tsx index 1dcb5c5..aa2a0ff 100644 --- a/src/kitUI/Cart/Cart.tsx +++ b/src/kitUI/Cart/Cart.tsx @@ -1,36 +1,35 @@ +import { calcCart } from "@frontend/kitui"; +import Input from "@kitUI/input"; import { - Button, - Paper, - Box, - Typography, - TableHead, - TableRow, - TableCell, - TableBody, - Table, Alert, + Box, + Button, Checkbox, FormControlLabel, - useTheme, useMediaQuery + Paper, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Typography, + useMediaQuery, + useTheme } from "@mui/material"; -import Input from "@kitUI/input"; -import { useState } from "react"; +import { requestDiscounts } from "@root/services/discounts.service"; +import { requestPrivileges } from "@root/services/privilegies.service"; import { setCartData, useCartStore } from "@root/stores/cart"; import { useTariffStore } from "@root/stores/tariffs"; -import { useDiscountStore } from "@root/stores/discounts"; -import { requestPrivileges } from "@root/services/privilegies.service"; -import { requestDiscounts } from "@root/services/discounts.service"; -import { DiscountTooltip } from "./DiscountTooltip"; -import CartItemRow from "./CartItemRow"; -import { calcCart, formatDiscountFactor } from "@root/utils/calcCart/calcCart"; -import { Discount, findDiscountFactor } from "@frontend/kitui"; import { currencyFormatter } from "@root/utils/currencyFormatter"; +import { useState } from "react"; +import CartItemRow from "./CartItemRow"; +import { useDiscounts } from "@root/api/discounts"; export default function Cart() { const theme = useTheme(); const mobile = useMediaQuery(theme.breakpoints.down(400)); - let discounts = useDiscountStore(state => state.discounts); + const discounts = useDiscounts(); const cartData = useCartStore((store) => store.cartData); const tariffs = useTariffStore(state => state.tariffs); const [couponField, setCouponField] = useState(""); @@ -39,10 +38,6 @@ export default function Cart() { const [isNonCommercial, setIsNonCommercial] = useState(false); const selectedTariffIds = useTariffStore(state => state.selectedTariffIds); - const cartDiscounts = [cartData?.appliedCartPurchasesDiscount, cartData?.appliedLoyaltyDiscount].filter((d): d is Discount => !!d); - - const cartDiscountsResultFactor = findDiscountFactor(cartData?.appliedCartPurchasesDiscount) * findDiscountFactor(cartData?.appliedLoyaltyDiscount); - async function handleCalcCartClick() { await requestPrivileges(); await requestDiscounts(); @@ -224,45 +219,22 @@ export default function Cart() { - {cartData.services.flatMap(service => service.tariffs.map(taroffCartData => ( - - )))} + {cartData.services.flatMap(service => service.tariffs.map(tariffCartData => { + const appliedDiscounts = tariffCartData.privileges.flatMap( + privilege => Array.from(privilege.appliedDiscounts) + ).sort((a, b) => a.Layer - b.Layer); + + return ( + + ); + }))} - - Скидки корзины: - {cartDiscounts && ( - - {cartDiscounts?.map((discount, index, arr) => ( - - - {index < arr.length - 1 && } - - ))} -   - {cartDiscountsResultFactor && `= ${formatDiscountFactor(cartDiscountsResultFactor)}`} - - )} - - !!d); - useEffect(() => { if (tariffCartData.privileges.length > 1) { console.warn(`Количество привилегий в тарифе ${tariffCartData.name}(${tariffCartData.id}) больше одного`); @@ -39,22 +37,17 @@ export default function CartItemRow({ tariffCartData, appliedServiceDiscount }: {tariffCartData.privileges[0].description} - {envolvedDiscounts.map((discount, index, arr) => ( + {appliedDiscounts.map((discount, index, arr) => ( - {index < arr.length - (appliedServiceDiscount ? 0 : 1) && ( + {index < arr.length - 1 && ( )} ))} - {appliedServiceDiscount && ( - - - - )} - {currencyFormatter.format(tariffCartData.price)} + {currencyFormatter.format(tariffCartData.price / 100)} ); diff --git a/src/kitUI/Cart/DiscountTooltip.tsx b/src/kitUI/Cart/DiscountTooltip.tsx index 5b56dc9..2badb49 100644 --- a/src/kitUI/Cart/DiscountTooltip.tsx +++ b/src/kitUI/Cart/DiscountTooltip.tsx @@ -1,27 +1,28 @@ -import { Tooltip, Typography } from "@mui/material"; -import { Discount, findDiscountFactor } from "@frontend/kitui"; -import { formatDiscountFactor } from "@root/utils/calcCart/calcCart"; - - -interface Props { - discount: Discount; -} - -export function DiscountTooltip({ discount }: Props) { - const discountText = formatDiscountFactor(findDiscountFactor(discount)); - - return discountText ? ( - - Скидка: {discount?.Name} - {discount?.Description} - - } - > - {discountText} - - ) : ( - Ошибка поиска значения скидки - ); -} +import { Tooltip, Typography } from "@mui/material"; +import { Discount, findDiscountFactor } from "@frontend/kitui"; +import { formatDiscountFactor } from "@root/utils/formatDiscountFactor"; + + +interface Props { + discount: Discount; +} + +export function DiscountTooltip({ discount }: Props) { + const discountText = formatDiscountFactor(findDiscountFactor(discount)); + + return discountText ? ( + + Слой: {discount.Layer} + Название: {discount.Name} + Описание: {discount.Description} + + } + > + {discountText} + + ) : ( + Ошибка поиска значения скидки + ); +} diff --git a/src/model/tariff.ts b/src/model/tariff.ts index d03fd32..68535dc 100644 --- a/src/model/tariff.ts +++ b/src/model/tariff.ts @@ -1,4 +1,4 @@ -import { PrivilegeWithAmount } from "@frontend/kitui"; +import { Privilege } from "@frontend/kitui"; export const SERVICE_LIST = [ { @@ -25,5 +25,5 @@ export type EditTariffRequestBody = { order: number; price: number; isCustom: boolean; - privileges: Omit[]; + privileges: Omit[]; }; diff --git a/src/pages/Setting/CardPrivilegie.tsx b/src/pages/Setting/CardPrivilegie.tsx index 9fe3aad..f4de843 100644 --- a/src/pages/Setting/CardPrivilegie.tsx +++ b/src/pages/Setting/CardPrivilegie.tsx @@ -2,14 +2,14 @@ import { KeyboardEvent, useRef, useState } from "react"; import { enqueueSnackbar } from "notistack"; import {Box, IconButton, TextField, Tooltip, Typography, useMediaQuery, useTheme} from "@mui/material"; import ModeEditOutlineOutlinedIcon from "@mui/icons-material/ModeEditOutlineOutlined"; -import { PrivilegeWithAmount } from "@frontend/kitui"; +import { CustomPrivilege } from "@frontend/kitui"; import { putPrivilege } from "@root/api/privilegies"; import SaveIcon from '@mui/icons-material/Save'; import { currencyFormatter } from "@root/utils/currencyFormatter"; interface CardPrivilege { - privilege: PrivilegeWithAmount; + privilege: CustomPrivilege; } export const СardPrivilege = ({ privilege }: CardPrivilege) => { @@ -27,7 +27,7 @@ export const СardPrivilege = ({ privilege }: CardPrivilege) => { const putPrivileges = async () => { - const [_, putedPrivilegeError] = await putPrivilege({ + const [, putedPrivilegeError] = await putPrivilege({ name: privilege.name, privilegeId: privilege.privilegeId, serviceKey: privilege.serviceKey, diff --git a/src/pages/dashboard/Content/DiscountManagement/ControlPanel.tsx b/src/pages/dashboard/Content/DiscountManagement/ControlPanel.tsx index 9a40332..65c7c63 100644 --- a/src/pages/dashboard/Content/DiscountManagement/ControlPanel.tsx +++ b/src/pages/dashboard/Content/DiscountManagement/ControlPanel.tsx @@ -6,6 +6,7 @@ import { changeDiscount } from "@root/api/discounts"; import { findDiscountsById } from "@root/stores/discounts"; import { requestDiscounts } from "@root/services/discounts.service"; +import { mutate } from "swr"; interface Props { selectedRows: GridSelectionModel; @@ -24,7 +25,7 @@ export default function DiscountDataGrid({ selectedRows }: Props) { return enqueueSnackbar("Скидка не найдена"); } - const [_, changedDiscountError] = await changeDiscount(String(id), { + const [, changedDiscountError] = await changeDiscount(String(id), { ...discount, Deprecated: isActive, }); @@ -34,6 +35,7 @@ export default function DiscountDataGrid({ selectedRows }: Props) { } else { fatal += 1; } + mutate("discounts"); } await requestDiscounts(); diff --git a/src/pages/dashboard/Content/DiscountManagement/CreateDiscount.tsx b/src/pages/dashboard/Content/DiscountManagement/CreateDiscount.tsx index 525e7e4..5eabd34 100644 --- a/src/pages/dashboard/Content/DiscountManagement/CreateDiscount.tsx +++ b/src/pages/dashboard/Content/DiscountManagement/CreateDiscount.tsx @@ -23,6 +23,7 @@ import { DiscountType, discountTypes } from "@root/model/discount"; import { createDiscount } from "@root/api/discounts"; import usePrivileges from "@root/utils/hooks/usePrivileges"; import { Formik, Field, Form, FormikHelpers } from "formik"; +import { mutate } from "swr"; interface Values { discountNameField: string, @@ -92,6 +93,7 @@ export default function CreateDiscount() { } if (createdDiscountResponse) { + mutate("discounts"); addDiscount(createdDiscountResponse); } } diff --git a/src/pages/dashboard/Content/DiscountManagement/DiscountDataGrid.tsx b/src/pages/dashboard/Content/DiscountManagement/DiscountDataGrid.tsx index b4e1ef4..5dcb364 100644 --- a/src/pages/dashboard/Content/DiscountManagement/DiscountDataGrid.tsx +++ b/src/pages/dashboard/Content/DiscountManagement/DiscountDataGrid.tsx @@ -18,7 +18,8 @@ import { deleteDiscount } from "@root/api/discounts"; import { GridSelectionModel } from "@mui/x-data-grid"; import { requestDiscounts } from "@root/services/discounts.service"; import AutorenewIcon from "@mui/icons-material/Autorenew"; -import { formatDiscountFactor } from "@root/utils/calcCart/calcCart"; +import { formatDiscountFactor } from "@root/utils/formatDiscountFactor"; +import { mutate } from "swr"; const columns: GridColDef[] = [ // { @@ -93,6 +94,7 @@ const columns: GridColDef[] = [ disabled={row.deleted} onClick={() => { deleteDiscount(row.id).then(([discount]) => { + mutate("discounts"); if (discount) { updateDiscount(discount); } diff --git a/src/pages/dashboard/Content/DiscountManagement/EditDiscountDialog.tsx b/src/pages/dashboard/Content/DiscountManagement/EditDiscountDialog.tsx index 47d1293..06fab07 100644 --- a/src/pages/dashboard/Content/DiscountManagement/EditDiscountDialog.tsx +++ b/src/pages/dashboard/Content/DiscountManagement/EditDiscountDialog.tsx @@ -31,6 +31,7 @@ import { getDiscountTypeFromLayer } from "@root/utils/discount"; import usePrivileges from "@root/utils/hooks/usePrivileges"; import { enqueueSnackbar } from "notistack"; import { useEffect, useState } from "react"; +import { mutate } from "swr"; export default function EditDiscountDialog() { const theme = useTheme(); @@ -59,17 +60,17 @@ export default function EditDiscountDialog() { function setDiscountFields() { if (!discount) return; - setServiceType(discount.Condition.Group); + setServiceType(discount.Condition.Group ?? ""); setDiscountType(getDiscountTypeFromLayer(discount.Layer)); setDiscountNameField(discount.Name); setDiscountDescriptionField(discount.Description); - setPrivilegeIdField(discount.Condition.Product); + setPrivilegeIdField(discount.Condition.Product ?? ""); setDiscountFactorField(((1 - discount.Target.Factor) * 100).toFixed(2)); - setPurchasesAmountField(discount.Condition.PurchasesAmount.toString()); + setPurchasesAmountField(discount.Condition.PurchasesAmount ?? ""); setCartPurchasesAmountField( - discount.Condition.CartPurchasesAmount.toString() + discount.Condition.CartPurchasesAmount ?? "" ); - setDiscountMinValueField(discount.Condition.PriceFrom.toString()); + setDiscountMinValueField(discount.Condition.PriceFrom ?? ""); }, [discount] ); @@ -137,6 +138,7 @@ export default function EditDiscountDialog() { } if (patchedDiscountResponse) { + mutate("discounts"); updateDiscount(patchedDiscountResponse); closeEditDiscountDialog(); } diff --git a/src/pages/dashboard/Content/Tariffs/CreateTariff.tsx b/src/pages/dashboard/Content/Tariffs/CreateTariff.tsx index f3785e1..b7f0ea4 100644 --- a/src/pages/dashboard/Content/Tariffs/CreateTariff.tsx +++ b/src/pages/dashboard/Content/Tariffs/CreateTariff.tsx @@ -17,7 +17,7 @@ import { findPrivilegeById, usePrivilegeStore, } from "@root/stores/privilegesStore"; -import { PrivilegeWithAmount } from "@frontend/kitui"; +import { Privilege } from "@frontend/kitui"; import { currencyFormatter } from "@root/utils/currencyFormatter"; import { Formik, Field, Form, FormikHelpers } from "formik"; @@ -28,7 +28,7 @@ interface Values { customPriceField: string, privilegeIdField: string, orderField: number, - privilege: PrivilegeWithAmount | null + privilege: Privilege | null } export default function CreateTariff() { @@ -68,7 +68,7 @@ export default function CreateTariff() { formikHelpers: FormikHelpers ) => { if (values.privilege !== null) { - const [_, createdTariffError] = await createTariff({ + const [, createdTariffError] = await createTariff({ name: values.nameField, price: Number(values.customPriceField) * 100, order: values.orderField, @@ -185,7 +185,7 @@ export default function CreateTariff() { {privileges.map((privilege) => ( diff --git a/src/pages/dashboard/Content/Tariffs/TariffsInfo.tsx b/src/pages/dashboard/Content/Tariffs/TariffsInfo.tsx index 04b8734..cc4be27 100644 --- a/src/pages/dashboard/Content/Tariffs/TariffsInfo.tsx +++ b/src/pages/dashboard/Content/Tariffs/TariffsInfo.tsx @@ -1,17 +1,23 @@ -import { Typography } from "@mui/material"; -import Cart from "@root/kitUI/Cart/Cart"; -import TariffsDG from "./tariffsDG"; - - -export default function TariffsInfo() { - - return ( - <> - - Список тарифов - - - - - ); -} +import { CircularProgress, Typography } from "@mui/material"; +import Cart from "@root/kitUI/Cart/Cart"; +import TariffsDG from "./tariffsDG"; +import { Suspense } from "react"; +import { ErrorBoundary } from "react-error-boundary"; + + +export default function TariffsInfo() { + + return ( + <> + + Список тарифов + + + Что-то пошло не так}> + }> + + + + + ); +} diff --git a/src/pages/dashboard/Content/Tariffs/tariffsDG.tsx b/src/pages/dashboard/Content/Tariffs/tariffsDG.tsx index a5018e4..ac5fc23 100644 --- a/src/pages/dashboard/Content/Tariffs/tariffsDG.tsx +++ b/src/pages/dashboard/Content/Tariffs/tariffsDG.tsx @@ -34,7 +34,7 @@ const columns: GridColDef[] = [ { field: "type", headerName: "Единица", width: 100, valueGetter: ({ row }) => row.privileges[0].type }, { field: "pricePerUnit", headerName: "Цена за ед.", width: 100, valueGetter: ({ row }) => currencyFormatter.format(row.privileges[0].price / 100) }, { field: "isCustom", headerName: "Кастомная цена", width: 130, valueGetter: ({ row }) => row.isCustom ? "Да" : "Нет" }, - { field: "total", headerName: "Сумма", width: 60, valueGetter: ({ row }) => currencyFormatter.format(getTariffPrice(row) / 100) }, + { field: "total", headerName: "Сумма", width: 100, valueGetter: ({ row }) => currencyFormatter.format(getTariffPrice(row) / 100) }, { field: "delete", headerName: "Удаление", diff --git a/src/services/privilegies.service.ts b/src/services/privilegies.service.ts index d2acf83..89ecd32 100644 --- a/src/services/privilegies.service.ts +++ b/src/services/privilegies.service.ts @@ -1,10 +1,10 @@ import { resetPrivilegeArray } from "@root/stores/privilegesStore"; import { requestServicePrivileges } from "@root/api/privilegies"; -import type { PrivilegeWithAmount } from "@frontend/kitui"; +import type { CustomPrivilege } from "@frontend/kitui"; -const mutatePrivileges = (privileges: PrivilegeWithAmount[]) => { - let extracted: PrivilegeWithAmount[] = []; +const mutatePrivileges = (privileges: CustomPrivilege[]) => { + let extracted: CustomPrivilege[] = []; for (let serviceKey in privileges) { //Приходит объект. В его значениях массивы привилегий для разных сервисов. Высыпаем в общую кучу и обновляем стор extracted = extracted.concat(privileges[serviceKey]); diff --git a/src/stores/privilegesStore.ts b/src/stores/privilegesStore.ts index 0e31994..f0fd652 100644 --- a/src/stores/privilegesStore.ts +++ b/src/stores/privilegesStore.ts @@ -1,26 +1,26 @@ -import { create } from "zustand"; -import { devtools } from "zustand/middleware"; -import { PrivilegeWithAmount } from "@frontend/kitui"; - - -interface PrivilegeStore { - privileges: PrivilegeWithAmount[]; -} - -export const usePrivilegeStore = create()( - devtools( - (set, get) => ({ - privileges: [], - }), - { - name: "Privileges", - enabled: process.env.NODE_ENV === "development", - } - ) -); - -export const resetPrivilegeArray = (privileges: PrivilegeStore["privileges"]) => usePrivilegeStore.setState({ privileges }); - -export const findPrivilegeById = (privilegeId: string) => { - return usePrivilegeStore.getState().privileges.find((privilege) => privilege._id === privilegeId || privilege.privilegeId === privilegeId) ?? null; -}; +import { create } from "zustand"; +import { devtools } from "zustand/middleware"; +import { CustomPrivilege } from "@frontend/kitui"; + + +interface PrivilegeStore { + privileges: CustomPrivilege[]; +} + +export const usePrivilegeStore = create()( + devtools( + (set, get) => ({ + privileges: [], + }), + { + name: "Privileges", + enabled: process.env.NODE_ENV === "development", + } + ) +); + +export const resetPrivilegeArray = (privileges: PrivilegeStore["privileges"]) => usePrivilegeStore.setState({ privileges }); + +export const findPrivilegeById = (privilegeId: string) => { + return usePrivilegeStore.getState().privileges.find((privilege) => privilege._id === privilegeId || privilege.privilegeId === privilegeId) ?? null; +}; diff --git a/src/utils/calcCart/calcCart.test.ts b/src/utils/calcCart/calcCart.test.ts deleted file mode 100644 index 04ac79c..0000000 --- a/src/utils/calcCart/calcCart.test.ts +++ /dev/null @@ -1,1204 +0,0 @@ -/// -import { CartData, Discount, Tariff } from "@frontend/kitui"; -import { calcCart } from "./calcCart"; - - -describe("Cart calculations", () => { - describe("without discounts", () => { - it("calculates cart with 1 item", () => { - const cart = calcCart([templategenTariff1], [], 0); - - const expectedCart: CartData = { - itemCount: 1, - priceAfterDiscounts: 100 * 100, - priceBeforeDiscounts: 100 * 100, - services: [ - { - serviceKey: "templategen", - price: 100 * 100, - tariffs: [ - { - id: "t1", - name: "templategenTariff1", - price: 100 * 100, - isCustom: false, - privileges: [ - { - privilegeId: "p1", - serviceKey: "templategen", - description: "d1", - price: 100 * 100, - amount: 100, - appliedPrivilegeDiscount: null, - }, - ], - } - ], - appliedServiceDiscount: null, - }, - ], - allAppliedDiscounts: [], - appliedCartPurchasesDiscount: null, - appliedLoyaltyDiscount: null, - }; - expect(cart).toStrictEqual(expectedCart); - }); - - it("calculates cart with 3 items", () => { - const cart = calcCart([templategenTariff1, squizTariff, reducerTariff], [], 0); - - const expectedCart: CartData = { - itemCount: 3, - priceAfterDiscounts: 100 * 100 + 200 * 200 + 300 * 300, - priceBeforeDiscounts: 100 * 100 + 200 * 200 + 300 * 300, - services: [ - { - serviceKey: "templategen", - price: 100 * 100, - tariffs: [ - { - id: "t1", - name: "templategenTariff1", - price: 100 * 100, - isCustom: false, - privileges: [ - { - privilegeId: "p1", - serviceKey: "templategen", - description: "d1", - price: 100 * 100, - amount: 100, - appliedPrivilegeDiscount: null, - }, - ], - }, - ], - appliedServiceDiscount: null, - }, - { - serviceKey: "squiz", - price: 200 * 200, - tariffs: [ - { - id: "t2", - name: "squizTariff", - price: 200 * 200, - isCustom: false, - privileges: [ - { - description: "d2", - price: 200 * 200, - privilegeId: "p2", - serviceKey: "squiz", - amount: 100, - appliedPrivilegeDiscount: null, - }, - ], - }, - ], - appliedServiceDiscount: null, - }, - { - serviceKey: "reducer", - price: 300 * 300, - tariffs: [ - { - id: "t3", - name: "reducerTariff", - price: 300 * 300, - isCustom: false, - privileges: [ - { - description: "d3", - amount: 100, - price: 300 * 300, - privilegeId: "p3", - serviceKey: "reducer", - appliedPrivilegeDiscount: null, - }, - ], - }, - ], - appliedServiceDiscount: null, - }, - ], - allAppliedDiscounts: [], - appliedCartPurchasesDiscount: null, - appliedLoyaltyDiscount: null, - }; - expect(cart).toStrictEqual(expectedCart); - }); - - it("calculates cart with items for the same service", () => { - const cart = calcCart([templategenTariff1, templategenTariff2, reducerTariff], [], 0); - - const expectedCart: CartData = { - itemCount: 3, - priceAfterDiscounts: 100 * 100 + 600 * 600 + 300 * 300, - priceBeforeDiscounts: 100 * 100 + 600 * 600 + 300 * 300, - services: [ - { - serviceKey: "templategen", - price: 100 * 100 + 600 * 600, - tariffs: [ - { - id: "t1", - name: "templategenTariff1", - price: 100 * 100, - isCustom: false, - privileges: [ - { - privilegeId: "p1", - amount: 100, - serviceKey: "templategen", - description: "d1", - price: 100 * 100, - appliedPrivilegeDiscount: null, - }, - ], - }, - { - id: "t5", - name: "templategenTariff2", - price: 600 * 600, - isCustom: false, - privileges: [ - { - amount: 100, - description: "d5", - price: 600 * 600, - privilegeId: "p5", - serviceKey: "templategen", - appliedPrivilegeDiscount: null, - }, - ], - }, - ], - appliedServiceDiscount: null, - }, - { - serviceKey: "reducer", - price: 300 * 300, - tariffs: [ - { - id: "t3", - name: "reducerTariff", - price: 300 * 300, - isCustom: false, - privileges: [ - { - amount: 100, - description: "d3", - price: 300 * 300, - privilegeId: "p3", - serviceKey: "reducer", - appliedPrivilegeDiscount: null, - }, - ], - }, - ], - appliedServiceDiscount: null, - }, - ], - allAppliedDiscounts: [], - appliedCartPurchasesDiscount: null, - appliedLoyaltyDiscount: null, - }; - expect(cart).toStrictEqual(expectedCart); - }); - - it("returns blank cart when no tariffs", () => { - const cart = calcCart([], [], 0); - - const expectedCart: CartData = { - itemCount: 0, - priceAfterDiscounts: 0, - priceBeforeDiscounts: 0, - services: [], - allAppliedDiscounts: [], - appliedCartPurchasesDiscount: null, - appliedLoyaltyDiscount: null, - }; - expect(cart).toStrictEqual(expectedCart); - }); - - it("throw when tariffs are incompatible", () => { - expect(() => { - calcCart([customTemplategenTariff, templategenTariff1], [], 0); - }).toThrow(); - }); - }); - - describe("with single discount", () => { - it("applies privilege discount to tariff 1", () => { - const cart = calcCart([templategenTariff1], [templategenP1PrivilegeDiscount], 0); - - const expectedCart: CartData = { - itemCount: 1, - priceAfterDiscounts: 100 * 100 * 0.9, - priceBeforeDiscounts: 100 * 100, - services: [ - { - serviceKey: "templategen", - price: 100 * 100 * 0.9, - tariffs: [ - { - id: "t1", - name: "templategenTariff1", - price: 100 * 100 * 0.9, - isCustom: false, - privileges: [ - { - amount: 100, - privilegeId: "p1", - serviceKey: "templategen", - description: "d1", - price: 100 * 100 * 0.9, - appliedPrivilegeDiscount: templategenP1PrivilegeDiscount, - }, - ], - }, - ], - appliedServiceDiscount: null, - }, - ], - allAppliedDiscounts: [templategenP1PrivilegeDiscount], - appliedCartPurchasesDiscount: null, - appliedLoyaltyDiscount: null, - }; - expect(cart).toStrictEqual(expectedCart); - }); - - it("applies privilege discount to tariff 2", () => { - const cart = calcCart([reducerTariff], [reducerPrivilegeDiscount], 0); - - const expectedCart: CartData = { - itemCount: 1, - priceAfterDiscounts: 300 * 300 * 0.95, - priceBeforeDiscounts: 300 * 300, - services: [ - { - serviceKey: "reducer", - price: 300 * 300 * 0.95, - tariffs: [ - { - id: "t3", - name: "reducerTariff", - price: 300 * 300 * 0.95, - isCustom: false, - privileges: [ - { - amount: 100, - description: "d3", - price: 300 * 300 * 0.95, - privilegeId: "p3", - serviceKey: "reducer", - appliedPrivilegeDiscount: reducerPrivilegeDiscount, - }, - ], - }, - ], - appliedServiceDiscount: null, - }, - ], - allAppliedDiscounts: [reducerPrivilegeDiscount], - appliedCartPurchasesDiscount: null, - appliedLoyaltyDiscount: null, - }; - expect(cart).toStrictEqual(expectedCart); - }); - - it("applies service discount to tariffs", () => { - const cart = calcCart([templategenTariff1, templategenTariff2], [templategenServiceDiscount], 0); - - const expectedCart: CartData = { - itemCount: 2, - priceAfterDiscounts: (100 * 100 + 600 * 600) * 0.8, - priceBeforeDiscounts: 100 * 100 + 600 * 600, - services: [ - { - serviceKey: "templategen", - price: (100 * 100 + 600 * 600) * 0.8, - tariffs: [ - { - id: "t1", - name: "templategenTariff1", - price: 100 * 100 * 0.8, - isCustom: false, - privileges: [ - { - privilegeId: "p1", - amount: 100, - serviceKey: "templategen", - description: "d1", - price: 100 * 100 * 0.8, - appliedPrivilegeDiscount: null, - }, - ], - }, - { - id: "t5", - name: "templategenTariff2", - price: 600 * 600 * 0.8, - isCustom: false, - privileges: [ - { - description: "d5", - amount: 100, - price: 600 * 600 * 0.8, - privilegeId: "p5", - serviceKey: "templategen", - appliedPrivilegeDiscount: null, - }, - ], - }, - ], - appliedServiceDiscount: templategenServiceDiscount, - }, - ], - allAppliedDiscounts: [templategenServiceDiscount], - appliedCartPurchasesDiscount: null, - appliedLoyaltyDiscount: null, - }; - expect(cart).toStrictEqual(expectedCart); - }); - - it("applies privilege discount to 2 tariffs", () => { - const cart = calcCart([templategenTariff1, templategenTariff2], [templategenP1PrivilegeDiscount], 0); - - const expectedCart: CartData = { - itemCount: 2, - priceAfterDiscounts: 100 * 100 * 0.9 + 600 * 600, - priceBeforeDiscounts: 100 * 100 + 600 * 600, - services: [ - { - serviceKey: "templategen", - price: 100 * 100 * 0.9 + 600 * 600, - tariffs: [ - { - id: "t1", - name: "templategenTariff1", - price: 100 * 100 * 0.9, - isCustom: false, - privileges: [ - { - amount: 100, - privilegeId: "p1", - serviceKey: "templategen", - description: "d1", - price: 100 * 100 * 0.9, - appliedPrivilegeDiscount: templategenP1PrivilegeDiscount, - }, - ], - }, - { - id: "t5", - name: "templategenTariff2", - price: 600 * 600, - isCustom: false, - privileges: [ - { - description: "d5", - price: 600 * 600, - amount: 100, - privilegeId: "p5", - serviceKey: "templategen", - appliedPrivilegeDiscount: null, - }, - ], - }, - ], - appliedServiceDiscount: null, - }, - ], - allAppliedDiscounts: [templategenP1PrivilegeDiscount], - appliedCartPurchasesDiscount: null, - appliedLoyaltyDiscount: null, - }; - expect(cart).toStrictEqual(expectedCart); - }); - - it("applies cart purchases discount to tariff", () => { - const cart = calcCart([templategenTariff1], [cartPurchasesDiscount], 0); - - const expectedCart: CartData = { - itemCount: 1, - priceAfterDiscounts: 100 * 100 * 0.7, - priceBeforeDiscounts: 100 * 100, - services: [ - { - serviceKey: "templategen", - price: 100 * 100, - tariffs: [ - { - id: "t1", - name: "templategenTariff1", - price: 100 * 100, - isCustom: false, - privileges: [ - { - amount: 100, - privilegeId: "p1", - serviceKey: "templategen", - description: "d1", - price: 100 * 100, - appliedPrivilegeDiscount: null, - }, - ], - }, - ], - appliedServiceDiscount: null, - }, - ], - allAppliedDiscounts: [cartPurchasesDiscount], - appliedCartPurchasesDiscount: cartPurchasesDiscount, - appliedLoyaltyDiscount: null, - }; - expect(cart).toStrictEqual(expectedCart); - }); - - it("doesn't apply cart purchases discount when cartPurchasesAmount is not enough", () => { - const cart = calcCart([templategenTariff1], [highAmountCartPurchasesDiscount], 0); - - const expectedCart: CartData = { - itemCount: 1, - priceAfterDiscounts: 100 * 100, - priceBeforeDiscounts: 100 * 100, - services: [ - { - serviceKey: "templategen", - price: 100 * 100, - tariffs: [ - { - id: "t1", - name: "templategenTariff1", - price: 100 * 100, - isCustom: false, - privileges: [ - { - amount: 100, - privilegeId: "p1", - serviceKey: "templategen", - description: "d1", - price: 100 * 100, - appliedPrivilegeDiscount: null, - }, - ], - }, - ], - appliedServiceDiscount: null, - }, - ], - allAppliedDiscounts: [], - appliedCartPurchasesDiscount: null, - appliedLoyaltyDiscount: null, - }; - expect(cart).toStrictEqual(expectedCart); - }); - - it("applies loyalty discount to tariff", () => { - const cart = calcCart([templategenTariff1], [loyaltyDiscount], 1001); - - const expectedCart: CartData = { - itemCount: 1, - priceAfterDiscounts: 100 * 100 * 0.6, - priceBeforeDiscounts: 100 * 100, - services: [ - { - serviceKey: "templategen", - price: 100 * 100, - tariffs: [ - { - id: "t1", - name: "templategenTariff1", - price: 100 * 100, - isCustom: false, - privileges: [ - { - amount: 100, - privilegeId: "p1", - serviceKey: "templategen", - description: "d1", - price: 100 * 100, - appliedPrivilegeDiscount: null, - }, - ], - }, - ], - appliedServiceDiscount: null, - }, - ], - allAppliedDiscounts: [loyaltyDiscount], - appliedCartPurchasesDiscount: null, - appliedLoyaltyDiscount: loyaltyDiscount, - }; - expect(cart).toStrictEqual(expectedCart); - }); - - it("doesn't apply loyalty discount when purchasesAmount is not enough", () => { - const cart = calcCart([templategenTariff1], [loyaltyDiscount], 0); - - const expectedCart: CartData = { - itemCount: 1, - priceAfterDiscounts: 100 * 100, - priceBeforeDiscounts: 100 * 100, - services: [ - { - serviceKey: "templategen", - price: 100 * 100, - tariffs: [ - { - id: "t1", - name: "templategenTariff1", - price: 100 * 100, - isCustom: false, - privileges: [ - { - amount: 100, - privilegeId: "p1", - serviceKey: "templategen", - description: "d1", - price: 100 * 100, - appliedPrivilegeDiscount: null, - }, - ], - }, - ], - appliedServiceDiscount: null, - }, - ], - allAppliedDiscounts: [], - appliedCartPurchasesDiscount: null, - appliedLoyaltyDiscount: null, - }; - expect(cart).toStrictEqual(expectedCart); - }); - }); - - describe("with multiple discounts", () => { - it("applies privilege and service discounts to tariff", () => { - const cart = calcCart([templategenTariff1], [templategenP1PrivilegeDiscount, templategenServiceDiscount], 0); - - const expectedCart: CartData = { - itemCount: 1, - priceAfterDiscounts: 100 * 100 * 0.9 * 0.8, - priceBeforeDiscounts: 100 * 100, - services: [ - { - serviceKey: "templategen", - price: 100 * 100 * 0.9 * 0.8, - tariffs: [ - { - id: "t1", - name: "templategenTariff1", - price: 100 * 100 * 0.9 * 0.8, - isCustom: false, - privileges: [ - { - amount: 100, - privilegeId: "p1", - serviceKey: "templategen", - description: "d1", - price: 100 * 100 * 0.9 * 0.8, - appliedPrivilegeDiscount: templategenP1PrivilegeDiscount, - }, - ], - }, - ], - appliedServiceDiscount: templategenServiceDiscount, - }, - ], - allAppliedDiscounts: [templategenP1PrivilegeDiscount, templategenServiceDiscount], - appliedCartPurchasesDiscount: null, - appliedLoyaltyDiscount: null, - }; - expect(cart).toStrictEqual(expectedCart); - }); - - it("applies all types of discounts to tariff", () => { - const cart = calcCart( - [templategenTariff1], - [templategenP1PrivilegeDiscount, templategenServiceDiscount, cartPurchasesDiscount, loyaltyDiscount], - 1001 - ); - - const expectedCart: CartData = { - itemCount: 1, - priceAfterDiscounts: 100 * 100 * 0.9 * 0.8 * 0.7 * 0.6, - priceBeforeDiscounts: 100 * 100, - services: [ - { - serviceKey: "templategen", - price: 100 * 100 * 0.9 * 0.8, - tariffs: [ - { - id: "t1", - name: "templategenTariff1", - price: 100 * 100 * 0.9 * 0.8, - isCustom: false, - privileges: [ - { - amount: 100, - privilegeId: "p1", - serviceKey: "templategen", - description: "d1", - price: 100 * 100 * 0.9 * 0.8, - appliedPrivilegeDiscount: templategenP1PrivilegeDiscount, - }, - ], - }, - ], - appliedServiceDiscount: templategenServiceDiscount, - }, - ], - allAppliedDiscounts: [templategenP1PrivilegeDiscount, templategenServiceDiscount, cartPurchasesDiscount, loyaltyDiscount], - appliedCartPurchasesDiscount: cartPurchasesDiscount, - appliedLoyaltyDiscount: loyaltyDiscount, - }; - expect(cart).toStrictEqual(expectedCart); - }); - - it("applies different discounts to different tariffs", () => { - const cart = calcCart( - [templategenTariff1, reducerTariff], - [templategenP1PrivilegeDiscount, reducerPrivilegeDiscount], - 1001 - ); - - const expectedCart: CartData = { - itemCount: 2, - priceAfterDiscounts: 100 * 100 * 0.9 + 300 * 300 * 0.95, - priceBeforeDiscounts: 100 * 100 + 300 * 300, - services: [ - { - serviceKey: "templategen", - price: 100 * 100 * 0.9, - tariffs: [ - { - id: "t1", - name: "templategenTariff1", - price: 100 * 100 * 0.9, - isCustom: false, - privileges: [ - { - amount: 100, - privilegeId: "p1", - serviceKey: "templategen", - description: "d1", - price: 100 * 100 * 0.9, - appliedPrivilegeDiscount: templategenP1PrivilegeDiscount, - }, - ], - }, - ], - appliedServiceDiscount: null, - }, - { - serviceKey: "reducer", - price: 300 * 300 * 0.95, - tariffs: [ - { - id: "t3", - name: "reducerTariff", - price: 300 * 300 * 0.95, - isCustom: false, - privileges: [ - { - amount: 100, - description: "d3", - price: 300 * 300 * 0.95, - privilegeId: "p3", - serviceKey: "reducer", - appliedPrivilegeDiscount: reducerPrivilegeDiscount, - }, - ], - }, - ], - appliedServiceDiscount: null, - }, - ], - allAppliedDiscounts: [templategenP1PrivilegeDiscount, reducerPrivilegeDiscount], - appliedCartPurchasesDiscount: null, - appliedLoyaltyDiscount: null, - }; - expect(cart).toStrictEqual(expectedCart); - }); - - it("applies all types of discounts to multiple tariffs", () => { - const cart = calcCart( - [templategenTariff1, templategenTariff2, squizTariff, reducerTariff], - [templategenP1PrivilegeDiscount, reducerPrivilegeDiscount, templategenServiceDiscount, cartPurchasesDiscount, loyaltyDiscount], - 1001 - ); - - const expectedCart: CartData = { - itemCount: 4, - priceAfterDiscounts: ((100 * 100 * 0.9 + 600 * 600) * 0.8 + 200 * 200 + 300 * 300 * 0.95) * 0.7 * 0.6, - priceBeforeDiscounts: 100 * 100 + 600 * 600 + 200 * 200 + 300 * 300, - services: [ - { - serviceKey: "templategen", - price: (100 * 100 * 0.9 + 600 * 600) * 0.8, - tariffs: [ - { - id: "t1", - name: "templategenTariff1", - price: 100 * 100 * 0.9 * 0.8, - isCustom: false, - privileges: [ - { - amount: 100, - privilegeId: "p1", - serviceKey: "templategen", - description: "d1", - price: 100 * 100 * 0.9 * 0.8, - appliedPrivilegeDiscount: templategenP1PrivilegeDiscount, - }, - ], - }, - { - id: "t5", - name: "templategenTariff2", - price: 600 * 600 * 0.8, - isCustom: false, - privileges: [ - { - amount: 100, - description: "d5", - price: 600 * 600 * 0.8, - privilegeId: "p5", - serviceKey: "templategen", - appliedPrivilegeDiscount: null, - }, - ], - }, - ], - appliedServiceDiscount: templategenServiceDiscount, - }, - { - serviceKey: "squiz", - price: 200 * 200, - tariffs: [ - { - id: "t2", - name: "squizTariff", - price: 200 * 200, - isCustom: false, - privileges: [ - { - amount: 100, - description: "d2", - price: 200 * 200, - privilegeId: "p2", - serviceKey: "squiz", - appliedPrivilegeDiscount: null, - }, - ], - }, - ], - appliedServiceDiscount: null, - }, - { - serviceKey: "reducer", - price: 300 * 300 * 0.95, - tariffs: [ - { - id: "t3", - name: "reducerTariff", - price: 300 * 300 * 0.95, - isCustom: false, - privileges: [ - { - amount: 100, - description: "d3", - price: 300 * 300 * 0.95, - privilegeId: "p3", - serviceKey: "reducer", - appliedPrivilegeDiscount: reducerPrivilegeDiscount, - }, - ], - }, - ], - appliedServiceDiscount: null, - }, - ], - allAppliedDiscounts: [templategenP1PrivilegeDiscount, reducerPrivilegeDiscount, templategenServiceDiscount, cartPurchasesDiscount, loyaltyDiscount], - appliedCartPurchasesDiscount: cartPurchasesDiscount, - appliedLoyaltyDiscount: loyaltyDiscount, - }; - expect(cart).toStrictEqual(expectedCart); - }); - }); -}); - -const templategenTariff1: Tariff = { - _id: "t1", - name: "templategenTariff1", - price: 0, - description: "test", - isCustom: false, - privileges: [ - { - _id: "p1", - name: "n1", - privilegeId: "p1", - serviceKey: "templategen", - description: "d1", - type: "count", - value: "МБ", - price: 100, - amount: 100, - }, - ], - isDeleted: false, - createdAt: "", - updatedAt: "" -}; - -const templategenTariff2: Tariff = { - description: "test", - _id: "t5", - name: "templategenTariff2", - price: 0, - isCustom: false, - privileges: [ - { - _id: "p5", - name: "n5", - privilegeId: "p5", - serviceKey: "templategen", - description: "d5", - type: "count", - value: "МБ", - price: 600, - amount: 600, - }, - ], - isDeleted: false, - createdAt: "", - updatedAt: "" -}; - -const customTemplategenTariff: Tariff = { - description: "test", - _id: "t1", - name: "templategenTariff3", - price: 0, - isCustom: true, - privileges: [ - { - _id: "p1", - name: "n1", - privilegeId: "p1", - serviceKey: "templategen", - description: "d1", - type: "count", - value: "МБ", - price: 100, - amount: 100, - }, - ], - isDeleted: false, - createdAt: "", - updatedAt: "" -}; - -const squizTariff: Tariff = { - description: "test", - _id: "t2", - name: "squizTariff", - price: 0, - isCustom: false, - privileges: [ - { - _id: "p2", - name: "n2", - privilegeId: "p2", - serviceKey: "squiz", - description: "d2", - type: "count", - value: "МБ", - price: 200, - amount: 200, - }, - ], - isDeleted: false, - createdAt: "", - updatedAt: "" -}; - -const reducerTariff: Tariff = { - description: "test", - _id: "t3", - name: "reducerTariff", - price: 0, - isCustom: false, - privileges: [ - { - _id: "p3", - name: "n3", - privilegeId: "p3", - serviceKey: "reducer", - description: "d3", - type: "count", - value: "МБ", - price: 300, - amount: 300, - }, - ], - isDeleted: false, - createdAt: "", - updatedAt: "" -}; - -const templategenP1PrivilegeDiscount: Discount = { - ID: "id1", - Name: "n1", - Layer: 1, - Description: "d1", - Condition: { - Period: { - From: "", - To: "" - }, - User: "", - UserType: "", - Coupon: "", - PurchasesAmount: 0, - CartPurchasesAmount: 0, - Product: "p1", - Term: "1000", - Usage: "0", - PriceFrom: 0, - Group: "templategen" - }, - Target: { - Products: [ - { - ID: "p1", - Factor: 0.9, - Overhelm: false - } - ], - Factor: 0.9, - TargetScope: "Sum", - TargetGroup: "templategen", - Overhelm: false - }, - Audit: { - UpdatedAt: "", - CreatedAt: "", - Deleted: false - }, - Deprecated: false -}; - -const templategenServiceDiscount: Discount = { - ID: "id2", - Name: "n2", - Layer: 2, - Description: "d2", - Condition: { - Period: { - From: "", - To: "" - }, - User: "", - UserType: "", - Coupon: "", - PurchasesAmount: 0, - CartPurchasesAmount: 0, - Product: "", - Term: "1000", - Usage: "0", - PriceFrom: 0, - Group: "templategen" - }, - Target: { - Products: [ - { - ID: "", - Factor: 0.8, - Overhelm: false - } - ], - Factor: 0.8, - TargetScope: "Sum", - TargetGroup: "templategen", - Overhelm: false - }, - Audit: { - UpdatedAt: "", - CreatedAt: "", - Deleted: false - }, - Deprecated: false -}; - -const reducerPrivilegeDiscount: Discount = { - ID: "id11", - Name: "n11", - Layer: 1, - Description: "d11", - Condition: { - Period: { - From: "", - To: "" - }, - User: "", - UserType: "", - Coupon: "", - PurchasesAmount: 0, - CartPurchasesAmount: 0, - Product: "p3", - Term: "1000", - Usage: "0", - PriceFrom: 0, - Group: "reducer" - }, - Target: { - Products: [ - { - ID: "p1", - Factor: 0.95, - Overhelm: false - } - ], - Factor: 0.95, - TargetScope: "Sum", - TargetGroup: "reducer", - Overhelm: false - }, - Audit: { - UpdatedAt: "", - CreatedAt: "", - Deleted: false - }, - Deprecated: false -}; - -const cartPurchasesDiscount: Discount = { - ID: "id3", - Name: "n3", - Layer: 3, - Description: "d3", - Condition: { - Period: { - From: "", - To: "" - }, - User: "", - UserType: "", - Coupon: "", - PurchasesAmount: 0, - CartPurchasesAmount: 1000, - Product: "", - Term: "0", - Usage: "0", - PriceFrom: 0, - Group: "templategen" - }, - Target: { - Products: [ - { - ID: "p1", - Factor: 0.7, - Overhelm: false - } - ], - Factor: 0.7, - TargetScope: "Sum", - TargetGroup: "templategen", - Overhelm: false - }, - Audit: { - UpdatedAt: "", - CreatedAt: "", - Deleted: false - }, - Deprecated: false -}; - -const highAmountCartPurchasesDiscount: Discount = { - ID: "id3", - Name: "n3", - Layer: 3, - Description: "d3", - Condition: { - Period: { - From: "", - To: "" - }, - User: "", - UserType: "", - Coupon: "", - PurchasesAmount: 0, - CartPurchasesAmount: 1000000, - Product: "", - Term: "0", - Usage: "0", - PriceFrom: 0, - Group: "templategen" - }, - Target: { - Products: [ - { - ID: "p1", - Factor: 0.7, - Overhelm: false - } - ], - Factor: 0.7, - TargetScope: "Sum", - TargetGroup: "templategen", - Overhelm: false - }, - Audit: { - UpdatedAt: "", - CreatedAt: "", - Deleted: false - }, - Deprecated: false -}; - -const loyaltyDiscount: Discount = { - ID: "id3", - Name: "n3", - Layer: 4, - Description: "d3", - Condition: { - Period: { - From: "", - To: "" - }, - User: "", - UserType: "", - Coupon: "", - PurchasesAmount: 1000, - CartPurchasesAmount: 0, - Product: "", - Term: "0", - Usage: "0", - PriceFrom: 0, - Group: "templategen" - }, - Target: { - Products: [ - { - ID: "p1", - Factor: 0.6, - Overhelm: false - } - ], - Factor: 0.6, - TargetScope: "Sum", - TargetGroup: "templategen", - Overhelm: false - }, - Audit: { - UpdatedAt: "", - CreatedAt: "", - Deleted: false - }, - Deprecated: false -}; diff --git a/src/utils/calcCart/calcCart.ts b/src/utils/calcCart/calcCart.ts deleted file mode 100644 index 8275e76..0000000 --- a/src/utils/calcCart/calcCart.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { CartData, Discount, PrivilegeCartData, Tariff, TariffCartData, applyCartDiscount, applyLoyaltyDiscount, applyPrivilegeDiscounts, applyServiceDiscounts } from "@frontend/kitui"; - - -export function calcCart( - tariffs: Tariff[], - discounts: Discount[], - purchasesAmount: number, - coupon?: string, -): CartData { - const cartData: CartData = { - services: [], - priceBeforeDiscounts: 0, - priceAfterDiscounts: 0, - itemCount: 0, - appliedCartPurchasesDiscount: null, - appliedLoyaltyDiscount: null, - allAppliedDiscounts: [], - }; - - const serviceTariffType: Record = {}; - - tariffs.forEach(tariff => { - let serviceData = cartData.services.find(service => service.serviceKey === tariff.privileges[0].serviceKey); - if (!serviceData) { - serviceData = { - serviceKey: tariff.privileges[0].serviceKey, - tariffs: [], - price: 0, - appliedServiceDiscount: null, - }; - cartData.services.push(serviceData); - } - - const tariffCartData: TariffCartData = { - price: tariff.price ?? 0, - isCustom: tariff.isCustom, - privileges: [], - id: tariff._id, - name: tariff.name, - }; - serviceData.tariffs.push(tariffCartData); - - tariff.privileges.forEach(privilege => { - serviceTariffType[privilege.serviceKey] ??= +tariff.isCustom; - const isIncompatibleTariffs = serviceTariffType[privilege.serviceKey] ^ +tariff.isCustom; - if (isIncompatibleTariffs) throw new Error("Если взят готовый тариф, то кастомный на этот сервис сделать уже нельзя"); - - const privilegePrice = privilege.amount * privilege.price; - - if (!tariff.price) tariffCartData.price += privilegePrice; - - const privilegeCartData: PrivilegeCartData = { - serviceKey: privilege.serviceKey, - amount: privilege.amount, - privilegeId: privilege.privilegeId, - description: privilege.description, - price: privilegePrice, - appliedPrivilegeDiscount: null, - }; - - tariffCartData.privileges.push(privilegeCartData); - cartData.priceAfterDiscounts += privilegePrice; - cartData.itemCount++; - }); - - cartData.priceBeforeDiscounts += tariffCartData.price; - serviceData.price += tariffCartData.price; - }); - - applyPrivilegeDiscounts(cartData, discounts); - applyServiceDiscounts(cartData, discounts); - applyCartDiscount(cartData, discounts); - applyLoyaltyDiscount(cartData, discounts, purchasesAmount); - - cartData.allAppliedDiscounts = Array.from(new Set(cartData.allAppliedDiscounts)); - - return cartData; -} - -export function formatDiscountFactor(factor: number): string { - return `${((1 - factor) * 100).toFixed(1)}%`; -} diff --git a/src/utils/formatDiscountFactor.ts b/src/utils/formatDiscountFactor.ts new file mode 100644 index 0000000..6ed645e --- /dev/null +++ b/src/utils/formatDiscountFactor.ts @@ -0,0 +1,3 @@ +export function formatDiscountFactor(factor: number): string { + return `${((1 - factor) * 100).toFixed(1)}%`; +} diff --git a/src/utils/hooks/usePrivileges.ts b/src/utils/hooks/usePrivileges.ts index 81d8489..8969731 100644 --- a/src/utils/hooks/usePrivileges.ts +++ b/src/utils/hooks/usePrivileges.ts @@ -1,29 +1,28 @@ -import { useEffect } from "react"; - -import { requestPrivileges } from "@root/api/privilegies"; - -import type { PrivilegeWithAmount } from "@frontend/kitui"; - -export default function usePrivileges({ - onError, - onNewPrivileges, -}: { - onNewPrivileges: (response: PrivilegeWithAmount[]) => void; - onError?: (error: any) => void; -}) { - useEffect(() => { - const controller = new AbortController(); - - requestPrivileges(controller.signal).then( - ([privilegesResponse, privilegesError]) => { - if (privilegesError) { - return onError?.(privilegesError); - } - - onNewPrivileges(privilegesResponse); - } - ); - - return () => controller.abort(); - }, [onError, onNewPrivileges]); -} +import { useEffect } from "react"; + +import { requestPrivileges } from "@root/api/privilegies"; + +import type { CustomPrivilege } from "@frontend/kitui"; + +export default function usePrivileges({ + onError, + onNewPrivileges, +}: { + onNewPrivileges: (response: CustomPrivilege[]) => void; + onError?: (error: any) => void; +}) { + useEffect(() => { + const controller = new AbortController(); + + requestPrivileges(controller.signal).then( + ([privilegesResponse, privilegesError]) => { + if (privilegesError) { + return onError?.(privilegesError); + } + onNewPrivileges(privilegesResponse); + } + ); + + return () => controller.abort(); + }, [onError, onNewPrivileges]); +} diff --git a/yarn.lock b/yarn.lock index e38adc7..f88ce4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1433,10 +1433,10 @@ lodash.isundefined "^3.0.1" lodash.uniq "^4.5.0" -"@frontend/kitui@^1.0.59": - version "1.0.59" - resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/@frontend/kitui/-/@frontend/kitui-1.0.59.tgz#c4584506bb5cab4fc1df35f5b1d0d66ec379a9a1" - integrity sha1-xFhFBrtcq0/B3zX1sdDWbsN5qaE= +"@frontend/kitui@^1.0.77": + version "1.0.77" + resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/@frontend/kitui/-/@frontend/kitui-1.0.77.tgz#a749dee0e7622b4c4509e8354ace47299b0606f7" + integrity sha1-p0ne4OdiK0xFCeg1Ss5HKZsGBvc= dependencies: immer "^10.0.2" reconnecting-eventsource "^1.6.2" @@ -10472,6 +10472,13 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-error-boundary@^4.0.13: + version "4.0.13" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.13.tgz#80386b7b27b1131c5fbb7368b8c0d983354c7947" + integrity sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ== + dependencies: + "@babel/runtime" "^7.12.5" + react-error-overlay@^6.0.11: version "6.0.11" resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz" From e09443b4c6eebd019346f44c04c5b314492f00e0 Mon Sep 17 00:00:00 2001 From: nflnkr Date: Wed, 27 Mar 2024 20:31:51 +0300 Subject: [PATCH 08/17] fix cart promocode apply --- src/api/promocode/requests.ts | 30 ++++++++++++++++- src/api/promocode/swr.ts | 15 ++++++++- src/kitUI/Cart/Cart.tsx | 20 +++++++++-- src/model/cart.ts | 8 ----- src/utils/createDiscountFromPromocode.ts | 42 ++++++++++++++++++++++++ 5 files changed, 103 insertions(+), 12 deletions(-) delete mode 100644 src/model/cart.ts create mode 100644 src/utils/createDiscountFromPromocode.ts diff --git a/src/api/promocode/requests.ts b/src/api/promocode/requests.ts index 1e7e114..d5da8d1 100644 --- a/src/api/promocode/requests.ts +++ b/src/api/promocode/requests.ts @@ -25,6 +25,33 @@ const getPromocodeList = async (body: GetPromocodeListBody) => { } }; +export const getAllPromocodes = async () => { + try { + const promocodes: Promocode[] = []; + + let page = 0; + while (true) { + const promocodeList = await getPromocodeList({ + limit: 2, + filter: { + active: true, + }, + page, + }); + + if (promocodeList.items.length === 0) break; + + promocodes.push(...promocodeList.items); + page++; + } + + return promocodes; + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); + throw new Error(`Ошибка при получении списка промокодов. ${error}`); + } +}; + const createPromocode = async (body: CreatePromocodeBody) => { try { const createPromocodeResponse = await makeRequest< @@ -42,7 +69,7 @@ const createPromocode = async (body: CreatePromocodeBody) => { if (isAxiosError(nativeError) && nativeError.response?.data.error === "Duplicate Codeword") { throw new Error(`Промокод уже существует`); } - + const [error] = parseAxiosError(nativeError); throw new Error(`Ошибка создания промокода. ${error}`); } @@ -65,4 +92,5 @@ export const promocodeApi = { getPromocodeList, createPromocode, deletePromocode, + getAllPromocodes, }; diff --git a/src/api/promocode/swr.ts b/src/api/promocode/swr.ts index 064d41a..afa0d4e 100644 --- a/src/api/promocode/swr.ts +++ b/src/api/promocode/swr.ts @@ -78,4 +78,17 @@ export function usePromocodes(page: number, pageSize: number) { deletePromocode, promocodesCount: promocodesCountRef.current, }; -} +} + +export function useAllPromocodes() { + const swrResponse = useSwr("allPromocodes", promocodeApi.getAllPromocodes, { + keepPreviousData: true, + suspense: true, + onError(err) { + console.log("Error fetching all promocodes", err); + enqueueSnackbar(err.message, { variant: "error" }); + }, + }); + + return swrResponse.data; +} diff --git a/src/kitUI/Cart/Cart.tsx b/src/kitUI/Cart/Cart.tsx index aa2a0ff..35f1faf 100644 --- a/src/kitUI/Cart/Cart.tsx +++ b/src/kitUI/Cart/Cart.tsx @@ -16,20 +16,23 @@ import { useMediaQuery, useTheme } from "@mui/material"; +import { useDiscounts } from "@root/api/discounts"; +import { useAllPromocodes } from "@root/api/promocode/swr"; import { requestDiscounts } from "@root/services/discounts.service"; import { requestPrivileges } from "@root/services/privilegies.service"; import { setCartData, useCartStore } from "@root/stores/cart"; import { useTariffStore } from "@root/stores/tariffs"; +import { createDiscountFromPromocode } from "@root/utils/createDiscountFromPromocode"; import { currencyFormatter } from "@root/utils/currencyFormatter"; import { useState } from "react"; import CartItemRow from "./CartItemRow"; -import { useDiscounts } from "@root/api/discounts"; export default function Cart() { const theme = useTheme(); const mobile = useMediaQuery(theme.breakpoints.down(400)); const discounts = useDiscounts(); + const promocodes = useAllPromocodes(); const cartData = useCartStore((store) => store.cartData); const tariffs = useTariffStore(state => state.tariffs); const [couponField, setCouponField] = useState(""); @@ -48,8 +51,21 @@ export default function Cart() { if (!isFinite(loyaltyValue)) loyaltyValue = 0; + const promocode = promocodes.find(promocode => { + if (promocode.dueTo < (Date.now() / 1000)) return false; + + return promocode.codeword === couponField; + }); + + const userId = crypto.randomUUID(); + + const discountsWithPromocodeDiscount = promocode ? [ + ...discounts, + createDiscountFromPromocode(promocode, userId), + ] : discounts; + try { - const cartData = calcCart(cartTariffs, discounts, loyaltyValue, couponField); + const cartData = calcCart(cartTariffs, discountsWithPromocodeDiscount, loyaltyValue, userId); setErrorMessage(null); setCartData(cartData); diff --git a/src/model/cart.ts b/src/model/cart.ts deleted file mode 100644 index e5263b3..0000000 --- a/src/model/cart.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface Promocode { - id: string; - name: string; - endless: boolean; - from: string; - dueTo: string; - privileges: string[]; -} diff --git a/src/utils/createDiscountFromPromocode.ts b/src/utils/createDiscountFromPromocode.ts new file mode 100644 index 0000000..392b9e4 --- /dev/null +++ b/src/utils/createDiscountFromPromocode.ts @@ -0,0 +1,42 @@ +import { Promocode } from "@root/model/promocodes"; + + +export function createDiscountFromPromocode(promocode: Promocode, userId: string) { + return { + "ID": crypto.randomUUID(), + "Name": promocode.codeword, + "Layer": promocode.bonus.discount.layer, + "Description": "", + "Condition": { + "User": userId, + "UserType": "", + "Coupon": promocode.codeword, + "PurchasesAmount": "0", + "CartPurchasesAmount": "0", + "Product": promocode.bonus.discount.target, + "Term": "0", + "Usage": "0", + "PriceFrom": "0", + "Group": promocode.bonus.discount.target + }, + "Target": { + "Products": promocode.bonus.discount.layer === 1 ? [ + { + "ID": promocode.bonus.discount.target, + "Factor": promocode.bonus.discount.factor, + "Overhelm": false + } + ] : [], + "Factor": promocode.bonus.discount.layer === 2 ? promocode.bonus.discount.factor : 0, + "TargetScope": "Sum", + "TargetGroup": promocode.bonus.discount.target, + "Overhelm": true + }, + "Audit": { + "UpdatedAt": "", + "CreatedAt": "", + "Deleted": false + }, + "Deprecated": false + }; +} From e2f5781d0c7427a0d0ac73a86e8b276cea53b91f Mon Sep 17 00:00:00 2001 From: nflnkr Date: Wed, 27 Mar 2024 20:34:17 +0300 Subject: [PATCH 09/17] trim promocode field on use --- src/kitUI/Cart/Cart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kitUI/Cart/Cart.tsx b/src/kitUI/Cart/Cart.tsx index 35f1faf..f5d77c9 100644 --- a/src/kitUI/Cart/Cart.tsx +++ b/src/kitUI/Cart/Cart.tsx @@ -54,7 +54,7 @@ export default function Cart() { const promocode = promocodes.find(promocode => { if (promocode.dueTo < (Date.now() / 1000)) return false; - return promocode.codeword === couponField; + return promocode.codeword === couponField.trim(); }); const userId = crypto.randomUUID(); From 7daa2f7e99cf13ec804c309d7b366084c6d1f76f Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 28 Mar 2024 23:31:28 +0300 Subject: [PATCH 10/17] =?UTF-8?q?=D0=BA=D0=B2=D0=B8=D0=B7=D1=8B=20=D0=B2?= =?UTF-8?q?=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=8E=D0=B7=D0=B5?= =?UTF-8?q?=D1=80=D0=B0(=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE?= =?UTF-8?q?=20=D1=82=D1=80=D0=B5=D0=B1=D1=83=D0=B5=D1=82=20=D0=B4=D0=BE?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BE=D0=BA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/promocode/swr.ts | 1 + .../PromocodeManagement/StatisticsModal.tsx | 3 +- src/pages/dashboard/ModalUser/QuizTab.tsx | 48 +++++++++++++++++++ src/pages/dashboard/ModalUser/index.tsx | 5 +- 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/pages/dashboard/ModalUser/QuizTab.tsx diff --git a/src/api/promocode/swr.ts b/src/api/promocode/swr.ts index 8b0da14..7a664e5 100644 --- a/src/api/promocode/swr.ts +++ b/src/api/promocode/swr.ts @@ -113,6 +113,7 @@ export function usePromocodes( ...swrResponse, createPromocode, deletePromocode, + promocodeStatistics, promocodesCount: promocodesCountRef.current, }; } diff --git a/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx b/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx index 100e5e5..666d8b7 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx @@ -19,7 +19,8 @@ import type { PromocodeStatistics } from "@root/model/promocodes"; type StatisticsModalProps = { id: string; setId: (id: string) => void; - promocodeStatistics: PromocodeStatistics[] | null | undefined; + // promocodeStatistics: PromocodeStatistics[] | null | undefined; + promocodeStatistics: any; }; const COLUMNS: GridColDef[] = [ diff --git a/src/pages/dashboard/ModalUser/QuizTab.tsx b/src/pages/dashboard/ModalUser/QuizTab.tsx new file mode 100644 index 0000000..0781d50 --- /dev/null +++ b/src/pages/dashboard/ModalUser/QuizTab.tsx @@ -0,0 +1,48 @@ +import {Box, Button, TextField, Typography} from "@mui/material"; +import {ChangeEvent, useState} from "react"; +import {makeRequest} from "@frontend/kitui"; + +type QuizTabProps = { + userId: string; +}; + +export default function QuizTab({ userId }: QuizTabProps) { + const [quizId, setQuizId] = useState("") + console.log(quizId) + return( + + + Передача Квиза + + + )=>{ + setQuizId(event.target.value.split("link/")[1]) + + }} + /> + + + + + ) +} \ No newline at end of file diff --git a/src/pages/dashboard/ModalUser/index.tsx b/src/pages/dashboard/ModalUser/index.tsx index 63014df..da0686e 100644 --- a/src/pages/dashboard/ModalUser/index.tsx +++ b/src/pages/dashboard/ModalUser/index.tsx @@ -21,10 +21,11 @@ import { ReactComponent as PackageIcon } from "@root/assets/icons/package.svg"; import { ReactComponent as TransactionsIcon } from "@root/assets/icons/transactions.svg"; import { ReactComponent as CheckIcon } from "@root/assets/icons/check.svg"; import { ReactComponent as CloseIcon } from "@root/assets/icons/close.svg"; - +import QuizIcon from '@mui/icons-material/Quiz'; import forwardIcon from "@root/assets/icons/forward.svg"; import type { SyntheticEvent } from "react"; +import QuizTab from "@pages/dashboard/ModalUser/QuizTab"; const TABS = [ { name: "Пользователь", icon: UserIcon, activeStyles: { fill: "#7E2AEA" } }, @@ -39,6 +40,7 @@ const TABS = [ activeStyles: { stroke: "#7E2AEA" }, }, { name: "Верификация", icon: CheckIcon, activeStyles: { stroke: "#7E2AEA" } }, + { name: "Квизы", icon: QuizIcon, activeStyles: { stroke: "#7E2AEA" } }, ]; type ModalUserProps = { @@ -194,6 +196,7 @@ const ModalUser = ({ open, onClose, userId }: ModalUserProps) => { {value === 1 && } {value === 2 && } {value === 3 && } + {value === 4 && } From 153b518914f156080505bcd147a4dcc636f71d2f Mon Sep 17 00:00:00 2001 From: Tamara Date: Fri, 29 Mar 2024 23:27:07 +0300 Subject: [PATCH 11/17] =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81?= =?UTF-8?q?=D1=8B=20=D1=81=D1=82=D0=B0=D1=82=D0=B8=D1=81=D1=82=D0=B8=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BF=D1=80=D0=BE=D0=BC=D0=BE=D0=BA=D0=BE=D0=B4=D0=BE?= =?UTF-8?q?=D0=B2(=D1=81=D1=8B=D1=80=D1=8B=D0=B5,=20=D0=BD=D1=83=D0=B6?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D0=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/promocode/requests.ts | 11 +++-- src/api/promocode/swr.ts | 48 ++++++++++--------- .../PromocodeManagement/StatisticsModal.tsx | 46 ++++++++++++++---- .../Content/PromocodeManagement/index.tsx | 14 ++++-- .../usePromocodeGridColDef.tsx | 11 +++-- 5 files changed, 87 insertions(+), 43 deletions(-) diff --git a/src/api/promocode/requests.ts b/src/api/promocode/requests.ts index d907e90..98283f0 100644 --- a/src/api/promocode/requests.ts +++ b/src/api/promocode/requests.ts @@ -98,14 +98,19 @@ const deletePromocode = async (id: string): Promise => { } }; -const getPromocodeStatistics = async (id: string) => { +const getPromocodeStatistics = async (id: string, from: number, to: number,) => { try { const promocodeStatisticsResponse = await makeRequest< unknown, PromocodeStatistics[] >({ - url: baseUrl + `/getStatistics/${id}`, - method: "GET", + url: baseUrl + `/stats`, + body: { + "id": id, + "from": from, + "to": to + }, + method: "POST", useToken: false, }); diff --git a/src/api/promocode/swr.ts b/src/api/promocode/swr.ts index 7a664e5..f17f520 100644 --- a/src/api/promocode/swr.ts +++ b/src/api/promocode/swr.ts @@ -11,7 +11,9 @@ import type { export function usePromocodes( page: number, pageSize: number, - promocodeId: string + promocodeId: string, + to: number, + from: number, ) { const promocodesCountRef = useRef(0); const swrResponse = useSwr( @@ -87,33 +89,33 @@ export function usePromocodes( [page, pageSize] ); - const promocodeStatistics = useSwr( - ["promocodeStatistics", promocodeId], - async ([_, id]) => { - if (!id) { - return null; - } - - const promocodeStatisticsResponse = - await promocodeApi.getPromocodeStatistics(id); - - return promocodeStatisticsResponse; - }, - { - onError(err) { - console.log("Error fetching promocode statistics", err); - enqueueSnackbar(err.message, { variant: "error" }); - }, - focusThrottleInterval: 60e3, - keepPreviousData: true, - } - ); + // const promocodeStatistics = useSwr( + // ["promocodeStatistics", promocodeId, from, to], + // async ([_, id, from, to]) => { + // if (!id) { + // return null; + // } + // + // const promocodeStatisticsResponse = + // await promocodeApi.getPromocodeStatistics(id, from, to); + // + // return promocodeStatisticsResponse; + // }, + // { + // onError(err) { + // console.log("Error fetching promocode statistics", err); + // enqueueSnackbar(err.message, { variant: "error" }); + // }, + // focusThrottleInterval: 60e3, + // keepPreviousData: true, + // } + // ); return { ...swrResponse, createPromocode, deletePromocode, - promocodeStatistics, + //promocodeStatistics, promocodesCount: promocodesCountRef.current, }; } diff --git a/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx b/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx index 666d8b7..aee23ff 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import {useEffect, useState} from "react"; import { Box, Button, @@ -9,18 +9,24 @@ import { useMediaQuery, } from "@mui/material"; import { DataGrid, GridLoadingOverlay, GridToolbar } from "@mui/x-data-grid"; -import { DesktopDatePicker } from "@mui/x-date-pickers/DesktopDatePicker"; +import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { fadeIn } from "@root/utils/style/keyframes"; import type { GridColDef } from "@mui/x-data-grid"; import type { PromocodeStatistics } from "@root/model/promocodes"; +import moment from "moment"; +import {promocodeApi} from "@root/api/promocode/requests"; type StatisticsModalProps = { id: string; + to: number; + from: number; setId: (id: string) => void; + setTo: (date: number) => void; + setFrom: (date: number) => void; // promocodeStatistics: PromocodeStatistics[] | null | undefined; - promocodeStatistics: any; + // promocodeStatistics: any; }; const COLUMNS: GridColDef[] = [ @@ -50,17 +56,34 @@ const COLUMNS: GridColDef[] = [ export const StatisticsModal = ({ id, setId, - promocodeStatistics, + setFrom, + from, + to, + setTo, + //promocodeStatistics, }: StatisticsModalProps) => { const [startDate, setStartDate] = useState(new Date()); const [endDate, setEndDate] = useState(new Date()); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(550)); - + const [general, setGeneral] = useState({}); + // const formatTo = to === null ? 0 : moment(to).unix() + // const formatFrom = from === null ? 0 : moment(from).unix() + console.log(startDate) + // useEffect(() => { + // (async () => { + // const gottenGeneral = await promocodeStatistics(id, startDate, endDate) + // setGeneral(gottenGeneral[0]) + // })() + // }, [to, from]); return ( setId("")} + onClose={() => { + setId("") + setStartDate(new Date()) + setEndDate(new Date()) + }} sx={{ "& > .MuiBox-root": { outline: "none" } }} > @@ -113,7 +139,7 @@ export const StatisticsModal = ({ от - date && setStartDate(date)} @@ -147,7 +173,7 @@ export const StatisticsModal = ({ до - date && setEndDate(date)} @@ -174,7 +200,7 @@ export const StatisticsModal = ({ { const [showStatisticsModalId, setShowStatisticsModalId] = useState(""); const [page, setPage] = useState(0); + const [to, setTo] = useState(0); + const [from, setFrom] = useState(0); const [pageSize, setPageSize] = useState(10); const { data, error, isValidating, promocodesCount, - promocodeStatistics, + //promocodeStatistics, deletePromocode, createPromocode, - } = usePromocodes(page, pageSize, showStatisticsModalId); + } = usePromocodes(page, pageSize, showStatisticsModalId, to, from); const columns = usePromocodeGridColDef( setShowStatisticsModalId, deletePromocode ); - + console.log(showStatisticsModalId) if (error) return Ошибка загрузки промокодов; return ( @@ -89,7 +91,11 @@ export const PromocodeManagement = () => { ); diff --git a/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx b/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx index 14b1b03..278baa9 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx @@ -4,12 +4,14 @@ import { Promocode } from "@root/model/promocodes"; import { useMemo } from "react"; import { BarChart, Delete } from "@mui/icons-material"; +import {promocodeApi} from "@root/api/promocode/requests"; export function usePromocodeGridColDef( setStatistics: (id: string) => void, deletePromocode: (id: string) => void ) { - return useMemo[]>( + const validity = (value:string|number) => {if(value===0){return "неоганичен"}else {return new Date(value).toLocaleString()}} + return useMemo[]>( () => [ { field: "id", @@ -46,7 +48,7 @@ export function usePromocodeGridColDef( width: 160, sortable: false, valueGetter: ({ row }) => row.dueTo * 1000, - valueFormatter: ({ value }) => new Date(value).toLocaleString(), + valueFormatter: ({ value }) => `${validity(value)}`, }, { field: "settings", @@ -55,7 +57,10 @@ export function usePromocodeGridColDef( sortable: false, renderCell: (params) => { return ( - setStatistics(params.row.id)}> + { + setStatistics(params.row.id,) + promocodeApi.getPromocodeStatistics(params.row.id, 0, 0) + }}> ); From f3b66d92e0a9f17016d66b4444ca57ef79417643 Mon Sep 17 00:00:00 2001 From: Nastya Date: Sat, 30 Mar 2024 23:19:53 +0300 Subject: [PATCH 12/17] =?UTF-8?q?=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=20=D1=82?= =?UTF-8?q?=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/promocode/requests.ts | 2 +- .../PromocodeManagement/DeleteModal.tsx | 59 +++++++++++++++++ .../PromocodeManagement/StatisticsModal.tsx | 66 ++++++++++++------- .../Content/PromocodeManagement/index.tsx | 8 ++- .../usePromocodeGridColDef.tsx | 2 +- 5 files changed, 112 insertions(+), 25 deletions(-) create mode 100644 src/pages/dashboard/Content/PromocodeManagement/DeleteModal.tsx diff --git a/src/api/promocode/requests.ts b/src/api/promocode/requests.ts index 98283f0..005ccf0 100644 --- a/src/api/promocode/requests.ts +++ b/src/api/promocode/requests.ts @@ -113,7 +113,7 @@ const getPromocodeStatistics = async (id: string, from: number, to: number,) => method: "POST", useToken: false, }); - +console.log(promocodeStatisticsResponse) return promocodeStatisticsResponse; } catch (nativeError) { const [error] = parseAxiosError(nativeError); diff --git a/src/pages/dashboard/Content/PromocodeManagement/DeleteModal.tsx b/src/pages/dashboard/Content/PromocodeManagement/DeleteModal.tsx new file mode 100644 index 0000000..5302a00 --- /dev/null +++ b/src/pages/dashboard/Content/PromocodeManagement/DeleteModal.tsx @@ -0,0 +1,59 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Modal from '@mui/material/Modal'; +import Button from '@mui/material/Button'; +import { Typography } from '@mui/material'; + +const style = { + position: 'absolute' as 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: 400, + bgcolor: '#c1c1c1', + border: '2px solid #000', + boxShadow: 24, + pt: 2, + px: 4, + pb: 3, +}; + +interface Props { + id: string; + setModal: (id: string) => void; + deletePromocode: (id: string) => Promise; +} + +export default function ({ + id, + setModal, + deletePromocode +}: Props) { + return ( + setModal("")} + > + + Точно удалить промокод? + + + + + + + ); +} diff --git a/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx b/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx index aee23ff..ae80115 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx @@ -1,4 +1,4 @@ -import {useEffect, useState} from "react"; +import { useEffect, useState } from "react"; import { Box, Button, @@ -16,7 +16,7 @@ import { fadeIn } from "@root/utils/style/keyframes"; import type { GridColDef } from "@mui/x-data-grid"; import type { PromocodeStatistics } from "@root/model/promocodes"; import moment from "moment"; -import {promocodeApi} from "@root/api/promocode/requests"; +import { promocodeApi } from "@root/api/promocode/requests"; type StatisticsModalProps = { id: string; @@ -26,7 +26,7 @@ type StatisticsModalProps = { setTo: (date: number) => void; setFrom: (date: number) => void; // promocodeStatistics: PromocodeStatistics[] | null | undefined; - // promocodeStatistics: any; + // promocodeStatistics: any; }; const COLUMNS: GridColDef[] = [ @@ -53,6 +53,11 @@ const COLUMNS: GridColDef[] = [ }, ]; +type ROW = { + link: string, + useCount: number +} + export const StatisticsModal = ({ id, setId, @@ -66,23 +71,43 @@ export const StatisticsModal = ({ const [endDate, setEndDate] = useState(new Date()); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(550)); - const [general, setGeneral] = useState({}); - // const formatTo = to === null ? 0 : moment(to).unix() - // const formatFrom = from === null ? 0 : moment(from).unix() - console.log(startDate) - // useEffect(() => { - // (async () => { - // const gottenGeneral = await promocodeStatistics(id, startDate, endDate) - // setGeneral(gottenGeneral[0]) - // })() - // }, [to, from]); + const [general, setGeneral] = useState({}); + const [rows, setRows] = useState([]); + const getParseData = async () => { + const promocodeStatistics = await promocodeApi.getPromocodeStatistics(id, moment(startDate).unix(), moment(endDate).unix()) + const rows = [] as ROW[] + //@ts-ignore + for (const [key, value] of Object.entries(promocodeStatistics.usageMap)) { + rows.push({ + link: key, + //@ts-ignore + useCount: value, + id:rows.length + }) + } + setGeneral(promocodeStatistics) + //@ts-ignore + setRows(rows) + } + useEffect(() => { + if (id.length > 0) getParseData() + }, [id]) + // const formatTo = to === null ? 0 : moment(to).unix() + // const formatFrom = from === null ? 0 : moment(from).unix() + console.log(general) + // useEffect(() => { + // (async () => { + // const gottenGeneral = await promocodeStatistics(id, startDate, endDate) + // setGeneral(gottenGeneral[0]) + // })() + // }, [to, from]); return ( { - setId("") - setStartDate(new Date()) - setEndDate(new Date()) + setId("") + setStartDate(new Date()) + setEndDate(new Date()) }} sx={{ "& > .MuiBox-root": { outline: "none" } }} > @@ -120,16 +145,13 @@ export const StatisticsModal = ({ > @@ -200,7 +222,7 @@ export const StatisticsModal = ({ { const theme = useTheme(); + + const [deleteModal, setDeleteModal] = useState("") + const deleteModalHC = (id:string) => setDeleteModal(id) + const [showStatisticsModalId, setShowStatisticsModalId] = useState(""); const [page, setPage] = useState(0); @@ -28,7 +33,7 @@ export const PromocodeManagement = () => { } = usePromocodes(page, pageSize, showStatisticsModalId, to, from); const columns = usePromocodeGridColDef( setShowStatisticsModalId, - deletePromocode + deleteModalHC ); console.log(showStatisticsModalId) if (error) return Ошибка загрузки промокодов; @@ -97,6 +102,7 @@ export const PromocodeManagement = () => { from={from} setFrom={setFrom} /> + ); }; diff --git a/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx b/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx index 278baa9..2bd6da9 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx @@ -1,7 +1,7 @@ import { IconButton } from "@mui/material"; import { GridColDef } from "@mui/x-data-grid"; import { Promocode } from "@root/model/promocodes"; -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import { BarChart, Delete } from "@mui/icons-material"; import {promocodeApi} from "@root/api/promocode/requests"; From 78c3e9683dbfea5298dc7378470375f2833b7532 Mon Sep 17 00:00:00 2001 From: Nastya Date: Sat, 30 Mar 2024 23:54:37 +0300 Subject: [PATCH 13/17] =?UTF-8?q?=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D1=84=D0=B0=D1=81=D1=82=D0=BB=D0=B8=D0=BD=D0=BA?= =?UTF-8?q?=20=D0=B8=20=D0=B5=D0=B3=D0=BE=20=D0=BA=D0=BE=D0=BF=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/promocode/requests.ts | 18 +++++++++++++++ .../PromocodeManagement/StatisticsModal.tsx | 22 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/api/promocode/requests.ts b/src/api/promocode/requests.ts index 005ccf0..6f3015c 100644 --- a/src/api/promocode/requests.ts +++ b/src/api/promocode/requests.ts @@ -31,6 +31,23 @@ const getPromocodeList = async (body: GetPromocodeListBody) => { throw new Error(`Ошибка при получении списка промокодов. ${error}`); } }; +const createFastlink = async (id: string) => { + try { + return await makeRequest< + {id:string}, + PromocodeList + >({ + url: baseUrl + "/fastlink", + method: "POST", + body: {id}, + }); + + + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); + throw new Error(`Ошибка при создании фастлинка. ${error}`); + } +}; export const getAllPromocodes = async () => { try { @@ -127,4 +144,5 @@ export const promocodeApi = { deletePromocode, getAllPromocodes, getPromocodeStatistics, + createFastlink, }; diff --git a/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx b/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx index ae80115..9dc3c6a 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx @@ -7,6 +7,7 @@ import { TextField, useTheme, useMediaQuery, + IconButton, } from "@mui/material"; import { DataGrid, GridLoadingOverlay, GridToolbar } from "@mui/x-data-grid"; import { DatePicker } from "@mui/x-date-pickers/DatePicker"; @@ -17,6 +18,7 @@ import type { GridColDef } from "@mui/x-data-grid"; import type { PromocodeStatistics } from "@root/model/promocodes"; import moment from "moment"; import { promocodeApi } from "@root/api/promocode/requests"; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; type StatisticsModalProps = { id: string; @@ -30,6 +32,20 @@ type StatisticsModalProps = { }; const COLUMNS: GridColDef[] = [ + { + field: "copy", + headerName: "копировать", + width: 50, + sortable: false, + valueGetter: ({ row }) => String(row.purchasesCount), + renderCell: (params) => { + return ( + navigator.clipboard.writeText(params.row.link)}> + + + ); + }, + }, { field: "link", headerName: "Ссылка", @@ -73,6 +89,10 @@ export const StatisticsModal = ({ const isMobile = useMediaQuery(theme.breakpoints.down(550)); const [general, setGeneral] = useState({}); const [rows, setRows] = useState([]); + const createFastlink = async () => { + await promocodeApi.createFastlink(id) + getParseData() + } const getParseData = async () => { const promocodeStatistics = await promocodeApi.getPromocodeStatistics(id, moment(startDate).unix(), moment(endDate).unix()) const rows = [] as ROW[] @@ -145,7 +165,7 @@ export const StatisticsModal = ({ > From fc5096d238bd567675f2008639c2b069a16df76e Mon Sep 17 00:00:00 2001 From: IlyaDoronin Date: Tue, 2 Apr 2024 14:43:00 +0300 Subject: [PATCH 14/17] fix: statistics --- src/api/promocode/swr.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/promocode/swr.ts b/src/api/promocode/swr.ts index 8b0da14..51a6155 100644 --- a/src/api/promocode/swr.ts +++ b/src/api/promocode/swr.ts @@ -113,6 +113,7 @@ export function usePromocodes( ...swrResponse, createPromocode, deletePromocode, + promocodeStatistics: promocodeStatistics.data, promocodesCount: promocodesCountRef.current, }; } From 5f593cf8dc47c8c1ef14e4037788bad40df97397 Mon Sep 17 00:00:00 2001 From: IlyaDoronin Date: Tue, 2 Apr 2024 16:21:17 +0300 Subject: [PATCH 15/17] fix: statistics modal link --- src/api/promocode/requests.ts | 21 ++-- src/model/promocodes.ts | 6 +- .../PromocodeManagement/StatisticsModal.tsx | 101 ++++++++++-------- .../Content/PromocodeManagement/index.tsx | 15 ++- 4 files changed, 78 insertions(+), 65 deletions(-) diff --git a/src/api/promocode/requests.ts b/src/api/promocode/requests.ts index 6f3015c..d807d41 100644 --- a/src/api/promocode/requests.ts +++ b/src/api/promocode/requests.ts @@ -33,16 +33,11 @@ const getPromocodeList = async (body: GetPromocodeListBody) => { }; const createFastlink = async (id: string) => { try { - return await makeRequest< - {id:string}, - PromocodeList - >({ + return await makeRequest<{ id: string }, { fastlink: string }>({ url: baseUrl + "/fastlink", method: "POST", - body: {id}, + body: { id }, }); - - } catch (nativeError) { const [error] = parseAxiosError(nativeError); throw new Error(`Ошибка при создании фастлинка. ${error}`); @@ -115,22 +110,22 @@ const deletePromocode = async (id: string): Promise => { } }; -const getPromocodeStatistics = async (id: string, from: number, to: number,) => { +const getPromocodeStatistics = async (id: string, from: number, to: number) => { try { const promocodeStatisticsResponse = await makeRequest< unknown, - PromocodeStatistics[] + PromocodeStatistics >({ url: baseUrl + `/stats`, body: { - "id": id, - "from": from, - "to": to + id: id, + from: from, + to: to, }, method: "POST", useToken: false, }); -console.log(promocodeStatisticsResponse) + console.log(promocodeStatisticsResponse); return promocodeStatisticsResponse; } catch (nativeError) { const [error] = parseAxiosError(nativeError); diff --git a/src/model/promocodes.ts b/src/model/promocodes.ts index 3b1b524..599508c 100644 --- a/src/model/promocodes.ts +++ b/src/model/promocodes.ts @@ -33,6 +33,7 @@ export type Promocode = CreatePromocodeBody & { offLimit: boolean; delete: boolean; createdAt: string; + fastLinks: string[]; }; export type PromocodeList = { @@ -42,7 +43,6 @@ export type PromocodeList = { export type PromocodeStatistics = { id: string; - link: string; - useCount: number; - purchasesCount: number; + usageCount: number; + usageMap: Record; }; diff --git a/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx b/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx index 9dc3c6a..0e06222 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx @@ -14,11 +14,12 @@ import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { fadeIn } from "@root/utils/style/keyframes"; -import type { GridColDef } from "@mui/x-data-grid"; -import type { PromocodeStatistics } from "@root/model/promocodes"; import moment from "moment"; import { promocodeApi } from "@root/api/promocode/requests"; -import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import ContentCopyIcon from "@mui/icons-material/ContentCopy"; + +import type { GridColDef } from "@mui/x-data-grid"; +import type { Promocode } from "@root/model/promocodes"; type StatisticsModalProps = { id: string; @@ -27,20 +28,29 @@ type StatisticsModalProps = { setId: (id: string) => void; setTo: (date: number) => void; setFrom: (date: number) => void; + promocodes: Promocode[]; // promocodeStatistics: PromocodeStatistics[] | null | undefined; // promocodeStatistics: any; }; -const COLUMNS: GridColDef[] = [ +type Row = { + id: number; + link: string; + useCount: number; +}; + +const COLUMNS: GridColDef[] = [ { field: "copy", headerName: "копировать", width: 50, sortable: false, - valueGetter: ({ row }) => String(row.purchasesCount), + valueGetter: ({ row }) => String(row.useCount), renderCell: (params) => { return ( - navigator.clipboard.writeText(params.row.link)}> + navigator.clipboard.writeText(params.row.link)} + > ); @@ -65,15 +75,10 @@ const COLUMNS: GridColDef[] = [ headerName: "Покупок", width: 70, sortable: false, - valueGetter: ({ row }) => String(row.purchasesCount), + valueGetter: ({ row }) => String(1), }, ]; -type ROW = { - link: string, - useCount: number -} - export const StatisticsModal = ({ id, setId, @@ -81,40 +86,54 @@ export const StatisticsModal = ({ from, to, setTo, - //promocodeStatistics, + promocodes, }: StatisticsModalProps) => { const [startDate, setStartDate] = useState(new Date()); const [endDate, setEndDate] = useState(new Date()); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(550)); const [general, setGeneral] = useState({}); - const [rows, setRows] = useState([]); + const [rows, setRows] = useState([]); const createFastlink = async () => { - await promocodeApi.createFastlink(id) - getParseData() - } + await promocodeApi.createFastlink(id); + getParseData(); + }; + const getParseData = async () => { - const promocodeStatistics = await promocodeApi.getPromocodeStatistics(id, moment(startDate).unix(), moment(endDate).unix()) - const rows = [] as ROW[] - //@ts-ignore - for (const [key, value] of Object.entries(promocodeStatistics.usageMap)) { - rows.push({ - link: key, - //@ts-ignore + const promocodeStatistics = await promocodeApi.getPromocodeStatistics( + id, + moment(startDate).unix(), + moment(endDate).unix() + ); + + const rows = Object.values(promocodeStatistics.usageMap).map( + (value, index) => ({ + link: + promocodes.find((promocode) => promocode.id === id)?.fastLinks[0] ?? + "", useCount: value, - id:rows.length + id: index, }) - } - setGeneral(promocodeStatistics) - //@ts-ignore - setRows(rows) - } + ) as Row[]; + + setGeneral(promocodeStatistics); + setRows(rows); + }; + useEffect(() => { - if (id.length > 0) getParseData() - }, [id]) + if (id.length > 0) { + getParseData(); + } + + if (!id) { + setRows([]); + setGeneral({}); + } + }, [id]); + // const formatTo = to === null ? 0 : moment(to).unix() // const formatFrom = from === null ? 0 : moment(from).unix() - console.log(general) + console.log(general); // useEffect(() => { // (async () => { // const gottenGeneral = await promocodeStatistics(id, startDate, endDate) @@ -125,9 +144,9 @@ export const StatisticsModal = ({ { - setId("") - setStartDate(new Date()) - setEndDate(new Date()) + setId(""); + setStartDate(new Date()); + setEndDate(new Date()); }} sx={{ "& > .MuiBox-root": { outline: "none" } }} > @@ -163,16 +182,10 @@ export const StatisticsModal = ({ alignItems: "center", }} > - - diff --git a/src/pages/dashboard/Content/PromocodeManagement/index.tsx b/src/pages/dashboard/Content/PromocodeManagement/index.tsx index 3baaeac..13b5c71 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/index.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/index.tsx @@ -12,9 +12,9 @@ import DeleteModal from "./DeleteModal"; export const PromocodeManagement = () => { const theme = useTheme(); - - const [deleteModal, setDeleteModal] = useState("") - const deleteModalHC = (id:string) => setDeleteModal(id) + + const [deleteModal, setDeleteModal] = useState(""); + const deleteModalHC = (id: string) => setDeleteModal(id); const [showStatisticsModalId, setShowStatisticsModalId] = useState(""); @@ -35,7 +35,7 @@ export const PromocodeManagement = () => { setShowStatisticsModalId, deleteModalHC ); - console.log(showStatisticsModalId) + console.log(showStatisticsModalId); if (error) return Ошибка загрузки промокодов; return ( @@ -101,8 +101,13 @@ export const PromocodeManagement = () => { setTo={setTo} from={from} setFrom={setFrom} + promocodes={data?.items ?? []} + /> + - ); }; From 3f3c4125ba6b5fc737ac076448e31a43c2dd45cf Mon Sep 17 00:00:00 2001 From: IlyaDoronin Date: Tue, 2 Apr 2024 17:56:23 +0300 Subject: [PATCH 16/17] fix: swr --- src/api/promocode/swr.ts | 88 +++++++++++-------- .../PromocodeManagement/StatisticsModal.tsx | 46 +++++----- .../Content/PromocodeManagement/index.tsx | 6 +- 3 files changed, 75 insertions(+), 65 deletions(-) diff --git a/src/api/promocode/swr.ts b/src/api/promocode/swr.ts index f17f520..ce1126e 100644 --- a/src/api/promocode/swr.ts +++ b/src/api/promocode/swr.ts @@ -13,7 +13,7 @@ export function usePromocodes( pageSize: number, promocodeId: string, to: number, - from: number, + from: number ) { const promocodesCountRef = useRef(0); const swrResponse = useSwr( @@ -89,46 +89,58 @@ export function usePromocodes( [page, pageSize] ); - // const promocodeStatistics = useSwr( - // ["promocodeStatistics", promocodeId, from, to], - // async ([_, id, from, to]) => { - // if (!id) { - // return null; - // } - // - // const promocodeStatisticsResponse = - // await promocodeApi.getPromocodeStatistics(id, from, to); - // - // return promocodeStatisticsResponse; - // }, - // { - // onError(err) { - // console.log("Error fetching promocode statistics", err); - // enqueueSnackbar(err.message, { variant: "error" }); - // }, - // focusThrottleInterval: 60e3, - // keepPreviousData: true, - // } - // ); + const promocodeStatistics = useSwr( + ["promocodeStatistics", promocodeId, from, to], + async ([_, id, from, to]) => { + if (!id) { + return null; + } - return { - ...swrResponse, - createPromocode, - deletePromocode, - //promocodeStatistics, - promocodesCount: promocodesCountRef.current, - }; + const promocodeStatisticsResponse = + await promocodeApi.getPromocodeStatistics(id, from, to); + + return promocodeStatisticsResponse; + }, + { + onError(err) { + console.log("Error fetching promocode statistics", err); + enqueueSnackbar(err.message, { variant: "error" }); + }, + focusThrottleInterval: 60e3, + keepPreviousData: true, + } + ); + + const createFastLink = useCallback(async function (id: string) { + try { + await promocodeApi.createFastlink(id); + mutate(["promocodes", page, pageSize]); + } catch (error) { + console.log("Error creating fast link", error); + if (error instanceof Error) + enqueueSnackbar(error.message, { variant: "error" }); + } + }, []); + + return { + ...swrResponse, + createPromocode, + deletePromocode, + createFastLink, + promocodeStatistics: promocodeStatistics.data, + promocodesCount: promocodesCountRef.current, + }; } export function useAllPromocodes() { - const swrResponse = useSwr("allPromocodes", promocodeApi.getAllPromocodes, { - keepPreviousData: true, - suspense: true, - onError(err) { - console.log("Error fetching all promocodes", err); - enqueueSnackbar(err.message, { variant: "error" }); - }, - }); + const swrResponse = useSwr("allPromocodes", promocodeApi.getAllPromocodes, { + keepPreviousData: true, + suspense: true, + onError(err) { + console.log("Error fetching all promocodes", err); + enqueueSnackbar(err.message, { variant: "error" }); + }, + }); - return swrResponse.data; + return swrResponse.data; } diff --git a/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx b/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx index 0e06222..301e3f9 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/StatisticsModal.tsx @@ -14,12 +14,10 @@ import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { fadeIn } from "@root/utils/style/keyframes"; -import moment from "moment"; -import { promocodeApi } from "@root/api/promocode/requests"; import ContentCopyIcon from "@mui/icons-material/ContentCopy"; import type { GridColDef } from "@mui/x-data-grid"; -import type { Promocode } from "@root/model/promocodes"; +import type { Promocode, PromocodeStatistics } from "@root/model/promocodes"; type StatisticsModalProps = { id: string; @@ -29,8 +27,8 @@ type StatisticsModalProps = { setTo: (date: number) => void; setFrom: (date: number) => void; promocodes: Promocode[]; - // promocodeStatistics: PromocodeStatistics[] | null | undefined; - // promocodeStatistics: any; + promocodeStatistics: PromocodeStatistics | null | undefined; + createFastLink: (id: string) => Promise; }; type Row = { @@ -62,6 +60,8 @@ const COLUMNS: GridColDef[] = [ width: 320, sortable: false, valueGetter: ({ row }) => row.link, + renderCell: ({ value }) => + value?.split("|").map((link) => {link}), }, { field: "useCount", @@ -75,7 +75,7 @@ const COLUMNS: GridColDef[] = [ headerName: "Покупок", width: 70, sortable: false, - valueGetter: ({ row }) => String(1), + valueGetter: ({ row }) => String(0), }, ]; @@ -86,37 +86,30 @@ export const StatisticsModal = ({ from, to, setTo, + promocodeStatistics, promocodes, + createFastLink, }: StatisticsModalProps) => { const [startDate, setStartDate] = useState(new Date()); const [endDate, setEndDate] = useState(new Date()); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(550)); - const [general, setGeneral] = useState({}); const [rows, setRows] = useState([]); const createFastlink = async () => { - await promocodeApi.createFastlink(id); + await createFastLink(id); + getParseData(); }; const getParseData = async () => { - const promocodeStatistics = await promocodeApi.getPromocodeStatistics( - id, - moment(startDate).unix(), - moment(endDate).unix() - ); - - const rows = Object.values(promocodeStatistics.usageMap).map( - (value, index) => ({ - link: - promocodes.find((promocode) => promocode.id === id)?.fastLinks[0] ?? - "", - useCount: value, + const rows = promocodes + .find((promocode) => promocode.id === id) + ?.fastLinks?.map((link, index) => ({ + link, id: index, - }) - ) as Row[]; + useCount: promocodeStatistics?.usageMap[link] ?? 0, + })) as Row[]; - setGeneral(promocodeStatistics); setRows(rows); }; @@ -127,13 +120,11 @@ export const StatisticsModal = ({ if (!id) { setRows([]); - setGeneral({}); } }, [id]); // const formatTo = to === null ? 0 : moment(to).unix() // const formatFrom = from === null ? 0 : moment(from).unix() - console.log(general); // useEffect(() => { // (async () => { // const gottenGeneral = await promocodeStatistics(id, startDate, endDate) @@ -275,6 +266,11 @@ export const StatisticsModal = ({ backgroundColor: "rgba(255, 255, 255, 0.1)", animation: `${fadeIn} 0.5s ease-out`, }, + "& .MuiDataGrid-virtualScrollerContent": { maxHeight: "200px" }, + "& .MuiDataGrid-virtualScrollerRenderZone": { + maxHeight: "200px", + overflowY: "auto", + }, }} components={{ Toolbar: GridToolbar, diff --git a/src/pages/dashboard/Content/PromocodeManagement/index.tsx b/src/pages/dashboard/Content/PromocodeManagement/index.tsx index 13b5c71..c2335e9 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/index.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/index.tsx @@ -27,9 +27,10 @@ export const PromocodeManagement = () => { error, isValidating, promocodesCount, - //promocodeStatistics, + promocodeStatistics, deletePromocode, createPromocode, + createFastLink, } = usePromocodes(page, pageSize, showStatisticsModalId, to, from); const columns = usePromocodeGridColDef( setShowStatisticsModalId, @@ -96,12 +97,13 @@ export const PromocodeManagement = () => { Date: Thu, 4 Apr 2024 00:53:48 +0300 Subject: [PATCH 17/17] =?UTF-8?q?=D1=81=D1=82=D0=B0=D1=82=D0=B8=D1=81?= =?UTF-8?q?=D1=82=D0=B8=D0=BA=D0=B0=20=D0=BA=D0=B2=D0=B8=D0=B7=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/quizStatistic.ts | 28 +++ src/index.tsx | 10 ++ .../dashboard/Content/QuizStatistic/index.tsx | 169 ++++++++++++++++++ src/pages/dashboard/Content/Users.tsx | 4 +- src/pages/dashboard/Menu/index.tsx | 1 + src/utils/hooks/useQuizStatistic.ts | 34 ++++ 6 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 src/api/quizStatistic.ts create mode 100644 src/pages/dashboard/Content/QuizStatistic/index.tsx create mode 100644 src/utils/hooks/useQuizStatistic.ts diff --git a/src/api/quizStatistic.ts b/src/api/quizStatistic.ts new file mode 100644 index 0000000..2cc5f48 --- /dev/null +++ b/src/api/quizStatistic.ts @@ -0,0 +1,28 @@ +import { makeRequest } from "@frontend/kitui"; + +export type QuizStatisticResponse = { + Registrations: number; + Quizes: number; + Results: number +}; + +type TRequest = { + to: number; + from: number; +}; + +export const getStatistic = async ( + to: number, + from: number, +): Promise => { + try { + const generalResponse = await makeRequest({ + url: `${process.env.REACT_APP_DOMAIN}/squiz/statistic`, + body: { to, from } + }) + return generalResponse; + } catch (nativeError) { + + return { Registrations: 0, Quizes: 0, Results: 0 }; + } +}; \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index 0223de9..31299ff 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -24,9 +24,11 @@ import { PromocodeManagement } from "@root/pages/dashboard/Content/PromocodeMana import { SettingRoles } from "@pages/Setting/SettingRoles"; import Support from "@pages/dashboard/Content/Support/Support"; import ChatImageNewWindow from "@pages/dashboard/Content/Support/ChatImageNewWindow"; +import QuizStatistic from "@pages/dashboard/Content/QuizStatistic"; import theme from "./theme"; import "./index.css"; +import { makeRequest } from "@frontend/kitui"; const componentsArray = [ ["/users", ], @@ -104,6 +106,14 @@ root.render( } /> + + + + } + /> {componentsArray.map((element) => ( { + const theme = useTheme() + + const [isOpen, setOpen] = useState(false); + const [isOpenEnd, setOpenEnd] = useState(false); + + const [from, setFrom] = useState(null); + const [to, setTo] = useState(moment(Date.now())); + + + const { Registrations, Quizes, Results } = useQuizStatistic({ + from, + to, + }); + + const resetTime = () => { + setFrom(moment(0)); + setTo(moment(Date.now())); + }; + + const handleClose = () => { + setOpen(false); + }; + + const handleOpen = () => { + setOpen(true); + }; + + const onAdornmentClick = () => { + setOpen((old) => !old); + if (isOpenEnd) { + handleCloseEnd(); + } + }; + + const handleCloseEnd = () => { + setOpenEnd(false); + }; + + const handleOpenEnd = () => { + setOpenEnd(true); + }; + + const onAdornmentClickEnd = () => { + setOpenEnd((old) => !old); + if (isOpen) { + handleClose(); + } + }; + + + return <> + + + + Дата начала + + date && setFrom(date)} + renderInput={(params) => ( + + )} + InputProps={{ + sx: { + height: "40px", + color: theme.palette.secondary.main, + border: "1px solid", + borderColor: theme.palette.secondary.main, + "& .MuiSvgIcon-root": { + color: theme.palette.secondary.main, + }, + }, + }} + /> + + + + Дата окончания + + date && setTo(date)} + renderInput={(params) => ( + + )} + InputProps={{ + sx: { + height: "40px", + color: theme.palette.secondary.main, + border: "1px solid", + borderColor: theme.palette.secondary.main, + "& .MuiSvgIcon-root": { + color: theme.palette.secondary.main, + }, + }, + }} + /> + + + + + + + Регистраций + Quiz + Результаты + + + + {Registrations} + {Quizes} + {Results} + +
+
+ +} \ No newline at end of file diff --git a/src/pages/dashboard/Content/Users.tsx b/src/pages/dashboard/Content/Users.tsx index dfc4a89..3ea33aa 100644 --- a/src/pages/dashboard/Content/Users.tsx +++ b/src/pages/dashboard/Content/Users.tsx @@ -131,7 +131,7 @@ const Users: React.FC = () => { ); return ( - + */} prop !== "open" })); const links: { path: string; element: JSX.Element; title: string; className: string }[] = [ + { path: "/quizStatistic", element: <>📝, title: "Статистика Quiz", className: "menu" }, { path: "/users", element: , title: "Информация о проекте", className: "menu" }, { path: "/entities", element: , title: "Юридические лица", className: "menu" }, { path: "/tariffs", element: , title: "Тарифы", className: "menu" }, diff --git a/src/utils/hooks/useQuizStatistic.ts b/src/utils/hooks/useQuizStatistic.ts new file mode 100644 index 0000000..f4554f1 --- /dev/null +++ b/src/utils/hooks/useQuizStatistic.ts @@ -0,0 +1,34 @@ +import { useEffect, useState } from "react"; +import { + QuizStatisticResponse, + getStatistic +} from "@root/api/quizStatistic"; + +import type { Moment } from "moment"; + +interface useQuizStatisticProps { + to: Moment | null; + from: Moment | null; +} + +export function useQuizStatistic({ to, from }: useQuizStatisticProps) { + const formatTo = to?.unix(); + const formatFrom = from?.unix(); + + const [data, setData] = useState({ Registrations: 0, Quizes: 0, Results: 0 }); + + useEffect(() => { + + const requestStatistics = async () => { + console.log("работаю раз") + console.log("работаю два") + + const gottenData = await getStatistic(Number(formatTo), Number(formatFrom)); + setData(gottenData) + } + + requestStatistics(); + }, [to, from]); + + return { ...data }; +}