This commit is contained in:
Nastya 2025-12-03 00:28:36 +03:00
parent e734e8b28b
commit 4997179b75
23 changed files with 740 additions and 381 deletions

@ -1,3 +1,4 @@
1.0.15 _ 2025-12-03 _ Merge branch 'pin' into staging
1.0.14 _ 2025-10-20 _ логика overtime для публички 1.0.14 _ 2025-10-20 _ логика overtime для публички
1.0.13 _ 2025-10-18 _ Визуал utm + логика 1.0.13 _ 2025-10-18 _ Визуал utm + логика
1.0.12 _ 2025-10-12 _ ютм с дизайном и беком, но без логики 1.0.12 _ 2025-10-12 _ ютм с дизайном и беком, но без логики

@ -16,13 +16,10 @@ const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz/bitrix`;
export type AccountResponse = { export type AccountResponse = {
id: number; id: number;
accountID: string; accountID: string;
amoID: number; bitrixID: number;
name: string;
deleted: boolean; deleted: boolean;
createdAt: string; createdAt: string;
subdomain: string; subdomain: string;
country: string;
driveURL: string;
stale: boolean; stale: boolean;
}; };
@ -56,10 +53,13 @@ export function useBitrixAccount() {
export const connectBitrix = async (): Promise<[string | null, string?]> => { export const connectBitrix = async (): Promise<[string | null, string?]> => {
try { try {
const response = await makeRequest<void, { link: string }>({ const response = await makeRequest<{client_bitrix_url: string}, { link: string }>({
method: "POST", method: "POST",
url: `${API_URL}/account`, url: `${API_URL}/account`,
useToken: true, useToken: true,
body: {
client_bitrix_url: 'penadigitaltech.bitrix24.ru'
},
withCredentials: true, withCredentials: true,
}); });
return [response.link]; return [response.link];
@ -125,16 +125,18 @@ export const getTags = async ({ page, size }: PaginationRequest): Promise<[TagsR
export type User = { export type User = {
id: number; id: number;
amoID: number; accountID: string;
bitrixUserID: string;
name: string; name: string;
lastName: string;
secondName: string;
title: string;
email: string; email: string;
role: number; uf_department: [ number ];
group: number;
deleted: boolean; deleted: boolean;
createdAt: string; createdAt: string;
amoUserID: number; workPosition: string;
}; };
export type UsersResponse = { export type UsersResponse = {
count: number; count: number;
items: User[]; items: User[];
@ -157,13 +159,16 @@ export const getUsers = async ({ page, size }: PaginationRequest): Promise<[User
export type Step = { export type Step = {
ID: number; ID: number;
AmoID: number; accountID: string;
PipelineID: number; bitrixID: string;
AccountID: number; entityID: string;
Name: string; statusID: string;
Color: string; name: string;
Deleted: boolean; nameInit: string;
CreatedAt: number; color: string;
pipelineID: number;
deleted: boolean;
createdAt: number;
}; };
export type StepsResponse = { export type StepsResponse = {
@ -192,12 +197,12 @@ export const getSteps = async ({
export type Pipeline = { export type Pipeline = {
ID: number; ID: number;
AmoID: number; bitrixID: number;
AccountID: number; accountID: number;
Name: string; name: string;
IsArchive: boolean; entityTypeId: boolean;
Deleted: boolean; deleted: boolean;
CreatedAt: number; createdAt: number;
}; };
export type PipelinesResponse = { export type PipelinesResponse = {
@ -310,15 +315,16 @@ export type CustomField = {
}; };
export type Field = { export type Field = {
ID: number; ID: number;
AmoID: number; accountID: string;
Code: string; bitrixID: string;
AccountID: number; entityID: string;
Name: string; fieldName: string;
Entity: string; editFromLabel: string;
Type: string; fieldType: string;
Deleted: boolean; deleted: boolean;
CreatedAt: number; createdAt: number;
}; };
export type CustomFieldsResponse = { export type CustomFieldsResponse = {
count: number; count: number;
items: CustomField[]; items: CustomField[];

@ -24,6 +24,8 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
console.log("CustomRadioGroup")
console.log(items)
const currentItem = const currentItem =
(selectedItemId !== null && selectedItemId.length > 0) ? (selectedItemId !== null && selectedItemId.length > 0) ?
items.find((item) => item.id === selectedItemId) || null items.find((item) => item.id === selectedItemId) || null

@ -26,6 +26,7 @@ export const CustomSelect: FC<CustomSelectProps> = ({ items, selectedItemId, set
const currentItem = useMemo(() => items.find((item) => item.id === selectedItemId) || null, [selectedItemId, items]); const currentItem = useMemo(() => items.find((item) => item.id === selectedItemId) || null, [selectedItemId, items]);
const menuItems = useMemo(() => { const menuItems = useMemo(() => {
if (items.length !== 0) { if (items.length !== 0) {
return items.map((item) => ( return items.map((item) => (

@ -113,21 +113,23 @@ export const useAmoIntegration = ({ isModalOpen, isTryRemoveAccount, quizID, que
) { ) {
const gottenList = settingsResponse.FieldsRule[key as QuestionKeys]; const gottenList = settingsResponse.FieldsRule[key as QuestionKeys];
if (gottenList !== null) { console.log("gottenList-----")
Object.keys(gottenList.QuestionID).forEach((qId) => { console.log(gottenList)
const q = questions.find(e => e.backendId === Number(qId)) || {} // if (gottenList !== null) {
// Object.keys(gottenList.QuestionID).forEach((qId) => {
// const q = questions.find(e => e.backendId === Number(qId)) || {}
if (gottenQuestions[key as QuestionKeys] === undefined) gottenQuestions[key as QuestionKeys] = [] // if (gottenQuestions[key as QuestionKeys] === undefined) gottenQuestions[key as QuestionKeys] = []
gottenQuestions[key as QuestionKeys].push({ // gottenQuestions[key as QuestionKeys].push({
id: qId, // id: qId,
title: q.title, // title: q.title,
entity: key, // entity: key,
}) // })
}) // })
} // }
if (key === "Contact") { if (key === "Contact") {
const MAP = settingsResponse.FieldsRule[key as QuestionKeys].ContactRuleMap const MAP = settingsResponse.FieldsRule[key as QuestionKeys].ContactRuleMap

@ -4,13 +4,13 @@ import { FC } from "react";
import { AccountResponse } from "@/api/bitrixIntegration"; import { AccountResponse } from "@/api/bitrixIntegration";
import AccountSetting from "@icons/AccountSetting"; import AccountSetting from "@icons/AccountSetting";
type AmoAccountInfoProps = { type BitrixAccountInfoProps = {
handleNextStep: () => void; handleNextStep: () => void;
accountInfo: AccountResponse | null; accountInfo: AccountResponse | null;
toChangeAccount: () => void; toChangeAccount: () => void;
}; };
export const AccountInfo: FC<AmoAccountInfoProps> = ({ handleNextStep, accountInfo, toChangeAccount }) => { export const AccountInfo: FC<BitrixAccountInfoProps> = ({ handleNextStep, accountInfo, toChangeAccount }) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
@ -119,11 +119,9 @@ export const AccountInfo: FC<AmoAccountInfoProps> = ({ handleNextStep, accountIn
> >
1 шаг 1 шаг
</Typography> </Typography>
{infoItem("Amo ID", accountInfo?.amoID)} {infoItem("Bitrix ID", accountInfo?.bitrixID)}
{infoItem("Имя аккаунта", accountInfo?.name)} {/* {infoItemLink("ЛК в bitrix", `https://${accountInfo?.subdomain}/dashboard/`)}
{infoItemLink("ЛК в amo", `https://${accountInfo?.subdomain}/dashboard/`)} {infoItemLink("Профиль пользователя в bitrix", `https://${accountInfo?.subdomain}/settings/users/`)} */}
{infoItemLink("Профиль пользователя в amo", `https://${accountInfo?.subdomain}/settings/users/`)}
{infoItem("Страна пользователя", accountInfo?.country)}
</Box> </Box>
<Box> <Box>

@ -22,7 +22,7 @@ type IntegrationStep1Props = {
// password: string().required("Поле обязательно").min(8, "Минимум 8 символов"), // password: string().required("Поле обязательно").min(8, "Минимум 8 символов"),
// }); // });
export const AmoLogin: FC<IntegrationStep1Props> = ({ handleNextStep }) => { export const BitrixLogin: FC<IntegrationStep1Props> = ({ handleNextStep }) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));

