realized steps, users, tags... WIP

This commit is contained in:
aleksandr-raw 2024-05-20 22:15:36 +04:00
parent 9e21fc491d
commit 73fa046cc8
9 changed files with 356 additions and 60 deletions

114
src/api/integration.ts Normal file

@ -0,0 +1,114 @@
import { makeRequest } from "@api/makeRequest";
import { parseAxiosError } from "@utils/parse-error";
export type PaginationRequest = {
page: number;
size: number;
};
export type Tag = {
ID: number;
AmoID: number;
AccountID: number;
Entity: string;
Name: string;
Color: string;
Deleted: boolean;
CreatedAt: number;
};
export type TagsResponse = {
count: number;
items: Tag[];
};
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz/amocrm/amocrm`;
export const getTags = async ({
page,
size,
}: PaginationRequest): Promise<[TagsResponse | null, string?]> => {
try {
const tagsResponse = await makeRequest<PaginationRequest, TagsResponse>({
method: "GET",
url: `${API_URL}/tags?page=${page}&size=${size}`,
});
return [tagsResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить список тегов. ${error}`];
}
};
export type User = {
ID: number;
AccountID: string;
AmoID: number;
Name: string;
Email: string;
Role: string;
Group: number;
Deleted: boolean;
CreatedAt: number;
Subdomain: string;
Amoiserid: number;
Country: string;
};
export type UsersResponse = {
count: number;
items: User[];
};
export const getUsers = async ({
page,
size,
}: PaginationRequest): Promise<[UsersResponse | null, string?]> => {
try {
const usersResponse = await makeRequest<PaginationRequest, UsersResponse>({
method: "GET",
url: `${API_URL}/users?page=${page}&size=${size}`,
});
return [usersResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить список пользователей. ${error}`];
}
};
export type Step = {
ID: number;
AmoID: number;
PipelineID: number;
AccountID: number;
Name: string;
Color: string;
Deleted: boolean;
CreatedAt: number;
};
export type StepsResponse = {
count: number;
items: Step[];
};
export const getSteps = async ({
page,
size,
pipelineId,
}: PaginationRequest & { pipelineId: number }): Promise<
[StepsResponse | null, string?]
> => {
try {
const stepsResponse = await makeRequest<
PaginationRequest & { pipelineId: number },
StepsResponse
>({
method: "GET",
url: `${API_URL}/steps?page=${page}&size=${size}&pipelineID=${pipelineId}`,
});
return [stepsResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить список шагов. ${error}`];
}
};

