feat: scroll DataGrid buttons logic

This commit is contained in:
IlyaDoronin 2023-07-27 15:49:47 +03:00
parent 89d44b64fd
commit b1b267fdb7
6 changed files with 510 additions and 201 deletions

@ -1,6 +1,12 @@
import { Box } from "@mui/material"; import { useEffect, useRef, useState } from "react";
import { Box, useTheme, useMediaQuery } from "@mui/material";
import { DataGrid } from "@mui/x-data-grid"; import { DataGrid } from "@mui/x-data-grid";
import { scrollBlock } from "@root/utils/scrollBlock";
import forwardIcon from "@root/assets/icons/forward.svg";
import type { ChangeEvent } from "react";
import type { GridColDef } from "@mui/x-data-grid"; import type { GridColDef } from "@mui/x-data-grid";
const COLUMNS: GridColDef[] = [ const COLUMNS: GridColDef[] = [
@ -62,8 +68,68 @@ const ROWS = [
}, },
]; ];
export const PurchaseTab = () => ( export const PurchaseTab = () => {
const [canScrollToRight, setCanScrollToRight] = useState<boolean>(true);
const [canScrollToLeft, setCanScrollToLeft] = useState<boolean>(false);
const theme = useTheme();
const smallScreen = useMediaQuery(theme.breakpoints.down(830));
const gridContainer = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleScroll = (nativeEvent: unknown) => {
const { target } = nativeEvent as ChangeEvent<HTMLDivElement>;
if (target.scrollLeft > 0) {
setCanScrollToLeft(true);
} else {
setCanScrollToLeft(false);
}
if (target.clientWidth + target.scrollLeft >= target.scrollWidth - 1) {
setCanScrollToRight(false);
} else {
setCanScrollToRight(true);
}
};
const addScrollEvent = () => {
const grid = gridContainer.current?.querySelector(
".MuiDataGrid-virtualScroller"
);
if (grid) {
grid.addEventListener("scroll", handleScroll);
return;
}
setTimeout(addScrollEvent, 100);
};
addScrollEvent();
return () => {
const grid = gridContainer.current?.querySelector(
".MuiDataGrid-virtualScroller"
);
grid?.removeEventListener("scroll", handleScroll);
};
}, []);
const scrollDataGrid = (toStart = false) => {
const grid = gridContainer.current?.querySelector(
".MuiDataGrid-virtualScroller"
);
if (grid) {
scrollBlock(grid, { left: toStart ? 0 : grid.scrollWidth });
}
};
return (
<Box <Box
ref={gridContainer}
sx={{ sx={{
height: "100%", height: "100%",
padding: "0 25px", padding: "0 25px",
@ -120,5 +186,56 @@ export const PurchaseTab = () => (
}, },
}} }}
/> />
{smallScreen && (
<Box
sx={{
position: "absolute",
right: "15px",
bottom: "50px",
display: "flex",
columnGap: "5px",
}}
>
{canScrollToLeft && (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "30px",
height: "30px",
borderRadius: "8px",
border: "1px solid #7E2AEA",
background: "rgba(126, 42, 234, 0.07)",
}}
onClick={() => scrollDataGrid(true)}
>
<img
src={forwardIcon}
alt="forward"
style={{ transform: "rotate(180deg)" }}
/>
</Box>
)}
{canScrollToRight && (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "30px",
height: "30px",
borderRadius: "8px",
border: "1px solid #7E2AEA",
background: "rgba(126, 42, 234, 0.07)",
}}
onClick={() => scrollDataGrid(false)}
>
<img src={forwardIcon} alt="forward" />
</Box>
)}
</Box>
)}
</Box> </Box>
); );
};