@ -1,5 +1,5 @@
import { connectBitrix } from "@/api/bitrixIntegration"; import { connectBitrix } from "@/api/bitrixIntegration";
import { setTryShowAmoTokenExpiredDialog } from "@/stores/uiTools/actions"; import { setTryShowBitrixTokenExpiredDialog } from "@/stores/uiTools/actions";
import { useUiTools } from "@/stores/uiTools/store"; import { useUiTools } from "@/stores/uiTools/store";
import CustomCheckbox from "@/ui_kit/CustomCheckbox"; import CustomCheckbox from "@/ui_kit/CustomCheckbox";
import { Box, Button, Dialog, Typography, useTheme } from "@mui/material"; import { Box, Button, Dialog, Typography, useTheme } from "@mui/material";
@ -9,17 +9,17 @@ import { useLocation } from "react-router-dom";
const HIDE_DIALOG_EXPIRATION_PERIOD = 24 * 60 * 60 * 1000; const HIDE_DIALOG_EXPIRATION_PERIOD = 24 * 60 * 60 * 1000;
interface Props { interface Props {
isAmoTokenExpired: boolean; isBitrixTokenExpired: boolean;
} }
export default function AmoTokenExpiredDialog({ isAmoTokenExpired }: Props) { export default function BitrixTokenExpiredDialog({ isBitrixTokenExpired }: Props) {
const theme = useTheme(); const theme = useTheme();
const tryShowAmoTokenExpiredDialog = useUiTools((state) => state.tryShowAmoTokenExpiredDialog); const tryShowBitrixTokenExpiredDialog = useUiTools((state) => state.tryShowBitrixTokenExpiredDialog);
const [isHideDialogForADayChecked, setIsHideDialogForADayChecked] = useState<boolean>(false); const [isHideDialogForADayChecked, setIsHideDialogForADayChecked] = useState<boolean>(false);
// const { hash, pathname, search } = useLocation(); // const { hash, pathname, search } = useLocation();
const location = useLocation(); const location = useLocation();
const onAmoClick = async () => { const onBitrixClick = async () => {
const [url, error] = await connectBitrix(); const [url, error] = await connectBitrix();
if (url && !error) { if (url && !error) {
window.open(url, "_blank"); window.open(url, "_blank");
@ -29,19 +29,19 @@ export default function AmoTokenExpiredDialog({ isAmoTokenExpired }: Props) {
function handleDialogClose() { function handleDialogClose() {
if (isHideDialogForADayChecked) { if (isHideDialogForADayChecked) {
const expirationDate = Date.now() + HIDE_DIALOG_EXPIRATION_PERIOD; const expirationDate = Date.now() + HIDE_DIALOG_EXPIRATION_PERIOD;
localStorage.setItem("hideAmoTokenExpiredDialogExpirationTime", expirationDate.toString()); localStorage.setItem("hideBitrixTokenExpiredDialogExpirationTime", expirationDate.toString());
} }
setTryShowAmoTokenExpiredDialog(false); setTryShowBitrixTokenExpiredDialog(false);
} }
useEffect(() => { useEffect(() => {
setTryShowAmoTokenExpiredDialog(true); setTryShowBitrixTokenExpiredDialog(true);
}, [location]); }, [location]);
return ( return (
<Dialog <Dialog
open={isAmoTokenExpired && tryShowAmoTokenExpiredDialog && location.pathname !== "/"} open={isBitrixTokenExpired && tryShowBitrixTokenExpiredDialog && location.pathname !== "/"}
onClose={handleDialogClose} onClose={handleDialogClose}
PaperProps={{ PaperProps={{
sx: { sx: {
@ -96,7 +96,7 @@ export default function AmoTokenExpiredDialog({ isAmoTokenExpired }: Props) {
</Button> </Button>
<Button <Button
variant="contained" variant="contained"
onClick={onAmoClick} onClick={onBitrixClick}
sx={{ sx={{
flex: "1 0 0", flex: "1 0 0",
}} }}

@ -17,7 +17,7 @@ type Props = {
title: string; title: string;
desc: string; desc: string;
toSettings: () => void; toSettings: () => void;
} };
onScrollUsers: () => void; onScrollUsers: () => void;
}; };
@ -36,39 +36,37 @@ export const DealPerformers: FC<Props> = ({
return ( return (
<> <>
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
height: "100%",
overflow: "auto",
flexGrow: 1,
}}
>
<Box sx={{ width: "100%", zIndex: 3 }}>
<ModalTitle
{...titleProps}
/>
<CustomSelect
items={users}
selectedItemId={selectedDealUser}
setSelectedItem={setSelectedDealPerformer}
handleScroll={onScrollUsers}
/>
</Box>
<Box <Box
sx={{ sx={{
marginTop: "auto", display: "flex",
alignSelf: "end", flexDirection: "column",
alignItems: "center",
height: "100%",
overflow: "auto",
flexGrow: 1,
}} }}
> >
<StepButtonsBlock <Box sx={{ width: "100%", zIndex: 3 }}>
onLargeBtnClick={handleNextStep} <ModalTitle {...titleProps} />
onSmallBtnClick={handlePrevStep} <CustomSelect
/> items={users}
selectedItemId={selectedDealUser}
setSelectedItem={setSelectedDealPerformer}
handleScroll={onScrollUsers}
/>
</Box>
<Box
sx={{
marginTop: "auto",
alignSelf: "end",
}}
>
<StepButtonsBlock
onLargeBtnClick={handleNextStep}
onSmallBtnClick={handlePrevStep}
/>
</Box>
</Box> </Box>
</Box>
</> </>
); );
}; };

@ -15,13 +15,14 @@ type Props = {
setSelectedDealPerformer: (value: string | null) => void; setSelectedDealPerformer: (value: string | null) => void;
selectedStep: string | null; selectedStep: string | null;
setSelectedStep: (value: string | null) => void; setSelectedStep: (value: string | null) => void;
leadFlag: boolean;
titleProps: { titleProps: {
step: number; step: number;
title: string; title: string;
desc: string; desc: string;
toSettings: () => void; toSettings: () => void;
} };
onScroll: () => void; onScroll: () => void;
onScrollUsers: () => void; onScrollUsers: () => void;
}; };
@ -36,17 +37,18 @@ export const PipelineSteps: FC<Props> = ({
setSelectedStep, setSelectedStep,
onScroll, onScroll,
onScrollUsers, onScrollUsers,
leadFlag,
handlePrevStep, handlePrevStep,
handleNextStep, handleNextStep,
titleProps titleProps,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isTablet = useMediaQuery(theme.breakpoints.down(1000));
console.log("leadFlag")
console.log(leadFlag)
return ( return (
<> <>
<Box <Box
@ -65,11 +67,10 @@ export const PipelineSteps: FC<Props> = ({
overflow: "auto", overflow: "auto",
zIndex: 3, zIndex: 3,
width: "100%", width: "100%",
}}> }}
>
<Box sx={{ width: "100%", zIndex: 3 }}> <Box sx={{ width: "100%", zIndex: 3 }}>
<ModalTitle <ModalTitle {...titleProps} />
{...titleProps}
/>
<CustomSelect <CustomSelect
items={users} items={users}
selectedItemId={selectedDealUser} selectedItemId={selectedDealUser}
@ -85,7 +86,7 @@ export const PipelineSteps: FC<Props> = ({
}} }}
> >
<CustomRadioGroup <CustomRadioGroup
items={steps} items={leadFlag ? steps : steps.filter(step => step.entity !== "STATUS")}
selectedItemId={selectedStep} selectedItemId={selectedStep}
setSelectedItem={setSelectedStep} setSelectedItem={setSelectedStep}
handleScroll={onScroll} handleScroll={onScroll}

@ -8,7 +8,7 @@ import { diffArr } from "..";
import { DataConstrictor } from "../Components/DataConstrictor"; import { DataConstrictor } from "../Components/DataConstrictor";
import { ModalTitle } from "../ModalTitle"; import { ModalTitle } from "../ModalTitle";
import { StepButtonsBlock } from "../StepButtonsBlock"; import { StepButtonsBlock } from "../StepButtonsBlock";
import { resetBitrixTagsFields } from "../useAmoIntegration"; import { resetBitrixTagsFields } from "../useBitrixIntegration";
type Props = { type Props = {
selectedCurrentFields: MinifiedData[] | []; selectedCurrentFields: MinifiedData[] | [];
@ -39,11 +39,11 @@ const FCTranslate = {
"text": "номер", "text": "номер",
"address": "адрес", "address": "адрес",
} }
export const AmoQuestions: FC<Props> = ({ export const BitrixQuestions: FC<Props> = ({
selectedCurrentFields, selectedCurrentFields,
questionsItems, questionsItems,
fieldsItems, fieldsItems,
selectedQuestions = [], selectedQuestions = {},
handleAddQuestion, handleAddQuestion,
handlePrevStep, handlePrevStep,
handleNextStep, handleNextStep,
@ -53,17 +53,54 @@ export const AmoQuestions: FC<Props> = ({
onScroll, onScroll,
titleProps, titleProps,
}) => { }) => {
if (!selectedQuestions.hasOwnProperty('Contact')) { console.log("---------------------------------------------------------------------")
selectedQuestions.Contact = [] console.log(
{
selectedCurrentFields,
questionsItems,
fieldsItems,
selectedQuestions,
handleAddQuestion,
handlePrevStep,
handleNextStep,
openDelete,
FieldsAllowedFC,
setSelectedCurrentFields,
onScroll,
titleProps,
}
)
// "lead": "Лид",
// "company": "Компания",
// "contact": "Контакт",
// "deal": "Сделка",
// "CRM_INVOICE": "Счёт (старый)",
// "CRM_SMART_INVOICE": "Cчёт (новый)",
// "CRM_QUOTE": "Предложение",
// "CRM_REQUISITE": "Реквизит"
if (!selectedQuestions.hasOwnProperty('lead')) {
selectedQuestions.lead = []
} }
if (!selectedQuestions.hasOwnProperty('Customer')) { if (!selectedQuestions.hasOwnProperty('company')) {
selectedQuestions.Customer = [] selectedQuestions.company = []
} }
if (!selectedQuestions.hasOwnProperty('Company')) { if (!selectedQuestions.hasOwnProperty('contact')) {
selectedQuestions.Company = [] selectedQuestions.contact = []
} }
if (!selectedQuestions.hasOwnProperty('Lead')) { if (!selectedQuestions.hasOwnProperty('deal')) {
selectedQuestions.Lead = [] selectedQuestions.deal = []
}
if (!selectedQuestions.hasOwnProperty('CRM_INVOICE')) {
selectedQuestions.CRM_INVOICE = []
}
if (!selectedQuestions.hasOwnProperty('CRM_SMART_INVOICE')) {
selectedQuestions.CRM_SMART_INVOICE = []
}
if (!selectedQuestions.hasOwnProperty('CRM_QUOTE')) {
selectedQuestions.CRM_QUOTE = []
}
if (!selectedQuestions.hasOwnProperty('CRM_REQUISITE')) {
selectedQuestions.CRM_REQUISITE = []
} }
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
@ -95,7 +132,7 @@ export const AmoQuestions: FC<Props> = ({
id: selectedQuestion, id: selectedQuestion,
title: questionsItems.find(e => e.id === selectedQuestion)?.title || FCTranslate[selectedQuestion], title: questionsItems.find(e => e.id === selectedQuestion)?.title || FCTranslate[selectedQuestion],
entity: activeScope, entity: activeScope,
amoId: selectedField, bitrixId: selectedField,
}) })
setSelectedCurrentFields(newArray); setSelectedCurrentFields(newArray);
}; };
@ -117,10 +154,14 @@ export const AmoQuestions: FC<Props> = ({
}; };
const SCFworld = (() => { const SCFworld = (() => {
const obj = { const obj = {
Lead: [], lead: [],
Company: [], company: [],
Customer: [], contact: [],
Contact: [] deal: [],
// CRM_INVOICE: [],
// CRM_SMART_INVOICE: [],
// CRM_QUOTE: [],
// CRM_REQUISITE: [],
} }
selectedCurrentFields.forEach((e) => { selectedCurrentFields.forEach((e) => {
if (!obj[e.entity]?.includes(e.id)) { if (!obj[e.entity]?.includes(e.id)) {
@ -216,10 +257,14 @@ export const AmoQuestions: FC<Props> = ({
items={[...questionsItems, ...FieldsAllowedFC]} items={[...questionsItems, ...FieldsAllowedFC]}
setActiveScope={setActiveScope} setActiveScope={setActiveScope}
selectedQuestions={{ selectedQuestions={{
Lead: [...selectedQuestions.Lead, ...SCFworld.Lead], lead: [...selectedQuestions.lead, ...SCFworld.lead],
Company: [...selectedQuestions.Company, ...SCFworld.Company], company: [...selectedQuestions.company, ...SCFworld.company],
Customer: [...selectedQuestions.Customer, ...SCFworld.Customer], contact: [...selectedQuestions.contact, ...SCFworld.contact],
Contact: [...selectedQuestions.Contact, ...SCFworld.Contact] deal: [...selectedQuestions.deal, ...SCFworld.deal],
// CRM_INVOICE: [...selectedQuestions.CRM_INVOICE, ...SCFworld.CRM_INVOICE],
// CRM_SMART_INVOICE: [...selectedQuestions.CRM_SMART_INVOICE, ...SCFworld.CRM_SMART_INVOICE],
// CRM_QUOTE: [...selectedQuestions.CRM_QUOTE, ...SCFworld.CRM_QUOTE],
// CRM_REQUISITE: [...selectedQuestions.CRM_REQUISITE, ...SCFworld.CRM_REQUISITE]
}} }}
setIsSelection={setIsSelection} setIsSelection={setIsSelection}
deleteHC={handleDelete} deleteHC={handleDelete}

@ -4,7 +4,7 @@ import { FC, useState } from "react";
import { MinifiedData, TagKeys } from "../types"; import { MinifiedData, TagKeys } from "../types";
import { CurrentFields } from "./CurrentFields"; import { CurrentFields } from "./CurrentFields";
import { NewFields } from "./NewFields"; import { NewFields } from "./NewFields";
import { QuestionPair } from "./AmoQuestions"; import { QuestionPair } from "./BitrixQuestions";
import { diffArr } from ".."; import { diffArr } from "..";
type ItemsSelectionViewProps = { type ItemsSelectionViewProps = {

@ -15,12 +15,20 @@ export const ItemForQuestions: FC<ItemProps> = ({ items, title, onAddBtnClick, d
const theme = useTheme(); const theme = useTheme();
const titleDictionary = { const titleDictionary = {
Company: "Компания", "lead": "Лид",
Lead: "Сделка", "company": "Компания",
Contact: "Контакты", "contact": "Контакт",
Customer: "Покупатели", "deal": "Сделка",
// "CRM_INVOICE": "Счёт (старый)",
// "CRM_SMART_INVOICE": "Счёт (новый)",
// "CRM_QUOTE": "Предложение",
// "CRM_REQUISITE": "Реквизит"
// Company: "Компания",
// Lead: "Сделка",
// Contact: "Контакты",
// Customer: "Покупатели",
}; };
console.log("title: " + title)
const translatedTitle = titleDictionary[title]; const translatedTitle = titleDictionary[title];
const selectedOptions = data[title]; const selectedOptions = data[title];
return ( return (

@ -16,7 +16,7 @@ type SettingItemProps = {
selectedDealUser: string | null; selectedDealUser: string | null;
selectedStage: string | null; selectedStage: string | null;
selectedQuestions: SelectedQuestions; selectedQuestions: SelectedQuestions;
selectedTags: SelectedTags; leadFlag: boolean
}; };
export const SettingItem: FC<SettingItemProps> = ({ export const SettingItem: FC<SettingItemProps> = ({
@ -29,7 +29,7 @@ export const SettingItem: FC<SettingItemProps> = ({
selectedDealUser, selectedDealUser,
selectedStage, selectedStage,
selectedQuestions, selectedQuestions,
selectedTags, leadFlag
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
@ -42,31 +42,7 @@ export const SettingItem: FC<SettingItemProps> = ({
return ( return (
<> <>
<ResponsiblePerson performer={selectedDealUser} /> <ResponsiblePerson performer={selectedDealUser} />
<SelectedParameter parameter={selectedFunnel} /> <Box>
</>
);
}
if (step === 2) {
return (
<>
<ResponsiblePerson performer={selectedDealUser} />
<SelectedParameter parameter={selectedStage} />
</>
);
}
if (step === 3) {
return (
<>
<ResponsiblePerson performer={selectedDealUser} />
</>
);
}
if (step === 4) {
const isFilled = Object.values(selectedTags).some((array) => array.length > 0);
const status = isFilled ? "Заполнено" : "Не заполнено";
return (
<>
<Typography <Typography
sx={{ sx={{
color: theme.palette.grey2.main, color: theme.palette.grey2.main,
@ -76,7 +52,7 @@ export const SettingItem: FC<SettingItemProps> = ({
}} }}
display={"inline-block"} display={"inline-block"}
> >
Статус: Выбранный этап сделки
</Typography> </Typography>
<Typography <Typography
sx={{ sx={{
@ -86,8 +62,33 @@ export const SettingItem: FC<SettingItemProps> = ({
}} }}
display={"inline"} display={"inline"}
> >
{status} {leadFlag ? "Лид сделки" : "Дил сделки"}
</Typography> </Typography>
</Box>
</>
);
}
if (step === 2) {
return (
<>
<ResponsiblePerson performer={selectedDealUser} />
<SelectedParameter parameter={selectedFunnel} />
</>
);
}
if (step === 3) {
return (
<>
<ResponsiblePerson performer={selectedDealUser} />
<SelectedParameter parameter={selectedStage} />
</>
);
}
if (step === 4) {
return (
<>
<ResponsiblePerson performer={selectedDealUser} />
</> </>
); );
} }
@ -130,7 +131,6 @@ export const SettingItem: FC<SettingItemProps> = ({
selectedDealUser, selectedDealUser,
selectedStage, selectedStage,
selectedQuestions, selectedQuestions,
selectedTags,
]); ]);
return ( return (

@ -10,9 +10,9 @@ type AmoSettingsBlockProps = {
selectedStage: string | null; selectedStage: string | null;
selectedDealUser: string | null; selectedDealUser: string | null;
selectedQuestions: SelectedQuestions; selectedQuestions: SelectedQuestions;
selectedTags: SelectedTags;
toBack: () => void toBack: () => void
setStep: (step: number) => void setStep: (step: number) => void
leadFlag: boolean
}; };
export const SettingsBlock: FC<AmoSettingsBlockProps> = ({ export const SettingsBlock: FC<AmoSettingsBlockProps> = ({
@ -21,9 +21,10 @@ export const SettingsBlock: FC<AmoSettingsBlockProps> = ({
selectedDealUser, selectedDealUser,
selectedStage, selectedStage,
selectedQuestions, selectedQuestions,
selectedTags, // selectedTags,
toBack, toBack,
setStep, setStep,
leadFlag,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
@ -64,13 +65,13 @@ export const SettingsBlock: FC<AmoSettingsBlockProps> = ({
{stepTitles && {stepTitles &&
stepTitles.map((title, index) => ( stepTitles.map((title, index) => (
<SettingItem <SettingItem
leadFlag={leadFlag}
step={index+1} step={index+1}
title={title} title={title}
selectedDealUser={selectedDealUser} selectedDealUser={selectedDealUser}
selectedFunnel={selectedFunnel} selectedFunnel={selectedFunnel}
selectedStage={selectedStage} selectedStage={selectedStage}
selectedQuestions={selectedQuestions} selectedQuestions={selectedQuestions}
selectedTags={selectedTags}
setStep={setStep} setStep={setStep}
/> />

@ -1,4 +1,4 @@
import { useMemo, useState } from "react" import { useMemo, useState } from "react";
import { Dialog, IconButton, Typography, useMediaQuery, useTheme, Box, Skeleton } from "@mui/material"; import { Dialog, IconButton, Typography, useMediaQuery, useTheme, Box, Skeleton } from "@mui/material";
import { useQuestions } from "@/stores/questions/hooks"; import { useQuestions } from "@/stores/questions/hooks";
import { redirect } from "react-router-dom"; import { redirect } from "react-router-dom";
@ -8,28 +8,28 @@ import CloseIcon from "@mui/icons-material/Close";
import { RemoveAccount } from "./RemoveAccount"; import { RemoveAccount } from "./RemoveAccount";
import { DeleteTagQuestion } from "./DeleteTagQuestion"; import { DeleteTagQuestion } from "./DeleteTagQuestion";
import { AmoLogin } from "./AmoLogin"; import { BitrixLogin } from "./BitrixLogin";
import { Pipelines } from "./Pipelines"; import { Pipelines } from "./Pipelines";
import { PipelineSteps } from "./PipelineSteps"; import { PipelineSteps } from "./PipelineSteps";
import { DealPerformers } from "./DealPerformers"; import { DealPerformers } from "./DealPerformers";
import { AmoTags } from "./Tags/AmoTags"; import { СhoosePerson } from "./СhoosePerson";
import { AmoQuestions } from "./Questions/AmoQuestions"; import { BitrixQuestions } from "./Questions/BitrixQuestions";
import { ModalTitle } from "./ModalTitle"; import { ModalTitle } from "./ModalTitle";
import { SettingsBlock } from "./SettingsBlock/SettingsBlock"; import { SettingsBlock } from "./SettingsBlock/SettingsBlock";
import { AccountInfo } from "./AccountInfo"; import { AccountInfo } from "./AccountInfo";
import { MinifiedData, QuestionKeys, TagKeys, TagQuestionHC } from "./types"; import { MinifiedData, QuestionKeys, TagKeys, TagQuestionHC } from "./types";
import { Quiz } from "@/model/quiz/quiz"; import { Quiz } from "@/model/quiz/quiz";
import { AccountResponse, setIntegrationRules, updateIntegrationRules } from "@/api/integration"; import { AccountResponse, setIntegrationRules, updateIntegrationRules } from "@/api/bitrixIntegration";
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer"; import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
import { UntypedQuizQuestion } from "@/model/questionTypes/shared"; import { UntypedQuizQuestion } from "@/model/questionTypes/shared";
const FCTranslate = { const FCTranslate = {
"name": "имя", name: "имя",
"email": "почта", email: "почта",
"phone": "телефон", phone: "телефон",
"text": "номер", text: "номер",
"address": "адрес", address: "адрес",
} };
interface Props { interface Props {
quiz: Quiz; quiz: Quiz;
@ -59,6 +59,8 @@ interface Props {
setPageOfFields: () => void; setPageOfFields: () => void;
setSelectedCurrentFields: any; setSelectedCurrentFields: any;
handleCloseModal: any; handleCloseModal: any;
leadFlag: boolean;
leadFlagHC: (s: boolean) => void;
} }
export const SwitchPages = ({ export const SwitchPages = ({
@ -89,16 +91,20 @@ export const SwitchPages = ({
setPageOfFields, setPageOfFields,
setSelectedCurrentFields, setSelectedCurrentFields,
handleCloseModal, handleCloseModal,
leadFlag,
leadFlagHC,
fullArrayOfPipelinesSteps,
}: Props) => { }: Props) => {
const [step, setStep] = useState(0) const [step, setStep] = useState(0);
const [specialPage, setSpecialPage] = useState<"deleteCell" | "removeAccount" | "settingsBlock" | "accountInfo" | "amoLogin" | "">(accountInfo ? "accountInfo" : "amoLogin") const [specialPage, setSpecialPage] = useState<
"deleteCell" | "removeAccount" | "settingsBlock" | "accountInfo" | "bitrixLogin" | ""
>(accountInfo ? "accountInfo" : "bitrixLogin");
const [openDelete, setOpenDelete] = useState<TagQuestionHC | null>(null); const [openDelete, setOpenDelete] = useState<TagQuestionHC | null>(null);
const startDeleteTagQuestion = (itemForDelete) => { const startDeleteTagQuestion = (itemForDelete) => {
setOpenDelete(itemForDelete) setOpenDelete(itemForDelete);
setSpecialPage("deleteCell") setSpecialPage("deleteCell");
} };
const minifiedQuestions = useMemo( const minifiedQuestions = useMemo(
() => () =>
@ -110,47 +116,50 @@ export const SwitchPages = ({
})), })),
[questions] [questions]
); );
const FieldsAllowedFC = useMemo( const FieldsAllowedFC = useMemo(() => {
() => { const list: MinifiedData[] = [];
const list: MinifiedData[] = [] if (quiz.config.showfc) {
if (quiz.config.showfc) { const fields = quiz.config.formContact.fields;
const fields = quiz.config.formContact.fields for (let key in fields) {
for (let key in fields) { if (fields[key].used)
if (fields[key].used) list.push({ list.push({
id: key, id: key,
title: FCTranslate[key], title: FCTranslate[key],
entity: "Contact", entity: "Contact",
}) });
}
} }
return list; }
}, return list;
[quiz] }, [quiz]);
);
const handleAddTagQuestion = (scope: QuestionKeys | TagKeys, id: string, type: "question" | "tag") => { const handleAddTagQuestion = (scope: QuestionKeys | TagKeys, id: string, type: "question" | "tag") => {
if (!scope || !id) return; if (!scope || !id) return;
if (type === "tag") { if (type === "tag") {
setSelectedTags((prevState) => { setSelectedTags((prevState) => {
return({ return {
...prevState, ...prevState,
[scope]: [...prevState[scope as TagKeys], id], [scope]: [...prevState[scope as TagKeys], id],
})}); };
});
} }
if (type === "question") { if (type === "question") {
const q = questions.find(e => e.backendId === Number(id)) const q = questions.find((e) => e.backendId === Number(id));
setSelectedQuestions((prevState) => { setSelectedQuestions((prevState) => {
return ({ return {
...prevState, ...prevState,
[scope]: [...prevState[scope as QuestionKeys], { [scope]: [
id, ...prevState[scope as QuestionKeys],
title: q?.title || "вопрос", {
entity: scope, id,
}], title: q?.title || "вопрос",
})}); entity: scope,
},
],
};
});
} }
} };
const handleDeleteTagQuestion = () => { const handleDeleteTagQuestion = () => {
if (openDelete === null || !openDelete.scope || !openDelete.id || !openDelete.type) return; if (openDelete === null || !openDelete.scope || !openDelete.id || !openDelete.type) return;
@ -166,15 +175,16 @@ export const SwitchPages = ({
} }
if (openDelete.type === "question") { if (openDelete.type === "question") {
let newArray = selectedQuestions let newArray = selectedQuestions;
newArray[openDelete.scope as QuestionKeys] = newArray[openDelete.scope as QuestionKeys].filter(e => e.id !== openDelete.id) newArray[openDelete.scope as QuestionKeys] = newArray[openDelete.scope as QuestionKeys].filter(
(e) => e.id !== openDelete.id
);
setSelectedQuestions(newArray); setSelectedQuestions(newArray);
setSelectedCurrentFields(selectedCurrentFields.filter(e => e.id !== openDelete.id)); setSelectedCurrentFields(selectedCurrentFields.filter((e) => e.id !== openDelete.id));
} }
setOpenDelete(null); setOpenDelete(null);
closeSpecialPage(); closeSpecialPage();
} };
const handleNextStep = () => { const handleNextStep = () => {
setStep((prevState) => prevState + 1); setStep((prevState) => prevState + 1);
@ -190,20 +200,39 @@ export const SwitchPages = ({
const body = { const body = {
PipelineID: Number(selectedPipeline), PipelineID: Number(selectedPipeline),
StepID: Number(selectedPipelineStep), StepID: Number(selectedPipelineStep),
PerformerID: Number(selectedDealUser), PerformerID: selectedDealUser,
LeadFlag: leadFlag,
StageID: "",
SourceID: "",
StatusID: "",
// FieldsRule: questionsBackend, // FieldsRule: questionsBackend,
TagsToAdd: selectedTags, // TagsToAdd: selectedTags,
}; };
const step = fullArrayOfPipelinesSteps.find((step) => step.bitrixID === selectedPipelineStep);
console.log("CURRENT step CURRENT step CURRENT step CURRENT step CURRENT step CURRENT step CURRENT step ");
console.log(step);
console.log(step.entityID);
console.log(step.entityID === "STATUS");
// if (step.entityId === undefined) return
if (step.entityID === "SOURCE") body.SourceID = step.statusID;
if (step.entityID === "STATUS") body.StatusID = step.statusID;
if (step.entityID.startsWith("DEAL_STAGE") && leadFlag) body.StageID = step.statusID;
const FieldsRule = { const FieldsRule = {
Company: { QuestionID: {} }, lead: { QuestionID: {} },
Lead: { QuestionID: {} }, deal: { QuestionID: {} },
Customer: { QuestionID: {} }, company: { QuestionID: {} },
Contact: { contact: {
QuestionID: {}, QuestionID: {},
ContactRuleMap: { ContactRuleMap: {},
}
}, },
// CRM_INVOICE: { QuestionID: {} },
// CRM_SMART_INVOICE: { QuestionID: {} },
// CRM_QUOTE: { QuestionID: {} },
// CRM_REQUISITE: { QuestionID: {} },
}; };
for (let key in FieldsRule) { for (let key in FieldsRule) {
@ -213,13 +242,12 @@ export const SwitchPages = ({
} }
selectedCurrentFields.forEach((data) => { selectedCurrentFields.forEach((data) => {
if (data.entity === "Contact") { if (data.entity === "contact") {
FieldsRule.Contact.ContactRuleMap[data.id] = Number(data.amoId) FieldsRule.contact.ContactRuleMap[data.id] = Number(data.bitrixId);
} else { } else {
FieldsRule[data.entity].QuestionID[data.id] = Number(data.amoId) || 0 FieldsRule[data.entity].QuestionID[data.id] = Number(data.bitrixId) || 0;
} }
}) });
for (let key in body.TagsToAdd) { for (let key in body.TagsToAdd) {
body.TagsToAdd[key as TagKeys] = body.TagsToAdd[key as TagKeys].map((id) => Number(id)); body.TagsToAdd[key as TagKeys] = body.TagsToAdd[key as TagKeys].map((id) => Number(id));
@ -235,17 +263,33 @@ export const SwitchPages = ({
handleCloseModal(); handleCloseModal();
}; };
const closeSpecialPage = () => setSpecialPage("");
const closeSpecialPage = () => setSpecialPage("")
const steps = [ const steps = [
{
isSettingsAvailable: true,
component: (
<СhoosePerson
leadFlag={leadFlag}
leadFlagHC={leadFlagHC}
handlePrevStep={() => setSpecialPage("accountInfo")}
handleNextStep={handleNextStep}
titleProps={{
step: step + 2,
title: "Выбор этапа взаимодействия",
desc: "",
toSettings: () => setSpecialPage("settingsBlock"),
}}
/>
),
},
{ {
isSettingsAvailable: true, isSettingsAvailable: true,
component: ( component: (
<Pipelines <Pipelines
users={arrayOfUsers} users={arrayOfUsers}
pipelines={arrayOfPipelines} pipelines={arrayOfPipelines}
handlePrevStep={() => setSpecialPage("accountInfo")} handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep} handleNextStep={handleNextStep}
selectedDealUser={selectedDealUser} selectedDealUser={selectedDealUser}
setSelectedDealPerformer={setSelectedDealPerformer} setSelectedDealPerformer={setSelectedDealPerformer}
@ -255,7 +299,7 @@ export const SwitchPages = ({
step: step + 2, step: step + 2,
title: "Выбор воронки", title: "Выбор воронки",
desc: "На этом этапе вы можете выбрать нужную воронку и ответственного за сделку", desc: "На этом этапе вы можете выбрать нужную воронку и ответственного за сделку",
toSettings: () => setSpecialPage("settingsBlock") toSettings: () => setSpecialPage("settingsBlock"),
}} }}
onScroll={setPageOfPipelines} onScroll={setPageOfPipelines}
onScrollUsers={setPageOfUsers} onScrollUsers={setPageOfUsers}
@ -278,10 +322,11 @@ export const SwitchPages = ({
step: step + 2, step: step + 2,
title: "Выбор этапа воронки", title: "Выбор этапа воронки",
desc: "На этом этапе вы можете выбрать нужный этап и ответственного за сделку", desc: "На этом этапе вы можете выбрать нужный этап и ответственного за сделку",
toSettings: () => setSpecialPage("settingsBlock") toSettings: () => setSpecialPage("settingsBlock"),
}} }}
onScroll={setPageOfPipelinesSteps} onScroll={setPageOfPipelinesSteps}
onScrollUsers={setPageOfUsers} onScrollUsers={setPageOfUsers}
leadFlag={leadFlag}
/> />
), ),
}, },
@ -298,36 +343,36 @@ export const SwitchPages = ({
step: step + 2, step: step + 2,
title: "Сделка", title: "Сделка",
desc: "На этом этапе вы можете выбрать ответственного за сделку", desc: "На этом этапе вы можете выбрать ответственного за сделку",
toSettings: () => setSpecialPage("settingsBlock") toSettings: () => setSpecialPage("settingsBlock"),
}} }}
onScrollUsers={setPageOfUsers} onScrollUsers={setPageOfUsers}
/> />
), ),
}, },
// {
// isSettingsAvailable: true,
// component: (
// <BitrixTags
// tagsItems={arrayOfTags}
// selectedTags={selectedTags}
// openDelete={startDeleteTagQuestion}
// handleAddTag={handleAddTagQuestion}
// handlePrevStep={handlePrevStep}
// handleNextStep={handleNextStep}
// titleProps={{
// step: step + 2,
// title: "Добавление тегов",
// desc: "На этом этапе вы можете добавить теги с результатами",
// toSettings: () => setSpecialPage("settingsBlock")
// }}
// onScroll={setPageOfTags}
// />
// ),
// },
{ {
isSettingsAvailable: true, isSettingsAvailable: true,
component: ( component: (
<AmoTags <BitrixQuestions
tagsItems={arrayOfTags}
selectedTags={selectedTags}
openDelete={startDeleteTagQuestion}
handleAddTag={handleAddTagQuestion}
handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep}
titleProps={{
step: step + 2,
title: "Добавление тегов",
desc: "На этом этапе вы можете добавить теги с результатами",
toSettings: () => setSpecialPage("settingsBlock")
}}
onScroll={setPageOfTags}
/>
),
},
{
isSettingsAvailable: true,
component: (
<AmoQuestions
setSelectedCurrentFields={setSelectedCurrentFields} setSelectedCurrentFields={setSelectedCurrentFields}
fieldsItems={arrayOfFields} fieldsItems={arrayOfFields}
selectedCurrentFields={selectedCurrentFields} selectedCurrentFields={selectedCurrentFields}
@ -341,56 +386,71 @@ export const SwitchPages = ({
titleProps={{ titleProps={{
step: step + 2, step: step + 2,
title: "Соотнесение вопросов и сущностей", title: "Соотнесение вопросов и сущностей",
toSettings: () => setSpecialPage("settingsBlock") toSettings: () => setSpecialPage("settingsBlock"),
}} }}
onScroll={setPageOfFields} onScroll={setPageOfFields}
/> />
), ),
}, },
] ];
const stepTitles = steps.map((step) => step.title); const stepTitles = steps.map((step) => step.title);
switch (specialPage) { switch (specialPage) {
case "deleteCell": case "deleteCell":
return <DeleteTagQuestion return (
close={closeSpecialPage} <DeleteTagQuestion
deleteItem={handleDeleteTagQuestion} close={closeSpecialPage}
/> deleteItem={handleDeleteTagQuestion}
/>
);
case "removeAccount": case "removeAccount":
return <RemoveAccount return (
handleCloseModal={handleCloseModal} <RemoveAccount
stopThisPage={closeSpecialPage} handleCloseModal={handleCloseModal}
/> stopThisPage={closeSpecialPage}
/>
);
case "settingsBlock": case "settingsBlock":
return <SettingsBlock return (
stepTitles={stepTitles} <SettingsBlock
selectedDealUser={arrayOfUsers.find((u) => u.id === selectedDealUser)?.title || "не указан"} stepTitles={stepTitles}
selectedFunnel={arrayOfPipelines.find((p) => p.id === selectedPipeline)?.title || "нет данных"} selectedDealUser={arrayOfUsers.find((u) => u.id === selectedDealUser)?.title || "не указан"}
selectedStage={ selectedFunnel={arrayOfPipelines.find((p) => p.id === selectedPipeline)?.title || "нет данных"}
arrayOfPipelinesSteps.find((s) => s.id === selectedPipelineStep)?.title || "нет данных" selectedStage={arrayOfPipelinesSteps.find((s) => s.id === selectedPipelineStep)?.title || "нет данных"}
} selectedQuestions={selectedQuestions}
selectedQuestions={selectedQuestions} selectedTags={selectedTags}
selectedTags={selectedTags} toBack={() => closeSpecialPage()}
toBack={() => closeSpecialPage()} setStep={(step: number) => {
setStep={(step: number) => { closeSpecialPage();
closeSpecialPage() setStep(step - 1);
setStep(step - 1) }}
}} leadFlag={leadFlag}
/> />
case "amoLogin": return <AmoLogin handleNextStep={handleNextStep} /> );
case "accountInfo": return <AccountInfo case "bitrixLogin":
handleNextStep={() => closeSpecialPage()} return <BitrixLogin handleNextStep={handleNextStep} />;
accountInfo={accountInfo} case "accountInfo":
toChangeAccount={() => setSpecialPage("removeAccount")} return (
/> <AccountInfo
handleNextStep={() => closeSpecialPage()}
accountInfo={accountInfo}
toChangeAccount={() => setSpecialPage("removeAccount")}
/>
);
default:
default: return <Box sx={{ return (
flexGrow: 1, <Box
width: "100%", sx={{
height: "100%", flexGrow: 1,
overflow: "auto" width: "100%",
}}>{steps[step].component}</Box> height: "100%",
overflow: "auto",
}}
>
{steps[step].component}
</Box>
);
} }
} };

@ -6,7 +6,7 @@ import { MinifiedData, QuestionKeys, SelectedTags, TagKeys, TagQuestionHC } from
import { DataConstrictor } from "../Components/DataConstrictor"; import { DataConstrictor } from "../Components/DataConstrictor";
import { ModalTitle } from "../ModalTitle"; import { ModalTitle } from "../ModalTitle";
import { StepButtonsBlock } from "../StepButtonsBlock"; import { StepButtonsBlock } from "../StepButtonsBlock";
import { resetBitrixTagsFields } from "../useAmoIntegration"; import { resetBitrixTagsFields } from "../useBitrixIntegration";
type Props = { type Props = {
tagsItems: MinifiedData[] | []; tagsItems: MinifiedData[] | [];
@ -25,7 +25,7 @@ type Props = {
}; };
export const AmoTags: FC<Props> = ({ export const BitrixTags: FC<Props> = ({
tagsItems, tagsItems,
selectedTags, selectedTags,
handleAddTag, handleAddTag,

@ -5,7 +5,7 @@ import { redirect } from "react-router-dom";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import { useBitrixIntegration } from "./useAmoIntegration"; import { useBitrixIntegration } from "./useBitrixIntegration";
import { MinifiedData } from "./types"; import { MinifiedData } from "./types";
import { Quiz } from "@/model/quiz/quiz"; import { Quiz } from "@/model/quiz/quiz";
import { SwitchPages } from "./SwitchPages"; import { SwitchPages } from "./SwitchPages";
@ -60,11 +60,14 @@ export const BitrixModal: FC<IntegrationsModalProps> = ({ isModalOpen, handleClo
setPageOfTags, setPageOfTags,
setPageOfFields, setPageOfFields,
setSelectedCurrentFields, setSelectedCurrentFields,
leadFlag,
leadFlagHC,
fullArrayOfPipelinesSteps
} = useBitrixIntegration({ } = useBitrixIntegration({
quizID: quiz.backendId, quizID: quiz.backendId,
isModalOpen, isModalOpen,
isTryRemoveAccount, isTryRemoveAccount,
questions, questions
}); });
return ( return (
@ -160,6 +163,9 @@ export const BitrixModal: FC<IntegrationsModalProps> = ({ isModalOpen, handleClo
setPageOfFields={setPageOfFields} setPageOfFields={setPageOfFields}
setSelectedCurrentFields={setSelectedCurrentFields} setSelectedCurrentFields={setSelectedCurrentFields}
handleCloseModal={handleCloseModal} handleCloseModal={handleCloseModal}
leadFlag={leadFlag}
leadFlagHC={leadFlagHC}
fullArrayOfPipelinesSteps={fullArrayOfPipelinesSteps}
/> />
} }
</Box> </Box>

@ -1,7 +1,22 @@
export type TagKeys = "Company" | "Lead" | "Customer" | "Contact"; export type TagKeys = "lead"
| "company"
| "contact"
| "deal"
| "CRM_INVOICE"
| "CRM_SMART_INVOICE"
| "CRM_QUOTE"
| "CRM_REQUISITE";
export type SelectedTags = Record<TagKeys, number[]>; export type SelectedTags = Record<TagKeys, number[]>;
export type QuestionKeys = "Company" | "Lead" | "Customer" | "Contact"; export type QuestionKeys = "lead"
| "company"
| "contact"
| "deal"
| "CRM_INVOICE"
| "CRM_SMART_INVOICE"
| "CRM_QUOTE"
| "CRM_REQUISITE";
export type SelectedQuestions = Record<QuestionKeys, MinifiedData[]>; export type SelectedQuestions = Record<QuestionKeys, MinifiedData[]>;
export type MinifiedData = { export type MinifiedData = {

@ -11,6 +11,7 @@ import {
getAccount, getAccount,
FieldsRule, FieldsRule,
getFields, getFields,
connectBitrix,
} from "@/api/bitrixIntegration"; } from "@/api/bitrixIntegration";
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer"; import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
import { UntypedQuizQuestion } from "@/model/questionTypes/shared"; import { UntypedQuizQuestion } from "@/model/questionTypes/shared";
@ -43,8 +44,11 @@ export const useBitrixIntegration = ({ isModalOpen, isTryRemoveAccount, quizID,
const [firstRules, setFirstRules] = useState<boolean>(false); const [firstRules, setFirstRules] = useState<boolean>(false);
const [accountInfo, setAccountInfo] = useState<AccountResponse | null>(null); const [accountInfo, setAccountInfo] = useState<AccountResponse | null>(null);
const [leadFlag, setLeadFlag] = useState<boolean>(false);
const [arrayOfPipelines, setArrayOfPipelines] = useState<MinifiedData[]>([]); const [arrayOfPipelines, setArrayOfPipelines] = useState<MinifiedData[]>([]);
const [arrayOfPipelinesSteps, setArrayOfPipelinesSteps] = useState<MinifiedData[]>([]); const [arrayOfPipelinesSteps, setArrayOfPipelinesSteps] = useState<MinifiedData[]>([]);
const [fullArrayOfPipelinesSteps, setFullArrayOfPipelinesSteps] = useState<any[]>([]);
const [arrayOfUsers, setArrayOfUsers] = useState<MinifiedData[]>([]); const [arrayOfUsers, setArrayOfUsers] = useState<MinifiedData[]>([]);
const [arrayOfTags, setArrayOfTags] = useState<MinifiedData[]>([]); const [arrayOfTags, setArrayOfTags] = useState<MinifiedData[]>([]);
const [arrayOfFields, setArrayOfFields] = useState<MinifiedData[]>([]); const [arrayOfFields, setArrayOfFields] = useState<MinifiedData[]>([]);
@ -62,10 +66,14 @@ export const useBitrixIntegration = ({ isModalOpen, isTryRemoveAccount, quizID,
Customer: [], Customer: [],
}); });
const [selectedQuestions, setSelectedQuestions] = useState<SelectedQuestions>({ const [selectedQuestions, setSelectedQuestions] = useState<SelectedQuestions>({
Lead: [], lead: [],
Company: [], company: [],
Customer: [], contact: [],
Contact: [] deal: [],
CRM_INVOICE: [],
CRM_SMART_INVOICE: [],
CRM_QUOTE: [],
CRM_REQUISITE: [],
}); });
const [pageOfPipelines, setPageOfPipelines] = useState(1); const [pageOfPipelines, setPageOfPipelines] = useState(1);
@ -74,12 +82,37 @@ export const useBitrixIntegration = ({ isModalOpen, isTryRemoveAccount, quizID,
const [pageOfTags, setPageOfTags] = useState(1); const [pageOfTags, setPageOfTags] = useState(1);
const [pageOfFields, setPageOfFields] = useState(1); const [pageOfFields, setPageOfFields] = useState(1);
const leadFlagHC = (s:boolean) => {
setLeadFlag(s)
};
const selectedPipelineHC = (id:string | null) => { const selectedPipelineHC = (id:string | null) => {
setSelectedPipeline(id); setSelectedPipeline(id);
isReadyGetPipelineStep = true; isReadyGetPipelineStep = true;
setPageOfPipelinesSteps(1); setPageOfPipelinesSteps(1);
} }
// useEffect(() => {
// (async () => {
// const API_URL = `https://penadigitaltech.bitrix24.ru`;
// try {
// const response = await makeRequest<void, { link: string }>({
// method: "POST",
// url: `${API_URL}/account`,
// useToken: true,
// withCredentials: true,
// body: {
// "client_bitrix_url": "penadigitaltech.bitrix24.ru"
// }
// });
// window.open(response.link, "_blank");
// } catch (nativeError) {
// return [null, `Не удалось подключить аккаунт. `];
// }
// })()
// }, [isModalOpen])
useEffect(() => { useEffect(() => {
const fetchAccountRules = async () => { const fetchAccountRules = async () => {
setIsLoadingPage(true); setIsLoadingPage(true);
@ -138,7 +171,7 @@ export const useBitrixIntegration = ({ isModalOpen, isTryRemoveAccount, quizID,
id: key, id: key,
title: FCTranslate[key], title: FCTranslate[key],
entity: "Contact", entity: "Contact",
amoId: MAP[key].toString(), bitrixId: MAP[key].toString(),
}) })
} }
setSelectedCurrentFields(list) setSelectedCurrentFields(list)
@ -196,8 +229,8 @@ export const useBitrixIntegration = ({ isModalOpen, isTryRemoveAccount, quizID,
response.items.forEach((step) => { response.items.forEach((step) => {
minifiedPipelines.push({ minifiedPipelines.push({
id: step.AmoID.toString(), id: step.bitrixID.toString(),
title: step.Name, title: step.name || "Нет названия",
}); });
}); });
setArrayOfPipelines((prevItems) => [...prevItems, ...minifiedPipelines]); setArrayOfPipelines((prevItems) => [...prevItems, ...minifiedPipelines]);
@ -208,31 +241,44 @@ export const useBitrixIntegration = ({ isModalOpen, isTryRemoveAccount, quizID,
}); });
} }
}, [pageOfPipelines]); }, [pageOfPipelines]);
useEffect(() => { useEffect(() => {
if (isReadyGetPipelineStep) { if (isReadyGetPipelineStep && selectedPipeline !== null) {
const oldData = pageOfPipelinesSteps === 1 ? [] : arrayOfPipelinesSteps; const oldData = pageOfPipelinesSteps === 1 ? [] : arrayOfPipelinesSteps;
if (selectedPipeline !== null) const oldFullData = pageOfPipelinesSteps === 1 ? [] : fullArrayOfPipelinesSteps;
getSteps({
page: pageOfPipelinesSteps,
size: SIZE,
pipelineId: Number(selectedPipeline),
}).then(([response]) => {
if (response && response.items !== null) {
const minifiedSteps: MinifiedData[] = [];
response.items.forEach((step) => { getSteps({
minifiedSteps.push({ page: pageOfPipelinesSteps,
id: step.AmoID.toString(), size: SIZE,
title: step.Name, pipelineId: Number(selectedPipeline),
}); }).then(([response]) => {
}); if (response && response.items !== null) {
setArrayOfPipelinesSteps([...oldData, ...minifiedSteps]); // Фильтруем только нужные элементы
} else { const filteredItems = response.items.filter(item =>
isReadyGetPipelineStep = false item.entityID === "STATUS" ||
} item.entityID === "SOURCE" ||
}); (typeof item.entityID === 'string' && item.entityID.startsWith("DEAL_STAGE"))
} );
}, [selectedPipeline, pageOfPipelinesSteps]);
// Минифицируем отфильтрованные данные
const minifiedSteps: MinifiedData[] = filteredItems.map((step) => ({
id: step.bitrixID.toString(),
title: step.name,
entity: step.entityID
}));
// Обновляем массивы
setArrayOfPipelinesSteps([...oldData, ...minifiedSteps]);
setFullArrayOfPipelinesSteps([...oldFullData, ...filteredItems]);
} else {
// Если нет данных, отключаем дальнейшие запросы
isReadyGetPipelineStep = false;
}
}).catch(error => {
console.error("Ошибка при получении шагов:", error);
isReadyGetPipelineStep = false;
});
}
}, [selectedPipeline, pageOfPipelinesSteps]);
useEffect(() => { useEffect(() => {
if (isReadyGetUsers) { if (isReadyGetUsers) {
getUsers({ getUsers({
@ -244,8 +290,8 @@ export const useBitrixIntegration = ({ isModalOpen, isTryRemoveAccount, quizID,
response.items.forEach((step) => { response.items.forEach((step) => {
minifiedUsers.push({ minifiedUsers.push({
id: step.amoUserID.toString(), id: step.bitrixUserID.toString(),
title: step.name, title: step.name || "Нет имени",
}); });
}); });
setArrayOfUsers((prevItems) => [...prevItems, ...minifiedUsers]); setArrayOfUsers((prevItems) => [...prevItems, ...minifiedUsers]);
@ -255,66 +301,77 @@ export const useBitrixIntegration = ({ isModalOpen, isTryRemoveAccount, quizID,
}); });
} }
}, [pageOfUsers]); }, [pageOfUsers]);
useEffect(() => { // useEffect(() => {
if (isReadyGetTags) { // if (isReadyGetTags) {
getTags({ // getTags({
page: pageOfTags, // page: pageOfTags,
size: SIZE, // size: SIZE,
}).then(([response]) => { // }).then(([response]) => {
if (response && response.items !== null) { // if (response && response.items !== null) {
const minifiedTags: MinifiedData[] = []; // const minifiedTags: MinifiedData[] = [];
response.items.forEach((step) => { // response.items.forEach((step) => {
minifiedTags.push({ // minifiedTags.push({
id: step.AmoID.toString(), // id: step.BitrixID.toString(),
title: step.Name, // title: step.Name,
entity: // entity:
step.Entity === "leads" // step.Entity === "leads"
? "Lead" // ? "Lead"
: step.Entity === "contacts" // : step.Entity === "contacts"
? "Contact" // ? "Contact"
: step.Entity === "companies" // : step.Entity === "companies"
? "Company" // ? "Company"
: "Customer", // : "Customer",
}); // });
}); // });
setArrayOfTags((prevItems) => [...prevItems, ...minifiedTags]); // setArrayOfTags((prevItems) => [...prevItems, ...minifiedTags]);
} else { // } else {
isReadyGetTags = false // isReadyGetTags = false
} // }
}); // });
} // }
}, [pageOfTags]); // }, [pageOfTags]);
useEffect(() => { useEffect(() => {
if (isReadyGetFields) { if (isReadyGetFields) {
getFields({ getFields({
page: pageOfFields, page: pageOfFields,
size: 1000, size: 1000,
}).then(([response]) => { }).then(([response]) => {
if (response && response.items !== null) { if (response && response.items !== null) {
const minifiedTags: MinifiedData[] = []; const minifiedTags: MinifiedData[] = [];
response.items.forEach((field) => { console.log("fields: ")
console.log(response.items)
const entityMap = {
'CRM_LEAD': 'lead',
'CRM_DEAL': 'deal',
'CRM_COMPANY': 'company',
'CRM_CONTACT': 'contact'
};
response.items.forEach((field) => {
console.log("поле: ")
console.log(field)
const entity = entityMap[field.entityID];
if (entity) {
minifiedTags.push({ minifiedTags.push({
id: field.AmoID.toString(), id: field.bitrixID.toString(),
title: field.Name, title: field.editFromLabel,
entity: entity: entity,
field.Entity === "leads"
? "Lead"
: field.Entity === "contacts"
? "Contact"
: field.Entity === "companies"
? "Company"
: "Customer",
}); });
}); }
setArrayOfFields((prevItems) => [...prevItems, ...minifiedTags]); });
}
}); setArrayOfFields((prevItems) => [...prevItems, ...minifiedTags]);
} else { }
isReadyGetFields = false });
} } else {
}, [pageOfFields]); isReadyGetFields = false
}
}, [pageOfFields]);
useEffect(() => () => { useEffect(() => () => {
isReadyGetPipeline = true; isReadyGetPipeline = true;
isReadyGetPipelineStep = true; isReadyGetPipelineStep = true;
@ -350,6 +407,9 @@ export const useBitrixIntegration = ({ isModalOpen, isTryRemoveAccount, quizID,
setPageOfTags: () => setPageOfTags(old => old + 1), setPageOfTags: () => setPageOfTags(old => old + 1),
setPageOfFields: () => setPageOfFields(old => old + 1), setPageOfFields: () => setPageOfFields(old => old + 1),
setSelectedCurrentFields, setSelectedCurrentFields,
leadFlag,
leadFlagHC,
fullArrayOfPipelinesSteps
}; };
}; };

@ -0,0 +1,136 @@
import { Box, FormControl, FormControlLabel, Radio, RadioGroup, useMediaQuery, useTheme } from "@mui/material";
import { FC } from "react";
import { StepButtonsBlock } from "./StepButtonsBlock";
import CheckboxIcon from "@/assets/icons/Checkbox";
import { ModalTitle } from "./ModalTitle";
type Props = {
leadFlag: boolean;
leadFlagHC: (a: boolean) => void;
handlePrevStep: () => void;
handleNextStep: () => void;
titleProps: {
step: number;
title: string;
desc: string;
toSettings: () => void;
};
};
export const СhoosePerson: FC<Props> = ({
leadFlag,
leadFlagHC,
handlePrevStep,
handleNextStep,
titleProps,
}) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
return (
<>
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
height: "100%",
overflow: "auto",
flexGrow: 1,
justifyContent: "space-between",
}}
>
<Box sx={{ width: "100%", zIndex: 3 }}>
<ModalTitle {...titleProps} />
</Box>
<Box>
<FormControl component="fieldset">
<RadioGroup
row
aria-label="тип сделки"
name="dealType"
value={leadFlag ? "lead" : "deal"}
onChange={(e) => leadFlagHC(e.target.value === "lead")}
sx={{ gap: 3 }}
>
<FormControlLabel
value="lead"
control={
<Radio
checkedIcon={
<CheckboxIcon
checked
isRounded
color={theme.palette.brightPurple.main}
/>
}
icon={<CheckboxIcon isRounded />}
/>
}
label="Лид сделки"
sx={{
color: "black",
padding: "15px",
border: `1px solid ${theme.palette.background.default}`,
borderRadius: "12px",
margin: 0,
backgroundColor: "#eff0f5",
"&.MuiFormControlLabel-root > .MuiTypography-root": {
width: isMobile ? "150px" : "200px",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
},
}}
/>
<FormControlLabel
value="deal"
control={
<Radio
checkedIcon={
<CheckboxIcon
checked
isRounded
color={theme.palette.brightPurple.main}
/>
}
icon={<CheckboxIcon isRounded />}
/>
}
label="Дил сделки"
sx={{
color: "black",
padding: "15px",
border: `1px solid ${theme.palette.background.default}`,
borderRadius: "12px",
margin: 0,
backgroundColor: "#eff0f5",
"&.MuiFormControlLabel-root > .MuiTypography-root": {
width: isMobile ? "150px" : "200px",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
},
}}
/>
</RadioGroup>
</FormControl>
</Box>
<Box
sx={{
alignSelf: "end",
}}
>
<StepButtonsBlock
onLargeBtnClick={handleNextStep}
onSmallBtnClick={handlePrevStep}
/>
</Box>
</Box>
</>
);
};

@ -7,6 +7,7 @@ import { useNavigate } from "react-router-dom";
import { PartnersBoard } from "./PartnersBoard/PartnersBoard"; import { PartnersBoard } from "./PartnersBoard/PartnersBoard";
import { getLeadTargetsByQuiz, LeadTargetModel } from "@/api/leadtarget"; import { getLeadTargetsByQuiz, LeadTargetModel } from "@/api/leadtarget";
import { QuizMetricType } from "@model/quizSettings"; import { QuizMetricType } from "@model/quizSettings";
import { makeRequest } from "@frontend/kitui";
interface IntegrationsPageProps { interface IntegrationsPageProps {
heightSidebar: number; heightSidebar: number;
@ -56,6 +57,24 @@ export const IntegrationsPage = ({
load(); load();
}, [leadTargetsLoaded, quiz?.id]); }, [leadTargetsLoaded, quiz?.id]);
// useEffect(() => {
// (async () => {
// const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz/yclients`;
// try {
// const response = await makeRequest<void, { link: string }>({
// method: "POST",
// url: `${API_URL}/account`,
// useToken: true,
// withCredentials: true,
// });
// window.open(response.link, "_blank");
// } catch (nativeError) {
// return [null, `Не удалось подключить аккаунт. `];
// }
// })()
// }, [])
const refreshLeadTargets = async () => { const refreshLeadTargets = async () => {
if (!quiz?.id) return; if (!quiz?.id) return;
const [items] = await getLeadTargetsByQuiz(quiz.backendId); const [items] = await getLeadTargetsByQuiz(quiz.backendId);

@ -140,7 +140,7 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
/> />
</Box> </Box>
<Typography variant="h6" sx={sectionTitleStyles}> <Typography variant="h6" sx={sectionTitleStyles}>
Автоматизация Автоматизация
</Typography> </Typography>
<Box sx={containerStyles}> <Box sx={containerStyles}>