@ -1,33 +1,195 @@
import * as React from "react";
import { FC } from "react";
import { FC, useEffect, useMemo, useRef, useState } from "react";
import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import FormControlLabel from "@mui/material/FormControlLabel";
import Box from "@mui/material/Box";
import CheckboxIcon from "@icons/Checkbox";
import { useTheme } from "@mui/material";
import { Typography, useTheme } from "@mui/material";
import {
getSteps,
getTags,
PaginationRequest,
Step,
Tag,
} from "@api/integration";
type CustomRadioGroupProps = {
items: string[];
type?: string;
selectedValue: string | null;
setSelectedValue: (value: string | null) => void;
pipelineId?: number | null;
};
const SIZE = 25;
export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
items,
type,
selectedValue,
setSelectedValue,
pipelineId,
}) => {
const theme = useTheme();
const [currentValue, setCurrentValue] = React.useState<string | null>(
const [currentValue, setCurrentValue] = useState<string | null>(
selectedValue,
);
const [page, setPage] = useState(1);
const [isLoading, setIsLoading] = useState(false);
const [tags, setTags] = useState<Tag[]>([]);
const [steps, setSteps] = useState<Step[]>([]);
const [hasMoreItems, setHasMoreItems] = useState(true);
const boxRef = useRef(null);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelectedValue((event.target as HTMLInputElement).value);
setCurrentValue((event.target as HTMLInputElement).value);
};
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
const scrollHeight = e.currentTarget.scrollHeight;
const scrollTop = e.currentTarget.scrollTop;
const clientHeight = e.currentTarget.clientHeight;
const scrolledToBottom = scrollTop / (scrollHeight - clientHeight) > 0.9;
if (scrolledToBottom && !isLoading && hasMoreItems) {
setPage((prevPage) => prevPage + 1);
}
};
useEffect(() => {
if (type === "typeTags" && hasMoreItems) {
setIsLoading(true);
const pagination: PaginationRequest = {
page: page,
size: SIZE,
};
getTags(pagination).then(([response]) => {
if (response && response.items !== null) {
setTags((prevItems) => [...prevItems, ...response.items]);
if (response.items.length < SIZE) {
setHasMoreItems(false);
}
}
setIsLoading(false);
});
}
if (type === "typeSteps" && hasMoreItems && pipelineId) {
setIsLoading(true);
const pagination: PaginationRequest & { pipelineId: number } = {
page: page,
size: SIZE,
pipelineId: pipelineId,
};
getSteps(pagination).then(([response]) => {
if (response && response.items !== null) {
setSteps((prevItems) => [...prevItems, ...response.items]);
if (response.items.length < SIZE) {
setHasMoreItems(false);
}
}
setIsLoading(false);
});
}
}, [page, type, hasMoreItems, pipelineId]);
const formControlLabels = useMemo(() => {
if (type === "typeTags" && tags && tags.length !== 0) {
return tags.map((item) => (
<FormControlLabel
key={item.ID}
sx={{
color: "black",
padding: "15px",
borderBottom: `1px solid ${theme.palette.background.default}`,
display: "flex",
justifyContent: "space-between",
borderRadius: "12px",
margin: 0,
backgroundColor:
currentValue === item.Name
? theme.palette.background.default
: theme.palette.common.white,
}}
value={item.Name}
control={
<Radio
checkedIcon={
<CheckboxIcon
checked
isRounded
color={theme.palette.brightPurple.main}
/>
}
icon={<CheckboxIcon isRounded />}
/>
}
// label={item.Name}
label={
<Box sx={{ display: "flex", flexDirection: "column" }}>
<Typography sx={{ color: `${item.Color}` }}>
{item.Name}
</Typography>
<Typography>{item.Entity}</Typography>
</Box>
}
labelPlacement={"start"}
/>
));
}
if (type === "typeSteps" && steps && steps.length !== 0) {
return steps.map((step) => (
<FormControlLabel
key={step.ID}
sx={{
color: "black",
padding: "15px",
borderBottom: `1px solid ${theme.palette.background.default}`,
display: "flex",
justifyContent: "space-between",
borderRadius: "12px",
margin: 0,
backgroundColor:
currentValue === step.Name
? theme.palette.background.default
: theme.palette.common.white,
}}
value={step.Name}
control={
<Radio
checkedIcon={
<CheckboxIcon
checked
isRounded
color={theme.palette.brightPurple.main}
/>
}
icon={<CheckboxIcon isRounded />}
/>
}
label={step.Name}
labelPlacement={"start"}
/>
));
}
return (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100%",
padding: "15px",
}}
>
<Typography>Нет элементов</Typography>
</Box>
);
}, [tags, currentValue, type]);
return (
<Box
ref={boxRef}
onScroll={handleScroll}
sx={{
border: `1px solid ${theme.palette.grey2.main}`,
borderRadius: "12px",
@ -42,39 +204,7 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
value={currentValue}
onChange={handleChange}
>
{items.map((item) => (
<FormControlLabel
key={item}
sx={{
color: "black",
padding: "15px",
borderBottom: `1px solid ${theme.palette.background.default}`,
display: "flex",
justifyContent: "space-between",
borderRadius: "12px",
margin: 0,
backgroundColor:
currentValue === item
? theme.palette.background.default
: theme.palette.common.white,
}}
value={item}
control={
<Radio
checkedIcon={
<CheckboxIcon
checked
isRounded
color={theme.palette.brightPurple.main}
/>
}
icon={<CheckboxIcon isRounded />}
/>
}
label={item}
labelPlacement={"start"}
/>
))}
{formControlLabels}
</RadioGroup>
</Box>
);

@ -8,26 +8,34 @@ import {
useTheme,
} from "@mui/material";
import Box from "@mui/material/Box";
import { FC, useCallback, useRef, useState } from "react";
import * as React from "react";
import { FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
import "./CustomSelect.css";
import arrow_down from "../../assets/icons/arrow_down.svg";
import { getUsers, PaginationRequest, User } from "@api/integration";
type CustomSelectProps = {
items: string[];
selectedItem: string | null;
setSelectedItem: (value: string | null) => void;
type?: string;
};
export const CustomSelect: FC<CustomSelectProps> = ({
items,
selectedItem,
setSelectedItem,
type,
}) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const [opened, setOpened] = useState<boolean>(false);
const [currentValue, setCurrentValue] = useState<string | null>(selectedItem);
const [users, setUsers] = useState<User[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [page, setPage] = useState(1);
const [hasMoreItems, setHasMoreItems] = useState(true);
const SIZE = 25;
const ref = useRef<HTMLDivElement | null>(null);
console.log("page", page);
const onSelectItem = useCallback(
(event: SelectChangeEvent<HTMLDivElement>) => {
@ -42,6 +50,55 @@ export const CustomSelect: FC<CustomSelectProps> = ({
setOpened((isOpened) => !isOpened);
}, []);
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
const scrollHeight = e.currentTarget.scrollHeight;
const scrollTop = e.currentTarget.scrollTop;
const clientHeight = e.currentTarget.clientHeight;
const scrolledToBottom = scrollTop / (scrollHeight - clientHeight) > 0.9;
if (scrolledToBottom && !isLoading && hasMoreItems) {
setPage((prevPage) => prevPage + 1);
}
};
useEffect(() => {
if (type === "typeUsers" && hasMoreItems) {
setIsLoading(true);
const pagination: PaginationRequest = {
page: page,
size: SIZE,
};
getUsers(pagination).then(([response]) => {
if (response && response.items !== null) {
setUsers((prevItems) => [...prevItems, ...response.items]);
if (response.items.length < SIZE) {
setHasMoreItems(false);
}
}
setIsLoading(false);
});
}
}, [page, type, hasMoreItems]);
const menuItems = useMemo(() => {
if (type === "typeUsers" && users && users.length !== 0) {
return users.map((user) => (
<MenuItem
key={user.ID}
value={user.Name}
sx={{ padding: "12px", zIndex: 2 }}
>
{user.Name}
</MenuItem>
));
}
return (
<MenuItem key={"-1"} disabled sx={{ padding: "12px", zIndex: 2 }}>
нет данных
</MenuItem>
);
}, [users, type]);
return (
<Box>
<Box
@ -100,6 +157,7 @@ export const CustomSelect: FC<CustomSelectProps> = ({
MenuProps={{
disablePortal: true,
PaperProps: {
onScroll: handleScroll,
style: {
zIndex: 2,
maxHeight: "300px",
@ -111,18 +169,7 @@ export const CustomSelect: FC<CustomSelectProps> = ({
onChange={onSelectItem}
onClick={toggleOpened}
>
{items.map((item) => {
const uniqueKey = `${item}-${Date.now()}`;
return (
<MenuItem
key={uniqueKey}
value={item}
sx={{ padding: "12px", zIndex: 2 }}
>
{item}
</MenuItem>
);
})}
{menuItems}
</Select>
</Box>
);

@ -115,6 +115,7 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
setSelectedStage={setSelectedStage}
performers={performersMock}
stages={stagesMock}
pipelineId={selectedFunnel}
/>
),
},

@ -41,8 +41,8 @@ export const IntegrationStep2: FC<IntegrationStep2Props> = ({
<Box sx={{ width: "100%", marginTop: "20px", zIndex: 3 }}>
<CustomSelect
selectedItem={selectedFunnelPerformer}
items={performers}
setSelectedItem={setSelectedFunnelPerformer}
type={"typeUsers"}
/>
</Box>
<Box
@ -54,7 +54,6 @@ export const IntegrationStep2: FC<IntegrationStep2Props> = ({
}}
>
<CustomRadioGroup
items={funnels}
selectedValue={selectedFunnel}
setSelectedValue={setSelectedFunnel}
/>

@ -13,6 +13,7 @@ type IntegrationStep3Props = {
setSelectedStage: (value: string | null) => void;
performers: string[];
stages: string[];
pipelineId: string | null;
};
export const IntegrationStep3: FC<IntegrationStep3Props> = ({
@ -22,8 +23,7 @@ export const IntegrationStep3: FC<IntegrationStep3Props> = ({
setSelectedStagePerformer,
selectedStage,
setSelectedStage,
performers,
stages,
pipelineId,
}) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
@ -42,7 +42,7 @@ export const IntegrationStep3: FC<IntegrationStep3Props> = ({
<Box sx={{ width: "100%", marginTop: "20px", zIndex: 3 }}>
<CustomSelect
selectedItem={selectedStagePerformer}
items={performers}
type={"typeUsers"}
setSelectedItem={setSelectedStagePerformer}
/>
</Box>
@ -55,7 +55,8 @@ export const IntegrationStep3: FC<IntegrationStep3Props> = ({
}}
>
<CustomRadioGroup
items={stages}
pipelineId={pipelineId}
type={"typeSteps"}
selectedValue={selectedStage}
setSelectedValue={setSelectedStage}
/>

@ -35,8 +35,8 @@ export const IntegrationStep4: FC<IntegrationStep4Props> = ({
<Box sx={{ width: "100%", marginTop: "20px", zIndex: 3 }}>
<CustomSelect
selectedItem={selectedDealPerformer}
items={performers}
setSelectedItem={setSelectedDealPerformer}
type={"typeUsers"}
/>
</Box>
<Box

@ -4,6 +4,7 @@ import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock";
import { FC } from "react";
type ItemsSelectionViewProps = {
type?: string;
items: string[];
selectedValue: string | null;
setSelectedValue: (value: string | null) => void;
@ -17,6 +18,7 @@ export const ItemsSelectionView: FC<ItemsSelectionViewProps> = ({
setSelectedValue,
onLargeBtnClick,
onSmallBtnClick,
type,
}) => {
return (
<Box
@ -37,6 +39,7 @@ export const ItemsSelectionView: FC<ItemsSelectionViewProps> = ({
}}
>
<CustomRadioGroup
type={type}
items={items}
selectedValue={selectedValue}
setSelectedValue={setSelectedValue}

@ -59,6 +59,7 @@ export const IntegrationStep7: FC<IntegrationStep7Props> = ({
items={items}
selectedValue={selectedValue}
setSelectedValue={setSelectedValue}
type={"typeTags"}
onSmallBtnClick={() => {
setActiveItem(null);
setIsSelection(false);