@ -1,6 +1,12 @@
import { useEffect, useRef, useState } from "react";
import { Typography, Box, useTheme, useMediaQuery } from "@mui/material";
import { DataGrid } from "@mui/x-data-grid"; import { DataGrid } from "@mui/x-data-grid";
import { Typography } from "@mui/material";
import { scrollBlock } from "@root/utils/scrollBlock";
import forwardIcon from "@root/assets/icons/forward.svg";
import type { ChangeEvent } from "react";
import type { GridColDef } from "@mui/x-data-grid"; import type { GridColDef } from "@mui/x-data-grid";
const COLUMNS: GridColDef[] = [ const COLUMNS: GridColDef[] = [
@ -86,7 +92,67 @@ const ROWS = [
}, },
]; ];
export const TransactionsTab = () => ( export const TransactionsTab = () => {
const [canScrollToRight, setCanScrollToRight] = useState<boolean>(true);
const [canScrollToLeft, setCanScrollToLeft] = useState<boolean>(false);
const theme = useTheme();
const smallScreen = useMediaQuery(theme.breakpoints.down(1070));
const gridContainer = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleScroll = (nativeEvent: unknown) => {
const { target } = nativeEvent as ChangeEvent<HTMLDivElement>;
if (target.scrollLeft > 0) {
setCanScrollToLeft(true);
} else {
setCanScrollToLeft(false);
}
if (target.clientWidth + target.scrollLeft >= target.scrollWidth - 1) {
setCanScrollToRight(false);
} else {
setCanScrollToRight(true);
}
};
const addScrollEvent = () => {
const grid = gridContainer.current?.querySelector(
".MuiDataGrid-virtualScroller"
);
if (grid) {
grid.addEventListener("scroll", handleScroll);
return;
}
setTimeout(addScrollEvent, 100);
};
addScrollEvent();
return () => {
const grid = gridContainer.current?.querySelector(
".MuiDataGrid-virtualScroller"
);
grid?.removeEventListener("scroll", handleScroll);
};
}, []);
const scrollDataGrid = (toStart = false) => {
const grid = gridContainer.current?.querySelector(
".MuiDataGrid-virtualScroller"
);
if (grid) {
scrollBlock(grid, { left: toStart ? 0 : grid.scrollWidth });
}
};
return (
<Box sx={{ height: "100%" }} ref={gridContainer}>
<DataGrid <DataGrid
rows={ROWS} rows={ROWS}
columns={COLUMNS} columns={COLUMNS}
@ -131,4 +197,56 @@ export const TransactionsTab = () => (
}, },
}} }}
/> />
{smallScreen && (
<Box
sx={{
position: "absolute",
right: "15px",
bottom: "50px",
display: "flex",
columnGap: "5px",
}}
>
{canScrollToLeft && (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "30px",
height: "30px",
borderRadius: "8px",
border: "1px solid #7E2AEA",
background: "rgba(126, 42, 234, 0.07)",
}}
onClick={() => scrollDataGrid(true)}
>
<img
src={forwardIcon}
alt="forward"
style={{ transform: "rotate(180deg)" }}
/>
</Box>
)}
{canScrollToRight && (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "30px",
height: "30px",
borderRadius: "8px",
border: "1px solid #7E2AEA",
background: "rgba(126, 42, 234, 0.07)",
}}
onClick={() => scrollDataGrid(false)}
>
<img src={forwardIcon} alt="forward" />
</Box>
)}
</Box>
)}
</Box>
); );
};

@ -1,18 +1,6 @@
import { Box, Typography, useTheme, useMediaQuery } from "@mui/material"; import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
type UserTabProps = { export const UserTab = () => {
user: {
id: number;
registrationDate: string;
email: string;
phone: string;
type: string;
fullname: string;
walletBalance: string;
};
};
export const UserTab = ({ user }: UserTabProps) => {
const theme = useTheme(); const theme = useTheme();
const mobile = useMediaQuery(theme.breakpoints.down(700)); const mobile = useMediaQuery(theme.breakpoints.down(700));
@ -29,31 +17,31 @@ export const UserTab = ({ user }: UserTabProps) => {
<Typography sx={{ lineHeight: "20px" }}>ID</Typography> <Typography sx={{ lineHeight: "20px" }}>ID</Typography>
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}> <Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>
{" "} {" "}
{user.id} 2810
</Typography> </Typography>
</Box> </Box>
<Box sx={{ marginBottom: "25px" }}> <Box sx={{ marginBottom: "25px" }}>
<Typography sx={{ lineHeight: "20px" }}>Дата регистрации</Typography> <Typography sx={{ lineHeight: "20px" }}>Дата регистрации</Typography>
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}> <Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>
{user.registrationDate} 17.02.2023
</Typography> </Typography>
</Box> </Box>
<Box sx={{ marginBottom: "25px" }}> <Box sx={{ marginBottom: "25px" }}>
<Typography sx={{ lineHeight: "20px" }}>Email</Typography> <Typography sx={{ lineHeight: "20px" }}>Email</Typography>
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}> <Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>
{user.email} emailexamle@gmail.com
</Typography> </Typography>
</Box> </Box>
<Box sx={{ marginBottom: "25px" }}> <Box sx={{ marginBottom: "25px" }}>
<Typography sx={{ lineHeight: "20px" }}>Телефон</Typography> <Typography sx={{ lineHeight: "20px" }}>Телефон</Typography>
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}> <Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>
{user.phone} +7 123 456 78 90
</Typography> </Typography>
</Box> </Box>
<Box sx={{ marginBottom: "25px" }}> <Box sx={{ marginBottom: "25px" }}>
<Typography sx={{ lineHeight: "20px" }}>Тип:</Typography> <Typography sx={{ lineHeight: "20px" }}>Тип:</Typography>
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}> <Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>
{user.type} НКО
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
@ -61,7 +49,7 @@ export const UserTab = ({ user }: UserTabProps) => {
<Box sx={{ marginBottom: "25px" }}> <Box sx={{ marginBottom: "25px" }}>
<Typography sx={{ lineHeight: "20px" }}>ФИО:</Typography> <Typography sx={{ lineHeight: "20px" }}>ФИО:</Typography>
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}> <Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>
{user.fullname} Куликов Геннадий Викторович
</Typography> </Typography>
</Box> </Box>
<Box sx={{ marginBottom: "25px" }}> <Box sx={{ marginBottom: "25px" }}>
@ -69,7 +57,7 @@ export const UserTab = ({ user }: UserTabProps) => {
Внутренний кошелек Внутренний кошелек
</Typography> </Typography>
<Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}> <Typography sx={{ lineHeight: "20px", fontWeight: "bold" }}>
{user.walletBalance} 2 096 руб.
</Typography> </Typography>
</Box> </Box>
</Box> </Box>

