diff --git a/admin.conf b/admin.conf index 1a1fe0d..9867c72 100644 --- a/admin.conf +++ b/admin.conf @@ -21,7 +21,7 @@ server { return 200; } if ($host = sadmin.pena) { - proxy_pass http://10.6.0.11:59301; + proxy_pass http://10.8.0.6:59301; } if ($host != sadmin.pena) { proxy_pass http://10.8.0.8:59301; @@ -63,7 +63,7 @@ server { add_header Access-Control-Allow-Credentials true always; add_header Access-Control-Allow-Headers content-type,authorization always; add_header Access-Control-Allow-Methods OPTIONS,GET,POST,PATCH,PUT,DELETE; - proxy_pass http://10.6.0.11:59301/; + proxy_pass http://10.8.0.6:59301/; } location /swagger/ { diff --git a/src/api/promocode/swr.ts b/src/api/promocode/swr.ts index c11016c..2e58d67 100644 --- a/src/api/promocode/swr.ts +++ b/src/api/promocode/swr.ts @@ -136,7 +136,7 @@ export function usePromocodes( } export function useAllPromocodes() { - const swrResponse = useSwr("allPromocodes", promocodeApi.getAllPromocodes, { + const { data } = useSwr("allPromocodes", promocodeApi.getAllPromocodes, { keepPreviousData: true, suspense: true, onError(err) { @@ -145,5 +145,5 @@ export function useAllPromocodes() { }, }); - return swrResponse.data; + return data; } diff --git a/src/api/quizStatistic.ts b/src/api/quizStatistic.ts deleted file mode 100644 index 0c32a34..0000000 --- a/src/api/quizStatistic.ts +++ /dev/null @@ -1,28 +0,0 @@ -import makeRequest from "@root/api/makeRequest"; - -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/api/quizStatistics/index.ts b/src/api/quizStatistics/index.ts new file mode 100644 index 0000000..5cd9ff6 --- /dev/null +++ b/src/api/quizStatistics/index.ts @@ -0,0 +1,89 @@ +import makeRequest from "@root/api/makeRequest"; + +import type { + GetStatisticSchildBody, + QuizStatisticsItem, + GetPromocodeStatisticsBody, + AllPromocodeStatistics, +} from "./types"; + +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 }; + } +}; + +export const getStatisticSchild = async ( + from: number, + to: number +): Promise => { + try { + const StatisticResponse = await makeRequest< + GetStatisticSchildBody, + QuizStatisticsItem[] + >({ + url: process.env.REACT_APP_DOMAIN + "/customer/quizlogo/stat", + method: "post", + useToken: true, + body: { to, from, page: 0, limit: 100 }, + }); + + if (!StatisticResponse) { + throw new Error("Статистика не найдена"); + } + + return StatisticResponse; + } catch (nativeError) { + return [ + { + ID: "0", + Regs: 0, + Money: 0, + Quizes: [{ QuizID: "0", Regs: 0, Money: 0 }], + }, + ]; + } +}; + +export const getStatisticPromocode = async ( + from: number, + to: number +): Promise> => { + try { + const StatisticPromo = await makeRequest< + GetPromocodeStatisticsBody, + Record + >({ + url: process.env.REACT_APP_DOMAIN + "/customer/promocode/ltv", + method: "post", + useToken: true, + body: { to, from }, + }); + + return StatisticPromo; + } catch (nativeError) { + console.log(nativeError); + + return {}; + } +}; diff --git a/src/api/quizStatistics/types.ts b/src/api/quizStatistics/types.ts new file mode 100644 index 0000000..284b7c8 --- /dev/null +++ b/src/api/quizStatistics/types.ts @@ -0,0 +1,31 @@ +import { Moment } from "moment"; + +export type GetStatisticSchildBody = { + to: Moment | null; + from: Moment | null; + page: number; + limit: number; +}; + +type StatisticsQuizes = { + QuizID: string; + Money: number; + Regs: number; +}; + +export type QuizStatisticsItem = { + ID: string; + Money: number; + Quizes: StatisticsQuizes[]; + Regs: number; +}; + +export type GetPromocodeStatisticsBody = { + from: number; + to: number; +}; + +export type AllPromocodeStatistics = { + Money: number; + Regs: number; +}; diff --git a/src/index.tsx b/src/index.tsx index 5959a90..dcff6e9 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -24,7 +24,7 @@ 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 { QuizStatistics } from "@root/pages/dashboard/Content/QuizStatistics"; import theme from "./theme"; import "./index.css"; @@ -106,10 +106,10 @@ root.render( } /> - + } /> @@ -121,7 +121,7 @@ root.render( /> ))} - } /> + } /> } /> diff --git a/src/pages/dashboard/Content/PromocodeManagement/CreatePromocodeForm.tsx b/src/pages/dashboard/Content/PromocodeManagement/CreatePromocodeForm.tsx index 657ef7a..8bc598a 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/CreatePromocodeForm.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/CreatePromocodeForm.tsx @@ -21,6 +21,7 @@ import theme from "@root/theme"; import type { TextFieldProps } from "@mui/material"; import { CreatePromocodeBody } from "@root/model/promocodes"; import type { ChangeEvent } from "react"; +import { enqueueSnackbar } from "notistack"; type BonusType = "discount" | "privilege"; @@ -74,24 +75,34 @@ export const CreatePromocodeForm = ({ createPromocode }: Props) => { }, []); const submitForm = (values: FormValues) => { - const currentPrivilege = privileges.find( (item) => item.privilegeId === values.privilegeId ); - const body = { - ...values, - }; + const body = { ...values }; - if ((body.layer === 1 && bonusType === "discount") || bonusType === "privilege") { - if (currentPrivilege === undefined) return; - body.serviceKey = currentPrivilege?.serviceKey - body.target = body.privilegeId - } - if ((body.layer === 2 && bonusType === "discount")) { - if (body.serviceKey === undefined) return; - body.target = body.serviceKey - } + if ( + (body.layer === 1 && bonusType === "discount") || + bonusType === "privilege" + ) { + if (currentPrivilege === undefined) { + enqueueSnackbar("Привилегия не выбрана"); + + return; + } + + body.serviceKey = currentPrivilege?.serviceKey; + body.target = body.privilegeId; + } + if (body.layer === 2 && bonusType === "discount") { + if (!body.serviceKey) { + enqueueSnackbar("Сервис не выбран"); + + return; + } + + body.target = body.serviceKey; + } const factorFromDiscountValue = 1 - body.factor / 100; @@ -178,6 +189,7 @@ export const CreatePromocodeForm = ({ createPromocode }: Props) => { setFieldValue( "activationCount", @@ -253,64 +265,57 @@ export const CreatePromocodeForm = ({ createPromocode }: Props) => { > {values.layer === 1 ? "Выбор привилегии" : "Выбор сервиса"} - { - values.layer === 1 ? - - { - setFieldValue("target", target.value) - setFieldValue("privilegeId", target.value) - }} - children={ - privileges.map(({ name, privilegeId }) => ( - - {name} - - )) - } - /> - : - { - setFieldValue("target", target.value) - setFieldValue("serviceKey", target.value) - }} - children={ - SERVICE_LIST.map(({ displayName, serviceKey }) => ( - - {displayName} - - )) - } - /> - - } + {values.layer === 1 ? ( + { + setFieldValue("target", target.value); + setFieldValue("privilegeId", target.value); + }} + children={privileges.map(({ name, privilegeId }) => ( + + {name} + + ))} + /> + ) : ( + { + setFieldValue("target", target.value); + setFieldValue("serviceKey", target.value); + }} + children={SERVICE_LIST.map(({ displayName, serviceKey }) => ( + + {displayName} + + ))} + /> + )} { }} type="submit" > - Cоздать + Создать )} diff --git a/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx b/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx index 2bd6da9..707e7f2 100644 --- a/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx +++ b/src/pages/dashboard/Content/PromocodeManagement/usePromocodeGridColDef.tsx @@ -4,14 +4,20 @@ import { Promocode } from "@root/model/promocodes"; import { useMemo, useState } from "react"; import { BarChart, Delete } from "@mui/icons-material"; -import {promocodeApi} from "@root/api/promocode/requests"; +import { promocodeApi } from "@root/api/promocode/requests"; export function usePromocodeGridColDef( setStatistics: (id: string) => void, deletePromocode: (id: string) => void ) { - const validity = (value:string|number) => {if(value===0){return "неоганичен"}else {return new Date(value).toLocaleString()}} - return useMemo[]>( + const validity = (value: string | number) => { + if (value === 0) { + return "неоганичен"; + } else { + return new Date(value).toLocaleString(); + } + }; + return useMemo[]>( () => [ { field: "id", @@ -50,6 +56,14 @@ export function usePromocodeGridColDef( valueGetter: ({ row }) => row.dueTo * 1000, valueFormatter: ({ value }) => `${validity(value)}`, }, + { + field: "description", + headerName: "Описание", + minWidth: 200, + flex: 1, + sortable: false, + valueGetter: ({ row }) => row.description, + }, { field: "settings", headerName: "", @@ -57,10 +71,12 @@ export function usePromocodeGridColDef( sortable: false, renderCell: (params) => { return ( - { - setStatistics(params.row.id,) - promocodeApi.getPromocodeStatistics(params.row.id, 0, 0) - }}> + { + setStatistics(params.row.id); + promocodeApi.getPromocodeStatistics(params.row.id, 0, 0); + }} + > ); diff --git a/src/pages/dashboard/Content/QuizStatistic/index.tsx b/src/pages/dashboard/Content/QuizStatistic/index.tsx deleted file mode 100644 index 4c2b5e2..0000000 --- a/src/pages/dashboard/Content/QuizStatistic/index.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { Table, TableBody, TableCell, TableHead, TableRow, useTheme, Typography, Box, TextField, Button } from '@mui/material'; -import { useState } from 'react'; -import moment from "moment"; -import type { Moment } from "moment"; -import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers'; -import { useQuizStatistic } from '@root/utils/hooks/useQuizStatistic'; -import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment' - -export default () => { - 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/QuizStatistics/DateFilter.tsx b/src/pages/dashboard/Content/QuizStatistics/DateFilter.tsx new file mode 100644 index 0000000..93d052c --- /dev/null +++ b/src/pages/dashboard/Content/QuizStatistics/DateFilter.tsx @@ -0,0 +1,86 @@ +import { Box, TextField, Typography, useTheme } from "@mui/material"; +import { DatePicker } from "@mui/x-date-pickers"; + +import type { Moment } from "moment"; + +type DateFilterProps = { + from: Moment | null; + to: Moment | null; + setFrom: (date: Moment | null) => void; + setTo: (date: Moment | null) => void; +}; + +export const DateFilter = ({ to, setTo, from, setFrom }: DateFilterProps) => { + const theme = useTheme(); + + return ( + <> + + + Дата начала + + date && setFrom(date.startOf('day'))} + 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.endOf('day'))} + 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/QuizStatistics/QuizInfo.tsx b/src/pages/dashboard/Content/QuizStatistics/QuizInfo.tsx new file mode 100644 index 0000000..3c0c492 --- /dev/null +++ b/src/pages/dashboard/Content/QuizStatistics/QuizInfo.tsx @@ -0,0 +1,83 @@ +import { useState } from "react"; +import moment from "moment"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Button, + useTheme, +} from "@mui/material"; + +import { DateFilter } from "./DateFilter"; + +import { useQuizStatistic } from "@root/utils/hooks/useQuizStatistic"; + +import type { Moment } from "moment"; + +export const QuizInfo = () => { + const [from, setFrom] = useState(null); + const [to, setTo] = useState(moment()); + const theme = useTheme(); + const { Registrations, Quizes, Results } = useQuizStatistic({ + from, + to, + }); + + const resetTime = () => { + setFrom(moment()); + setTo(moment()); + }; + + return ( + <> + + + + + + + Регистраций + + + Quiz + + + Результаты + + + + + + + {Registrations} + + + {Quizes} + + + {Results} + + + +
+ + ); +}; diff --git a/src/pages/dashboard/Content/QuizStatistics/StastisticsPromocode.tsx b/src/pages/dashboard/Content/QuizStatistics/StastisticsPromocode.tsx new file mode 100644 index 0000000..5223fd7 --- /dev/null +++ b/src/pages/dashboard/Content/QuizStatistics/StastisticsPromocode.tsx @@ -0,0 +1,80 @@ +import { useState } from "react"; +import moment from "moment"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Typography, + useTheme, +} from "@mui/material"; + +import { DateFilter } from "./DateFilter"; + +import { useAllPromocodes } from "@root/api/promocode/swr"; +import { usePromocodeStatistics } from "@root/utils/hooks/usePromocodeStatistics"; + +import type { Moment } from "moment"; + +export const StatisticsPromocode = () => { + const [from, setFrom] = useState( + moment(moment().subtract(4, "weeks")) + ); + const [to, setTo] = useState(moment()); + const promocodes = useAllPromocodes(); + const promocodeStatistics = usePromocodeStatistics({ to, from }); + const theme = useTheme(); + + return ( + <> + Статистика промокодов + + + + + + Промокод + + + Регистации + + + Внесено + + + + {Object.entries(promocodeStatistics).map(([key, { Regs, Money }]) => ( + + + + {promocodes.find(({ id }) => id === key)?.codeword ?? ""} + + + {Regs} + + + {(Money / 100).toFixed(2)} + + + + ))} +
+ + ); +}; diff --git a/src/pages/dashboard/Content/QuizStatistics/StatisticsSchild.tsx b/src/pages/dashboard/Content/QuizStatistics/StatisticsSchild.tsx new file mode 100644 index 0000000..4567084 --- /dev/null +++ b/src/pages/dashboard/Content/QuizStatistics/StatisticsSchild.tsx @@ -0,0 +1,196 @@ +import moment from "moment"; +import { useEffect, useState } from "react"; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Typography, + Button, + useTheme, +} from "@mui/material"; +import { GridToolbar } from "@mui/x-data-grid"; +import { enqueueSnackbar } from "notistack"; +import DataGrid from "@kitUI/datagrid"; + +import ModalUser from "@pages/dashboard/ModalUser"; +import { DateFilter } from "./DateFilter"; + +import { useSchildStatistics } from "@root/utils/hooks/useSchildStatistics"; + +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; + +import type { Moment } from "moment"; +import type { QuizStatisticsItem } from "@root/api/quizStatistics/types"; +import type { GridColDef } from "@mui/x-data-grid"; + +const COLUMNS: GridColDef[] = [ + { + field: "user", + headerName: "Пользователь", + width: 250, + valueGetter: ({ row }) => row.ID, + }, + { + field: "regs", + headerName: "Регистраций", + width: 200, + valueGetter: ({ row }) => String(row.Regs), + }, + { + field: "money", + headerName: "Деньги", + width: 200, + valueGetter: ({ row }) => String(row.Money), + }, +]; + +export const StatisticsSchild = () => { + const [openUserModal, setOpenUserModal] = useState(false); + const [activeUserId, setActiveUserId] = useState(""); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + + const [from, setFrom] = useState( + moment(moment().subtract(4, "weeks")) + ); + const [to, setTo] = useState(moment()); + + const theme = useTheme(); + const statistics = useSchildStatistics(from, to) + .map((obj) => ({...obj, Money: Number((obj.Money / 100).toFixed(2))})); + + useEffect(() => { + if (!openUserModal) { + setActiveUserId(""); + } + }, [openUserModal]); + + useEffect(() => { + if (activeUserId) { + setOpenUserModal(true); + + return; + } + + setOpenUserModal(false); + }, [activeUserId]); + + const copyQuizLink = (quizId: string) => { + navigator.clipboard.writeText( + `https://${ + window.location.href.includes("/admin.") ? "" : "s." + }hbpn.link/${quizId}` + ); + + enqueueSnackbar("Ссылка успешно скопирована"); + }; + + return ( + <> + + Статистика переходов с шильдика + + + ID} + checkboxSelection={true} + rows={statistics} + components={{ Toolbar: GridToolbar }} + rowCount={statistics.length} + rowsPerPageOptions={[1, 10, 25, 50, 100]} + paginationMode="client" + disableSelectionOnClick + page={page} + pageSize={pageSize} + onPageChange={setPage} + onPageSizeChange={setPageSize} + onCellClick={({ id, field }) => + field === "user" && setActiveUserId(String(id)) + } + getRowHeight={() => "auto"} + columns={[ + ...COLUMNS, + { + field: "quizes", + headerName: "Квизы", + flex: 1, + minWidth: 220, + valueGetter: ({ row }) => String(row.Quizes.length), + renderCell: ({ row }) => ( + + } + aria-controls="panel1-content" + id="panel1-header" + > + Статистика по квизам + + + + + + + QuizID + + + Регистрации + + + Деньги + + + + + + {row.Quizes.map(({ QuizID, Regs, Money }) => ( + + + + + + {Regs} + + + {(Money / 100).toFixed(2)} + + + ))} + +
+
+
+ ), + }, + ]} + /> + setOpenUserModal(false)} + userId={activeUserId} + /> + + ); +}; diff --git a/src/pages/dashboard/Content/QuizStatistics/index.tsx b/src/pages/dashboard/Content/QuizStatistics/index.tsx new file mode 100644 index 0000000..5c256a4 --- /dev/null +++ b/src/pages/dashboard/Content/QuizStatistics/index.tsx @@ -0,0 +1,18 @@ +import { Suspense } from "react"; +import { Box } from "@mui/material"; +import { LocalizationProvider } from "@mui/x-date-pickers"; +import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment"; + +import { QuizInfo } from "./QuizInfo"; +import { StatisticsSchild } from "./StatisticsSchild"; +import { StatisticsPromocode } from "./StastisticsPromocode"; + +export const QuizStatistics = () => ( + + + + Loading...}> + + + +); diff --git a/src/pages/dashboard/Content/Support/TicketList/CloseTicketModal.tsx b/src/pages/dashboard/Content/Support/TicketList/CloseTicketModal.tsx new file mode 100644 index 0000000..1c12a27 --- /dev/null +++ b/src/pages/dashboard/Content/Support/TicketList/CloseTicketModal.tsx @@ -0,0 +1,87 @@ +import Modal from "@mui/material/Modal"; +import {closeDeleteTariffDialog} from "@stores/tariffs"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import Button from "@mui/material/Button"; +import makeRequest from "@root/api/makeRequest"; +import {parseAxiosError} from "@root/utils/parse-error"; +interface Props{ + ticketId: string | undefined, + openModal: boolean, + setOpenModal: (a: boolean) => void +} + +export default function CloseTicketModal({ticketId, openModal, setOpenModal}: Props) { + + const CloseTicket = async () => { + try { + const ticketCloseResponse = await makeRequest({ + url: process.env.REACT_APP_DOMAIN + "/heruvym/close" , + method: "post", + useToken: true, + body: { + "ticket": ticketId + }, + }); + + return [ticketCloseResponse]; + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); + + return [null, `Не удалось закрыть тикет. ${error}`]; + } + } + + return ( + setOpenModal(false)} + aria-labelledby="modal-modal-title" + aria-describedby="modal-modal-description" + > + + + Вы уверены, что хотите закрыть тикет? + + + + + + + + ) +} \ No newline at end of file diff --git a/src/pages/dashboard/Content/Support/TicketList/TicketList.tsx b/src/pages/dashboard/Content/Support/TicketList/TicketList.tsx index 0dcfcdf..433a7ac 100644 --- a/src/pages/dashboard/Content/Support/TicketList/TicketList.tsx +++ b/src/pages/dashboard/Content/Support/TicketList/TicketList.tsx @@ -3,9 +3,13 @@ import SearchOutlinedIcon from "@mui/icons-material/SearchOutlined"; import { Box, Button, useMediaQuery, useTheme } from "@mui/material"; import { Ticket } from "@root/model/ticket"; import { incrementTicketsApiPage, useTicketStore } from "@root/stores/tickets"; -import { useEffect, useRef } from "react"; +import {useEffect, useRef, useState} from "react"; import TicketItem from "./TicketItem"; import { throttle } from "@frontend/kitui"; +import makeRequest from "@root/api/makeRequest"; +import {parseAxiosError} from "@root/utils/parse-error"; +import {useParams} from "react-router-dom"; +import CloseTicketModal from "@pages/dashboard/Content/Support/TicketList/CloseTicketModal"; type TicketListProps = { closeCollapse?: () => void; @@ -21,6 +25,8 @@ export default function TicketList({ const tickets = useTicketStore((state) => state.tickets); const ticketsFetchState = useTicketStore((state) => state.ticketsFetchState); const ticketsBoxRef = useRef(null); + const ticketId = useParams().ticketId; + const [openModal, setOpenModal] = useState(false) useEffect( function updateCurrentPageOnScroll() { @@ -91,6 +97,7 @@ export default function TicketList({ + + values: Values, + formikHelpers: FormikHelpers ) => { if (values.privilege !== null) { const [, createdTariffError] = await createTariff({ @@ -111,241 +111,244 @@ export default function CreateTariff() { // } return ( - - {(props) => ( -
- + {(props) => ( + + - - Создание тарифа - - + + Создание тарифа + + + + Привилегия + + { - props.setFieldValue("privilegeIdField", e.target.value) - props.setFieldValue("privilege", findPrivilegeById(e.target.value)) - if (props.values.privilege === null) - return enqueueSnackbar("Привилегия не найдена"); - }} - onBlur={props.handleBlur} - sx={{ - color: theme.palette.secondary.main, - borderColor: theme.palette.secondary.main, - "&.Mui-focused .MuiOutlinedInput-notchedOutline": { - borderColor: theme.palette.secondary.main, - border: "1px solid", - }, - ".MuiSvgIcon-root ": { - fill: theme.palette.secondary.main, - }, - }} - inputProps={{ sx: { pt: "12px" } }} - > - {privileges.map((privilege) => ( - - {privilege.serviceKey}:{privilege.description} - - ))} - - - {props.values.privilege && ( - - - Имя: {props.values.privilege.name} - - - Сервис: {props.values.privilege.serviceKey} - - - Единица: {props.values.privilege.type} - - - Стандартная цена за единицу: {currencyFormatter.format(props.values.privilege.price / 100)} - - - )} - - {props.errors.nameField} - - } - InputProps={{ - style: { - backgroundColor: theme.palette.content.main, - color: theme.palette.secondary.main, - } - }} - InputLabelProps={{ - style: { - color: theme.palette.secondary.main - } - }} - /> - { - props.setFieldValue("amountField", e.target.value.replace(/[^\d]/g,'')) - }} - value={props.values.amountField} - onBlur={props.handleBlur} - label="Кол-во единиц привилегии" - error={props.touched.amountField && !!props.errors.amountField} - helperText={ - - {props.errors.amountField} - - } - InputProps={{ - style: { - backgroundColor: theme.palette.content.main, - color: theme.palette.secondary.main, - } - }} - InputLabelProps={{ - style: { - color: theme.palette.secondary.main - } - }} - /> - { - props.setFieldValue("customPriceField", e.target.value.replace(/[^\d]/g,'')) - }} - value={props.values.customPriceField} - onBlur={props.handleBlur} - label="Кастомная цена (не обязательно)" - InputProps={{ - style: { - backgroundColor: theme.palette.content.main, - color: theme.palette.secondary.main, - } - }} - InputLabelProps={{ - style: { - color: theme.palette.secondary.main - } - }} - /> - { - props.setFieldValue("descriptionField", e.target.value) - }} - value={props.values.descriptionField} - onBlur={props.handleBlur} - label="Описание" - multiline={true} - InputProps={{ - style: { - backgroundColor: theme.palette.content.main, - color: theme.palette.secondary.main, - } - }} - InputLabelProps={{ - style: { - color: theme.palette.secondary.main - } - }} - /> - { - props.setFieldValue("orderField", e.target.value) - }} - value={props.values.orderField} - onBlur={props.handleBlur} - label="порядковый номер" - InputProps={{ - style: { - backgroundColor: theme.palette.content.main, - color: theme.palette.secondary.main, - } - }} - type={'number'} - InputLabelProps={{ - style: { - color: theme.palette.secondary.main - } - }} - /> - - - - )} -
+ {privilege.serviceKey}:{privilege.description} + + ))} + + + {props.values.privilege && ( + + + Имя: {props.values.privilege.name} + + + Сервис: {props.values.privilege.serviceKey} + + + Единица: {props.values.privilege.type} + + + Стандартная цена за единицу: {currencyFormatter.format(props.values.privilege.price / 100)} + + + )} + + {props.errors.nameField} + + } + InputProps={{ + style: { + backgroundColor: theme.palette.content.main, + color: theme.palette.secondary.main, + } + }} + InputLabelProps={{ + style: { + color: theme.palette.secondary.main + } + }} + /> + { + props.setFieldValue("amountField", e.target.value.replace(/[^\d]/g, '')) + }} + value={props.values.amountField} + onBlur={props.handleBlur} + label="Кол-во единиц привилегии" + error={props.touched.amountField && !!props.errors.amountField} + helperText={ + + {props.errors.amountField} + + } + InputProps={{ + style: { + backgroundColor: theme.palette.content.main, + color: theme.palette.secondary.main, + } + }} + InputLabelProps={{ + style: { + color: theme.palette.secondary.main + } + }} + /> + { + props.setFieldValue("customPriceField", e.target.value.replace(/[^\d]/g, '')) + }} + value={props.values.customPriceField} + onBlur={props.handleBlur} + label="Кастомная цена (не обязательно)" + InputProps={{ + style: { + backgroundColor: theme.palette.content.main, + color: theme.palette.secondary.main, + } + }} + InputLabelProps={{ + style: { + color: theme.palette.secondary.main + } + }} + /> + { + props.setFieldValue("descriptionField", e.target.value) + }} + value={props.values.descriptionField} + onBlur={props.handleBlur} + label="Описание" + multiline={true} + InputProps={{ + style: { + backgroundColor: theme.palette.content.main, + color: theme.palette.secondary.main, + } + }} + InputLabelProps={{ + style: { + color: theme.palette.secondary.main + } + }} + /> + { + props.setFieldValue("orderField", e.target.value) + }} + value={props.values.orderField} + onBlur={props.handleBlur} + label="порядковый номер" + InputProps={{ + style: { + backgroundColor: theme.palette.content.main, + color: theme.palette.secondary.main, + } + }} + type={'number'} + InputLabelProps={{ + style: { + color: theme.palette.secondary.main + } + }} + /> + + + + )} + ); } diff --git a/src/pages/dashboard/Content/Tariffs/EditModal.tsx b/src/pages/dashboard/Content/Tariffs/EditModal.tsx index 627e0d0..fd16621 100644 --- a/src/pages/dashboard/Content/Tariffs/EditModal.tsx +++ b/src/pages/dashboard/Content/Tariffs/EditModal.tsx @@ -45,7 +45,6 @@ export default function EditModal() { updatedTariff.price = price; updatedTariff.description = descriptionField; updatedTariff.order = parseInt(orderField); - const [_, putedTariffError] = await putTariff(updatedTariff); @@ -99,20 +98,20 @@ export default function EditModal() { sx={{ marginBottom: "10px" }} /> - Цена: {tariff.price} + Цена: {Math.trunc((tariff.price ?? 0) / 100)} setPriceField(event.target.value)} + onChange={({ target }) => + setPriceField(String(+target.value * 100)) + } label="Цена" name="price" - value={priceField} + value={Math.trunc(Number(priceField) / 100)} sx={{ marginBottom: "10px" }} /> - - Описание: {tariff.description} - + Описание: {tariff.description} - - Порядок: {tariff.order} - + Порядок: {tariff.order} setOrderField(event.target.value)} diff --git a/src/pages/dashboard/Menu/index.tsx b/src/pages/dashboard/Menu/index.tsx index 8856555..a4e355c 100644 --- a/src/pages/dashboard/Menu/index.tsx +++ b/src/pages/dashboard/Menu/index.tsx @@ -100,7 +100,7 @@ const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== "open" })); const links: { path: string; element: JSX.Element; title: string; className: string }[] = [ - { path: "/quizStatistic", element: <>📝, title: "Статистика Quiz", className: "menu" }, + { path: "/quizStatistics", 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/pages/dashboard/ModalUser/UserTab.tsx b/src/pages/dashboard/ModalUser/UserTab.tsx index 4fc1b22..cc719ca 100644 --- a/src/pages/dashboard/ModalUser/UserTab.tsx +++ b/src/pages/dashboard/ModalUser/UserTab.tsx @@ -48,7 +48,7 @@ export const UserTab = ({ userId }: UserTabProps) => { Email - {user?.email} + {user?.email || user?.login} diff --git a/src/utils/hooks/usePromocodeStatistics.ts b/src/utils/hooks/usePromocodeStatistics.ts new file mode 100644 index 0000000..eece16b --- /dev/null +++ b/src/utils/hooks/usePromocodeStatistics.ts @@ -0,0 +1,35 @@ +import { useEffect, useState } from "react"; +import { getStatisticPromocode } from "@root/api/quizStatistics"; + +import type { Moment } from "moment"; +import type { AllPromocodeStatistics } from "@root/api/quizStatistics/types"; +import moment from "moment"; + +interface useStatisticProps { + to: Moment | null; + from: Moment | null; +} + +export function usePromocodeStatistics({ to, from }: useStatisticProps) { + const formatTo = to?.unix(); + const formatFrom = from?.unix() || moment().unix() - 604800; + + const [promocodeStatistics, setPromocodeStatistics] = useState< + Record + >({}); + + useEffect(() => { + const requestStatistics = async () => { + const gottenData = await getStatisticPromocode( + Number(formatFrom), + Number(formatTo) + ); + + setPromocodeStatistics(gottenData); + }; + + requestStatistics(); + }, [formatTo, formatFrom]); + + return promocodeStatistics; +} diff --git a/src/utils/hooks/useQuizStatistic.ts b/src/utils/hooks/useQuizStatistic.ts index 22458a7..8164965 100644 --- a/src/utils/hooks/useQuizStatistic.ts +++ b/src/utils/hooks/useQuizStatistic.ts @@ -1,8 +1,5 @@ import { useEffect, useState } from "react"; -import { - QuizStatisticResponse, - getStatistic -} from "@root/api/quizStatistic"; +import { QuizStatisticResponse, getStatistic } from "@root/api/quizStatistics"; import type { Moment } from "moment"; @@ -15,18 +12,23 @@ export function useQuizStatistic({ to, from }: useQuizStatisticProps) { const formatTo = to?.unix(); const formatFrom = from?.unix(); - const [data, setData] = useState({ Registrations: 0, Quizes: 0, Results: 0 }); + const [data, setData] = useState({ + Registrations: 0, + Quizes: 0, + Results: 0, + }); useEffect(() => { - const requestStatistics = async () => { - - const gottenData = await getStatistic(Number(formatTo), Number(formatFrom)); - setData(gottenData) - } + const gottenData = await getStatistic( + Number(formatTo), + Number(formatFrom) + ); + setData(gottenData); + }; requestStatistics(); - }, [to, from]); + }, [formatTo, formatFrom]); return { ...data }; } diff --git a/src/utils/hooks/useSchildStatistics.ts b/src/utils/hooks/useSchildStatistics.ts new file mode 100644 index 0000000..44dd2a8 --- /dev/null +++ b/src/utils/hooks/useSchildStatistics.ts @@ -0,0 +1,28 @@ +import { useEffect, useState } from "react"; +import { getStatisticSchild } from "@root/api/quizStatistics"; + +import type { Moment } from "moment"; + +import type { QuizStatisticsItem } from "@root/api/quizStatistics/types"; +import moment from "moment"; + +export const useSchildStatistics = (from: Moment | null, to: Moment | null) => { + const formatTo = to?.unix(); + const formatFrom = from?.unix() || moment().unix() - 604800; + const [statistics, setStatistics] = useState([]); + + useEffect(() => { + const StatisticsShild = async () => { + const gottenData = await getStatisticSchild( + Number(formatFrom), + Number(formatTo) + ); + + setStatistics(gottenData); + }; + + StatisticsShild(); + }, [formatTo, formatFrom]); + + return statistics; +}; diff --git a/tsconfig.json b/tsconfig.json index 64018cf..fe01ca0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,9 +16,7 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "types": ["node"], + "types": ["node"] }, "include": ["src", "**/*.ts"] } - -