перевод имён вопросов в амо модалке

This commit is contained in:
Nastya 2024-08-05 19:07:25 +03:00
parent d717322f87
commit 4fa98f644f
30 changed files with 677 additions and 573 deletions

@ -1,5 +1,5 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
import { StepButtonsBlock } from "./StepButtonsBlock";
import { FC } from "react";
import { AccountResponse } from "@api/integration";
@ -8,7 +8,7 @@ type AmoAccountInfoProps = {
accountInfo: AccountResponse;
};
export const AmoAccountInfo: FC<AmoAccountInfoProps> = ({ handleNextStep, accountInfo }) => {
export const AccountInfo: FC<AmoAccountInfoProps> = ({ handleNextStep, accountInfo }) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));

@ -1,464 +0,0 @@
import React, { FC, useCallback, useEffect, useMemo, useState } from "react";
import { Dialog, IconButton, Typography, useMediaQuery, useTheme, Box, Skeleton } from "@mui/material";
import { useQuestions } from "@/stores/questions/hooks";
import { redirect } from "react-router-dom";
import { enqueueSnackbar } from "notistack";
import CloseIcon from "@mui/icons-material/Close";
import { AmoRemoveAccount } from "./AmoRemoveAccount/AmoRemoveAccount";
import { AmoDeleteTagQuestion } from "./AmoRemoveAccount/AmoDeleteTagQuestion";
import { AmoLogin } from "./AmoLogin/AmoLogin";
import { Pipelines } from "./Pipelines/Pipelines";
import { PipelineSteps } from "./PipelineSteps/PipelineSteps";
import { DealPerformers } from "./DealPerformers/DealPerformers";
import { AmoTags } from "./AmoTags/AmoTags";
import { AmoQuestions } from "./AmoQuestions/AmoQuestions";
import { AmoModalTitle } from "./AmoModalTitle/AmoModalTitle";
import { AmoSettingsBlock } from "./SettingsBlock/AmoSettingsBlock";
import { AmoAccountInfo } from "./AmoAccountInfo/AmoAccountInfo";
import { useAmoIntegration } from "./useAmoIntegration";
import { MinifiedData, QuestionKeys, TagKeys, TagQuestionHC } from "./types";
import { Quiz } from "@/model/quiz/quiz";
import { setIntegrationRules, updateIntegrationRules } from "@/api/integration";
type IntegrationsModalProps = {
isModalOpen: boolean;
handleCloseModal: () => void;
companyName: string | null;
quiz: Quiz;
};
const FCTranslate = {
"name": "имя",
"email": "почта",
"phone": "телефон",
"text": "номер",
"address": "адрес",
}
export const AmoCRMModal: FC<IntegrationsModalProps> = ({ isModalOpen, handleCloseModal, companyName, quiz }) => {
//Если нет контекста квиза, то и делать на этой страничке нечего
if (quiz?.backendId === undefined) {
redirect("/list");
return null;
}
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const { questions } = useQuestions();
const minifiedQuestions = useMemo(
() =>
questions
.filter((q) => q.type !== "result" && q.type !== null)
.map(({ backendId, title }) => ({
id: backendId.toString() as string,
title,
})),
[questions]
);
const FieldsAllowedFC = useMemo(
() => {
const list: MinifiedData[] = []
if (quiz.config.showfc) {
const fields = quiz.config.formContact.fields
for (let key in fields) {
if (fields[key].used) list.push({
id: key,
title: FCTranslate[key],
entity: "Contact",
})
}
}
return list;
},
[quiz]
);
const [step, setStep] = useState<number>(0);
const [isSettingsBlock, setIsSettingsBlock] = useState<boolean>(false);
const [isTryRemoveAccount, setIsTryRemoveAccount] = useState<boolean>(false);
const [openDelete, setOpenDelete] = useState<TagQuestionHC | null>(null);
const {
isLoadingPage,
firstRules,
accountInfo,
arrayOfPipelines,
arrayOfPipelinesSteps,
arrayOfUsers,
arrayOfTags,
arrayOfFields,
selectedPipeline,
setSelectedPipeline,
selectedCurrentFields,
selectedPipelineStep,
setSelectedPipelineStep,
selectedDealUser,
setSelectedDealPerformer,
questionsBackend,
selectedTags,
setSelectedTags,
selectedQuestions,
setSelectedQuestions,
setPageOfPipelines,
setPageOfPipelinesSteps,
setPageOfUsers,
setPageOfTags,
setSelectedCurrentFields,
} = useAmoIntegration({
quizID: quiz.backendId,
isModalOpen,
isTryRemoveAccount,
questions,
});
const handleAddTagQuestion = useCallback(
(scope: QuestionKeys | TagKeys, id: string, type: "question" | "tag") => {
if (!scope || !id) return;
if (type === "tag") {
setSelectedTags((prevState) => ({
...prevState,
[scope]: [...prevState[scope as TagKeys], id],
}));
}
if (type === "question") {
const q = questions.find(e => e.backendId === Number(id))
setSelectedQuestions((prevState) => ({
...prevState,
[scope]: [...prevState[scope as QuestionKeys], {
id,
title: q?.title || "вопрос",
entity: scope,
}],
}));
}
},
[setSelectedQuestions, setSelectedTags, questions]
);
const handleDeleteTagQuestion = useCallback(() => {
if (openDelete === null || !openDelete.scope || !openDelete.id || !openDelete.type) return;
if (openDelete.type === "tag") {
let newArray = selectedTags[openDelete.scope];
const index = newArray.indexOf(openDelete.id);
if (index !== -1) newArray.splice(index, 1);
setSelectedTags((prevState) => ({
...prevState,
[openDelete.scope]: newArray,
}));
}
if (openDelete.type === "question") {
let newArray = selectedQuestions
newArray[openDelete.scope as QuestionKeys] = newArray[openDelete.scope as QuestionKeys].filter(e => e.id !== openDelete.id)
// let index = -1
// selectedQuestions[openDelete.scope].forEach((e, i) => {
// if (e.subTitle === openDelete.id) index = i
// })
// if (index !== -1) newArray.splice(index, 1);
// setSelectedQuestions((prevState) => ({
// ...prevState,
// [openDelete.scope]: newArray,
// }));
setSelectedQuestions(newArray);
setSelectedCurrentFields(selectedCurrentFields.filter(e => e.id !== openDelete.id));
}
setOpenDelete(null);
}, [openDelete]);
const handleNextStep = () => {
setStep((prevState) => prevState + 1);
};
const handlePrevStep = () => {
setStep((prevState) => prevState - 1);
};
const handleSave = () => {
if (quiz?.backendId === undefined) return;
if (selectedPipeline === null) return enqueueSnackbar("Выберите воронку");
if (selectedPipeline === null) return enqueueSnackbar("Выберите этап воронки");
const body = {
PipelineID: Number(selectedPipeline),
StepID: Number(selectedPipelineStep),
PerformerID: Number(selectedDealUser),
// FieldsRule: questionsBackend,
TagsToAdd: selectedTags,
};
const FieldsRule = {
Company: { QuestionID: {} },
Lead: { QuestionID: {} },
Customer: { QuestionID: {} },
Contact: {
QuestionID: {},
ContactRuleMap: {
}
},
};
for (let key in FieldsRule) {
selectedQuestions[key as QuestionKeys].forEach((data) => {
FieldsRule[key as QuestionKeys].QuestionID[data.id] = 0;
});
}
selectedCurrentFields.forEach((data) => {
if (data.entity === "Contact") {
FieldsRule.Contact.ContactRuleMap[data.id] = Number(data.amoId)
} else {
FieldsRule[data.entity].QuestionID[data.id] = Number(data.amoId) || 0
}
})
for (let key in body.TagsToAdd) {
body.TagsToAdd[key as TagKeys] = body.TagsToAdd[key as TagKeys].map((id) => Number(id));
}
body.FieldsRule = FieldsRule;
if (firstRules) {
setIntegrationRules(quiz.backendId.toString(), body);
} else {
updateIntegrationRules(quiz.backendId.toString(), body);
}
handleCloseModal();
setStep(1);
};
const steps = useMemo(
() => [
{
title: accountInfo ? "Информация об аккаунте" : "Авторизация в аккаунте",
isSettingsAvailable: false,
component: accountInfo ? (
<AmoAccountInfo
handleNextStep={handleNextStep}
accountInfo={accountInfo}
/>
) : (
<AmoLogin handleNextStep={handleNextStep} />
),
},
{
title: "Выбор воронки",
desc: "На этом этапе вы можете выбрать нужную воронку и ответственного за сделку",
isSettingsAvailable: true,
component: (
<Pipelines
users={arrayOfUsers}
pipelines={arrayOfPipelines}
handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep}
selectedDealUser={selectedDealUser}
setSelectedDealPerformer={setSelectedDealPerformer}
selectedPipeline={selectedPipeline}
setSelectedPipeline={setSelectedPipeline}
/>
),
},
{
title: "Выбор этапа воронки",
desc: "На этом этапе вы можете выбрать нужный этап и ответственного за сделку",
isSettingsAvailable: true,
component: (
<PipelineSteps
users={arrayOfUsers}
selectedDealUser={selectedDealUser}
selectedStep={selectedPipelineStep}
steps={arrayOfPipelinesSteps}
setSelectedDealPerformer={setSelectedDealPerformer}
setSelectedStep={setSelectedPipelineStep}
handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep}
/>
),
},
{
title: "Сделка",
desc: "На этом этапе вы можете выбрать ответственного за сделку",
isSettingsAvailable: true,
component: (
<DealPerformers
handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep}
users={arrayOfUsers}
selectedDealUser={selectedDealUser}
setSelectedDealPerformer={setSelectedDealPerformer}
/>
),
},
{
title: "Добавление тегов",
isSettingsAvailable: true,
component: (
<AmoTags
tagsItems={arrayOfTags}
selectedTags={selectedTags}
openDelete={setOpenDelete}
handleScroll={() => { }}
handleAddTag={handleAddTagQuestion}
handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep}
/>
),
},
{
title: "Соотнесение вопросов и сущностей",
isSettingsAvailable: true,
component: (
<AmoQuestions
setSelectedCurrentFields={setSelectedCurrentFields}
fieldsItems={arrayOfFields}
selectedCurrentFields={selectedCurrentFields}
questionsItems={minifiedQuestions}
selectedQuestions={selectedQuestions}
openDelete={setOpenDelete}
handleAddQuestion={handleAddTagQuestion}
handlePrevStep={handlePrevStep}
handleNextStep={handleSave}
FieldsAllowedFC={FieldsAllowedFC}
/>
),
},
],
[
arrayOfPipelines,
arrayOfPipelinesSteps,
arrayOfUsers,
arrayOfTags,
arrayOfFields,
selectedPipeline,
selectedPipelineStep,
selectedDealUser,
selectedQuestions,
selectedTags,
arrayOfPipelines,
arrayOfPipelinesSteps,
arrayOfUsers,
minifiedQuestions,
selectedCurrentFields,
]
);
const stepTitles = steps.map((step) => step.title);
return (
<Dialog
open={isModalOpen}
onClose={handleCloseModal}
fullWidth
// fullScreen={isMobile}
PaperProps={{
sx: {
maxWidth: isTablet ? "100%" : "920px",
maxHeight: isTablet ? "100%" : "680px",
borderRadius: "12px",
},
}}
>
<Box
sx={{
width: "100%",
height: "68px",
backgroundColor: theme.palette.background.default,
}}
>
<Typography
sx={{
fontSize: isMobile ? "20px" : "24px",
fontWeight: "500",
padding: "20px",
color: theme.palette.grey2.main,
}}
>
Интеграция с {companyName ? companyName : "партнером"}
</Typography>
</Box>
<IconButton
onClick={handleCloseModal}
sx={{
width: "12px",
height: "12px",
position: "absolute",
right: "15px",
top: "15px",
}}
>
<CloseIcon sx={{ width: "12px", height: "12px", transform: "scale(1.5)" }} />
</IconButton>
<Box
sx={{
display: "flex",
flexDirection: "column",
width: isTablet ? "100%" : "920px",
height: "600px",
padding: "15px 20px 15px",
flexGrow: 1,
}}
>
{isLoadingPage ?
<Skeleton
sx={{
width: "100%",
height: "100%",
transform: "none",
}}
/> :
<>
<AmoModalTitle
step={step}
steps={steps}
isSettingsBlock={isSettingsBlock}
setIsSettingsBlock={setIsSettingsBlock}
setStep={setStep}
startRemoveAccount={() => setIsTryRemoveAccount(true)}
/>
{openDelete !== null ? (
<AmoDeleteTagQuestion
close={() => setOpenDelete(null)}
deleteItem={handleDeleteTagQuestion}
/>
) : (
<>
{isTryRemoveAccount && <AmoRemoveAccount handleCloseModal={handleCloseModal} stopThisPage={() => setIsTryRemoveAccount(false)} />}
{isSettingsBlock && (
<Box sx={{ flexGrow: 1, width: "100%" }}>
<AmoSettingsBlock
stepTitles={stepTitles}
setIsSettingsBlock={setIsSettingsBlock}
setStep={setStep}
selectedDealUser={arrayOfUsers.find((u) => u.id === selectedDealUser)?.title || "не указан"}
selectedFunnel={arrayOfPipelines.find((p) => p.id === selectedPipeline)?.title || "нет данных"}
selectedStage={
arrayOfPipelinesSteps.find((s) => s.id === selectedPipelineStep)?.title || "нет данных"
}
selectedQuestions={selectedQuestions}
selectedTags={selectedTags}
/>
</Box>
)}
{!isSettingsBlock && !isTryRemoveAccount && (
<Box sx={{ flexGrow: 1, width: "100%" }}>{steps[step].component}</Box>
)}
</>
)}
</>
}
</Box>
</Dialog>
);
};
export const diffArr = (arr_A: MinifiedData[], arr_B: MinifiedData[]) => {
return arr_A.filter(person_A => !arr_B.some(person_B => person_A.id === person_B.id));
}

