WIP... realized pipelines, users, removed utm, refactored modals with lazy
This commit is contained in:
parent
73fa046cc8
commit
a66a4764cf
@ -21,7 +21,7 @@ export type TagsResponse = {
|
|||||||
count: number;
|
count: number;
|
||||||
items: Tag[];
|
items: Tag[];
|
||||||
};
|
};
|
||||||
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz/amocrm/amocrm`;
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz/amocrm`;
|
||||||
|
|
||||||
export const getTags = async ({
|
export const getTags = async ({
|
||||||
page,
|
page,
|
||||||
@ -112,3 +112,37 @@ export const getSteps = async ({
|
|||||||
return [null, `Не удалось получить список шагов. ${error}`];
|
return [null, `Не удалось получить список шагов. ${error}`];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Pipeline = {
|
||||||
|
ID: number;
|
||||||
|
AmoID: number;
|
||||||
|
AccountID: number;
|
||||||
|
Name: string;
|
||||||
|
IsArchive: boolean;
|
||||||
|
Deleted: boolean;
|
||||||
|
CreatedAt: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PipelinesResponse = {
|
||||||
|
count: number;
|
||||||
|
items: Pipeline[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPipelines = async ({
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
}: PaginationRequest): Promise<[PipelinesResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const pipelinesResponse = await makeRequest<
|
||||||
|
PaginationRequest,
|
||||||
|
PipelinesResponse
|
||||||
|
>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/pipelines?page=${page}&size=${size}`,
|
||||||
|
});
|
||||||
|
return [pipelinesResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Не удалось получить список воронок. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@ -7,9 +7,11 @@ import Box from "@mui/material/Box";
|
|||||||
import CheckboxIcon from "@icons/Checkbox";
|
import CheckboxIcon from "@icons/Checkbox";
|
||||||
import { Typography, useTheme } from "@mui/material";
|
import { Typography, useTheme } from "@mui/material";
|
||||||
import {
|
import {
|
||||||
|
getPipelines,
|
||||||
getSteps,
|
getSteps,
|
||||||
getTags,
|
getTags,
|
||||||
PaginationRequest,
|
PaginationRequest,
|
||||||
|
Pipeline,
|
||||||
Step,
|
Step,
|
||||||
Tag,
|
Tag,
|
||||||
} from "@api/integration";
|
} from "@api/integration";
|
||||||
@ -37,6 +39,7 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [tags, setTags] = useState<Tag[]>([]);
|
const [tags, setTags] = useState<Tag[]>([]);
|
||||||
const [steps, setSteps] = useState<Step[]>([]);
|
const [steps, setSteps] = useState<Step[]>([]);
|
||||||
|
const [pipelines, setPipelines] = useState<Pipeline[]>([]);
|
||||||
const [hasMoreItems, setHasMoreItems] = useState(true);
|
const [hasMoreItems, setHasMoreItems] = useState(true);
|
||||||
const boxRef = useRef(null);
|
const boxRef = useRef(null);
|
||||||
|
|
||||||
@ -90,6 +93,22 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (type === "typePipelines" && hasMoreItems) {
|
||||||
|
setIsLoading(true);
|
||||||
|
const pagination: PaginationRequest = {
|
||||||
|
page: page,
|
||||||
|
size: SIZE,
|
||||||
|
};
|
||||||
|
getPipelines(pagination).then(([response]) => {
|
||||||
|
if (response && response.items !== null) {
|
||||||
|
setPipelines((prevItems) => [...prevItems, ...response.items]);
|
||||||
|
if (response.items.length < SIZE) {
|
||||||
|
setHasMoreItems(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
}, [page, type, hasMoreItems, pipelineId]);
|
}, [page, type, hasMoreItems, pipelineId]);
|
||||||
|
|
||||||
const formControlLabels = useMemo(() => {
|
const formControlLabels = useMemo(() => {
|
||||||
@ -123,7 +142,6 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
|
|||||||
icon={<CheckboxIcon isRounded />}
|
icon={<CheckboxIcon isRounded />}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
// label={item.Name}
|
|
||||||
label={
|
label={
|
||||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||||
<Typography sx={{ color: `${item.Color}` }}>
|
<Typography sx={{ color: `${item.Color}` }}>
|
||||||
@ -171,6 +189,41 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
|
|||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
if (type === "typePipelines" && pipelines && pipelines.length !== 0) {
|
||||||
|
return pipelines.map((pipeline) => (
|
||||||
|
<FormControlLabel
|
||||||
|
key={pipeline.ID}
|
||||||
|
sx={{
|
||||||
|
color: "black",
|
||||||
|
padding: "15px",
|
||||||
|
borderBottom: `1px solid ${theme.palette.background.default}`,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
borderRadius: "12px",
|
||||||
|
margin: 0,
|
||||||
|
backgroundColor:
|
||||||
|
currentValue === pipeline.Name
|
||||||
|
? theme.palette.background.default
|
||||||
|
: theme.palette.common.white,
|
||||||
|
}}
|
||||||
|
value={pipeline.Name}
|
||||||
|
control={
|
||||||
|
<Radio
|
||||||
|
checkedIcon={
|
||||||
|
<CheckboxIcon
|
||||||
|
checked
|
||||||
|
isRounded
|
||||||
|
color={theme.palette.brightPurple.main}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
icon={<CheckboxIcon isRounded />}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={pipeline.Name}
|
||||||
|
labelPlacement={"start"}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -184,7 +237,7 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
|
|||||||
<Typography>Нет элементов</Typography>
|
<Typography>Нет элементов</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}, [tags, currentValue, type]);
|
}, [tags, steps, currentValue, type, pipelines]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@ -35,15 +35,22 @@ export const CustomSelect: FC<CustomSelectProps> = ({
|
|||||||
const [hasMoreItems, setHasMoreItems] = useState(true);
|
const [hasMoreItems, setHasMoreItems] = useState(true);
|
||||||
const SIZE = 25;
|
const SIZE = 25;
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
console.log("page", page);
|
const selectWidth = ref.current ? ref.current.offsetWidth : undefined;
|
||||||
|
|
||||||
|
const [savedValue, setSavedValue] = useState<number | null>(null);
|
||||||
|
|
||||||
const onSelectItem = useCallback(
|
const onSelectItem = useCallback(
|
||||||
(event: SelectChangeEvent<HTMLDivElement>) => {
|
(event: SelectChangeEvent<HTMLDivElement>) => {
|
||||||
const newValue = event.target.value.toString();
|
const newValue = event.target.value.toString();
|
||||||
|
const selectedUser = users.find((user) => user.Name === newValue);
|
||||||
|
if (selectedUser) {
|
||||||
|
//для сохранения ID выбранного пользователя в стейт или конфиг...
|
||||||
|
setSavedValue(selectedUser.ID);
|
||||||
|
}
|
||||||
setCurrentValue(newValue);
|
setCurrentValue(newValue);
|
||||||
setSelectedItem(newValue);
|
setSelectedItem(newValue);
|
||||||
},
|
},
|
||||||
[setSelectedItem, setCurrentValue],
|
[setSelectedItem, setCurrentValue, setSavedValue, users],
|
||||||
);
|
);
|
||||||
|
|
||||||
const toggleOpened = useCallback(() => {
|
const toggleOpened = useCallback(() => {
|
||||||
@ -86,9 +93,56 @@ export const CustomSelect: FC<CustomSelectProps> = ({
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
key={user.ID}
|
key={user.ID}
|
||||||
value={user.Name}
|
value={user.Name}
|
||||||
sx={{ padding: "12px", zIndex: 2 }}
|
sx={{
|
||||||
|
padding: "6px 0",
|
||||||
|
zIndex: 2,
|
||||||
|
borderTop: "1px solid rgba(154, 154, 175, 0.1)",
|
||||||
|
width: "auto",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{user.Name}
|
<Box
|
||||||
|
sx={{
|
||||||
|
fontSize: "16px",
|
||||||
|
color: "#4D4D4D",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: isMobile ? "column" : "row",
|
||||||
|
justifyContent: "space-evenly",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
width: "33%",
|
||||||
|
borderRight: isMobile
|
||||||
|
? "none"
|
||||||
|
: "1px solid rgba(154, 154, 175, 0.1)",
|
||||||
|
padding: isMobile ? "5px 0 5px 20px" : "10px 0 10px 20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{user.Name}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
width: "33%",
|
||||||
|
borderRight: isMobile
|
||||||
|
? "none"
|
||||||
|
: "1px solid rgba(154, 154, 175, 0.1)",
|
||||||
|
padding: isMobile ? "5px 0 5px 20px" : "10px 0 10px 20px",
|
||||||
|
color: isMobile ? "#9A9AAF" : "#4D4D4D",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{user.Email}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
width: "33%",
|
||||||
|
padding: isMobile ? "5px 0 5px 20px" : "10px 0 10px 20px",
|
||||||
|
color: isMobile ? "#9A9AAF" : "#4D4D4D",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{user.Role}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -162,10 +216,12 @@ export const CustomSelect: FC<CustomSelectProps> = ({
|
|||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
maxHeight: "300px",
|
maxHeight: "300px",
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
|
overflowX: "auto",
|
||||||
|
maxWidth: selectWidth,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
sx={{ width: "100%" }}
|
sx={{}}
|
||||||
onChange={onSelectItem}
|
onChange={onSelectItem}
|
||||||
onClick={toggleOpened}
|
onClick={toggleOpened}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -12,9 +12,8 @@ import { IntegrationStep1 } from "./IntegrationStep1/IntegrationStep1";
|
|||||||
import { IntegrationStep2 } from "./IntegrationStep2/IntegrationStep2";
|
import { IntegrationStep2 } from "./IntegrationStep2/IntegrationStep2";
|
||||||
import { IntegrationStep3 } from "./IntegrationStep3/IntegrationStep3";
|
import { IntegrationStep3 } from "./IntegrationStep3/IntegrationStep3";
|
||||||
import { IntegrationStep4 } from "./IntegrationStep4/IntegrationStep4";
|
import { IntegrationStep4 } from "./IntegrationStep4/IntegrationStep4";
|
||||||
import { IntegrationStep5 } from "./IntegrationStep5/IntegrationStep5";
|
|
||||||
import { IntegrationStep6 } from "./IntegrationStep6/IntegrationStep6";
|
import { IntegrationStep6 } from "./IntegrationStep6/IntegrationStep6";
|
||||||
import { funnelsMock, performersMock, stagesMock } from "../mocks/MockData";
|
import { performersMock } from "../mocks/MockData";
|
||||||
import { IntegrationsModalTitle } from "./IntegrationsModalTitle/IntegrationsModalTitle";
|
import { IntegrationsModalTitle } from "./IntegrationsModalTitle/IntegrationsModalTitle";
|
||||||
import { SettingsBlock } from "./SettingsBlock/SettingsBlock";
|
import { SettingsBlock } from "./SettingsBlock/SettingsBlock";
|
||||||
import { IntegrationStep7 } from "./IntegrationStep7/IntegrationStep7";
|
import { IntegrationStep7 } from "./IntegrationStep7/IntegrationStep7";
|
||||||
@ -42,18 +41,17 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
|
|||||||
|
|
||||||
const [step, setStep] = useState<number>(0);
|
const [step, setStep] = useState<number>(0);
|
||||||
const [isSettingsBlock, setIsSettingsBlock] = useState<boolean>(false);
|
const [isSettingsBlock, setIsSettingsBlock] = useState<boolean>(false);
|
||||||
const [selectedFunnelPerformer, setSelectedFunnelPerformer] = useState<
|
const [selectedPipelinePerformer, setSelectedPipelinePerformer] = useState<
|
||||||
string | null
|
string | null
|
||||||
>(null);
|
>(null);
|
||||||
const [selectedFunnel, setSelectedFunnel] = useState<string | null>(null);
|
const [selectedPipeline, setSelectedPipeline] = useState<string | null>(null);
|
||||||
const [selectedStagePerformer, setSelectedStagePerformer] = useState<
|
const [selectedStepsPerformer, setSelectedStepsPerformer] = useState<
|
||||||
string | null
|
string | null
|
||||||
>(null);
|
>(null);
|
||||||
const [selectedStage, setSelectedStage] = useState<string | null>(null);
|
const [selectedStep, setSelectedStep] = useState<string | null>(null);
|
||||||
const [selectedDealPerformer, setSelectedDealPerformer] = useState<
|
const [selectedDealPerformer, setSelectedDealPerformer] = useState<
|
||||||
string | null
|
string | null
|
||||||
>(null);
|
>(null);
|
||||||
const [utmFile, setUtmFile] = useState<File | null>(null);
|
|
||||||
const [questionEntity, setQuestionEntity] = useState<TQuestionEntity>({
|
const [questionEntity, setQuestionEntity] = useState<TQuestionEntity>({
|
||||||
contacts: [],
|
contacts: [],
|
||||||
company: [],
|
company: [],
|
||||||
@ -93,12 +91,10 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
|
|||||||
<IntegrationStep2
|
<IntegrationStep2
|
||||||
handlePrevStep={handlePrevStep}
|
handlePrevStep={handlePrevStep}
|
||||||
handleNextStep={handleNextStep}
|
handleNextStep={handleNextStep}
|
||||||
selectedFunnelPerformer={selectedFunnelPerformer}
|
selectedPipelinePerformer={selectedPipelinePerformer}
|
||||||
setSelectedFunnelPerformer={setSelectedFunnelPerformer}
|
setSelectedPipelinePerformer={setSelectedPipelinePerformer}
|
||||||
selectedFunnel={selectedFunnel}
|
selectedPipeline={selectedPipeline}
|
||||||
setSelectedFunnel={setSelectedFunnel}
|
setSelectedPipeline={setSelectedPipeline}
|
||||||
performers={performersMock}
|
|
||||||
funnels={funnelsMock}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -109,13 +105,11 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
|
|||||||
<IntegrationStep3
|
<IntegrationStep3
|
||||||
handlePrevStep={handlePrevStep}
|
handlePrevStep={handlePrevStep}
|
||||||
handleNextStep={handleNextStep}
|
handleNextStep={handleNextStep}
|
||||||
selectedStagePerformer={selectedStagePerformer}
|
selectedStepsPerformer={selectedStepsPerformer}
|
||||||
setSelectedStagePerformer={setSelectedStagePerformer}
|
setSelectedStepsPerformer={setSelectedStepsPerformer}
|
||||||
selectedStage={selectedStage}
|
selectedStep={selectedStep}
|
||||||
setSelectedStage={setSelectedStage}
|
setSelectedStep={setSelectedStep}
|
||||||
performers={performersMock}
|
pipelineId={selectedPipeline}
|
||||||
stages={stagesMock}
|
|
||||||
pipelineId={selectedFunnel}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -132,18 +126,6 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "Добавление utm-меток",
|
|
||||||
isSettingsAvailable: false,
|
|
||||||
component: (
|
|
||||||
<IntegrationStep5
|
|
||||||
handlePrevStep={handlePrevStep}
|
|
||||||
handleNextStep={handleNextStep}
|
|
||||||
utmFile={utmFile}
|
|
||||||
setUtmFile={setUtmFile}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "Соотнесение вопросов и сущностей",
|
title: "Соотнесение вопросов и сущностей",
|
||||||
isSettingsAvailable: true,
|
isSettingsAvailable: true,
|
||||||
@ -171,11 +153,11 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
questionEntity,
|
questionEntity,
|
||||||
utmFile,
|
// utmFile,
|
||||||
selectedFunnelPerformer,
|
selectedPipelinePerformer,
|
||||||
selectedFunnel,
|
selectedPipeline,
|
||||||
selectedStagePerformer,
|
selectedStepsPerformer,
|
||||||
selectedStage,
|
selectedStep,
|
||||||
selectedDealPerformer,
|
selectedDealPerformer,
|
||||||
tags,
|
tags,
|
||||||
],
|
],
|
||||||
@ -188,7 +170,7 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
|
|||||||
open={isModalOpen}
|
open={isModalOpen}
|
||||||
onClose={handleCloseModal}
|
onClose={handleCloseModal}
|
||||||
fullWidth
|
fullWidth
|
||||||
fullScreen={isMobile}
|
// fullScreen={isMobile}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
sx: {
|
sx: {
|
||||||
maxWidth: isTablet ? "100%" : "920px",
|
maxWidth: isTablet ? "100%" : "920px",
|
||||||
@ -253,11 +235,10 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
|
|||||||
setIsSettingsBlock={setIsSettingsBlock}
|
setIsSettingsBlock={setIsSettingsBlock}
|
||||||
setStep={setStep}
|
setStep={setStep}
|
||||||
selectedDealPerformer={selectedDealPerformer}
|
selectedDealPerformer={selectedDealPerformer}
|
||||||
selectedFunnelPerformer={selectedFunnelPerformer}
|
selectedFunnelPerformer={selectedPipelinePerformer}
|
||||||
selectedFunnel={selectedFunnel}
|
selectedFunnel={selectedPipeline}
|
||||||
selectedStagePerformer={selectedStagePerformer}
|
selectedStagePerformer={selectedStepsPerformer}
|
||||||
selectedStage={selectedStage}
|
selectedStage={selectedStep}
|
||||||
utmFile={utmFile}
|
|
||||||
questionEntity={questionEntity}
|
questionEntity={questionEntity}
|
||||||
tags={tags}
|
tags={tags}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -7,23 +7,19 @@ import { CustomRadioGroup } from "../../../../components/CustomRadioGroup/Custom
|
|||||||
type IntegrationStep2Props = {
|
type IntegrationStep2Props = {
|
||||||
handlePrevStep: () => void;
|
handlePrevStep: () => void;
|
||||||
handleNextStep: () => void;
|
handleNextStep: () => void;
|
||||||
selectedFunnelPerformer: string | null;
|
selectedPipelinePerformer: string | null;
|
||||||
setSelectedFunnelPerformer: (value: string | null) => void;
|
setSelectedPipelinePerformer: (value: string | null) => void;
|
||||||
selectedFunnel: string | null;
|
selectedPipeline: string | null;
|
||||||
setSelectedFunnel: (value: string | null) => void;
|
setSelectedPipeline: (value: string | null) => void;
|
||||||
performers: string[];
|
|
||||||
funnels: string[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IntegrationStep2: FC<IntegrationStep2Props> = ({
|
export const IntegrationStep2: FC<IntegrationStep2Props> = ({
|
||||||
handlePrevStep,
|
handlePrevStep,
|
||||||
handleNextStep,
|
handleNextStep,
|
||||||
selectedFunnelPerformer,
|
selectedPipelinePerformer,
|
||||||
setSelectedFunnelPerformer,
|
setSelectedPipelinePerformer,
|
||||||
selectedFunnel,
|
selectedPipeline,
|
||||||
setSelectedFunnel,
|
setSelectedPipeline,
|
||||||
performers,
|
|
||||||
funnels,
|
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||||
@ -40,8 +36,8 @@ export const IntegrationStep2: FC<IntegrationStep2Props> = ({
|
|||||||
>
|
>
|
||||||
<Box sx={{ width: "100%", marginTop: "20px", zIndex: 3 }}>
|
<Box sx={{ width: "100%", marginTop: "20px", zIndex: 3 }}>
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
selectedItem={selectedFunnelPerformer}
|
selectedItem={selectedPipelinePerformer}
|
||||||
setSelectedItem={setSelectedFunnelPerformer}
|
setSelectedItem={setSelectedPipelinePerformer}
|
||||||
type={"typeUsers"}
|
type={"typeUsers"}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@ -54,8 +50,9 @@ export const IntegrationStep2: FC<IntegrationStep2Props> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CustomRadioGroup
|
<CustomRadioGroup
|
||||||
selectedValue={selectedFunnel}
|
selectedValue={selectedPipeline}
|
||||||
setSelectedValue={setSelectedFunnel}
|
setSelectedValue={setSelectedPipeline}
|
||||||
|
type={"typePipelines"}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@ -7,22 +7,20 @@ import { CustomRadioGroup } from "../../../../components/CustomRadioGroup/Custom
|
|||||||
type IntegrationStep3Props = {
|
type IntegrationStep3Props = {
|
||||||
handlePrevStep: () => void;
|
handlePrevStep: () => void;
|
||||||
handleNextStep: () => void;
|
handleNextStep: () => void;
|
||||||
selectedStagePerformer: string | null;
|
selectedStepsPerformer: string | null;
|
||||||
setSelectedStagePerformer: (value: string | null) => void;
|
setSelectedStepsPerformer: (value: string | null) => void;
|
||||||
selectedStage: string | null;
|
selectedStep: string | null;
|
||||||
setSelectedStage: (value: string | null) => void;
|
setSelectedStep: (value: string | null) => void;
|
||||||
performers: string[];
|
|
||||||
stages: string[];
|
|
||||||
pipelineId: string | null;
|
pipelineId: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IntegrationStep3: FC<IntegrationStep3Props> = ({
|
export const IntegrationStep3: FC<IntegrationStep3Props> = ({
|
||||||
handlePrevStep,
|
handlePrevStep,
|
||||||
handleNextStep,
|
handleNextStep,
|
||||||
selectedStagePerformer,
|
selectedStepsPerformer,
|
||||||
setSelectedStagePerformer,
|
setSelectedStepsPerformer,
|
||||||
selectedStage,
|
selectedStep,
|
||||||
setSelectedStage,
|
setSelectedStep,
|
||||||
pipelineId,
|
pipelineId,
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -41,9 +39,9 @@ export const IntegrationStep3: FC<IntegrationStep3Props> = ({
|
|||||||
>
|
>
|
||||||
<Box sx={{ width: "100%", marginTop: "20px", zIndex: 3 }}>
|
<Box sx={{ width: "100%", marginTop: "20px", zIndex: 3 }}>
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
selectedItem={selectedStagePerformer}
|
selectedItem={selectedStepsPerformer}
|
||||||
type={"typeUsers"}
|
type={"typeUsers"}
|
||||||
setSelectedItem={setSelectedStagePerformer}
|
setSelectedItem={setSelectedStepsPerformer}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
@ -57,8 +55,8 @@ export const IntegrationStep3: FC<IntegrationStep3Props> = ({
|
|||||||
<CustomRadioGroup
|
<CustomRadioGroup
|
||||||
pipelineId={pipelineId}
|
pipelineId={pipelineId}
|
||||||
type={"typeSteps"}
|
type={"typeSteps"}
|
||||||
selectedValue={selectedStage}
|
selectedValue={selectedStep}
|
||||||
setSelectedValue={setSelectedStage}
|
setSelectedValue={setSelectedStep}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@ -1,94 +0,0 @@
|
|||||||
import {
|
|
||||||
Box,
|
|
||||||
ButtonBase,
|
|
||||||
Typography,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import UploadIcon from "@icons/UploadIcon";
|
|
||||||
import { type DragEvent, FC, useRef, useState } from "react";
|
|
||||||
|
|
||||||
type TextFormat = "txt" | "docx";
|
|
||||||
|
|
||||||
interface CustomFileUploaderProps {
|
|
||||||
description?: string;
|
|
||||||
accept?: TextFormat[];
|
|
||||||
handleImageChange: (file: File) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CustomFileUploader: FC<CustomFileUploaderProps> = ({
|
|
||||||
accept,
|
|
||||||
description,
|
|
||||||
handleImageChange,
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const dropZone = useRef<HTMLDivElement>(null);
|
|
||||||
const [ready, setReady] = useState(false);
|
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(700));
|
|
||||||
|
|
||||||
const handleDragEnter = (event: DragEvent<HTMLDivElement>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
setReady(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDrop = (event: DragEvent<HTMLDivElement>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
const file = event.dataTransfer.files[0];
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
handleImageChange(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
const acceptedFormats = accept
|
|
||||||
? accept.map((format) => "." + format).join(", ")
|
|
||||||
: "";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ButtonBase component="label" sx={{ justifyContent: "flex-start" }}>
|
|
||||||
<input
|
|
||||||
onChange={(event) => {
|
|
||||||
const file = event.target.files?.[0];
|
|
||||||
if (file) handleImageChange(file);
|
|
||||||
}}
|
|
||||||
hidden
|
|
||||||
accept={acceptedFormats || ".jpg, .jpeg, .png , .gif"}
|
|
||||||
multiple
|
|
||||||
type="file"
|
|
||||||
data-cy="upload-image-input"
|
|
||||||
/>
|
|
||||||
<Box
|
|
||||||
onDragOver={(event: DragEvent<HTMLDivElement>) =>
|
|
||||||
event.preventDefault()
|
|
||||||
}
|
|
||||||
onDrop={handleDrop}
|
|
||||||
ref={dropZone}
|
|
||||||
sx={{
|
|
||||||
width: isMobile ? "100%" : "580px",
|
|
||||||
padding: isMobile ? "33px" : "33px 10px 33px 55px",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
backgroundColor: theme.palette.background.default,
|
|
||||||
border: `1px solid ${ready ? "red" : theme.palette.grey2.main}`,
|
|
||||||
borderRadius: "8px",
|
|
||||||
gap: "55px",
|
|
||||||
flexDirection: isMobile ? "column" : "row",
|
|
||||||
}}
|
|
||||||
onDragEnter={handleDragEnter}
|
|
||||||
>
|
|
||||||
<UploadIcon />
|
|
||||||
<Box>
|
|
||||||
<Typography sx={{ color: "#9A9AAF", fontWeight: "bold" }}>
|
|
||||||
Добавить файл
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
sx={{ color: theme.palette.grey2.main, fontSize: "16px" }}
|
|
||||||
>
|
|
||||||
{description || "Принимает JPG, PNG, и GIF формат — максимум 5mb"}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</ButtonBase>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
import React, { FC } from "react";
|
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import { IconButton, Typography, useTheme } from "@mui/material";
|
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
|
||||||
|
|
||||||
type FileBlockProps = {
|
|
||||||
file: File | null;
|
|
||||||
setFile?: (file: File | null) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FileBlock: FC<FileBlockProps> = ({ setFile, file }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Box sx={{ display: "flex", gap: "15px", alignItems: "center" }}>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: "18px",
|
|
||||||
fontWeight: "400",
|
|
||||||
color: theme.palette.grey3.main,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Вы загрузили:
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
backgroundColor: theme.palette.brightPurple.main,
|
|
||||||
borderRadius: "8px",
|
|
||||||
padding: setFile ? "5px 5px 5px 14px" : "5px 14px",
|
|
||||||
gap: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
sx={{ color: "white", fontSize: "14px", fontWeight: "400" }}
|
|
||||||
>
|
|
||||||
{file?.name}
|
|
||||||
</Typography>
|
|
||||||
{setFile && (
|
|
||||||
<IconButton
|
|
||||||
onClick={() => setFile(null)}
|
|
||||||
sx={{
|
|
||||||
backgroundColor: "#864BD9",
|
|
||||||
borderRadius: "50%",
|
|
||||||
width: "24px",
|
|
||||||
height: "24px",
|
|
||||||
color: "white",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloseIcon
|
|
||||||
sx={{ width: "14px", height: "14px", transform: "scale(1.5)" }}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
import { Box, useMediaQuery, useTheme } from "@mui/material";
|
|
||||||
import React, { FC } from "react";
|
|
||||||
import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
|
|
||||||
import File from "@ui_kit/QuizPreview/QuizPreviewQuestionTypes/File";
|
|
||||||
import { FileBlock } from "./FileBlock/FileBlock";
|
|
||||||
import { CustomFileUploader } from "./CustomFileUploader/CustomFileUploader";
|
|
||||||
|
|
||||||
type IntegrationStep5Props = {
|
|
||||||
handlePrevStep: () => void;
|
|
||||||
handleNextStep: () => void;
|
|
||||||
setUtmFile: (file: File | null) => void;
|
|
||||||
utmFile: File | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const IntegrationStep5: FC<IntegrationStep5Props> = ({
|
|
||||||
handlePrevStep,
|
|
||||||
handleNextStep,
|
|
||||||
utmFile,
|
|
||||||
setUtmFile,
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
height: "100%",
|
|
||||||
flexGrow: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ alignSelf: "start", marginTop: "20px" }}>
|
|
||||||
{utmFile ? (
|
|
||||||
<FileBlock file={utmFile} setFile={setUtmFile} />
|
|
||||||
) : (
|
|
||||||
<CustomFileUploader
|
|
||||||
description={"Принимает .txt и .docx формат — максимум 100мб"}
|
|
||||||
accept={["txt", "docx"]}
|
|
||||||
handleImageChange={setUtmFile}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
<StepButtonsBlock
|
|
||||||
onLargeBtnClick={handleNextStep}
|
|
||||||
onSmallBtnClick={handlePrevStep}
|
|
||||||
isLargeBtnDisabled={!utmFile}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -4,7 +4,6 @@ import { Typography, useMediaQuery, useTheme } from "@mui/material";
|
|||||||
import { SettingItemHeader } from "./SettingItemHeader/SettingItemHeader";
|
import { SettingItemHeader } from "./SettingItemHeader/SettingItemHeader";
|
||||||
import { ResponsiblePerson } from "./ResponsiblePerson/ResponsiblePerson";
|
import { ResponsiblePerson } from "./ResponsiblePerson/ResponsiblePerson";
|
||||||
import { SelectedParameter } from "./SelectedParameter/SelectedParameter";
|
import { SelectedParameter } from "./SelectedParameter/SelectedParameter";
|
||||||
import { FileBlock } from "../../IntegrationStep5/FileBlock/FileBlock";
|
|
||||||
import { TQuestionEntity, TTags } from "../../AmoCRMModal";
|
import { TQuestionEntity, TTags } from "../../AmoCRMModal";
|
||||||
|
|
||||||
type SettingItemProps = {
|
type SettingItemProps = {
|
||||||
@ -17,7 +16,6 @@ type SettingItemProps = {
|
|||||||
selectedStagePerformer: string | null;
|
selectedStagePerformer: string | null;
|
||||||
selectedDealPerformer: string | null;
|
selectedDealPerformer: string | null;
|
||||||
selectedStage: string | null;
|
selectedStage: string | null;
|
||||||
utmFile: File | null;
|
|
||||||
questionEntity: TQuestionEntity;
|
questionEntity: TQuestionEntity;
|
||||||
tags: TTags;
|
tags: TTags;
|
||||||
};
|
};
|
||||||
@ -32,7 +30,6 @@ export const SettingItem: FC<SettingItemProps> = ({
|
|||||||
selectedStagePerformer,
|
selectedStagePerformer,
|
||||||
selectedDealPerformer,
|
selectedDealPerformer,
|
||||||
selectedStage,
|
selectedStage,
|
||||||
utmFile,
|
|
||||||
questionEntity,
|
questionEntity,
|
||||||
tags,
|
tags,
|
||||||
}) => {
|
}) => {
|
||||||
@ -68,17 +65,6 @@ export const SettingItem: FC<SettingItemProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (step === 4) {
|
if (step === 4) {
|
||||||
return (
|
|
||||||
<Box sx={{ display: "flex", gap: "15px", marginTop: "20px" }}>
|
|
||||||
{utmFile ? (
|
|
||||||
<FileBlock file={utmFile} />
|
|
||||||
) : (
|
|
||||||
<Typography>Файл не загружен</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (step === 5) {
|
|
||||||
const isFilled = Object.values(questionEntity).some(
|
const isFilled = Object.values(questionEntity).some(
|
||||||
(array) => array.length > 0,
|
(array) => array.length > 0,
|
||||||
);
|
);
|
||||||
@ -110,7 +96,7 @@ export const SettingItem: FC<SettingItemProps> = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (step === 6) {
|
if (step === 5) {
|
||||||
const isFilled = Object.values(tags).some((array) => array.length > 0);
|
const isFilled = Object.values(tags).some((array) => array.length > 0);
|
||||||
const status = isFilled ? "Заполнено" : "Не заполнено";
|
const status = isFilled ? "Заполнено" : "Не заполнено";
|
||||||
|
|
||||||
@ -148,7 +134,6 @@ export const SettingItem: FC<SettingItemProps> = ({
|
|||||||
selectedStagePerformer,
|
selectedStagePerformer,
|
||||||
selectedDealPerformer,
|
selectedDealPerformer,
|
||||||
selectedStage,
|
selectedStage,
|
||||||
utmFile,
|
|
||||||
questionEntity,
|
questionEntity,
|
||||||
tags,
|
tags,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -13,7 +13,6 @@ type SettingsBlockProps = {
|
|||||||
selectedStagePerformer: string | null;
|
selectedStagePerformer: string | null;
|
||||||
selectedStage: string | null;
|
selectedStage: string | null;
|
||||||
selectedDealPerformer: string | null;
|
selectedDealPerformer: string | null;
|
||||||
utmFile: File | null;
|
|
||||||
questionEntity: TQuestionEntity;
|
questionEntity: TQuestionEntity;
|
||||||
tags: TTags;
|
tags: TTags;
|
||||||
};
|
};
|
||||||
@ -27,7 +26,6 @@ export const SettingsBlock: FC<SettingsBlockProps> = ({
|
|||||||
selectedStagePerformer,
|
selectedStagePerformer,
|
||||||
selectedDealPerformer,
|
selectedDealPerformer,
|
||||||
selectedStage,
|
selectedStage,
|
||||||
utmFile,
|
|
||||||
questionEntity,
|
questionEntity,
|
||||||
tags,
|
tags,
|
||||||
}) => {
|
}) => {
|
||||||
@ -68,7 +66,6 @@ export const SettingsBlock: FC<SettingsBlockProps> = ({
|
|||||||
selectedStagePerformer={selectedStagePerformer}
|
selectedStagePerformer={selectedStagePerformer}
|
||||||
selectedDealPerformer={selectedDealPerformer}
|
selectedDealPerformer={selectedDealPerformer}
|
||||||
selectedStage={selectedStage}
|
selectedStage={selectedStage}
|
||||||
utmFile={utmFile}
|
|
||||||
questionEntity={questionEntity}
|
questionEntity={questionEntity}
|
||||||
tags={tags}
|
tags={tags}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -25,11 +25,11 @@ interface Props {
|
|||||||
companyName: keyof typeof QuizMetricType;
|
companyName: keyof typeof QuizMetricType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AnalyticsModal({
|
export const AnalyticsModal = ({
|
||||||
isModalOpen,
|
isModalOpen,
|
||||||
handleCloseModal,
|
handleCloseModal,
|
||||||
companyName,
|
companyName,
|
||||||
}: Props) {
|
}: Props) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const quiz = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||||
@ -260,4 +260,4 @@ export default function AnalyticsModal({
|
|||||||
</Box>
|
</Box>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,12 +1,23 @@
|
|||||||
import { Box, Typography, useTheme } from "@mui/material";
|
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import React, { FC } from "react";
|
import React, { FC, lazy, Suspense } from "react";
|
||||||
import { ServiceButton } from "./ServiceButton/ServiceButton";
|
import { ServiceButton } from "./ServiceButton/ServiceButton";
|
||||||
import { YandexMetricaLogo } from "../mocks/YandexMetricaLogo";
|
import { YandexMetricaLogo } from "../mocks/YandexMetricaLogo";
|
||||||
import AnalyticsModal from "./AnalyticsModal/AnalyticsModal";
|
// import AnalyticsModal from "./AnalyticsModal/AnalyticsModal";
|
||||||
import { VKPixelLogo } from "../mocks/VKPixelLogo";
|
import { VKPixelLogo } from "../mocks/VKPixelLogo";
|
||||||
import { QuizMetricType } from "@model/quizSettings";
|
import { QuizMetricType } from "@model/quizSettings";
|
||||||
import { AmoCRMLogo } from "../mocks/AmoCRMLogo";
|
import { AmoCRMLogo } from "../mocks/AmoCRMLogo";
|
||||||
import { AmoCRMModal } from "../IntegrationsModal/AmoCRMModal";
|
|
||||||
|
const AnalyticsModal = lazy(() =>
|
||||||
|
import("./AnalyticsModal/AnalyticsModal").then((module) => ({
|
||||||
|
default: module.AnalyticsModal,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
const AmoCRMModal = lazy(() =>
|
||||||
|
import("../IntegrationsModal/AmoCRMModal").then((module) => ({
|
||||||
|
default: module.AmoCRMModal,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
type PartnersBoardProps = {
|
type PartnersBoardProps = {
|
||||||
setIsModalOpen: (value: boolean) => void;
|
setIsModalOpen: (value: boolean) => void;
|
||||||
@ -30,6 +41,7 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
|
|||||||
handleCloseAmoSRMModal,
|
handleCloseAmoSRMModal,
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -99,18 +111,22 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
{companyName && (
|
{companyName && (
|
||||||
<AnalyticsModal
|
<Suspense>
|
||||||
isModalOpen={isModalOpen}
|
<AnalyticsModal
|
||||||
handleCloseModal={handleCloseModal}
|
isModalOpen={isModalOpen}
|
||||||
companyName={companyName}
|
handleCloseModal={handleCloseModal}
|
||||||
/>
|
companyName={companyName}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
)}
|
)}
|
||||||
{companyName && (
|
{companyName && (
|
||||||
<AmoCRMModal
|
<Suspense>
|
||||||
isModalOpen={isAmoCrmModalOpen}
|
<AmoCRMModal
|
||||||
handleCloseModal={handleCloseAmoSRMModal}
|
isModalOpen={isAmoCrmModalOpen}
|
||||||
companyName={companyName}
|
handleCloseModal={handleCloseAmoSRMModal}
|
||||||
/>
|
companyName={companyName}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import { DatePicker } from "@mui/x-date-pickers";
|
|||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import type { Moment } from "moment";
|
import type { Moment } from "moment";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
label?: string;
|
label?: string;
|
||||||
sx?: SxProps<Theme>;
|
sx?: SxProps<Theme>;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user