@ -1,15 +1,89 @@
import { Box, Typography } from "@mui/material"; import { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { Box, Typography, TextField, Button } from "@mui/material";
import type { File } from "./index"; import { authStore } from "@root/stores/auth";
type VerificationTabProps = { import type { ChangeEvent } from "react";
type File = {
name: "inn" | "rule" | "egrule" | "certificate";
url: string;
};
type Verification = {
_id: string;
accepted: boolean;
status: "org" | "nko";
updated_at: string;
comment: string;
files: File[]; files: File[];
}; };
export const VerificationTab = ({ files }: VerificationTabProps) => ( type PatchVerificationBody = {
id: string;
status: "org" | "nko";
comment: string;
accepted: boolean;
};
const baseUrl =
process.env.NODE_ENV === "production" ? "" : "https://hub.pena.digital";
export const VerificationTab = () => {
const [user, setUser] = useState<Verification | null>(null);
const [comment, setComment] = useState<string>("");
const { userId } = useParams();
const { makeRequest } = authStore();
const requestVefification = async () =>
makeRequest<never, Verification>({
method: "get",
url: baseUrl + `/verification/verification/${userId}`,
}).then((verification) => {
setUser(verification);
setComment(verification.comment);
});
useEffect(() => {
requestVefification();
}, []);
const verify = async (accepted: boolean) => {
if (!user) {
return;
}
try {
await makeRequest<PatchVerificationBody, never>({
method: "patch",
useToken: true,
url: baseUrl + `/verification/verification`,
body: {
accepted,
comment,
id: user._id,
status: user.status,
},
});
await requestVefification();
} catch {}
};
return (
<Box sx={{ padding: "25px" }}> <Box sx={{ padding: "25px" }}>
{files.map(({ name, url }, index) => ( <Typography
<Box sx={{ marginBottom: "25px" }}> sx={{
marginBottom: "10px",
fontWeight: "bold",
color: user?.accepted ? "#0D9F00" : "#E02C2C",
}}
>
{user?.accepted ? "Верификация пройдена" : "Не верифицирован"}
</Typography>
{user?.files?.map(({ name, url }, index) => (
<Box sx={{ marginBottom: "25px" }} key={name + url}>
<Typography sx={{ fontWeight: "bold", fontSize: "18px" }}> <Typography sx={{ fontWeight: "bold", fontSize: "18px" }}>
{index + 1}.{" "} {index + 1}.{" "}
{name === "inn" {name === "inn"
@ -34,5 +108,48 @@ export const VerificationTab = ({ files }: VerificationTabProps) => (
</Typography> </Typography>
</Box> </Box>
))} ))}
{user?.comment && (
<Box sx={{ marginBottom: "15px" }}>
<Typography
component="span"
sx={{ fontWeight: "bold", marginBottom: "10px" }}
>
Комментарий:
</Typography>
<Typography component="span"> {user.comment}</Typography>
</Box>
)}
<TextField
multiline
value={comment}
rows={4}
label="Комментарий"
type=""
sx={{
width: "100%",
maxWidth: "500px",
marginBottom: "10px",
}}
onChange={(event: ChangeEvent<HTMLTextAreaElement>) =>
setComment(event.target.value)
}
/>
<Box sx={{ display: "flex", columnGap: "10px" }}>
<Button
variant="text"
sx={{ background: "#9A9AAF" }}
onClick={() => verify(false)}
>
Отклонить
</Button>
<Button
variant="text"
sx={{ background: "#9A9AAF" }}
onClick={() => verify(true)}
>
Подтвердить
</Button>
</Box>
</Box> </Box>
); );
};

@ -1,5 +1,5 @@
import { useEffect, useState } from "react"; import { useState } from "react";
import { useLinkClickHandler, useParams } from "react-router-dom"; import { useLinkClickHandler } from "react-router-dom";
import { import {
Box, Box,
Modal, Modal,
@ -17,8 +17,6 @@ import { PurchaseTab } from "./PurchaseTab";
import { TransactionsTab } from "./TransactionsTab"; import { TransactionsTab } from "./TransactionsTab";
import { VerificationTab } from "./VerificationTab"; import { VerificationTab } from "./VerificationTab";
import { authStore } from "@root/stores/auth";
import userIcon from "@root/assets/icons/user.svg"; import userIcon from "@root/assets/icons/user.svg";
import packageIcon from "@root/assets/icons/package.svg"; import packageIcon from "@root/assets/icons/package.svg";
import transactionsIcon from "@root/assets/icons/transactions.svg"; import transactionsIcon from "@root/assets/icons/transactions.svg";
@ -34,40 +32,13 @@ const TABS = [
{ name: "Верификация", icon: checkIcon }, { name: "Верификация", icon: checkIcon },
]; ];
const baseUrl =
process.env.NODE_ENV === "production" ? "" : "https://hub.pena.digital";
export type File = {
name: "inn" | "rule" | "egrule" | "certificate";
url: string;
};
export type Verification = {
_id: string;
accepted: boolean;
status: "org" | "nko";
updated_at: string;
comment: string;
files: File[];
};
const ModalUser = () => { const ModalUser = () => {
const [user, setUser] = useState<Verification | null>(null);
const [value, setValue] = useState<number>(0); const [value, setValue] = useState<number>(0);
const [openNavigation, setOpenNavigation] = useState<boolean>(false); const [openNavigation, setOpenNavigation] = useState<boolean>(false);
const { userId } = useParams();
const { makeRequest } = authStore();
const theme = useTheme(); const theme = useTheme();
const tablet = useMediaQuery(theme.breakpoints.down(1070)); const tablet = useMediaQuery(theme.breakpoints.down(1070));
const mobile = useMediaQuery(theme.breakpoints.down(700)); const mobile = useMediaQuery(theme.breakpoints.down(700));
useEffect(() => {
makeRequest<never, Verification>({
method: "get",
url: baseUrl + `/verification/verification/${userId}`,
}).then(setUser);
}, []);
return ( return (
<> <>
<Modal <Modal
@ -98,7 +69,7 @@ const ModalUser = () => {
boxShadow: 24, boxShadow: 24,
borderRadius: mobile ? "0" : "12px", borderRadius: mobile ? "0" : "12px",
outline: "none", outline: "none",
overflow: "hidden", overflowX: "hidden",
}} }}
> >
<Typography <Typography
@ -192,22 +163,10 @@ const ModalUser = () => {
boxShadow: "inset 30px 0px 40px 0px rgba(210, 208, 225, 0.2)", boxShadow: "inset 30px 0px 40px 0px rgba(210, 208, 225, 0.2)",
}} }}
> >
{value === 0 && ( {value === 0 && <UserTab />}
<UserTab
user={{
id: 2810,
registrationDate: "17.02.2023",
email: "emailexamle@gmail.com",
phone: "+7 123 456 78 90",
type: "НКО",
fullname: "Куликов Геннадий Викторович",
walletBalance: "2 096 руб.",
}}
/>
)}
{value === 1 && <PurchaseTab />} {value === 1 && <PurchaseTab />}
{value === 2 && <TransactionsTab />} {value === 2 && <TransactionsTab />}
{value === 3 && <VerificationTab files={user?.files || []} />} {value === 3 && <VerificationTab />}
</Box> </Box>
</Box> </Box>
</Box> </Box>

10
src/utils/scrollBlock.ts Normal file

@ -0,0 +1,10 @@
type Coordinates = {
top?: number;
left?: number;
};
export const scrollBlock = (
block: Element,
coordinates: Coordinates,
smooth = true
) => block.scroll({ ...coordinates, behavior: smooth ? "smooth" : "auto" });