realized steps, users, tags... WIP
This commit is contained in:
parent
9e21fc491d
commit
73fa046cc8
114
src/api/integration.ts
Normal file
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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user