diff --git a/package.json b/package.json index 786b98d..38c4ac5 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "axios": "^1.4.0", "craco": "^0.0.3", "cypress": "^12.17.2", + "date-fns": "^3.3.1", "dayjs": "^1.11.5", "formik": "^2.2.9", "immer": "^10.0.2", diff --git a/src/api/history/requests.ts b/src/api/history/requests.ts new file mode 100644 index 0000000..fdc638f --- /dev/null +++ b/src/api/history/requests.ts @@ -0,0 +1,50 @@ +import { makeRequest } from "@frontend/kitui"; + +import { parseAxiosError } from "@root/utils/parse-error"; + +type RawDetail = { + Key: string; + Value: number | string | RawDetail[]; +}; + +type History = { + id: string; + userId: string; + comment: string; + key: string; + rawDetails: RawDetail[]; + isDeleted: boolean; + createdAt: string; + updatedAt: string; +}; + +type HistoryResponse = { + records: History[]; + totalPages: number; +}; + +const baseUrl = process.env.REACT_APP_DOMAIN + "/customer"; + +const getUserHistory = async ( + accountId: string, + page: number +): Promise<[HistoryResponse | null, string?]> => { + try { + const historyResponse = await makeRequest({ + method: "GET", + url: + baseUrl + + `/history?page=${page}&limit=${100}&accountID=${accountId}&type=payCart`, + }); + + return [historyResponse]; + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); + + return [null, `Ошибка при получении пользователей. ${error}`]; + } +}; + +export const historyApi = { + getUserHistory, +}; diff --git a/src/api/history/swr.ts b/src/api/history/swr.ts new file mode 100644 index 0000000..489d37b --- /dev/null +++ b/src/api/history/swr.ts @@ -0,0 +1,43 @@ +import { useState } from "react"; +import useSWRInfinite from "swr/infinite"; +import { enqueueSnackbar } from "notistack"; + +import { historyApi } from "./requests"; + +export function useHistory(accountId: string) { + const [currentPage, setCurrentPage] = useState(1); + + const swrResponse = useSWRInfinite( + () => `history-${currentPage}`, + async () => { + const [historyResponse, error] = await historyApi.getUserHistory( + accountId, + currentPage + ); + + if (error) { + throw new Error(error); + } + + if (!historyResponse) { + throw new Error("Empty history data"); + } + + if (currentPage < historyResponse.totalPages) { + setCurrentPage((page) => page + 1); + } + + return historyResponse; + }, + { + onError(err) { + console.log("Error fetching users", err); + enqueueSnackbar(err.message, { variant: "error" }); + }, + focusThrottleInterval: 60e3, + keepPreviousData: true, + } + ); + + return swrResponse; +} diff --git a/src/api/user.ts b/src/api/user.ts deleted file mode 100644 index aa6be3b..0000000 --- a/src/api/user.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { makeRequest } from "@frontend/kitui"; - -import { parseAxiosError } from "@root/utils/parse-error"; - -import type { UserType } from "@root/api/roles"; - -type RegisteredUsersResponse = { - tatalPages: number; - users: UserType[]; -}; - -const baseUrl = process.env.REACT_APP_DOMAIN + "/user"; - -export const getUserInfo = async ( - id: string -): Promise<[UserType | null, string?]> => { - try { - const userInfoResponse = await makeRequest({ - url: `${baseUrl}/${id}`, - method: "GET", - useToken: true, - }); - - return [userInfoResponse]; - } catch (nativeError) { - const [error] = parseAxiosError(nativeError); - - return [null, `Ошибка получения информации о пользователе. ${error}`]; - } -}; - -export const getRegisteredUsers = async (): Promise< - [RegisteredUsersResponse | null, string?] -> => { - try { - const registeredUsersResponse = await makeRequest< - never, - RegisteredUsersResponse - >({ - method: "get", - url: baseUrl + "/", - }); - - return [registeredUsersResponse]; - } catch (nativeError) { - const [error] = parseAxiosError(nativeError); - - return [null, `Ошибка при получении пользователей. ${error}`]; - } -}; - -export const getManagersList = async (): Promise< - [RegisteredUsersResponse | null, string?] -> => { - try { - const managersListResponse = await makeRequest< - never, - RegisteredUsersResponse - >({ - method: "get", - url: baseUrl + "/", - }); - - return [managersListResponse]; - } catch (nativeError) { - const [error] = parseAxiosError(nativeError); - - return [null, `Ошибка при получении менеджеров. ${error}`]; - } -}; diff --git a/src/api/user/requests.ts b/src/api/user/requests.ts new file mode 100644 index 0000000..4ae9590 --- /dev/null +++ b/src/api/user/requests.ts @@ -0,0 +1,89 @@ +import { makeRequest } from "@frontend/kitui"; + +import { parseAxiosError } from "@root/utils/parse-error"; + +import type { UserType } from "@root/api/roles"; + +export type UsersListResponse = { + totalPages: number; + users: UserType[]; +}; + +const baseUrl = process.env.REACT_APP_DOMAIN + "/user"; + +const getUserInfo = async (id: string): Promise<[UserType | null, string?]> => { + try { + const userInfoResponse = await makeRequest({ + url: `${baseUrl}/${id}`, + method: "GET", + useToken: true, + }); + + return [userInfoResponse]; + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); + + return [null, `Ошибка получения информации о пользователе. ${error}`]; + } +}; + +const getUserList = async ( + page = 1, + limit = 10 +): Promise<[UsersListResponse | null, string?]> => { + try { + const userResponse = await makeRequest({ + method: "get", + url: baseUrl + `/?page=${page}&limit=${limit}`, + }); + + return [userResponse]; + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); + + return [null, `Ошибка при получении пользователей. ${error}`]; + } +}; + +const getManagerList = async ( + page = 1, + limit = 10 +): Promise<[UsersListResponse | null, string?]> => { + try { + const managerResponse = await makeRequest({ + method: "get", + url: baseUrl + `/?page=${page}&limit=${limit}`, + }); + + return [managerResponse]; + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); + + return [null, `Ошибка при получении менеджеров. ${error}`]; + } +}; + +const getAdminList = async ( + page = 1, + limit = 10 +): Promise<[UsersListResponse | null, string?]> => { + try { + const adminResponse = await makeRequest({ + method: "get", + url: baseUrl + `/?page=${page}&limit=${limit}`, + }); + + return [adminResponse]; + } catch (nativeError) { + const [error] = parseAxiosError(nativeError); + + return [null, `Ошибка при получении админов. ${error}`]; + } +}; + +export const userApi = { + getUserInfo, + getUserList, + getManagerList, + getAdminList, +}; diff --git a/src/api/user/swr.ts b/src/api/user/swr.ts new file mode 100644 index 0000000..23dc84d --- /dev/null +++ b/src/api/user/swr.ts @@ -0,0 +1,104 @@ +import { useRef } from "react"; +import useSwr from "swr"; +import { enqueueSnackbar } from "notistack"; + +import { userApi } from "./requests"; + +export function useAdmins(page: number, pageSize: number) { + const adminPagesRef = useRef(0); + + const swrResponse = useSwr( + ["admin", page, pageSize], + async ([_, page, pageSize]) => { + const [adminResponse, error] = await userApi.getManagerList( + page, + pageSize + ); + + if (error) { + throw new Error(error); + } + + adminPagesRef.current = adminResponse?.totalPages || 1; + return adminResponse; + }, + { + onError(err) { + console.log("Error fetching users", err); + enqueueSnackbar(err.message, { variant: "error" }); + }, + focusThrottleInterval: 60e3, + keepPreviousData: true, + } + ); + + return { + ...swrResponse, + adminPages: adminPagesRef.current, + }; +} + +export function useManagers(page: number, pageSize: number) { + const managerPagesRef = useRef(0); + + const swrResponse = useSwr( + ["manager", page, pageSize], + async ([_, page, pageSize]) => { + const [managerResponse, error] = await userApi.getManagerList( + page, + pageSize + ); + + if (error) { + throw new Error(error); + } + + managerPagesRef.current = managerResponse?.totalPages || 1; + return managerResponse; + }, + { + onError(err) { + console.log("Error fetching users", err); + enqueueSnackbar(err.message, { variant: "error" }); + }, + focusThrottleInterval: 60e3, + keepPreviousData: true, + } + ); + + return { + ...swrResponse, + managerPages: managerPagesRef.current, + }; +} + +export function useUsers(page: number, pageSize: number) { + const userPagesRef = useRef(0); + + const swrResponse = useSwr( + ["users", page, pageSize], + async ([_, page, pageSize]) => { + const [userResponse, error] = await userApi.getUserList(page, pageSize); + + if (error) { + throw new Error(error); + } + + userPagesRef.current = userResponse?.totalPages || 1; + return userResponse; + }, + { + onError(err) { + console.log("Error fetching users", err); + enqueueSnackbar(err.message, { variant: "error" }); + }, + focusThrottleInterval: 60e3, + keepPreviousData: true, + } + ); + + return { + ...swrResponse, + userPagesCount: userPagesRef.current, + }; +} diff --git a/src/pages/dashboard/Content/ServiceUsersDG.tsx b/src/pages/dashboard/Content/ServiceUsersDG.tsx index 9f0c9cd..2aca18d 100644 --- a/src/pages/dashboard/Content/ServiceUsersDG.tsx +++ b/src/pages/dashboard/Content/ServiceUsersDG.tsx @@ -42,11 +42,21 @@ const columns: GridColDef[] = [ interface Props { handleSelectionChange: (selectionModel: GridSelectionModel) => void; users: UserType[]; + page: number; + setPage: (page: number) => void; + pageSize: number; + pagesCount: number; + onPageSizeChange?: (count: number) => void; } export default function ServiceUsersDG({ handleSelectionChange, users = [], + page, + setPage, + pageSize = 10, + pagesCount = 1, + onPageSizeChange, }: Props) { const navigate = useNavigate(); @@ -60,6 +70,13 @@ export default function ServiceUsersDG({ rows={users} columns={columns} components={{ Toolbar: GridToolbar }} + rowCount={pageSize * pagesCount} + rowsPerPageOptions={[10, 25, 50, 100]} + paginationMode="server" + page={page} + pageSize={pageSize} + onPageChange={setPage} + onPageSizeChange={onPageSizeChange} onSelectionModelChange={handleSelectionChange} onCellClick={({ row }, event) => { event.stopPropagation(); diff --git a/src/pages/dashboard/Content/Users.tsx b/src/pages/dashboard/Content/Users.tsx index 1426cf5..dfc4a89 100644 --- a/src/pages/dashboard/Content/Users.tsx +++ b/src/pages/dashboard/Content/Users.tsx @@ -23,14 +23,24 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ConditionalRender from "@root/pages/Setting/ConditionalRender"; import ModalUser from "@root/pages/dashboard/ModalUser"; import ServiceUsersDG from "./ServiceUsersDG"; -import { getRegisteredUsers, getManagersList } from "@root/api/user"; +import { useUsers, useManagers, useAdmins } from "@root/api/user/swr"; import { getRoles } from "@root/api/privilegies"; import { getRoles_mock, TMockData } from "../../../api/roles"; import theme from "../../../theme"; -import type { UserType } from "../../../api/roles"; +type Pages = { + adminPage: number; + managerPage: number; + userPage: number; +}; + +type PagesSize = { + adminPageSize: number; + managerPageSize: number; + userPageSize: number; +}; const Users: React.FC = () => { const radioboxes = ["admin", "manager", "user"]; @@ -39,11 +49,11 @@ const Users: React.FC = () => { const navigate = useNavigate(); - const [data, setData] = React.useState([]); + const [mockData, setMockData] = React.useState([]); const handleChangeData = () => { getRoles_mock().then((mockdata) => { - setData(mockdata); + setMockData(mockdata); setAccordionText(mockdata[0].desc || ""); }); }; @@ -53,7 +63,7 @@ const Users: React.FC = () => { const handleChange = (value: string) => { setSelectedValue(value); - setAccordionText(data.find(({ name }) => name === value)?.desc || ""); + setAccordionText(mockData.find(({ name }) => name === value)?.desc || ""); if (selectedValue === "manager") { } @@ -64,12 +74,33 @@ const Users: React.FC = () => { }; const [roles, setRoles] = React.useState([]); - const [users, setUsers] = React.useState([]); - const [manager, setManager] = React.useState([]); + + const [page, setPage] = useState({ + adminPage: 0, + managerPage: 0, + userPage: 0, + }); + const [pageSize, setPageSize] = useState({ + adminPageSize: 10, + managerPageSize: 10, + userPageSize: 10, + }); const [openUserModal, setOpenUserModal] = useState(false); const [activeUserId, setActiveUserId] = useState(""); const { userId } = useParams(); + const { data: adminData, adminPages } = useAdmins( + page.adminPage + 1, + pageSize.adminPageSize + ); + const { data: managerData, managerPages } = useManagers( + page.managerPage + 1, + pageSize.managerPageSize + ); + const { data: userData, userPagesCount } = useUsers( + page.userPage + 1, + pageSize.userPageSize + ); useEffect(() => { handleChangeData(); @@ -88,18 +119,6 @@ const Users: React.FC = () => { }, [userId]); useEffect(() => { - getManagersList().then(([managersListResponse]) => { - if (managersListResponse) { - setManager(managersListResponse.users); - } - }); - - getRegisteredUsers().then(([registeredUsersResponse]) => { - if (registeredUsersResponse) { - setUsers(registeredUsersResponse.users); - } - }); - getRoles().then(([rolesResponse]) => { if (rolesResponse) { setRoles(rolesResponse); @@ -210,8 +229,8 @@ const Users: React.FC = () => { - {data.length ? ( - data.map(function (item, index) { + {mockData.length ? ( + mockData.map(function (item, index) { return ( { + setPage((pages) => ({ ...pages, adminPage })) + } + pagesCount={adminPages} + pageSize={pageSize.adminPageSize} + handleSelectionChange={setSelectedTariffs} + onPageSizeChange={(adminPageSize) => + setPageSize((pageSize) => ({ ...pageSize, adminPageSize })) + } + /> + } childrenManager={ + setPage((pages) => ({ ...pages, managerPage })) + } + pagesCount={managerPages} + pageSize={pageSize.managerPageSize} handleSelectionChange={setSelectedTariffs} + onPageSizeChange={(managerPageSize) => + setPageSize((pageSize) => ({ ...pageSize, managerPageSize })) + } /> } childrenUser={ + setPage((pages) => ({ ...pages, userPage })) + } + pagesCount={userPagesCount} + pageSize={pageSize.userPageSize} handleSelectionChange={setSelectedTariffs} + onPageSizeChange={(userPageSize) => + setPageSize((pageSize) => ({ ...pageSize, userPageSize })) + } /> } /> diff --git a/src/pages/dashboard/ModalUser/PurchaseTab.tsx b/src/pages/dashboard/ModalUser/PurchaseTab.tsx index dbc87cf..361b3f7 100644 --- a/src/pages/dashboard/ModalUser/PurchaseTab.tsx +++ b/src/pages/dashboard/ModalUser/PurchaseTab.tsx @@ -1,7 +1,9 @@ import { useEffect, useRef, useState } from "react"; import { Box, useTheme, useMediaQuery } from "@mui/material"; import { DataGrid } from "@mui/x-data-grid"; +import { format } from "date-fns"; +import { useHistory } from "@root/api/history/swr"; import { scrollBlock } from "@root/utils/scrollBlock"; import forwardIcon from "@root/assets/icons/forward.svg"; @@ -9,6 +11,10 @@ import forwardIcon from "@root/assets/icons/forward.svg"; import type { ChangeEvent } from "react"; import type { GridColDef } from "@mui/x-data-grid"; +type PurchaseTabProps = { + userId: string; +}; + const COLUMNS: GridColDef[] = [ { field: "date", @@ -68,12 +74,22 @@ const ROWS = [ }, ]; -export const PurchaseTab = () => { +export const PurchaseTab = ({ userId }: PurchaseTabProps) => { const [canScrollToRight, setCanScrollToRight] = useState(true); const [canScrollToLeft, setCanScrollToLeft] = useState(false); const theme = useTheme(); const smallScreen = useMediaQuery(theme.breakpoints.down(830)); const gridContainer = useRef(null); + const { data: historyData } = useHistory(userId); + + const rows = + historyData?.[0].records.map((history) => ({ + id: history.id, + date: format(history.updatedAt, "dd.MM.yyyy"), + time: format(history.updatedAt, "HH:mm"), + product: "", + amount: "", + })) ?? []; useEffect(() => { const handleScroll = (nativeEvent: unknown) => { @@ -145,10 +161,8 @@ export const PurchaseTab = () => { }} > { ); }; + +const a = { + id: "65e4f1b157004756bc5bb15c", + userId: "64eb6ce57047f28fdabf69ec", + comment: "Успешная оплата корзины", + key: "payCart", + rawDetails: [ + [ + { Key: "id", Value: "65e4f1881747c1eea8007d3b" }, + { + Key: "name", + Value: + "Количество Заявок, Скрытие шильдика в опроснике, 2024-03-03T21:54:16.434Z", + }, + { Key: "price", Value: 0 }, + { Key: "iscustom", Value: true }, + { + Key: "privileges", + Value: [ + [ + { Key: "id", Value: "" }, + { Key: "name", Value: "Количество Заявок" }, + { Key: "privilegeid", Value: "quizCnt" }, + { Key: "servicekey", Value: "squiz" }, + { + Key: "description", + Value: "Количество полных прохождений опросов", + }, + { Key: "amount", Value: 100 }, + { Key: "type", Value: "count" }, + { Key: "value", Value: "заявка" }, + { Key: "price", Value: 2000 }, + ], + [ + { Key: "id", Value: "" }, + { Key: "name", Value: "Скрытие шильдика в опроснике" }, + { Key: "privilegeid", Value: "squizHideBadge" }, + { Key: "servicekey", Value: "squiz" }, + { + Key: "description", + Value: "Количество дней скрытия шильдика в опроснике", + }, + { Key: "amount", Value: 30 }, + { Key: "type", Value: "day" }, + { Key: "value", Value: "день" }, + { Key: "price", Value: 0 }, + ], + ], + }, + { Key: "deleted", Value: false }, + { Key: "createdat", Value: "2024-03-03T21:54:16.825Z" }, + { Key: "updatedat", Value: "2024-03-03T21:54:16.825Z" }, + { Key: "deletedat", Value: null }, + ], + ], + isDeleted: false, + createdAt: "2024-03-03T21:54:57.433Z", + updatedAt: "2024-03-03T21:54:57.433Z", +}; diff --git a/src/pages/dashboard/ModalUser/UserTab.tsx b/src/pages/dashboard/ModalUser/UserTab.tsx index ad03896..4fc1b22 100644 --- a/src/pages/dashboard/ModalUser/UserTab.tsx +++ b/src/pages/dashboard/ModalUser/UserTab.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from "react"; import { Box, Typography, useTheme, useMediaQuery } from "@mui/material"; -import { getUserInfo } from "@root/api/user"; +import { userApi } from "@root/api/user/requests"; import { getAccountInfo } from "@root/api/account"; import type { UserType } from "@root/api/roles"; @@ -19,7 +19,7 @@ export const UserTab = ({ userId }: UserTabProps) => { useEffect(() => { if (userId) { - getUserInfo(userId).then(([userInfo]) => setUser(userInfo)); + userApi.getUserInfo(userId).then(([userInfo]) => setUser(userInfo)); getAccountInfo(userId).then(([accountsInfo]) => setAccount(accountsInfo)); } }, []); diff --git a/src/pages/dashboard/ModalUser/index.tsx b/src/pages/dashboard/ModalUser/index.tsx index b0b4171..63014df 100644 --- a/src/pages/dashboard/ModalUser/index.tsx +++ b/src/pages/dashboard/ModalUser/index.tsx @@ -191,7 +191,7 @@ const ModalUser = ({ open, onClose, userId }: ModalUserProps) => { }} > {value === 0 && } - {value === 1 && } + {value === 1 && } {value === 2 && } {value === 3 && } diff --git a/yarn.lock b/yarn.lock index 2d09764..e38adc7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5028,6 +5028,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +date-fns@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.3.1.tgz#7581daca0892d139736697717a168afbb908cfed" + integrity sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw== + dayjs@^1.10.4: version "1.11.9" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a"