@ -1,6 +1,6 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import { FC } from "react";
import { AmoButton } from "../../../../../components/AmoButton/AmoButton";
import { AmoButton } from "../../../../components/AmoButton/AmoButton";
import { connectAmo } from "@api/integration";
type IntegrationStep1Props = {

@ -1,8 +1,8 @@
import { Box, useMediaQuery, useTheme } from "@mui/material";
import { FC } from "react";
import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
import { CustomSelect } from "../../../../../components/CustomSelect/CustomSelect";
import { MinifiedData } from "../types";
import { StepButtonsBlock } from "./StepButtonsBlock";
import { CustomSelect } from "../../../../components/CustomSelect/CustomSelect";
import { MinifiedData } from "./types";
type Props = {
users: MinifiedData[];

@ -6,7 +6,7 @@ interface Props {
close: () => void;
}
export const AmoDeleteTagQuestion: FC<Props> = ({ close, deleteItem }) => {
export const DeleteTagQuestion: FC<Props> = ({ close, deleteItem }) => {
const theme = useTheme();
return (

@ -13,7 +13,7 @@ type AmoModalTitleProps = {
startRemoveAccount: () => void;
};
export const AmoModalTitle: FC<AmoModalTitleProps> = ({
export const ModalTitle: FC<AmoModalTitleProps> = ({
step,
steps,
setIsSettingsBlock,

@ -1,9 +1,9 @@
import { Box, useMediaQuery, useTheme } from "@mui/material";
import { FC } from "react";
import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
import { CustomSelect } from "../../../../../components/CustomSelect/CustomSelect";
import { CustomRadioGroup } from "../../../../../components/CustomRadioGroup/CustomRadioGroup";
import { MinifiedData } from "../types";
import { StepButtonsBlock } from "./StepButtonsBlock";
import { CustomSelect } from "../../../../components/CustomSelect/CustomSelect";
import { CustomRadioGroup } from "../../../../components/CustomRadioGroup/CustomRadioGroup";
import { MinifiedData } from "./types";
type Props = {
users: MinifiedData[];

@ -1,9 +1,9 @@
import { Box, useMediaQuery, useTheme } from "@mui/material";
import { FC } from "react";
import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
import { CustomSelect } from "../../../../../components/CustomSelect/CustomSelect";
import { CustomRadioGroup } from "../../../../../components/CustomRadioGroup/CustomRadioGroup";
import { MinifiedData } from "../types";
import { StepButtonsBlock } from "./StepButtonsBlock";
import { CustomSelect } from "../../../../components/CustomSelect/CustomSelect";
import { CustomRadioGroup } from "../../../../components/CustomRadioGroup/CustomRadioGroup";
import { MinifiedData } from "./types";
type Props = {
pipelines: MinifiedData[];

@ -4,7 +4,7 @@ import { ItemDetailsView } from "./ItemDetailsView/ItemDetailsView";
import { Box } from "@mui/material";
import { MinifiedData, QuestionKeys, SelectedQuestions, TagKeys, TagQuestionHC } from "../types";
import { EntitiesQuestions } from "./EntitiesQuestions";
import { diffArr } from "../AmoCRMModal";
import { diffArr } from "..";
import { DataConstrictor } from "../Components/DataConstrictor";
type Props = {

@ -2,7 +2,7 @@ import { CustomRadioGroup } from "@/components/CustomRadioGroup/CustomRadioGroup
import {Box, Typography, useMediaQuery, useTheme} from "@mui/material"
import { MinifiedData } from "../types";
import {CustomSelect} from "@/components/CustomSelect/CustomSelect";
import {CurrentFieldSelect} from "@/pages/IntegrationsPage/IntegrationsModal/Amo/AmoQuestions/CurrentFieldSelectMobile";
import {CurrentFieldSelect} from "@/pages/IntegrationsPage/IntegrationsModal/Amo/Questions/CurrentFieldSelectMobile";
interface Props {
items: MinifiedData[];

@ -1,11 +1,11 @@
import {Box, Button, useMediaQuery, useTheme} from "@mui/material"
import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock"
import { StepButtonsBlock } from "../StepButtonsBlock"
import { FC, useState } from "react";
import { MinifiedData, TagKeys } from "../types";
import { CurrentFields } from "./CurrentFields";
import { NewFields } from "./NewFields";
import { QuestionPair } from "./AmoQuestions";
import { diffArr } from "../AmoCRMModal";
import { diffArr } from "..";
type ItemsSelectionViewProps = {
items: MinifiedData[] | [];

@ -1,6 +1,6 @@
import { Box, useTheme } from "@mui/material";
import { ItemForQuestions } from "../ItemForQuestions";
import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock";
import { StepButtonsBlock } from "../../StepButtonsBlock";
import { FC } from "react";
import { MinifiedData, QuestionKeys, SelectedQuestions } from "../../types";

@ -1,6 +1,6 @@
import { Box } from "@mui/material";
import { CustomRadioGroup } from "../../../../../../components/CustomRadioGroup/CustomRadioGroup";
import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock";
import { StepButtonsBlock } from "../../StepButtonsBlock";
import { FC } from "react";
import { MinifiedData, TagKeys } from "../../types";

@ -10,7 +10,7 @@ interface Props {
}
export const AmoRemoveAccount: FC<Props> = ({
export const RemoveAccount: FC<Props> = ({
stopThisPage,
handleCloseModal,

@ -1,81 +0,0 @@
import { FC } from "react";
import { Box, useMediaQuery, useTheme } from "@mui/material";
import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
import { SettingItem } from "./SettingItem/SettingItem";
import { SelectedQuestions, SelectedTags } from "../types";
type AmoSettingsBlockProps = {
stepTitles: string[];
setStep: (value: number) => void;
setIsSettingsBlock: (value: boolean) => void;
selectedFunnel: string | null;
selectedStage: string | null;
selectedDealUser: string | null;
selectedQuestions: SelectedQuestions;
selectedTags: SelectedTags;
};
export const AmoSettingsBlock: FC<AmoSettingsBlockProps> = ({
stepTitles,
setStep,
setIsSettingsBlock,
selectedFunnel,
selectedDealUser,
selectedStage,
selectedQuestions,
selectedTags,
}) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
height: "100%",
flexGrow: 1,
}}
>
<Box
sx={{
marginTop: "10px",
width: "100%",
height: "443px",
borderRadius: "10px",
padding: " 0 20px",
boxShadow: "0 0 20px rgba(0, 0, 0, 0.15)",
overflowY: "auto",
flexGrow: 1,
}}
>
{stepTitles &&
stepTitles.map((title, index) => (
<SettingItem
step={index}
title={title}
setIsSettingsBlock={setIsSettingsBlock}
setStep={setStep}
selectedDealUser={selectedDealUser}
selectedFunnel={selectedFunnel}
selectedStage={selectedStage}
selectedQuestions={selectedQuestions}
selectedTags={selectedTags}
/>
))}
</Box>
<Box
sx={{
marginTop: "20px",
alignSelf: "end",
}}
>
<StepButtonsBlock
onSmallBtnClick={() => setIsSettingsBlock(false)}
isLargeBtnMissing={true}
/>
</Box>
</Box>
);
};

@ -0,0 +1,83 @@
import { FC } from "react";
import { Box, useMediaQuery, useTheme } from "@mui/material";
import { StepButtonsBlock } from "../StepButtonsBlock";
import { SettingItem } from "./SettingItem/SettingItem";
import { SelectedQuestions, SelectedTags } from "../types";
type AmoSettingsBlockProps = {
stepTitles: string[];
setStep: (value: number) => void;
setIsSettingsBlock: (value: boolean) => void;
selectedFunnel: string | null;
selectedStage: string | null;
selectedDealUser: string | null;
selectedQuestions: SelectedQuestions;
selectedTags: SelectedTags;
};
export const SettingsBlock: FC<AmoSettingsBlockProps> = ({
stepTitles,
setStep,
setIsSettingsBlock,
selectedFunnel,
selectedDealUser,
selectedStage,
selectedQuestions,
selectedTags,
}) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
return (
<Box sx={{ flexGrow: 1, width: "100%" }}>
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
height: "100%",
flexGrow: 1,
}}
>
<Box
sx={{
marginTop: "10px",
width: "100%",
height: "443px",
borderRadius: "10px",
padding: " 0 20px",
boxShadow: "0 0 20px rgba(0, 0, 0, 0.15)",
overflowY: "auto",
flexGrow: 1,
}}
>
{stepTitles &&
stepTitles.map((title, index) => (
<SettingItem
step={index}
title={title}
setIsSettingsBlock={setIsSettingsBlock}
setStep={setStep}
selectedDealUser={selectedDealUser}
selectedFunnel={selectedFunnel}
selectedStage={selectedStage}
selectedQuestions={selectedQuestions}
selectedTags={selectedTags}
/>
))}
</Box>
<Box
sx={{
marginTop: "20px",
alignSelf: "end",
}}
>
<StepButtonsBlock
onSmallBtnClick={() => setIsSettingsBlock(false)}
isLargeBtnMissing={true}
/>
</Box>
</Box>
</Box>
);
};

@ -0,0 +1,383 @@
import { useMemo, useState } from "react"
import { Dialog, IconButton, Typography, useMediaQuery, useTheme, Box, Skeleton } from "@mui/material";
import { useQuestions } from "@/stores/questions/hooks";
import { redirect } from "react-router-dom";
import { enqueueSnackbar } from "notistack";
import CloseIcon from "@mui/icons-material/Close";
import { RemoveAccount } from "./RemoveAccount";
import { DeleteTagQuestion } from "./DeleteTagQuestion";
import { AmoLogin } from "./AmoLogin";
import { Pipelines } from "./Pipelines";
import { PipelineSteps } from "./PipelineSteps";
import { DealPerformers } from "./DealPerformers";
import { AmoTags } from "./Tags/AmoTags";
import { AmoQuestions } from "./Questions/AmoQuestions";
import { ModalTitle } from "./ModalTitle";
import { SettingsBlock } from "./SettingsBlock/SettingsBlock";
import { AccountInfo } from "./AccountInfo";
import { useAmoIntegration } from "./useAmoIntegration";
import { MinifiedData, QuestionKeys, TagKeys, TagQuestionHC } from "./types";
import { Quiz } from "@/model/quiz/quiz";
import { AccountResponse, setIntegrationRules, updateIntegrationRules } from "@/api/integration";
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
import { UntypedQuizQuestion } from "@/model/questionTypes/shared";
const FCTranslate = {
"name": "имя",
"email": "почта",
"phone": "телефон",
"text": "номер",
"address": "адрес",
}
interface Props {
quiz: Quiz;
questions: (AnyTypedQuizQuestion | UntypedQuizQuestion)[];
firstRules: boolean;
accountInfo: AccountResponse | null;
arrayOfPipelines: MinifiedData[];
arrayOfPipelinesSteps: MinifiedData[];
arrayOfUsers: MinifiedData[];
arrayOfTags: MinifiedData[];
arrayOfFields: MinifiedData[];
selectedPipeline: string | null;
selectedCurrentFields: MinifiedData[];
selectedPipelineStep: string | null;
selectedDealUser: string | null;
setSelectedPipeline:any;
setSelectedPipelineStep:any;
setSelectedDealPerformer:any;
selectedTags:any;
setSelectedTags:any;
selectedQuestions:any;
setSelectedQuestions:any;
setPageOfPipelines:any;
setPageOfPipelinesSteps:any;
setPageOfUsers:any;
setPageOfTags:any;
setSelectedCurrentFields:any;
handleCloseModal:any;
}
export const SwitchPages = ({
quiz,
questions,
firstRules,
accountInfo,
arrayOfPipelines,
arrayOfPipelinesSteps,
arrayOfUsers,
arrayOfTags,
arrayOfFields,
selectedPipeline,
setSelectedPipeline,
selectedCurrentFields,
selectedPipelineStep,
setSelectedPipelineStep,
selectedDealUser,
setSelectedDealPerformer,
selectedTags,
setSelectedTags,
selectedQuestions,
setSelectedQuestions,
setPageOfPipelines,
setPageOfPipelinesSteps,
setPageOfUsers,
setPageOfTags,
setSelectedCurrentFields,
handleCloseModal,
}: Props) => {
const [step, setStep] = useState(0)
const [specialPage, setSpecialPage] = useState<"deleteCell" | "removeAccount" | "settingsBlock" | "accountInfo" | "amoLogin" | "">("")
const [openDelete, setOpenDelete] = useState<TagQuestionHC | null>(null);
const minifiedQuestions = useMemo(
() =>
questions
.filter((q) => q.type !== "result" && q.type !== null)
.map(({ backendId, title }) => ({
id: backendId.toString() as string,
title,
})),
[questions]
);
const FieldsAllowedFC = useMemo(
() => {
const list: MinifiedData[] = []
if (quiz.config.showfc) {
const fields = quiz.config.formContact.fields
for (let key in fields) {
if (fields[key].used) list.push({
id: key,
title: FCTranslate[key],
entity: "Contact",
})
}
}
return list;
},
[quiz]
);
const handleAddTagQuestion = (scope: QuestionKeys | TagKeys, id: string, type: "question" | "tag") => {
if (!scope || !id) return;
if (type === "tag") {
setSelectedTags((prevState) => ({
...prevState,
[scope]: [...prevState[scope as TagKeys], id],
}));
}
if (type === "question") {
const q = questions.find(e => e.backendId === Number(id))
setSelectedQuestions((prevState) => ({
...prevState,
[scope]: [...prevState[scope as QuestionKeys], {
id,
title: q?.title || "вопрос",
entity: scope,
}],
}));
}
}
const handleDeleteTagQuestion = () => {
if (openDelete === null || !openDelete.scope || !openDelete.id || !openDelete.type) return;
if (openDelete.type === "tag") {
let newArray = selectedTags[openDelete.scope];
const index = newArray.indexOf(openDelete.id);
if (index !== -1) newArray.splice(index, 1);
setSelectedTags((prevState) => ({
...prevState,
[openDelete.scope]: newArray,
}));
}
if (openDelete.type === "question") {
let newArray = selectedQuestions
newArray[openDelete.scope as QuestionKeys] = newArray[openDelete.scope as QuestionKeys].filter(e => e.id !== openDelete.id)
// let index = -1
// selectedQuestions[openDelete.scope].forEach((e, i) => {
// if (e.subTitle === openDelete.id) index = i
// })
// if (index !== -1) newArray.splice(index, 1);
// setSelectedQuestions((prevState) => ({
// ...prevState,
// [openDelete.scope]: newArray,
// }));
setSelectedQuestions(newArray);
setSelectedCurrentFields(selectedCurrentFields.filter(e => e.id !== openDelete.id));
}
setOpenDelete(null);
}
const handleNextStep = () => {
setStep((prevState) => prevState + 1);
};
const handlePrevStep = () => {
setStep((prevState) => prevState - 1);
};
const handleSave = () => {
if (quiz?.backendId === undefined) return;
if (selectedPipeline === null) return enqueueSnackbar("Выберите воронку");
if (selectedPipeline === null) return enqueueSnackbar("Выберите этап воронки");
const body = {
PipelineID: Number(selectedPipeline),
StepID: Number(selectedPipelineStep),
PerformerID: Number(selectedDealUser),
// FieldsRule: questionsBackend,
TagsToAdd: selectedTags,
};
const FieldsRule = {
Company: { QuestionID: {} },
Lead: { QuestionID: {} },
Customer: { QuestionID: {} },
Contact: {
QuestionID: {},
ContactRuleMap: {
}
},
};
for (let key in FieldsRule) {
selectedQuestions[key as QuestionKeys].forEach((data) => {
FieldsRule[key as QuestionKeys].QuestionID[data.id] = 0;
});
}
selectedCurrentFields.forEach((data) => {
if (data.entity === "Contact") {
FieldsRule.Contact.ContactRuleMap[data.id] = Number(data.amoId)
} else {
FieldsRule[data.entity].QuestionID[data.id] = Number(data.amoId) || 0
}
})
for (let key in body.TagsToAdd) {
body.TagsToAdd[key as TagKeys] = body.TagsToAdd[key as TagKeys].map((id) => Number(id));
}
body.FieldsRule = FieldsRule;
// if (firstRules) {
// setIntegrationRules(quiz.backendId.toString(), body);
// } else {
// updateIntegrationRules(quiz.backendId.toString(), body);
// }
handleCloseModal();
};
const closeSpecialPage = () => setSpecialPage("")
const steps = [
{
title: "Выбор воронки",
desc: "На этом этапе вы можете выбрать нужную воронку и ответственного за сделку",
isSettingsAvailable: true,
component: (
<Pipelines
users={arrayOfUsers}
pipelines={arrayOfPipelines}
handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep}
selectedDealUser={selectedDealUser}
setSelectedDealPerformer={setSelectedDealPerformer}
selectedPipeline={selectedPipeline}
setSelectedPipeline={setSelectedPipeline}
/>
),
},
{
title: "Выбор этапа воронки",
desc: "На этом этапе вы можете выбрать нужный этап и ответственного за сделку",
isSettingsAvailable: true,
component: (
<PipelineSteps
users={arrayOfUsers}
selectedDealUser={selectedDealUser}
selectedStep={selectedPipelineStep}
steps={arrayOfPipelinesSteps}
setSelectedDealPerformer={setSelectedDealPerformer}
setSelectedStep={setSelectedPipelineStep}
handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep}
/>
),
},
{
title: "Сделка",
desc: "На этом этапе вы можете выбрать ответственного за сделку",
isSettingsAvailable: true,
component: (
<DealPerformers
handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep}
users={arrayOfUsers}
selectedDealUser={selectedDealUser}
setSelectedDealPerformer={setSelectedDealPerformer}
/>
),
},
{
title: "Добавление тегов",
isSettingsAvailable: true,
component: (
<AmoTags
tagsItems={arrayOfTags}
selectedTags={selectedTags}
openDelete={setOpenDelete}
handleScroll={() => { }}
handleAddTag={handleAddTagQuestion}
handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep}
/>
),
},
{
title: "Соотнесение вопросов и сущностей",
isSettingsAvailable: true,
component: (
<AmoQuestions
setSelectedCurrentFields={setSelectedCurrentFields}
fieldsItems={arrayOfFields}
selectedCurrentFields={selectedCurrentFields}
questionsItems={minifiedQuestions}
selectedQuestions={selectedQuestions}
openDelete={setOpenDelete}
handleAddQuestion={handleAddTagQuestion}
handlePrevStep={handlePrevStep}
handleNextStep={handleSave}
FieldsAllowedFC={FieldsAllowedFC}
/>
),
},
]
const stepTitles = steps.map((step) => step.title);
switch (specialPage) {
case "deleteCell":
return <DeleteTagQuestion
close={closeSpecialPage}
deleteItem={handleDeleteTagQuestion}
/>
case "removeAccount":
return <RemoveAccount
handleCloseModal={closeSpecialPage}
stopThisPage={closeSpecialPage}
/>
case "settingsBlock":
return <SettingsBlock
stepTitles={stepTitles}
setIsSettingsBlock={setIsSettingsBlock}
setStep={setStep}
selectedDealUser={arrayOfUsers.find((u) => u.id === selectedDealUser)?.title || "не указан"}
selectedFunnel={arrayOfPipelines.find((p) => p.id === selectedPipeline)?.title || "нет данных"}
selectedStage={
arrayOfPipelinesSteps.find((s) => s.id === selectedPipelineStep)?.title || "нет данных"
}
selectedQuestions={selectedQuestions}
selectedTags={selectedTags}
/>
case "amoLogin": return <AmoLogin handleNextStep={handleNextStep} />
case "accountInfo": return <AmoAccountInfo
handleNextStep={handleNextStep}
accountInfo={accountInfo}
/>
default: <Box sx={{ flexGrow: 1, width: "100%" }}>{steps[step].component}</Box>
}
}
// const S = <ModalTitle
// step={1}
// steps={2}
// isSettingsBlock={true}
// setIsSettingsBlock={setIsSettingsBlock}
// setStep={setStep}
// startRemoveAccount={() => setSpecialPage("removeAccount")}
// />
// title: accountInfo ? "Информация об аккаунте" : "Авторизация в аккаунте",
// isSettingsAvailable: false,
// component: accountInfo ? (
// <AmoAccountInfo
// handleNextStep={handleNextStep}
// accountInfo={accountInfo}
// />
// ) : (
// <AmoLogin handleNextStep={handleNextStep} />
// ),
// },

@ -1,6 +1,6 @@
import { FC, useState } from "react";
import { Box } from "@mui/material";
import { ItemsSelectionView } from "../AmoQuestions/ItemsSelectionView/ItemsSelectionView";
import { ItemsSelectionView } from "../Questions/ItemsSelectionView/ItemsSelectionView";
import { TagsDetailsView } from "./TagsDetailsView/TagsDetailsView";
import { MinifiedData, QuestionKeys, SelectedTags, TagKeys, TagQuestionHC } from "../types";
import { DataConstrictor } from "../Components/DataConstrictor";

@ -1,7 +1,7 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock";
import { StepButtonsBlock } from "../../StepButtonsBlock";
import { FC } from "react";
import { Item } from "../../AmoQuestions/Item/Item";
import { Item } from "../../Questions/Item/Item";
import { MinifiedData, SelectedTags, TagKeys } from "../../types";
type TagsDetailsViewProps = {

@ -0,0 +1,166 @@
import { FC, useState } from "react";
import { Dialog, IconButton, Typography, useMediaQuery, useTheme, Box, Skeleton } from "@mui/material";
import { useQuestions } from "@/stores/questions/hooks";
import { redirect, useNavigate } from "react-router-dom";
import CloseIcon from "@mui/icons-material/Close";
import { useAmoIntegration } from "./useAmoIntegration";
import { MinifiedData } from "./types";
import { Quiz } from "@/model/quiz/quiz";
import { SwitchPages } from "./SwitchPages";
type IntegrationsModalProps = {
isModalOpen: boolean;
handleCloseModal: () => void;
companyName: string | null;
quiz: Quiz;
};
export const AmoCRMModal: FC<IntegrationsModalProps> = ({ isModalOpen, handleCloseModal, companyName, quiz }) => {
//Если нет контекста квиза, то и делать на этой страничке нечего
if (quiz.backendId === undefined) {
redirect("/list");
return null;
}
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const { questions } = useQuestions();
const [isTryRemoveAccount, setIsTryRemoveAccount] = useState<boolean>(false);
const {
isLoadingPage,
firstRules,
accountInfo,
arrayOfPipelines,
arrayOfPipelinesSteps,
arrayOfUsers,
arrayOfTags,
arrayOfFields,
selectedPipeline,
setSelectedPipeline,
selectedCurrentFields,
selectedPipelineStep,
setSelectedPipelineStep,
selectedDealUser,
setSelectedDealPerformer,
questionsBackend,
selectedTags,
setSelectedTags,
selectedQuestions,
setSelectedQuestions,
setPageOfPipelines,
setPageOfPipelinesSteps,
setPageOfUsers,
setPageOfTags,
setSelectedCurrentFields,
} = useAmoIntegration({
quizID: quiz.backendId,
isModalOpen,
isTryRemoveAccount,
questions,
});
return (
<Dialog
open={isModalOpen}
onClose={handleCloseModal}
fullWidth
// fullScreen={isMobile}
PaperProps={{
sx: {
maxWidth: isTablet ? "100%" : "920px",
maxHeight: isTablet ? "100%" : "680px",
borderRadius: "12px",
},
}}
>
<Box
sx={{
width: "100%",
height: "68px",
backgroundColor: theme.palette.background.default,
}}
>
<Typography
sx={{
fontSize: isMobile ? "20px" : "24px",
fontWeight: "500",
padding: "20px",
color: theme.palette.grey2.main,
}}
>
Интеграция с {companyName ? companyName : "партнером"}
</Typography>
</Box>
<IconButton
onClick={handleCloseModal}
sx={{
width: "12px",
height: "12px",
position: "absolute",
right: "15px",
top: "15px",
}}
>
<CloseIcon sx={{ width: "12px", height: "12px", transform: "scale(1.5)" }} />
</IconButton>
<Box
sx={{
display: "flex",
flexDirection: "column",
width: isTablet ? "100%" : "920px",
height: "600px",
padding: "15px 20px 15px",
flexGrow: 1,
}}
>
{isLoadingPage ?
<Skeleton
sx={{
width: "100%",
height: "100%",
transform: "none",
}}
/> :
<SwitchPages
quiz={quiz}
questions={questions}
firstRules={firstRules}
accountInfo={accountInfo}
arrayOfPipelines={arrayOfPipelines}
arrayOfPipelinesSteps={arrayOfPipelinesSteps}
arrayOfUsers={arrayOfUsers}
arrayOfTags={arrayOfTags}
arrayOfFields={arrayOfFields}
selectedPipeline={selectedPipeline}
setSelectedPipeline={setSelectedPipeline}
selectedCurrentFields={selectedCurrentFields}
selectedPipelineStep={selectedPipelineStep}
setSelectedPipelineStep={setSelectedPipelineStep}
selectedDealUser={selectedDealUser}
setSelectedDealPerformer={setSelectedDealPerformer}
selectedTags={selectedTags}
setSelectedTags={setSelectedTags}
selectedQuestions={selectedQuestions}
setSelectedQuestions={setSelectedQuestions}
setPageOfPipelines={setPageOfPipelines}
setPageOfPipelinesSteps={setPageOfPipelinesSteps}
setPageOfUsers={setPageOfUsers}
setPageOfTags={setPageOfTags}
setSelectedCurrentFields={setSelectedCurrentFields}
handleCloseModal={handleCloseModal}
/>
}
</Box>
</Dialog>
);
};
export const diffArr = (arr_A: MinifiedData[], arr_B: MinifiedData[]) => {
return arr_A.filter(person_A => !arr_B.some(person_B => person_A.id === person_B.id));
}

@ -13,6 +13,7 @@ import {
getFields,
} from "@/api/integration";
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
import { UntypedQuizQuestion } from "@/model/questionTypes/shared";
const SIZE = 275;
@ -20,7 +21,7 @@ interface Props {
isModalOpen: boolean;
isTryRemoveAccount: boolean;
quizID: number;
questions: AnyTypedQuizQuestion
questions: (AnyTypedQuizQuestion | UntypedQuizQuestion)[]
}
const FCTranslate = {
@ -151,6 +152,22 @@ export const useAmoIntegration = ({ isModalOpen, isTryRemoveAccount, quizID, que
}, [isModalOpen, isTryRemoveAccount]);
useEffect(() => {
const transletedQuestions = {}
Object.keys(selectedQuestions).forEach((column) => {
selectedQuestions[column].forEach((minifiedData) => {
const q = questions.find(e => e.backendId === Number(minifiedData.id)) || {}
transletedQuestions[column] = {
...minifiedData,
title: q.title || transletedQuestions[column].title
}
})
})
setSelectedQuestions(transletedQuestions)
}, [questions])
useEffect(() => {
getPipelines({
page: pageOfPipelines,

@ -16,7 +16,7 @@ const AnalyticsModal = lazy(() =>
);
const AmoCRMModal = lazy(() =>
import("../IntegrationsModal/Amo/AmoCRMModal").then((module) => ({
import("../IntegrationsModal/Amo").then((module) => ({
default: module.AmoCRMModal,
}))
);