Compare commits
3 Commits
3271b03b86
...
4997179b75
| Author | SHA1 | Date | |
|---|---|---|---|
| 4997179b75 | |||
| e734e8b28b | |||
| 6ceaf7e8bc |
@ -1,4 +1,5 @@
|
||||
1.0.14 _ 2025-10-20 _ utm
|
||||
1.0.15 _ 2025-12-03 _ Merge branch 'pin' into staging
|
||||
1.0.14 _ 2025-10-20 _ логика overtime для публички
|
||||
1.0.13 _ 2025-10-18 _ Визуал utm + логика
|
||||
1.0.12 _ 2025-10-12 _ ютм с дизайном и беком, но без логики
|
||||
1.0.11 _ 2025-10-06 _ Merge branch 'staging'
|
||||
|
||||
577
api-docs.html
577
api-docs.html
@ -1,577 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>QUIZ Service API Documentation</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #7E2AEA;
|
||||
--text-color: #333;
|
||||
--bg-color: #fff;
|
||||
--border-color: #e0e0e0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-color);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: var(--bg-color);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
padding: 2rem 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.endpoint {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.method {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.get { background: #61affe; }
|
||||
.post { background: #49cc90; }
|
||||
.put { background: #fca130; }
|
||||
.delete { background: #f93e3e; }
|
||||
.patch { background: #50e3c2; }
|
||||
|
||||
.schema {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.nav {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.nav a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.nav a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f1f1f1;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
|
||||
.response {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
background: #f8f9fa;
|
||||
border-left: 4px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.components {
|
||||
margin: 2rem 0;
|
||||
padding: 1rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.security {
|
||||
background: #fff3cd;
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.parameter {
|
||||
margin: 0.5rem 0;
|
||||
padding: 0.5rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.parameter.required {
|
||||
border-left: 4px solid #dc3545;
|
||||
}
|
||||
|
||||
.enum-values {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<h1>QUIZ Service API Documentation</h1>
|
||||
<p>Version 1.0.0</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav class="nav">
|
||||
<div class="container">
|
||||
<a href="#components">Components</a>
|
||||
<a href="#quiz">Quiz Endpoints</a>
|
||||
<a href="#question">Question Endpoints</a>
|
||||
<a href="#results">Results Endpoints</a>
|
||||
<a href="#statistics">Statistics Endpoints</a>
|
||||
<a href="#account">Account Endpoints</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container">
|
||||
<section id="components">
|
||||
<h2>Components</h2>
|
||||
|
||||
<div class="components">
|
||||
<h3>Quiz Model</h3>
|
||||
<div class="schema">
|
||||
<pre><code>{
|
||||
"id": integer, // Id of created quiz
|
||||
"qid": string, // string id for customers
|
||||
"deleted": boolean, // true if quiz deleted
|
||||
"archived": boolean, // true if quiz archived
|
||||
"fingerprinting": boolean, // set true for save deviceId
|
||||
"repeatable": boolean, // set true for allow user to repeat quiz
|
||||
"note_prevented": boolean, // set true for save statistic of incomplete quiz passing
|
||||
"mail_notifications": boolean, // set true for mail notification for each quiz passing
|
||||
"unique_answers": boolean, // set true for save statistics only for unique quiz passing
|
||||
"name": string, // name of quiz. max 280 length
|
||||
"description": string, // description of quiz
|
||||
"config": string, // config of quiz. serialized json for rules of quiz flow
|
||||
"status": string, // status of quiz. allow only '', 'draft', 'template', 'stop', 'start'
|
||||
"limit": integer, // limit is count of max quiz passing
|
||||
"due_to": integer, // last time when quiz is valid. timestamp in seconds
|
||||
"time_of_passing": integer, // seconds to pass quiz
|
||||
"pausable": boolean, // true if it is allowed for pause quiz
|
||||
"version": integer, // version of quiz
|
||||
"version_comment": string, // version comment to version of quiz
|
||||
"parent_ids": integer[], // array of previous versions of quiz
|
||||
"created_at": string, // time of creating
|
||||
"updated_at": string, // time of last updating
|
||||
"question_cnt": integer, // count of questions
|
||||
"passed_count": integer, // count passings
|
||||
"average_time": integer, // average time of passing
|
||||
"super": boolean, // set true if squiz realize group functionality
|
||||
"group_id": integer // group of new quiz
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="components">
|
||||
<h3>Question Model</h3>
|
||||
<div class="schema">
|
||||
<pre><code>{
|
||||
"id": integer, // Id of created question
|
||||
"quiz_id": integer, // relation to quiz
|
||||
"title": string, // title of question. max 512 length
|
||||
"description": string, // description of question
|
||||
"type": string, // status of question. allow only text, select, file, variant, images, varimg, emoji, date, number, page, rating
|
||||
"required": boolean, // user must pass this question
|
||||
"deleted": boolean, // true if question is deleted
|
||||
"page": integer, // page if question
|
||||
"content": string, // serialized json of created question
|
||||
"version": integer, // version of quiz
|
||||
"parent_ids": integer[], // array of previous versions of quiz
|
||||
"created_at": string, // time of creating
|
||||
"updated_at": string // time of last updating
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="components">
|
||||
<h3>Answer Model</h3>
|
||||
<div class="schema">
|
||||
<pre><code>{
|
||||
"Id": integer, // id ответа
|
||||
"Content": string, // контент ответа
|
||||
"QuestionId": integer, // id вопроса к которому ответ
|
||||
"QuizId": integer, // id опроса к которому ответ
|
||||
"Fingerprint": string, // fingerprint
|
||||
"Session": string, // сессия
|
||||
"Result": boolean, // true or false?
|
||||
"CreatedAt": string, // таймшап когда ответ создан
|
||||
"New": boolean, // новый ответ?
|
||||
"Deleted": boolean // удален?
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="components">
|
||||
<h3>LeadTarget Model</h3>
|
||||
<div class="schema">
|
||||
<pre><code>{
|
||||
"ID": integer, // primary key
|
||||
"AccountID": string, // account identifier
|
||||
"Type": string, // type of target (mail, telegram, whatsapp)
|
||||
"QuizID": integer, // ID of the quiz
|
||||
"Target": string, // target address
|
||||
"InviteLink": string, // invitation link
|
||||
"Deleted": boolean, // is deleted
|
||||
"CreatedAt": string // creation timestamp
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="components">
|
||||
<h3>TgAccount Model</h3>
|
||||
<div class="schema">
|
||||
<pre><code>{
|
||||
"ID": integer, // primary key
|
||||
"ApiID": integer, // Telegram API ID
|
||||
"ApiHash": string, // Telegram API Hash
|
||||
"PhoneNumber": string, // phone number
|
||||
"Password": string, // account password
|
||||
"Status": string, // account status (active, inactive, ban)
|
||||
"Deleted": boolean, // is deleted
|
||||
"CreatedAt": string // creation timestamp
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="quiz">
|
||||
<h2>Quiz Endpoints</h2>
|
||||
|
||||
<div class="endpoint">
|
||||
<h3>Create Quiz</h3>
|
||||
<span class="method post">POST</span>
|
||||
<code>/quiz/create</code>
|
||||
<p>Create a new quiz with specified parameters.</p>
|
||||
|
||||
<div class="security">
|
||||
<h4>Security</h4>
|
||||
<p>This endpoint requires authentication.</p>
|
||||
</div>
|
||||
|
||||
<h4>Request Body:</h4>
|
||||
<div class="schema">
|
||||
<pre><code>{
|
||||
"fingerprinting": boolean, // set true for save deviceId
|
||||
"repeatable": boolean, // set true for allow user to repeat quiz
|
||||
"note_prevented": boolean, // set true for save statistic of incomplete quiz passing
|
||||
"mail_notifications": boolean, // set true for mail notification for each quiz passing
|
||||
"unique_answers": boolean, // set true for save statistics only for unique quiz passing
|
||||
"name": string, // name of quiz. max 280 length
|
||||
"description": string, // description of quiz
|
||||
"config": string, // config of quiz. serialized json for rules of quiz flow
|
||||
"status": string, // status of quiz. allow only '', 'draft', 'template', 'stop', 'start'
|
||||
"limit": integer, // limit is count of max quiz passing
|
||||
"due_to": integer, // last time when quiz is valid. timestamp in seconds
|
||||
"time_of_passing": integer, // seconds to pass quiz
|
||||
"pausable": boolean, // true if it is allowed for pause quiz
|
||||
"question_cnt": integer, // count of questions
|
||||
"super": boolean, // set true if squiz realize group functionality
|
||||
"group_id": integer // group of new quiz
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Responses:</h4>
|
||||
<div class="response">
|
||||
<h5>201 Created</h5>
|
||||
<p>Quiz successfully created. Returns the created quiz object.</p>
|
||||
<div class="schema">
|
||||
<pre><code>{
|
||||
"id": integer,
|
||||
"qid": string,
|
||||
"name": string,
|
||||
"description": string,
|
||||
// ... other quiz properties
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="response">
|
||||
<h5>422 Unprocessable Entity</h5>
|
||||
<p>Name field should have less than 280 characters.</p>
|
||||
</div>
|
||||
|
||||
<div class="response">
|
||||
<h5>406 Not Acceptable</h5>
|
||||
<p>Status on creating must be only draft, template, stop, start or due to time must be lesser than now.</p>
|
||||
<div class="enum-values">
|
||||
Allowed status values: '', 'draft', 'template', 'stop', 'start'
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="response">
|
||||
<h5>409 Conflict</h5>
|
||||
<p>You can pause quiz only if it has deadline for passing.</p>
|
||||
</div>
|
||||
|
||||
<div class="response">
|
||||
<h5>500 Internal Server Error</h5>
|
||||
<p>If you get any content string send it to developer.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint">
|
||||
<h3>Get Quiz List</h3>
|
||||
<span class="method post">POST</span>
|
||||
<code>/quiz/getList</code>
|
||||
<p>Get paginated list of quizzes with filtering options.</p>
|
||||
|
||||
<h4>Request Body:</h4>
|
||||
<div class="schema">
|
||||
<pre><code>{
|
||||
"limit": integer,
|
||||
"offset": integer,
|
||||
"from": integer,
|
||||
"to": integer,
|
||||
"search": string,
|
||||
"status": string,
|
||||
"deleted": boolean,
|
||||
"archived": boolean,
|
||||
"super": boolean,
|
||||
"group_id": integer
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Responses:</h4>
|
||||
<div class="response">
|
||||
<h5>200 OK</h5>
|
||||
<p>Returns list of quizzes with total count.</p>
|
||||
<div class="schema">
|
||||
<pre><code>{
|
||||
"count": integer,
|
||||
"items": [
|
||||
{
|
||||
"id": integer,
|
||||
"qid": string,
|
||||
// ... other quiz properties
|
||||
}
|
||||
]
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="response">
|
||||
<h5>406 Not Acceptable</h5>
|
||||
<p>Inappropriate status, allowed only '', 'stop', 'start', 'draft', 'template', 'timeout', 'offlimit'.</p>
|
||||
</div>
|
||||
|
||||
<div class="response">
|
||||
<h5>500 Internal Server Error</h5>
|
||||
<p>If you get any content string send it to developer.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add more quiz endpoints -->
|
||||
</section>
|
||||
|
||||
<section id="question">
|
||||
<h2>Question Endpoints</h2>
|
||||
|
||||
<div class="endpoint">
|
||||
<h3>Create Question</h3>
|
||||
<span class="method post">POST</span>
|
||||
<code>/question/create</code>
|
||||
<p>Create a new question for a quiz.</p>
|
||||
</div>
|
||||
|
||||
<!-- Add more question endpoints -->
|
||||
</section>
|
||||
|
||||
<section id="results">
|
||||
<h2>Results Endpoints</h2>
|
||||
|
||||
<div class="endpoint">
|
||||
<h3>Get Quiz Results</h3>
|
||||
<span class="method post">POST</span>
|
||||
<code>/results/getResults/{quizId}</code>
|
||||
<p>Get list of quiz results with pagination.</p>
|
||||
|
||||
<h4>Path Parameters:</h4>
|
||||
<div class="parameter">
|
||||
<code>quizId</code> - ID of the quiz to get results for
|
||||
</div>
|
||||
|
||||
<h4>Request Body:</h4>
|
||||
<div class="schema">
|
||||
<pre><code>{
|
||||
"to": integer, // таймштамп времени, до которого выбирать статистику. если 0 или не передано - этого ограничения нет
|
||||
"from": integer, // таймштамп времени, после которого выбирать статистику. если 0 или не передано - этого ограничения нет
|
||||
"new": boolean, // флаг, по которому вернутся только новые результаты, ещё не просмотренные пользователем
|
||||
"page": integer, // номер страницы для пагинации
|
||||
"limit": integer // размер страницы для пагинации
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Responses:</h4>
|
||||
<div class="response">
|
||||
<h5>200 OK</h5>
|
||||
<p>Returns paginated list of results.</p>
|
||||
<div class="schema">
|
||||
<pre><code>{
|
||||
"total_count": integer, // общее количество элементов удволетворяющее фильтру
|
||||
"results": [
|
||||
{
|
||||
"content": string, // содержимое ответа
|
||||
"id": integer, // айдишник ответа
|
||||
"new": boolean, // статус, был ли просмотрен ответ
|
||||
"created_at": string // время создания этого результата
|
||||
}
|
||||
]
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint">
|
||||
<h3>Export Results</h3>
|
||||
<span class="method post">POST</span>
|
||||
<code>/results/{quizID}/export</code>
|
||||
<p>Export quiz results to CSV format.</p>
|
||||
|
||||
<h4>Path Parameters:</h4>
|
||||
<div class="parameter required">
|
||||
<code>quizID</code> - ID of the quiz to export results from
|
||||
</div>
|
||||
|
||||
<h4>Request Body:</h4>
|
||||
<div class="schema">
|
||||
<pre><code>{
|
||||
"to": string, // Дата окончания диапазона времени для экспорта результатов
|
||||
"from": string, // Дата начала временного диапазона для экспорта результатов
|
||||
"new": boolean // Экспортировать ли только новые результаты?
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Responses:</h4>
|
||||
<div class="response">
|
||||
<h5>200 OK</h5>
|
||||
<p>Returns CSV file with quiz results.</p>
|
||||
<div class="schema">
|
||||
<pre><code>Content-Type: text/csv</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="telegram">
|
||||
<h2>Telegram Endpoints</h2>
|
||||
|
||||
<div class="endpoint">
|
||||
<h3>Create Telegram Account</h3>
|
||||
<span class="method post">POST</span>
|
||||
<code>/telegram/create</code>
|
||||
<p>Authorize server in Telegram account.</p>
|
||||
|
||||
<h4>Request Body:</h4>
|
||||
<div class="schema">
|
||||
<pre><code>{
|
||||
"ApiID": integer, // Telegram API ID
|
||||
"ApiHash": string, // Telegram API Hash
|
||||
"PhoneNumber": string, // Phone number
|
||||
"Password": string // Account password
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Responses:</h4>
|
||||
<div class="response">
|
||||
<h5>200 OK</h5>
|
||||
<p>Returns signature for code verification.</p>
|
||||
<div class="schema">
|
||||
<pre><code>{
|
||||
"signature": string // Session identifier for code verification
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="response">
|
||||
<h5>409 Conflict</h5>
|
||||
<p>Account already exists and is active.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="audience">
|
||||
<h2>Audience Endpoints</h2>
|
||||
|
||||
<div class="endpoint">
|
||||
<h3>Create Quiz Audience</h3>
|
||||
<span class="method post">POST</span>
|
||||
<code>/quiz/{quizID}/auditory</code>
|
||||
<p>Create audience for a quiz.</p>
|
||||
|
||||
<h4>Path Parameters:</h4>
|
||||
<div class="parameter required">
|
||||
<code>quizID</code> - ID of the quiz
|
||||
</div>
|
||||
|
||||
<h4>Responses:</h4>
|
||||
<div class="response">
|
||||
<h5>200 OK</h5>
|
||||
<p>Returns ID of created audience.</p>
|
||||
<div class="schema">
|
||||
<pre><code>{
|
||||
"id": integer // ID of created auditory
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="statistics">
|
||||
<h2>Statistics Endpoints</h2>
|
||||
|
||||
<div class="endpoint">
|
||||
<h3>Get Question Statistics</h3>
|
||||
<span class="method post">POST</span>
|
||||
<code>/statistic/{quizID}/questions</code>
|
||||
<p>Get statistics for specific questions in a quiz.</p>
|
||||
</div>
|
||||
|
||||
<!-- Add more statistics endpoints -->
|
||||
</section>
|
||||
|
||||
<section id="account">
|
||||
<h2>Account Endpoints</h2>
|
||||
|
||||
<div class="endpoint">
|
||||
<h3>Add Lead Target</h3>
|
||||
<span class="method post">POST</span>
|
||||
<code>/account/leadtarget</code>
|
||||
<p>Add a target destination for lead notifications.</p>
|
||||
</div>
|
||||
|
||||
<!-- Add more account endpoints -->
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="container">
|
||||
<p>© 2024 QUIZ Service API Documentation</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
BIN
src.zip
BIN
src.zip
Binary file not shown.
380
src/api/bitrixIntegration.ts
Normal file
380
src/api/bitrixIntegration.ts
Normal file
@ -0,0 +1,380 @@
|
||||
import { QuestionKeys } from "@/pages/IntegrationsPage/IntegrationsModal/Amo/types";
|
||||
import { makeRequest } from "@frontend/kitui";
|
||||
import { useToken } from "@frontend/kitui";
|
||||
import { parseAxiosError } from "@utils/parse-error";
|
||||
import useSWR from "swr";
|
||||
|
||||
export type PaginationRequest = {
|
||||
page: number;
|
||||
size: number;
|
||||
};
|
||||
|
||||
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz/bitrix`;
|
||||
|
||||
// получение информации об аккаунте
|
||||
|
||||
export type AccountResponse = {
|
||||
id: number;
|
||||
accountID: string;
|
||||
bitrixID: number;
|
||||
deleted: boolean;
|
||||
createdAt: string;
|
||||
subdomain: string;
|
||||
stale: boolean;
|
||||
};
|
||||
|
||||
export const getAccount = async (): Promise<[AccountResponse | null, string?]> => {
|
||||
try {
|
||||
const response = await makeRequest<void, AccountResponse>({
|
||||
method: "GET",
|
||||
url: `${API_URL}/account`,
|
||||
useToken: true,
|
||||
});
|
||||
return [response];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, ""];
|
||||
}
|
||||
};
|
||||
|
||||
export function useBitrixAccount() {
|
||||
const token = useToken();
|
||||
|
||||
return useSWR(token ? "bitrixAccount" : null, () =>
|
||||
makeRequest<void, AccountResponse>({
|
||||
method: "GET",
|
||||
url: `${API_URL}/account`,
|
||||
useToken: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// подключить Bitrix
|
||||
|
||||
export const connectBitrix = async (): Promise<[string | null, string?]> => {
|
||||
try {
|
||||
const response = await makeRequest<{client_bitrix_url: string}, { link: string }>({
|
||||
method: "POST",
|
||||
url: `${API_URL}/account`,
|
||||
useToken: true,
|
||||
body: {
|
||||
client_bitrix_url: 'penadigitaltech.bitrix24.ru'
|
||||
},
|
||||
withCredentials: true,
|
||||
});
|
||||
return [response.link];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, `Не удалось подключить аккаунт. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
// получение токена
|
||||
|
||||
export type TokenPair = {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
};
|
||||
|
||||
export const getTokens = async (): Promise<[TokenPair | null, string?]> => {
|
||||
try {
|
||||
const response = await makeRequest<void, TokenPair>({
|
||||
method: "GET",
|
||||
url: `${API_URL}/webhook/create`,
|
||||
useToken: true,
|
||||
});
|
||||
return [response];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, `Failed to get tokens. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
//получение списка тегов
|
||||
|
||||
export type Tag = {
|
||||
ID: number;
|
||||
AmoID: number;
|
||||
AccountID: number;
|
||||
Entity: string;
|
||||
Name: string;
|
||||
Color: string;
|
||||
Deleted: boolean;
|
||||
CreatedAt: number;
|
||||
};
|
||||
|
||||
export type TagsResponse = {
|
||||
count: number;
|
||||
items: Tag[];
|
||||
};
|
||||
|
||||
export const getTags = async ({ page, size }: PaginationRequest): Promise<[TagsResponse | null, string?]> => {
|
||||
try {
|
||||
const tagsResponse = await makeRequest<PaginationRequest, TagsResponse>({
|
||||
method: "GET",
|
||||
url: `${API_URL}/tags?page=${page}&size=${size}`,
|
||||
});
|
||||
return [tagsResponse];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, `Не удалось получить список тегов. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
//получение списка пользователей
|
||||
|
||||
export type User = {
|
||||
id: number;
|
||||
accountID: string;
|
||||
bitrixUserID: string;
|
||||
name: string;
|
||||
lastName: string;
|
||||
secondName: string;
|
||||
title: string;
|
||||
email: string;
|
||||
uf_department: [ number ];
|
||||
deleted: boolean;
|
||||
createdAt: string;
|
||||
workPosition: string;
|
||||
};
|
||||
export type UsersResponse = {
|
||||
count: number;
|
||||
items: User[];
|
||||
};
|
||||
|
||||
export const getUsers = async ({ page, size }: PaginationRequest): Promise<[UsersResponse | null, string?]> => {
|
||||
try {
|
||||
const usersResponse = await makeRequest<PaginationRequest, UsersResponse>({
|
||||
method: "GET",
|
||||
url: `${API_URL}/users?page=${page}&size=${size}`,
|
||||
});
|
||||
return [usersResponse];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, `Не удалось получить список пользователей. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
//получение списка шагов
|
||||
|
||||
export type Step = {
|
||||
ID: number;
|
||||
accountID: string;
|
||||
bitrixID: string;
|
||||
entityID: string;
|
||||
statusID: string;
|
||||
name: string;
|
||||
nameInit: string;
|
||||
color: string;
|
||||
pipelineID: number;
|
||||
deleted: boolean;
|
||||
createdAt: number;
|
||||
};
|
||||
|
||||
export type StepsResponse = {
|
||||
count: number;
|
||||
items: Step[];
|
||||
};
|
||||
|
||||
export const getSteps = async ({
|
||||
page,
|
||||
size,
|
||||
pipelineId,
|
||||
}: PaginationRequest & { pipelineId: number }): Promise<[StepsResponse | null, string?]> => {
|
||||
try {
|
||||
const stepsResponse = await makeRequest<PaginationRequest & { pipelineId: number }, StepsResponse>({
|
||||
method: "GET",
|
||||
url: `${API_URL}/steps?page=${page}&size=${size}&pipelineID=${pipelineId}`,
|
||||
});
|
||||
return [stepsResponse];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, `Не удалось получить список шагов. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
//получение списка воронок
|
||||
|
||||
export type Pipeline = {
|
||||
ID: number;
|
||||
bitrixID: number;
|
||||
accountID: number;
|
||||
name: string;
|
||||
entityTypeId: boolean;
|
||||
deleted: boolean;
|
||||
createdAt: number;
|
||||
};
|
||||
|
||||
export type PipelinesResponse = {
|
||||
count: number;
|
||||
items: Pipeline[];
|
||||
};
|
||||
|
||||
export const getPipelines = async ({ page, size }: PaginationRequest): Promise<[PipelinesResponse | null, string?]> => {
|
||||
try {
|
||||
const pipelinesResponse = await makeRequest<PaginationRequest, PipelinesResponse>({
|
||||
method: "GET",
|
||||
url: `${API_URL}/pipelines?page=${page}&size=${size}`,
|
||||
});
|
||||
return [pipelinesResponse];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, `Не удалось получить список воронок. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
//получение настроек интеграции
|
||||
export type QuestionID = Record<string, number>;
|
||||
|
||||
export type IntegrationRules = {
|
||||
PipelineID: number;
|
||||
StepID: number;
|
||||
PerformerID?: number;
|
||||
FieldsRule: FieldsRule;
|
||||
TagsToAdd: {
|
||||
Lead: number[] | null;
|
||||
Contact: number[] | null;
|
||||
Company: number[] | null;
|
||||
Customer: number[] | null;
|
||||
};
|
||||
};
|
||||
export type FieldsRule = Record<Partial<QuestionKeys>, null | [{ QuestionID: QuestionID }]>;
|
||||
|
||||
export const getIntegrationRules = async (quizID: string): Promise<[IntegrationRules | null, string?]> => {
|
||||
try {
|
||||
const settingsResponse = await makeRequest<void, IntegrationRules>({
|
||||
method: "GET",
|
||||
url: `${API_URL}/rules/${quizID}`,
|
||||
});
|
||||
return [settingsResponse || null];
|
||||
} catch (nativeError) {
|
||||
if (nativeError.response?.status === 404) return [null, "first"];
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, `Не удалось получить настройки интеграции. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
//обновление настроек интеграции
|
||||
export type IntegrationRulesUpdate = {
|
||||
PerformerID: number;
|
||||
PipelineID: number;
|
||||
StepID: number;
|
||||
Utms: number[];
|
||||
FieldsRule: {
|
||||
Lead: { QuestionID: number }[];
|
||||
Contact: { ContactRuleMap: string }[];
|
||||
Company: { QuestionID: number }[];
|
||||
Customer: { QuestionID: number }[];
|
||||
};
|
||||
};
|
||||
|
||||
export const setIntegrationRules = async (
|
||||
quizID: string,
|
||||
settings: IntegrationRulesUpdate
|
||||
): Promise<[string | null, string?]> => {
|
||||
try {
|
||||
const updateResponse = await makeRequest<IntegrationRulesUpdate, string>({
|
||||
method: "POST",
|
||||
url: `${API_URL}/rules/${quizID}`,
|
||||
body: settings,
|
||||
});
|
||||
return [updateResponse];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, `Failed to update integration settings. ${error}`];
|
||||
}
|
||||
};
|
||||
export const updateIntegrationRules = async (
|
||||
quizID: string,
|
||||
settings: IntegrationRulesUpdate
|
||||
): Promise<[string | null, string?]> => {
|
||||
try {
|
||||
const updateResponse = await makeRequest<IntegrationRulesUpdate, string>({
|
||||
method: "PATCH",
|
||||
url: `${API_URL}/rules/${quizID}`,
|
||||
body: settings,
|
||||
});
|
||||
return [updateResponse];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, `Failed to update integration settings. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
// Получение кастомных полей
|
||||
export type CustomField = {
|
||||
ID: number;
|
||||
AmoID: number;
|
||||
Code: string;
|
||||
AccountID: number;
|
||||
Name: string;
|
||||
EntityType: string;
|
||||
Type: string;
|
||||
Deleted: boolean;
|
||||
CreatedAt: number;
|
||||
};
|
||||
export type Field = {
|
||||
ID: number;
|
||||
accountID: string;
|
||||
bitrixID: string;
|
||||
entityID: string;
|
||||
fieldName: string;
|
||||
editFromLabel: string;
|
||||
fieldType: string;
|
||||
deleted: boolean;
|
||||
createdAt: number;
|
||||
};
|
||||
|
||||
export type CustomFieldsResponse = {
|
||||
count: number;
|
||||
items: CustomField[];
|
||||
};
|
||||
export type FieldsResponse = {
|
||||
count: number;
|
||||
items: Field[];
|
||||
};
|
||||
|
||||
export const getCustomFields = async (
|
||||
pagination: PaginationRequest
|
||||
): Promise<[CustomFieldsResponse | null, string?]> => {
|
||||
try {
|
||||
const customFieldsResponse = await makeRequest<PaginationRequest, CustomFieldsResponse>({
|
||||
method: "GET",
|
||||
url: `${API_URL}/fields?page=${pagination.page}&size=${pagination.size}`,
|
||||
});
|
||||
return [customFieldsResponse];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, `Не удалось получить список кастомных полей. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
export const getFields = async (pagination: PaginationRequest): Promise<[FieldsResponse | null, string?]> => {
|
||||
try {
|
||||
const fieldsResponse = await makeRequest<PaginationRequest, FieldsResponse>({
|
||||
method: "GET",
|
||||
url: `${API_URL}/fields?page=${pagination.page}&size=${pagination.size}`,
|
||||
});
|
||||
return [fieldsResponse, ""];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, `Не удалось получить список полей. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
// Отвязать аккаунт Bitrix от публикации
|
||||
export const removeBitrixAccount = async (): Promise<[void | null, string?]> => {
|
||||
try {
|
||||
await makeRequest<void>({
|
||||
method: "DELETE",
|
||||
url: `${API_URL}/account`,
|
||||
});
|
||||
return [null, ""];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, `Не удалось отвязать аккаунт. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
21
src/assets/icons/Bitrix.tsx
Normal file
21
src/assets/icons/Bitrix.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { Box, SxProps, Theme } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
sx?: SxProps<Theme>;
|
||||
}
|
||||
export default function Bitrix({ sx }: Props) {
|
||||
return (
|
||||
|
||||
<Box
|
||||
sx={sx}
|
||||
>
|
||||
<svg data-logo="" width="195" height="35" viewBox="0 0 195 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.755 22.17v-7.991h1.921c1.72 0 3.136.2 4.146.81 1.01.605 1.62 1.62 1.62 3.235 0 2.73-1.62 3.946-5.461 3.946H4.755zM.1 26.01h6.776c7.587 0 10.321-3.34 10.321-7.991 0-3.136-1.315-5.26-3.64-6.476-1.82-1.01-4.146-1.315-6.981-1.315h-1.82v-6.07h10.216L16.187.412H0v25.594h.1v.004zm20.233 0h4.45l5.766-8.296c1.115-1.516 1.921-3.035 2.43-3.845h.1c-.1 1.115-.2 2.53-.2 3.945v8.092h4.551V7.703h-4.45l-5.766 8.296c-1.01 1.516-1.921 3.035-2.43 3.845h-.1c.1-1.114.2-2.53.2-3.945V7.807h-4.551V26.02v-.008zm24.888 0h4.655V11.544h5.461L56.552 7.7H39.656v3.845h5.565v14.467zm17.502 9.206v-9.206c.91.305 1.82.405 2.83.405 5.767 0 9.512-3.945 9.512-9.611s-3.44-9.611-10.016-9.611c-2.53 0-4.956.505-6.981 1.114l.1 26.91h4.555zm0-13.151V11.344c.71-.2 1.315-.305 2.125-.305 3.34 0 5.461 1.82 5.461 5.766 0 3.54-1.72 5.766-5.16 5.766-.91 0-1.62-.2-2.43-.506h.004zm15.072 3.945h4.451l5.766-8.296c1.115-1.516 1.92-3.035 2.43-3.845h.1c-.1 1.115-.2 2.53-.2 3.945v8.092h4.55V7.703h-4.45l-5.766 8.296c-1.01 1.516-1.92 3.035-2.43 3.845h-.1c.1-1.114.2-2.53.2-3.945V7.807h-4.55V26.02v-.008zm20.742 0h4.656v-7.586h2.73c.506 0 1.011.505 1.62 1.72l2.326 5.866h4.956l-3.34-6.98c-.606-1.216-1.215-1.921-2.126-2.226v-.1c1.516-.91 1.721-3.54 2.631-4.856.305-.405.71-.606 1.315-.606.305 0 .71 0 1.01.2V7.499c-.505-.2-1.415-.304-1.92-.304-1.62 0-2.631.605-3.34 1.62-1.516 2.225-1.516 6.07-3.745 6.07h-2.126V7.703h-4.655v18.312l.008-.004zm26.605.405c2.53 0 4.855-.81 6.271-1.82l-1.316-3.136c-1.315.71-2.53 1.215-4.25 1.215-3.135 0-5.16-2.025-5.16-5.766 0-3.34 2.025-5.97 5.461-5.97 1.82 0 3.135.505 4.45 1.415V8.41c-1.01-.606-2.63-1.215-4.955-1.215-5.462 0-9.712 4.045-9.712 9.811 0 5.26 3.236 9.407 9.206 9.407l.005.004z" fill="#0BBBEF">
|
||||
</path><path d="M185.084 19.828c-5.461 0-9.812-4.351-9.916-9.916 0-5.462 4.451-9.916 9.916-9.916 5.465 0 9.916 4.45 9.916 9.916 0 5.465-4.451 9.915-9.916 9.915zm0-17.41c-4.121 0-7.494 3.373-7.494 7.494.092 4.216 3.373 7.494 7.494 7.494 4.121 0 7.494-3.374 7.494-7.494 0-4.121-3.373-7.495-7.494-7.495z" fill="#005893">
|
||||
</path><path d="M183.827 4.839h1.933v6.521h-1.933V4.84z" fill="#005893">
|
||||
</path><path d="M190.353 9.427v1.933h-6.521V9.427h6.521zM134.047 26.011h17.807v-3.945h-11.736c1.62-6.476 11.532-7.891 11.532-15.073 0-3.845-2.631-6.676-8.196-6.676-3.441 0-6.476 1.01-8.497 2.025l1.215 3.64c1.821-.91 3.946-1.72 6.576-1.72 2.025 0 3.946.91 3.946 3.236 0 5.261-11.637 5.666-12.647 18.513zm29.74-6.271v6.271h4.551V19.74h3.845v-3.845h-3.845V.317h-3.341l-12.646 16.388v3.035h11.436zm-6.271-3.64 6.475-8.702c0 .71-.2 2.935-.2 4.956v3.64h-3.036c-.91 0-2.63.101-3.235.101l-.004.004z" fill="#005893">
|
||||
</path>
|
||||
</svg>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@ -24,6 +24,8 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
|
||||
console.log("CustomRadioGroup")
|
||||
console.log(items)
|
||||
const currentItem =
|
||||
(selectedItemId !== null && selectedItemId.length > 0) ?
|
||||
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 menuItems = useMemo(() => {
|
||||
if (items.length !== 0) {
|
||||
return items.map((item) => (
|
||||
|
||||
@ -113,21 +113,23 @@ export const useAmoIntegration = ({ isModalOpen, isTryRemoveAccount, quizID, que
|
||||
) {
|
||||
const gottenList = settingsResponse.FieldsRule[key as QuestionKeys];
|
||||
|
||||
if (gottenList !== null) {
|
||||
Object.keys(gottenList.QuestionID).forEach((qId) => {
|
||||
const q = questions.find(e => e.backendId === Number(qId)) || {}
|
||||
console.log("gottenList-----")
|
||||
console.log(gottenList)
|
||||
// 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({
|
||||
id: qId,
|
||||
title: q.title,
|
||||
entity: key,
|
||||
// gottenQuestions[key as QuestionKeys].push({
|
||||
// id: qId,
|
||||
// title: q.title,
|
||||
// entity: key,
|
||||
|
||||
})
|
||||
})
|
||||
// })
|
||||
// })
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
if (key === "Contact") {
|
||||
const MAP = settingsResponse.FieldsRule[key as QuestionKeys].ContactRuleMap
|
||||
|
||||
@ -0,0 +1,182 @@
|
||||
import { Box, Button, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { StepButtonsBlock } from "./StepButtonsBlock";
|
||||
import { FC } from "react";
|
||||
import { AccountResponse } from "@/api/bitrixIntegration";
|
||||
import AccountSetting from "@icons/AccountSetting";
|
||||
|
||||
type BitrixAccountInfoProps = {
|
||||
handleNextStep: () => void;
|
||||
accountInfo: AccountResponse | null;
|
||||
toChangeAccount: () => void;
|
||||
};
|
||||
|
||||
export const AccountInfo: FC<BitrixAccountInfoProps> = ({ handleNextStep, accountInfo, toChangeAccount }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
|
||||
const infoItem = (title: string, value: string | number | undefined) => (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
mt: "20px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: isMobile ? "100%" : "45%" }}>
|
||||
<Typography sx={{
|
||||
fontSize: "16px",
|
||||
lineHeight: "18.96px",
|
||||
|
||||
color: theme.palette.grey2.main
|
||||
}}>{title}:</Typography>
|
||||
</Box>
|
||||
<Box sx={{
|
||||
width: isMobile ? "100%" : "45%",
|
||||
mt: "5px",
|
||||
}}>
|
||||
<Typography>{value || "нет данных"}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const infoItemLink = (title: string, link: string | undefined) => (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
mt: "20px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: "100%" }}>
|
||||
<Typography sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
lineHeight: "18.96px",
|
||||
}}>{title}:</Typography>
|
||||
</Box>
|
||||
<Box sx={{ width: "100%" }}>
|
||||
{
|
||||
link ?
|
||||
<a
|
||||
target="_blank"
|
||||
href={link}
|
||||
style={{
|
||||
wordBreak: "break-word",
|
||||
fontSize: "18px",
|
||||
lineHeight: "21.33px",
|
||||
color: "#7E2AEA"
|
||||
}}
|
||||
>
|
||||
{link}
|
||||
</a>
|
||||
:
|
||||
<Typography>не указана</Typography>
|
||||
}
|
||||
</Box>
|
||||
</Box >
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "inline-flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
height: "100%",
|
||||
overflow: "auto",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
|
||||
flexDirection: isMobile ? "column" : "row",
|
||||
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "18px",
|
||||
color: theme.palette.grey3.main,
|
||||
lineHeight: "21.33px",
|
||||
}}
|
||||
>
|
||||
Информация об аккаунте
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
m: "5px 0 19px 0",
|
||||
lineHeight: "16.59px",
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
1 шаг
|
||||
</Typography>
|
||||
{infoItem("Bitrix ID", accountInfo?.bitrixID)}
|
||||
{/* {infoItemLink("ЛК в bitrix", `https://${accountInfo?.subdomain}/dashboard/`)}
|
||||
{infoItemLink("Профиль пользователя в bitrix", `https://${accountInfo?.subdomain}/settings/users/`)} */}
|
||||
</Box>
|
||||
<Box>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={
|
||||
<AccountSetting
|
||||
color={theme.palette.brightPurple.main}
|
||||
height={"20px"}
|
||||
width={"20px"}
|
||||
/>
|
||||
}
|
||||
onClick={toChangeAccount}
|
||||
sx={{
|
||||
height: "44px",
|
||||
padding: "0",
|
||||
mt: isMobile ? "30px" : "0",
|
||||
width: "205px",
|
||||
backgroundColor: "transparent",
|
||||
color: theme.palette.brightPurple.main,
|
||||
"& .MuiButton-startIcon": {
|
||||
marginRight: isMobile ? 0 : "8px",
|
||||
marginLeft: 0,
|
||||
},
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
color: theme.palette.common.white,
|
||||
"& path": {
|
||||
stroke: theme.palette.common.white,
|
||||
},
|
||||
"& circle": {
|
||||
stroke: theme.palette.common.white,
|
||||
},
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: "#581CA7",
|
||||
color: theme.palette.common.white,
|
||||
"& path": {
|
||||
stroke: theme.palette.common.white,
|
||||
},
|
||||
"& circle": {
|
||||
stroke: theme.palette.common.white,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
Сменить аккаунт
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
<StepButtonsBlock
|
||||
isSmallBtnDisabled={true}
|
||||
onLargeBtnClick={handleNextStep}
|
||||
largeBtnText={"Далее"}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,69 @@
|
||||
import { Box, IconButton, Typography, useTheme } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
import Trash from "@icons/trash";
|
||||
|
||||
type AnswerItemProps = {
|
||||
fieldName: string;
|
||||
fieldValue: string;
|
||||
deleteHC: () => void;
|
||||
};
|
||||
|
||||
export const AnswerItem: FC<AnswerItemProps> = ({ fieldName, fieldValue, deleteHC }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
padding: "10px 20px",
|
||||
height: "140px",
|
||||
borderBottom: `1px solid ${theme.palette.background.default}`,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontWeight: 500,
|
||||
color: theme.palette.grey3.main,
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{fieldName}
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontWeight: 400,
|
||||
color: theme.palette.grey3.main,
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{fieldValue}
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton
|
||||
sx={{
|
||||
m: "auto",
|
||||
}}
|
||||
onClick={deleteHC}
|
||||
>
|
||||
<Trash />
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,148 @@
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
import { AmoButton } from "../../../../components/AmoButton/AmoButton";
|
||||
import { connectBitrix } from "@/api/bitrixIntegration";
|
||||
|
||||
type IntegrationStep1Props = {
|
||||
handleNextStep: () => void;
|
||||
};
|
||||
|
||||
// interface Values {
|
||||
// login: string;
|
||||
// password: string;
|
||||
// }
|
||||
//
|
||||
// const initialValues: Values = {
|
||||
// login: "",
|
||||
// password: "",
|
||||
// };
|
||||
//
|
||||
// const validationSchema = object({
|
||||
// login: string().required("Поле обязательно"),
|
||||
// password: string().required("Поле обязательно").min(8, "Минимум 8 символов"),
|
||||
// });
|
||||
|
||||
export const BitrixLogin: FC<IntegrationStep1Props> = ({ handleNextStep }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
|
||||
const onAmoClick = async () => {
|
||||
const [url, error] = await connectBitrix();
|
||||
if (url && !error) {
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
};
|
||||
|
||||
// const formik = useFormik<Values>({
|
||||
// initialValues,
|
||||
// validationSchema,
|
||||
// onSubmit: async (values, formikHelpers) => {
|
||||
// const loginTrimmed = values.login.trim();
|
||||
// const passwordTrimmed = values.password.trim();
|
||||
// try {
|
||||
// // Simulate a network request
|
||||
// await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
// handleNextStep();
|
||||
// } catch (error) {
|
||||
// formikHelpers.setSubmitting(false);
|
||||
// if (error instanceof Error) {
|
||||
// formikHelpers.setErrors({
|
||||
// login: error.message,
|
||||
// password: error.message,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
|
||||
return (
|
||||
<Box
|
||||
// component="form"
|
||||
// onSubmit={formik.handleSubmit}
|
||||
// noValidate
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
{/*<Box*/}
|
||||
{/* sx={{*/}
|
||||
{/* marginTop: "68px",*/}
|
||||
{/* width: isMobile ? "100%" : "500px",*/}
|
||||
{/* display: "flex",*/}
|
||||
{/* flexDirection: "column",*/}
|
||||
{/* alignItems: "center",*/}
|
||||
{/* gap: "15px",*/}
|
||||
{/* }}*/}
|
||||
{/*>*/}
|
||||
{/* <InputTextfield*/}
|
||||
{/* TextfieldProps={{*/}
|
||||
{/* value: formik.values.login,*/}
|
||||
{/* placeholder: "+7 900 000 00 00 или username@penahaub.com",*/}
|
||||
{/* onBlur: formik.handleBlur,*/}
|
||||
{/* error: formik.touched.login && Boolean(formik.errors.login),*/}
|
||||
{/* helperText: formik.touched.login && formik.errors.login,*/}
|
||||
{/* "data-cy": "login",*/}
|
||||
{/* }}*/}
|
||||
{/* onChange={formik.handleChange}*/}
|
||||
{/* color={theme.palette.background.default}*/}
|
||||
{/* id="login"*/}
|
||||
{/* label="Телефон или E-mail"*/}
|
||||
{/* gap="10px"*/}
|
||||
{/* />*/}
|
||||
{/* <PasswordInput*/}
|
||||
{/* TextfieldProps={{*/}
|
||||
{/* value: formik.values.password,*/}
|
||||
{/* placeholder: "Не менее 8 символов",*/}
|
||||
{/* onBlur: formik.handleBlur,*/}
|
||||
{/* error: formik.touched.password && Boolean(formik.errors.password),*/}
|
||||
{/* helperText: formik.touched.password && formik.errors.password,*/}
|
||||
{/* type: "password",*/}
|
||||
{/* "data-cy": "password",*/}
|
||||
{/* }}*/}
|
||||
{/* onChange={formik.handleChange}*/}
|
||||
{/* color={theme.palette.background.default}*/}
|
||||
{/* id="password"*/}
|
||||
{/* label="Пароль"*/}
|
||||
{/* gap="10px"*/}
|
||||
{/* />*/}
|
||||
{/*</Box>*/}
|
||||
<Box sx={{ marginTop: "70px", width: isMobile ? "100%" : "500px" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
fontWeight: "400",
|
||||
color: theme.palette.grey2.main,
|
||||
lineHeight: "1",
|
||||
}}
|
||||
>
|
||||
Инструкция
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
marginTop: "12px",
|
||||
fontSize: "18px",
|
||||
fontWeight: "400",
|
||||
color: theme.palette.grey3.main,
|
||||
lineHeight: "1",
|
||||
}}
|
||||
>
|
||||
После нажатия на кнопку - "Подключить", вас переадресует на страницу подключения интеграции в ваш аккаунт
|
||||
Bitrix24. Пожалуйста, согласитесь на всё, что мы предлагаем, иначе чуда не случится.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ marginTop: "50px" }}>
|
||||
<AmoButton onClick={onAmoClick} />
|
||||
</Box>
|
||||
{/*<StepButtonsBlock*/}
|
||||
{/* isSmallBtnDisabled={true}*/}
|
||||
{/* largeBtnType={"submit"}*/}
|
||||
{/* // isLargeBtnDisabled={formik.isSubmitting}*/}
|
||||
{/* largeBtnText={"Войти"}*/}
|
||||
{/*/>*/}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,115 @@
|
||||
import { connectBitrix } from "@/api/bitrixIntegration";
|
||||
import { setTryShowBitrixTokenExpiredDialog } from "@/stores/uiTools/actions";
|
||||
import { useUiTools } from "@/stores/uiTools/store";
|
||||
import CustomCheckbox from "@/ui_kit/CustomCheckbox";
|
||||
import { Box, Button, Dialog, Typography, useTheme } from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
const HIDE_DIALOG_EXPIRATION_PERIOD = 24 * 60 * 60 * 1000;
|
||||
|
||||
interface Props {
|
||||
isBitrixTokenExpired: boolean;
|
||||
}
|
||||
|
||||
export default function BitrixTokenExpiredDialog({ isBitrixTokenExpired }: Props) {
|
||||
const theme = useTheme();
|
||||
const tryShowBitrixTokenExpiredDialog = useUiTools((state) => state.tryShowBitrixTokenExpiredDialog);
|
||||
const [isHideDialogForADayChecked, setIsHideDialogForADayChecked] = useState<boolean>(false);
|
||||
// const { hash, pathname, search } = useLocation();
|
||||
const location = useLocation();
|
||||
|
||||
const onBitrixClick = async () => {
|
||||
const [url, error] = await connectBitrix();
|
||||
if (url && !error) {
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
};
|
||||
|
||||
function handleDialogClose() {
|
||||
if (isHideDialogForADayChecked) {
|
||||
const expirationDate = Date.now() + HIDE_DIALOG_EXPIRATION_PERIOD;
|
||||
localStorage.setItem("hideBitrixTokenExpiredDialogExpirationTime", expirationDate.toString());
|
||||
}
|
||||
|
||||
setTryShowBitrixTokenExpiredDialog(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setTryShowBitrixTokenExpiredDialog(true);
|
||||
}, [location]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isBitrixTokenExpired && tryShowBitrixTokenExpiredDialog && location.pathname !== "/"}
|
||||
onClose={handleDialogClose}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
borderRadius: "12px",
|
||||
maxWidth: "620px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
p: "20px",
|
||||
backgroundColor: "#F2F3F7",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
color="#4D4D4D"
|
||||
fontSize="24px"
|
||||
fontWeight="medium"
|
||||
>
|
||||
Ваш bitrix-токен не работает
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
p: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "30px",
|
||||
}}
|
||||
>
|
||||
<Typography color="#4D4D4D">
|
||||
Bitrix отозвал ваш токен. Зайдите заново в свой аккаунт, чтобы вам снова начали приходить сделки.
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
flexDirection: "column-reverse",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={handleDialogClose}
|
||||
sx={{
|
||||
flex: "1 0 0",
|
||||
borderColor: "#9A9AAF",
|
||||
}}
|
||||
>
|
||||
Позже
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={onBitrixClick}
|
||||
sx={{
|
||||
flex: "1 0 0",
|
||||
}}
|
||||
>
|
||||
Перелогиниться
|
||||
</Button>
|
||||
</Box>
|
||||
<CustomCheckbox
|
||||
label={"Не показывать сутки"}
|
||||
checked={isHideDialogForADayChecked}
|
||||
handleChange={({ target }) => setIsHideDialogForADayChecked(target.checked)}
|
||||
/>
|
||||
</Box>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
import { useState } from "react"
|
||||
import { IconButton, Input, useMediaQuery, useTheme } from "@mui/material"
|
||||
import Trash from "@/assets/icons/trash"
|
||||
import { useDebouncedCallback } from "use-debounce"
|
||||
|
||||
interface Props {
|
||||
isError: boolean
|
||||
constrictor: (text: string) => void
|
||||
}
|
||||
|
||||
export const DataConstrictor = ({
|
||||
isError,
|
||||
constrictor,
|
||||
|
||||
}: Props) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
|
||||
const [text, setText] = useState("")
|
||||
const errCalc = isError && text.length > 0
|
||||
|
||||
const debouncedTestHC = useDebouncedCallback(
|
||||
(value: string) => {
|
||||
constrictor(value)
|
||||
},
|
||||
700
|
||||
);
|
||||
|
||||
return <Input
|
||||
value={text}
|
||||
placeholder="быстрый поиск"
|
||||
disableUnderline
|
||||
sx={{
|
||||
bgcolor: isError ? "#e6bbbb" : "#f2f3f7",
|
||||
borderRadius: "10px",
|
||||
maxWidth: isMobile ? "200px" : "300px",
|
||||
p: "0 5px 0 15px",
|
||||
mt: "10px",
|
||||
"&:hover": {
|
||||
bgcolor: errCalc ? "#e6bbbb" : "#ececec"
|
||||
}
|
||||
}}
|
||||
endAdornment={
|
||||
text.length > 0
|
||||
?
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setText("")
|
||||
constrictor("")
|
||||
}}
|
||||
>
|
||||
<Trash />
|
||||
</IconButton>
|
||||
:
|
||||
null
|
||||
}
|
||||
onChange={({ target }) => {
|
||||
setText(target.value)
|
||||
debouncedTestHC(target.value)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
import { Box, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
import { StepButtonsBlock } from "./StepButtonsBlock";
|
||||
import { CustomSelect } from "../../../../components/CustomSelect/CustomSelect";
|
||||
import { MinifiedData } from "./types";
|
||||
import { ModalTitle } from "./ModalTitle";
|
||||
|
||||
type Props = {
|
||||
users: MinifiedData[];
|
||||
handlePrevStep: () => void;
|
||||
handleNextStep: () => void;
|
||||
selectedDealUser: string | null;
|
||||
setSelectedDealPerformer: (value: string | null) => void;
|
||||
|
||||
titleProps: {
|
||||
step: number;
|
||||
title: string;
|
||||
desc: string;
|
||||
toSettings: () => void;
|
||||
};
|
||||
onScrollUsers: () => void;
|
||||
};
|
||||
|
||||
export const DealPerformers: FC<Props> = ({
|
||||
users,
|
||||
handlePrevStep,
|
||||
handleNextStep,
|
||||
selectedDealUser,
|
||||
setSelectedDealPerformer,
|
||||
onScrollUsers,
|
||||
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,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: "100%", zIndex: 3 }}>
|
||||
<ModalTitle {...titleProps} />
|
||||
<CustomSelect
|
||||
items={users}
|
||||
selectedItemId={selectedDealUser}
|
||||
setSelectedItem={setSelectedDealPerformer}
|
||||
handleScroll={onScrollUsers}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "auto",
|
||||
alignSelf: "end",
|
||||
}}
|
||||
>
|
||||
<StepButtonsBlock
|
||||
onLargeBtnClick={handleNextStep}
|
||||
onSmallBtnClick={handlePrevStep}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,50 @@
|
||||
import { FC } from "react";
|
||||
import { Button, Typography, useTheme, Box } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
deleteItem: () => void;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
export const DeleteTagQuestion: FC<Props> = ({ close, deleteItem }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
mt: "30px",
|
||||
}}
|
||||
>
|
||||
<Typography textAlign="center">Вы хотите удалить элемент?</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-evenly",
|
||||
flexWrap: "wrap",
|
||||
margin: "30px auto",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
width: "150px",
|
||||
mb: "15px"
|
||||
}}
|
||||
onClick={close}
|
||||
>
|
||||
отмена
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
width: "150px",
|
||||
mb: "15px"
|
||||
}}
|
||||
onClick={deleteItem}
|
||||
>
|
||||
удалить
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,113 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import { Button, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import GearIcon from "@icons/GearIcon";
|
||||
import React, { FC, useCallback, useMemo } from "react";
|
||||
import AccountSetting from "@icons/AccountSetting";
|
||||
|
||||
type AmoModalTitleProps = {
|
||||
step: number;
|
||||
title: string;
|
||||
desc: string;
|
||||
toSettings: () => void;
|
||||
};
|
||||
|
||||
export const ModalTitle: FC<AmoModalTitleProps> = ({
|
||||
step,
|
||||
title,
|
||||
desc,
|
||||
toSettings,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", width: "100%" }}>
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: isMobile ? "18px" : "24px",
|
||||
color: theme.palette.grey3.main,
|
||||
fontWeight: "500",
|
||||
lineHeight: "1",
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
{
|
||||
desc &&
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#4D4D4D",
|
||||
fontSize: "16px",
|
||||
m: "5px 0 15px 0",
|
||||
whiteSpace: "break-spaces",
|
||||
width: "90%"
|
||||
}}
|
||||
>
|
||||
{desc}
|
||||
</Typography>
|
||||
}
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
fontWeight: "400",
|
||||
marginTop: "4px",
|
||||
fontSize: "14px",
|
||||
lineHeight: "1",
|
||||
}}
|
||||
>
|
||||
{step} шаг
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
|
||||
<Box>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={
|
||||
<GearIcon
|
||||
color={theme.palette.brightPurple.main}
|
||||
height={"24px"}
|
||||
width={"24px"}
|
||||
/>
|
||||
}
|
||||
onClick={toSettings}
|
||||
sx={{
|
||||
height: "44px",
|
||||
padding: "0",
|
||||
width: isMobile ? "44px" : "205px",
|
||||
minWidth: isMobile ? "44px" : "auto",
|
||||
backgroundColor: "transparent",
|
||||
color: theme.palette.brightPurple.main,
|
||||
"& .MuiButton-startIcon": {
|
||||
marginRight: isMobile ? 0 : "8px",
|
||||
marginLeft: 0,
|
||||
},
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
color: theme.palette.common.white,
|
||||
"& path": {
|
||||
stroke: theme.palette.common.white,
|
||||
},
|
||||
"& circle": {
|
||||
stroke: theme.palette.common.white,
|
||||
},
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: "#581CA7",
|
||||
color: theme.palette.common.white,
|
||||
"& path": {
|
||||
stroke: theme.palette.common.white,
|
||||
},
|
||||
"& circle": {
|
||||
stroke: theme.palette.common.white,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{isMobile ? "" : "Мои настройки"}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,109 @@
|
||||
import { Box, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
import { StepButtonsBlock } from "./StepButtonsBlock";
|
||||
import { CustomSelect } from "../../../../components/CustomSelect/CustomSelect";
|
||||
import { CustomRadioGroup } from "../../../../components/CustomRadioGroup/CustomRadioGroup";
|
||||
import { MinifiedData } from "./types";
|
||||
import { ModalTitle } from "./ModalTitle";
|
||||
|
||||
type Props = {
|
||||
users: MinifiedData[];
|
||||
steps: MinifiedData[];
|
||||
handlePrevStep: () => void;
|
||||
handleNextStep: () => void;
|
||||
selectedDealUser: string | null;
|
||||
setSelectedDealPerformer: (value: string | null) => void;
|
||||
selectedStep: string | null;
|
||||
setSelectedStep: (value: string | null) => void;
|
||||
leadFlag: boolean;
|
||||
|
||||
titleProps: {
|
||||
step: number;
|
||||
title: string;
|
||||
desc: string;
|
||||
toSettings: () => void;
|
||||
};
|
||||
onScroll: () => void;
|
||||
onScrollUsers: () => void;
|
||||
};
|
||||
|
||||
export const PipelineSteps: FC<Props> = ({
|
||||
users,
|
||||
selectedDealUser,
|
||||
setSelectedDealPerformer,
|
||||
|
||||
steps,
|
||||
selectedStep,
|
||||
setSelectedStep,
|
||||
onScroll,
|
||||
onScrollUsers,
|
||||
leadFlag,
|
||||
|
||||
handlePrevStep,
|
||||
handleNextStep,
|
||||
|
||||
titleProps,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||
console.log("leadFlag")
|
||||
console.log(leadFlag)
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
overflow: "auto",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: "100%",
|
||||
overflow: "auto",
|
||||
zIndex: 3,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: "100%", zIndex: 3 }}>
|
||||
<ModalTitle {...titleProps} />
|
||||
<CustomSelect
|
||||
items={users}
|
||||
selectedItemId={selectedDealUser}
|
||||
setSelectedItem={setSelectedDealPerformer}
|
||||
handleScroll={onScrollUsers}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "13px",
|
||||
flexGrow: 1,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<CustomRadioGroup
|
||||
items={leadFlag ? steps : steps.filter(step => step.entity !== "STATUS")}
|
||||
selectedItemId={selectedStep}
|
||||
setSelectedItem={setSelectedStep}
|
||||
handleScroll={onScroll}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
alignSelf: "end",
|
||||
}}
|
||||
>
|
||||
<StepButtonsBlock
|
||||
onLargeBtnClick={handleNextStep}
|
||||
onSmallBtnClick={handlePrevStep}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,105 @@
|
||||
import { Box, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
import { StepButtonsBlock } from "./StepButtonsBlock";
|
||||
import { CustomSelect } from "../../../../components/CustomSelect/CustomSelect";
|
||||
import { CustomRadioGroup } from "../../../../components/CustomRadioGroup/CustomRadioGroup";
|
||||
import { MinifiedData } from "./types";
|
||||
import { ModalTitle } from "./ModalTitle";
|
||||
|
||||
type Props = {
|
||||
pipelines: MinifiedData[];
|
||||
users: MinifiedData[];
|
||||
handlePrevStep: () => void;
|
||||
handleNextStep: () => void;
|
||||
selectedDealUser: string | null;
|
||||
setSelectedDealPerformer: (value: string | null) => void;
|
||||
selectedPipeline: string | null;
|
||||
setSelectedPipeline: (value: string | null) => void;
|
||||
|
||||
titleProps: {
|
||||
step: number;
|
||||
title: string;
|
||||
desc: string;
|
||||
toSettings: () => void;
|
||||
}
|
||||
onScroll: () => void;
|
||||
onScrollUsers: () => void;
|
||||
};
|
||||
|
||||
export const Pipelines: FC<Props> = ({
|
||||
pipelines,
|
||||
selectedPipeline,
|
||||
setSelectedPipeline,
|
||||
titleProps,
|
||||
|
||||
users,
|
||||
selectedDealUser,
|
||||
setSelectedDealPerformer,
|
||||
onScroll,
|
||||
onScrollUsers,
|
||||
|
||||
handlePrevStep,
|
||||
handleNextStep,
|
||||
}) => {
|
||||
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,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: "100%",
|
||||
overflow: "auto",
|
||||
zIndex: 3,
|
||||
width: "100%",
|
||||
}}>
|
||||
<Box sx={{ width: "100%", zIndex: 3 }}>
|
||||
<ModalTitle
|
||||
{...titleProps}
|
||||
/>
|
||||
<CustomSelect
|
||||
items={users}
|
||||
selectedItemId={selectedDealUser}
|
||||
setSelectedItem={setSelectedDealPerformer}
|
||||
handleScroll={onScrollUsers}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "13px",
|
||||
flexGrow: 1,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<CustomRadioGroup
|
||||
items={pipelines}
|
||||
selectedItemId={selectedPipeline}
|
||||
setSelectedItem={setSelectedPipeline}
|
||||
handleScroll={onScroll}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
alignSelf: "end",
|
||||
}}
|
||||
>
|
||||
<StepButtonsBlock
|
||||
onLargeBtnClick={handleNextStep}
|
||||
onSmallBtnClick={handlePrevStep}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,306 @@
|
||||
import { FC, useEffect, useMemo, useState } from "react";
|
||||
import { ItemsSelectionView } from "./ItemsSelectionView/ItemsSelectionView";
|
||||
import { ItemDetailsView } from "./ItemDetailsView/ItemDetailsView";
|
||||
import { Box, Button, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { MinifiedData, QuestionKeys, SelectedQuestions, TagKeys, TagQuestionHC } from "../types";
|
||||
import { EntitiesQuestions } from "./EntitiesQuestions";
|
||||
import { diffArr } from "..";
|
||||
import { DataConstrictor } from "../Components/DataConstrictor";
|
||||
import { ModalTitle } from "../ModalTitle";
|
||||
import { StepButtonsBlock } from "../StepButtonsBlock";
|
||||
import { resetBitrixTagsFields } from "../useBitrixIntegration";
|
||||
|
||||
type Props = {
|
||||
selectedCurrentFields: MinifiedData[] | [];
|
||||
questionsItems: MinifiedData[] | [];
|
||||
fieldsItems: MinifiedData[] | [];
|
||||
selectedQuestions: SelectedQuestions;
|
||||
handleAddQuestion: (scope: QuestionKeys | TagKeys, id: string, type: "question" | "tag") => void;
|
||||
openDelete: (data: TagQuestionHC) => void;
|
||||
handlePrevStep: () => void;
|
||||
handleNextStep: () => void;
|
||||
FieldsAllowedFC: MinifiedData[]
|
||||
setSelectedCurrentFields: (value: MinifiedData[]) => void;
|
||||
titleProps: {
|
||||
step: number;
|
||||
title: string;
|
||||
toSettings: () => void;
|
||||
}
|
||||
onScroll: () => void;
|
||||
};
|
||||
export type QuestionPair = {
|
||||
question: string,
|
||||
field: string
|
||||
}
|
||||
const FCTranslate = {
|
||||
"name": "имя",
|
||||
"email": "почта",
|
||||
"phone": "телефон",
|
||||
"text": "номер",
|
||||
"address": "адрес",
|
||||
}
|
||||
export const BitrixQuestions: FC<Props> = ({
|
||||
selectedCurrentFields,
|
||||
questionsItems,
|
||||
fieldsItems,
|
||||
selectedQuestions = {},
|
||||
handleAddQuestion,
|
||||
handlePrevStep,
|
||||
handleNextStep,
|
||||
openDelete,
|
||||
FieldsAllowedFC,
|
||||
setSelectedCurrentFields,
|
||||
onScroll,
|
||||
titleProps,
|
||||
}) => {
|
||||
console.log("---------------------------------------------------------------------")
|
||||
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('company')) {
|
||||
selectedQuestions.company = []
|
||||
}
|
||||
if (!selectedQuestions.hasOwnProperty('contact')) {
|
||||
selectedQuestions.contact = []
|
||||
}
|
||||
if (!selectedQuestions.hasOwnProperty('deal')) {
|
||||
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 isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
const [isSelection, setIsSelection] = useState<boolean>(false);
|
||||
const [activeScope, setActiveScope] = useState<QuestionKeys | null>(null);
|
||||
const [selectedQuestion, setSelectedQuestion] = useState<string | null>(null);
|
||||
const [selectedField, setSelectedField] = useState<string | null>(null);
|
||||
|
||||
const [isCurrentFields, setIsCurrentFields] = useState(true);
|
||||
|
||||
const handleAddNewField = () => {
|
||||
if (activeScope === null || selectedQuestion === null) return;
|
||||
setActiveScope(null);
|
||||
handleAddQuestion(activeScope, selectedQuestion, "question");
|
||||
};
|
||||
const handleAddCurrentField = () => {
|
||||
if (activeScope === null || selectedField === null || selectedQuestion === null ||
|
||||
selectedCurrentFields.some(e => (e.id === selectedQuestion) && e.entity === activeScope)
|
||||
) return;
|
||||
setActiveScope(null);
|
||||
//Убедимся что такой ФК не добавлялось
|
||||
const newArray = selectedCurrentFields
|
||||
let index = -1
|
||||
selectedCurrentFields.forEach((e, i) => {
|
||||
if (e.subTitle === selectedQuestion) index = i
|
||||
})
|
||||
if (index !== -1) newArray.splice(index, 1);
|
||||
newArray.push({
|
||||
id: selectedQuestion,
|
||||
title: questionsItems.find(e => e.id === selectedQuestion)?.title || FCTranslate[selectedQuestion],
|
||||
entity: activeScope,
|
||||
bitrixId: selectedField,
|
||||
})
|
||||
setSelectedCurrentFields(newArray);
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
if (isCurrentFields) {
|
||||
handleAddCurrentField()
|
||||
} else {
|
||||
handleAddNewField()
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = (id: string, scope: QuestionKeys) => {
|
||||
openDelete({
|
||||
id,
|
||||
scope,
|
||||
type: "question",
|
||||
});
|
||||
};
|
||||
const SCFworld = (() => {
|
||||
const obj = {
|
||||
lead: [],
|
||||
company: [],
|
||||
contact: [],
|
||||
deal: [],
|
||||
// CRM_INVOICE: [],
|
||||
// CRM_SMART_INVOICE: [],
|
||||
// CRM_QUOTE: [],
|
||||
// CRM_REQUISITE: [],
|
||||
}
|
||||
selectedCurrentFields.forEach((e) => {
|
||||
if (!obj[e.entity]?.includes(e.id)) {
|
||||
obj[e.entity].push(e)
|
||||
}
|
||||
})
|
||||
return obj
|
||||
})()
|
||||
|
||||
const [sortedFieldsAllowedFC, setSortedFieldsAllowedFC] = useState<MinifiedData[]>(FieldsAllowedFC);
|
||||
const [sortedFieldsItems, setSortedFieldsItems] = useState<MinifiedData[]>(fieldsItems);
|
||||
const [sortedquestionsItems, setSortedquestionsItems] = useState<MinifiedData[]>(questionsItems);
|
||||
|
||||
const startConstrictor = (substr: string) => {
|
||||
const a = FieldsAllowedFC.filter((mData) => mData.title.toLowerCase().startsWith(substr.toLowerCase()))
|
||||
const b = fieldsItems.filter((mData) => mData.title.toLowerCase().startsWith(substr.toLowerCase()))
|
||||
const c = questionsItems.filter((mData) => mData.title.toLowerCase().startsWith(substr.toLowerCase()))
|
||||
setSortedFieldsAllowedFC(a);
|
||||
setSortedFieldsItems(b);
|
||||
setSortedquestionsItems(c);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setSortedFieldsAllowedFC(FieldsAllowedFC);
|
||||
setSortedFieldsItems(fieldsItems);
|
||||
setSortedquestionsItems(questionsItems);
|
||||
}, [activeScope])
|
||||
const [blockButton, setBlockButton] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
height: "calc( 100% - 70px )",
|
||||
overflow: "auto"
|
||||
}}
|
||||
>
|
||||
<ModalTitle
|
||||
{...titleProps}
|
||||
desc={isSelection && activeScope !== null ? "На этом этапе вы можете соотнести ваше ранее созданное поле с вопросом из квиза или добавить новое поле" : "На этом этапе вы можете добавить в поля соответствующие вопросы"}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
{isSelection && activeScope !== null ? (
|
||||
// Здесь выбираем элемент в табличку
|
||||
<>
|
||||
<Button
|
||||
disabled={blockButton}
|
||||
onClick={() => {
|
||||
setBlockButton(true)
|
||||
setTimeout(() => setBlockButton(false), 20000)
|
||||
resetBitrixTagsFields()
|
||||
}}
|
||||
sx={{
|
||||
width: !isMobile ? "250px" : "auto",
|
||||
borderRadius: "50px",
|
||||
p: "8px 20px",
|
||||
mr: "10px",
|
||||
fontSize: "16px",
|
||||
border: "#7E2AEA",
|
||||
bgcolor: "#7E2AEA1A",
|
||||
color: "#7E2AEA",
|
||||
}}
|
||||
>Обновить теги и сущности</Button>
|
||||
<DataConstrictor
|
||||
isError={false}
|
||||
constrictor={startConstrictor}
|
||||
/>
|
||||
<EntitiesQuestions
|
||||
FieldsAllowedFC={sortedFieldsAllowedFC}
|
||||
fieldsItems={sortedFieldsItems}
|
||||
items={(sortedquestionsItems.length === 0) ? [] : diffArr(sortedquestionsItems, selectedQuestions[activeScope])}
|
||||
selectedItemId={selectedQuestion}
|
||||
setSelectedQuestion={setSelectedQuestion}
|
||||
selectedField={selectedField}
|
||||
selectedCurrentFields={selectedCurrentFields}
|
||||
setSelectedField={setSelectedField}
|
||||
activeScope={activeScope}
|
||||
setIsCurrentFields={setIsCurrentFields}
|
||||
isCurrentFields={isCurrentFields}
|
||||
handleScroll={onScroll}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
// Табличка
|
||||
<ItemDetailsView
|
||||
items={[...questionsItems, ...FieldsAllowedFC]}
|
||||
setActiveScope={setActiveScope}
|
||||
selectedQuestions={{
|
||||
lead: [...selectedQuestions.lead, ...SCFworld.lead],
|
||||
company: [...selectedQuestions.company, ...SCFworld.company],
|
||||
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}
|
||||
deleteHC={handleDelete}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
alignSelf: "end",
|
||||
}}
|
||||
>
|
||||
{isSelection && activeScope !== null ?
|
||||
<StepButtonsBlock
|
||||
onLargeBtnClick={() => {
|
||||
handleAdd();
|
||||
setActiveScope(null);
|
||||
setIsSelection(false);
|
||||
}}
|
||||
largeBtnText={"Добавить"}
|
||||
onSmallBtnClick={() => {
|
||||
setActiveScope(null);
|
||||
setIsSelection(false);
|
||||
}}
|
||||
smallBtnText={"Отменить"}
|
||||
/>
|
||||
:
|
||||
<StepButtonsBlock
|
||||
onSmallBtnClick={handlePrevStep}
|
||||
onLargeBtnClick={handleNextStep}
|
||||
largeBtnText={"Сохранить"}
|
||||
/>
|
||||
|
||||
}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
97
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/Questions/CurrentFieldSelectMobile.tsx
Normal file
97
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/Questions/CurrentFieldSelectMobile.tsx
Normal file
@ -0,0 +1,97 @@
|
||||
import * as React from "react";
|
||||
import { FC, useCallback, useMemo, useRef, useState } from "react";
|
||||
import { Avatar, MenuItem, Select, SelectChangeEvent, Typography, useMediaQuery, useTheme, Box } from "@mui/material";
|
||||
import arrow_down from "@icons/arrow_down.svg";
|
||||
import { MinifiedData } from "@/pages/IntegrationsPage/IntegrationsModal/Bitrix/types";
|
||||
import { CustomRadioGroup } from "@/components/CustomRadioGroup/CustomRadioGroup";
|
||||
|
||||
type CustomSelectProps = {
|
||||
items: MinifiedData[] | [];
|
||||
selectedItemId: string | null;
|
||||
setSelectedItem: (value: string | null) => void;
|
||||
handleScroll: () => void;
|
||||
};
|
||||
|
||||
export const CurrentFieldSelect: FC<CustomSelectProps> = ({ items, selectedItemId, setSelectedItem, handleScroll }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
const [opened, setOpened] = useState<boolean>(false);
|
||||
|
||||
const toggleOpened = useCallback(() => {
|
||||
setOpened((isOpened) => !isOpened);
|
||||
}, []);
|
||||
|
||||
const onScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
|
||||
const scrollHeight = e.currentTarget.scrollHeight;
|
||||
const scrollTop = e.currentTarget.scrollTop;
|
||||
const clientHeight = e.currentTarget.clientHeight;
|
||||
const scrolledToBottom = scrollTop / (scrollHeight - clientHeight) > 0.9;
|
||||
|
||||
if (scrolledToBottom) {
|
||||
handleScroll();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const currentItem = items.find((item) => item.id === selectedItemId) || null
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
zIndex: 0,
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "56px",
|
||||
padding: "5px",
|
||||
color: "#4D4D4D",
|
||||
border: `2px solid ${theme.palette.common.white}`,
|
||||
borderRadius: "12px",
|
||||
background: "#EFF0F5",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={toggleOpened}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
marginLeft: isMobile ? "10px" : "20px",
|
||||
fontWeight: "400",
|
||||
fontSize: "18px",
|
||||
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
{currentItem?.title || "Выберите поле"}
|
||||
</Typography>
|
||||
<img
|
||||
src={arrow_down}
|
||||
alt="check"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
right: "10px",
|
||||
transform: `translateY(-50%) rotate(${opened ? "180deg" : "0deg"}`,
|
||||
height: "36px",
|
||||
width: "36px",
|
||||
}}
|
||||
className={`select-icon ${opened ? "opened" : ""}`}
|
||||
/>
|
||||
</Box>
|
||||
{opened &&
|
||||
<CustomRadioGroup
|
||||
items={items}
|
||||
selectedItemId={selectedItemId}
|
||||
setSelectedItem={setSelectedItem}
|
||||
handleScroll={() => { }}
|
||||
activeScope={undefined}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,109 @@
|
||||
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/Questions/CurrentFieldSelectMobile";
|
||||
|
||||
interface Props {
|
||||
items: MinifiedData[];
|
||||
fieldsItems: MinifiedData[];
|
||||
currentField: string;
|
||||
currentQuestion: string;
|
||||
setCurrentField: (value: string) => void;
|
||||
setCurrentQuestion: (value: string) => void;
|
||||
handleScroll: () => void;
|
||||
}
|
||||
export const CurrentFields = ({
|
||||
items,
|
||||
fieldsItems,
|
||||
currentField,
|
||||
currentQuestion,
|
||||
setCurrentField,
|
||||
setCurrentQuestion,
|
||||
handleScroll,
|
||||
}: Props) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "inline-flex",
|
||||
justifyContent: isMobile ? undefined : "space-between",
|
||||
width: "100%",
|
||||
height: "326px",
|
||||
flexDirection: isMobile ? "column" : undefined,
|
||||
gap: isMobile ? "30px" : undefined
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
mr: "22px",
|
||||
width: isMobile ? "100%" : "50%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
fontWeight: 400,
|
||||
lineHeight: "18.96px",
|
||||
textAlign: "left",
|
||||
color: "#9A9AAF",
|
||||
m: "15px 0 10px",
|
||||
|
||||
}}
|
||||
>Выберите поле</Typography>
|
||||
{isMobile &&
|
||||
<CurrentFieldSelect
|
||||
items={fieldsItems}
|
||||
selectedItemId={currentField}
|
||||
setSelectedItem={setCurrentField}
|
||||
handleScroll={handleScroll} />
|
||||
}
|
||||
{!isMobile &&
|
||||
<CustomRadioGroup
|
||||
items={fieldsItems}
|
||||
selectedItemId={currentField}
|
||||
setSelectedItem={setCurrentField}
|
||||
handleScroll={handleScroll}
|
||||
activeScope={undefined}
|
||||
/>
|
||||
}
|
||||
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "100%" : "50%",
|
||||
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
fontWeight: 400,
|
||||
lineHeight: "18.96px",
|
||||
textAlign: "left",
|
||||
color: "#9A9AAF",
|
||||
m: "15px 0 10px",
|
||||
}}
|
||||
>Выберите вопрос для этого поля</Typography>
|
||||
{isMobile &&
|
||||
<CurrentFieldSelect
|
||||
items={items}
|
||||
selectedItemId={currentQuestion}
|
||||
setSelectedItem={setCurrentQuestion}
|
||||
handleScroll={() => { }} />
|
||||
}
|
||||
{!isMobile &&
|
||||
<CustomRadioGroup
|
||||
items={items}
|
||||
selectedItemId={currentQuestion}
|
||||
setSelectedItem={setCurrentQuestion}
|
||||
handleScroll={() => { }}
|
||||
activeScope={undefined}
|
||||
/>
|
||||
}
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
import {Box, Button, useMediaQuery, useTheme} from "@mui/material"
|
||||
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 "./BitrixQuestions";
|
||||
import { diffArr } from "..";
|
||||
|
||||
type ItemsSelectionViewProps = {
|
||||
items: MinifiedData[] | [];
|
||||
fieldsItems: MinifiedData[] | [];
|
||||
selectedItemId?: string | null;
|
||||
setSelectedQuestion: (value: string | null) => void;
|
||||
selectedField?: string | null;
|
||||
setSelectedField: (value: string | null) => void;
|
||||
handleScroll: () => void;
|
||||
activeScope: TagKeys;
|
||||
FieldsAllowedFC: MinifiedData[];
|
||||
selectedCurrentFields: MinifiedData[];
|
||||
setIsCurrentFields: (a:boolean) => void;
|
||||
isCurrentFields: boolean
|
||||
}
|
||||
export const EntitiesQuestions: FC<ItemsSelectionViewProps> = ({
|
||||
items,
|
||||
fieldsItems,
|
||||
selectedItemId,
|
||||
setSelectedQuestion,
|
||||
selectedField,
|
||||
setSelectedField,
|
||||
handleScroll,
|
||||
activeScope,
|
||||
FieldsAllowedFC,
|
||||
selectedCurrentFields,
|
||||
setIsCurrentFields,
|
||||
isCurrentFields,
|
||||
}) => {
|
||||
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: "20px",
|
||||
flexGrow: 1,
|
||||
width: "100%",
|
||||
height: "346px",
|
||||
overflow: "auto"
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Button
|
||||
onClick={() => setIsCurrentFields(old => !old)}
|
||||
sx={{
|
||||
width: isMobile ? "146px" : "auto",
|
||||
borderRadius: "50px",
|
||||
p: isMobile ? "8px 20px" : "10px 30px",
|
||||
mr: "10px",
|
||||
fontSize: "16px",
|
||||
border: isCurrentFields ? "1px solid #7E2AEA" : "",
|
||||
bgcolor: isCurrentFields ? "#7E2AEA1A" : "#F2F3F7",
|
||||
color: isCurrentFields ? "#7E2AEA" : "",
|
||||
}}
|
||||
>Ваши готовые поля</Button>
|
||||
<Button
|
||||
onClick={() => setIsCurrentFields(old => !old)}
|
||||
sx={{
|
||||
width: isMobile ? "140px" : "auto",
|
||||
borderRadius: "50px",
|
||||
p: isMobile ? "8px 20px" : "10px 30px",
|
||||
fontSize: "16px",
|
||||
mt: isMobile ? "10px" : "0",
|
||||
border: !isCurrentFields ? "1px solid #7E2AEA" : "",
|
||||
bgcolor: !isCurrentFields ? "#7E2AEA1A" : "#F2F3F7",
|
||||
color: !isCurrentFields ? "#7E2AEA" : "",
|
||||
}}
|
||||
>Новые поля +</Button>
|
||||
</Box>
|
||||
{
|
||||
isCurrentFields ?
|
||||
<CurrentFields
|
||||
items={activeScope === "Contact" ? diffArr([...FieldsAllowedFC, ...items], selectedCurrentFields) : items}
|
||||
fieldsItems={fieldsItems.filter(e => e.entity === activeScope)}
|
||||
currentField={selectedField}
|
||||
currentQuestion={selectedItemId}
|
||||
setCurrentField={setSelectedField}
|
||||
setCurrentQuestion={setSelectedQuestion}
|
||||
handleScroll={handleScroll}
|
||||
/>
|
||||
:
|
||||
<NewFields
|
||||
items={items}
|
||||
currentQuestion={selectedItemId}
|
||||
setCurrentQuestion={setSelectedQuestion}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
69
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/Questions/Item/AnswerItem/AnswerItem.tsx
Normal file
69
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/Questions/Item/AnswerItem/AnswerItem.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { Box, IconButton, Typography, useTheme } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
import Trash from "@icons/trash";
|
||||
|
||||
type AnswerItemProps = {
|
||||
fieldName: string;
|
||||
fieldValue: string;
|
||||
deleteHC: () => void;
|
||||
};
|
||||
|
||||
export const AnswerItem: FC<AnswerItemProps> = ({ fieldName, fieldValue, deleteHC }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
padding: "10px 20px",
|
||||
height: "140px",
|
||||
borderBottom: `1px solid ${theme.palette.background.default}`,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontWeight: 500,
|
||||
color: theme.palette.grey3.main,
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{fieldName}
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontWeight: 400,
|
||||
color: theme.palette.grey3.main,
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{fieldValue}
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton
|
||||
sx={{
|
||||
m: "auto",
|
||||
}}
|
||||
onClick={deleteHC}
|
||||
>
|
||||
<Trash />
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
46
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/Questions/Item/IconBtnAdd/IconBtnAdd.tsx
Normal file
46
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/Questions/Item/IconBtnAdd/IconBtnAdd.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { Box, IconButton, useTheme } from "@mui/material";
|
||||
import AddPlus from "@icons/questionsPage/addPlus";
|
||||
import { FC } from "react";
|
||||
|
||||
type IconBtnAddProps = {
|
||||
onAddBtnClick: () => void;
|
||||
};
|
||||
|
||||
export const IconBtnAdd: FC<IconBtnAddProps> = ({ onAddBtnClick }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
borderBottom: `1px solid ${theme.palette.background.default}`,
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
onClick={onAddBtnClick}
|
||||
sx={{
|
||||
width: "fit-content",
|
||||
marginTop: "20px",
|
||||
marginBottom: "66px",
|
||||
circle: {
|
||||
fill: "#EEE4FC",
|
||||
},
|
||||
"&:hover": {
|
||||
circle: {
|
||||
fill: theme.palette.brightPurple.main,
|
||||
},
|
||||
},
|
||||
"&:active": {
|
||||
circle: {
|
||||
fill: "#581CA7",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<AddPlus />
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,62 @@
|
||||
import { Box, IconButton, Typography, useTheme } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
import { IconBtnAdd } from "./IconBtnAdd/IconBtnAdd";
|
||||
import { AnswerItem } from "./AnswerItem/AnswerItem";
|
||||
import { QuestionKeys, SelectedQuestions, TagKeys, SelectedTags, MinifiedData } from "../../types";
|
||||
|
||||
type ItemProps = {
|
||||
items: MinifiedData[];
|
||||
title: QuestionKeys | TagKeys;
|
||||
onAddBtnClick: () => void;
|
||||
data: SelectedTags | SelectedQuestions;
|
||||
deleteHC: (id: string, scope: QuestionKeys | TagKeys) => void;
|
||||
};
|
||||
export const Item: FC<ItemProps> = ({ items, title, onAddBtnClick, data, deleteHC }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const titleDictionary = {
|
||||
Company: "Компания",
|
||||
Lead: "Сделка",
|
||||
Contact: "Контакты",
|
||||
Customer: "Покупатели",
|
||||
};
|
||||
|
||||
const translatedTitle = titleDictionary[title];
|
||||
const selectedOptions = data[title];
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "172px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
borderRight: `1px solid ${theme.palette.background.default}`,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
alignSelf: "center",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderRadius: "12px",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
width: "156px",
|
||||
height: "40px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ fontSize: "16px", fontWeight: 500 }}>{translatedTitle}</Typography>
|
||||
</Box>
|
||||
{selectedOptions &&
|
||||
selectedOptions.map((id, index) => (
|
||||
<AnswerItem
|
||||
key={id + index}
|
||||
fieldValue={"Значение поля"}
|
||||
fieldName={items.find((e) => e.id === id)?.title || id}
|
||||
deleteHC={() => deleteHC(selectedOptions[index], title)}
|
||||
/>
|
||||
))}
|
||||
|
||||
<IconBtnAdd onAddBtnClick={onAddBtnClick} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
69
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/Questions/ItemDetailsView/ItemDetailsView.tsx
Normal file
69
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/Questions/ItemDetailsView/ItemDetailsView.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { Box, useTheme } from "@mui/material";
|
||||
import { ItemForQuestions } from "../ItemForQuestions";
|
||||
import { StepButtonsBlock } from "../../StepButtonsBlock";
|
||||
import { FC } from "react";
|
||||
import { MinifiedData, QuestionKeys, SelectedQuestions } from "../../types";
|
||||
|
||||
type TitleKeys = "Contact" | "Company" | "Lead" | "Customer";
|
||||
|
||||
type ItemDetailsViewProps = {
|
||||
items: MinifiedData[];
|
||||
setIsSelection: (value: boolean) => void;
|
||||
selectedQuestions: SelectedQuestions;
|
||||
setActiveScope: (value: QuestionKeys | null) => void;
|
||||
deleteHC: (id: string, scope: QuestionKeys) => void;
|
||||
};
|
||||
|
||||
export const ItemDetailsView: FC<ItemDetailsViewProps> = ({
|
||||
items,
|
||||
selectedQuestions,
|
||||
setIsSelection,
|
||||
setActiveScope,
|
||||
deleteHC,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "400px",
|
||||
flexGrow: 1,
|
||||
borderRadius: "10px",
|
||||
padding: "10px",
|
||||
boxShadow: "0 0 20px rgba(0, 0, 0, 0.15)",
|
||||
display: "flex",
|
||||
overflowY: "auto",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "start",
|
||||
}}
|
||||
>
|
||||
{selectedQuestions &&
|
||||
Object.keys(selectedQuestions).map((item) => (
|
||||
<ItemForQuestions
|
||||
key={item}
|
||||
title={item}
|
||||
onAddBtnClick={() => {
|
||||
setIsSelection(true);
|
||||
setActiveScope(item as QuestionKeys);
|
||||
}}
|
||||
items={items}
|
||||
data={selectedQuestions}
|
||||
deleteHC={deleteHC}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,70 @@
|
||||
import { Box, IconButton, Typography, useTheme } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
import { IconBtnAdd } from "./Item/IconBtnAdd/IconBtnAdd";
|
||||
import { AnswerItem } from "./Item/AnswerItem/AnswerItem";
|
||||
import { QuestionKeys, SelectedQuestions, TagKeys, SelectedTags, MinifiedData } from "../types";
|
||||
|
||||
type ItemProps = {
|
||||
items: MinifiedData[];
|
||||
title: QuestionKeys | TagKeys;
|
||||
onAddBtnClick: () => void;
|
||||
data: MinifiedData[];
|
||||
deleteHC: (id: string, scope: QuestionKeys | TagKeys) => void;
|
||||
};
|
||||
export const ItemForQuestions: FC<ItemProps> = ({ items, title, onAddBtnClick, data, deleteHC }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const titleDictionary = {
|
||||
"lead": "Лид",
|
||||
"company": "Компания",
|
||||
"contact": "Контакт",
|
||||
"deal": "Сделка",
|
||||
// "CRM_INVOICE": "Счёт (старый)",
|
||||
// "CRM_SMART_INVOICE": "Счёт (новый)",
|
||||
// "CRM_QUOTE": "Предложение",
|
||||
// "CRM_REQUISITE": "Реквизит"
|
||||
// Company: "Компания",
|
||||
// Lead: "Сделка",
|
||||
// Contact: "Контакты",
|
||||
// Customer: "Покупатели",
|
||||
};
|
||||
console.log("title: " + title)
|
||||
const translatedTitle = titleDictionary[title];
|
||||
const selectedOptions = data[title];
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "172px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
borderRight: `1px solid ${theme.palette.background.default}`,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
alignSelf: "center",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderRadius: "12px",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
width: "156px",
|
||||
height: "40px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ fontSize: "16px", fontWeight: 500 }}>{translatedTitle}</Typography>
|
||||
</Box>
|
||||
{selectedOptions &&
|
||||
selectedOptions.map((minifiedData, index) => (
|
||||
<AnswerItem
|
||||
key={minifiedData.id + index}
|
||||
fieldValue={"Значение поля"}
|
||||
fieldName={minifiedData.title}
|
||||
deleteHC={() => deleteHC(selectedOptions[index].id, title)}
|
||||
/>
|
||||
))}
|
||||
|
||||
<IconBtnAdd onAddBtnClick={onAddBtnClick} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
50
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/Questions/ItemsSelectionView/ItemsSelectionView.tsx
Normal file
50
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/Questions/ItemsSelectionView/ItemsSelectionView.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { CustomRadioGroup } from "../../../../../../components/CustomRadioGroup/CustomRadioGroup";
|
||||
import { StepButtonsBlock } from "../../StepButtonsBlock";
|
||||
import { FC } from "react";
|
||||
import { MinifiedData, TagKeys } from "../../types";
|
||||
|
||||
type ItemsSelectionViewProps = {
|
||||
items: MinifiedData[] | [];
|
||||
selectedItemId?: string | null;
|
||||
setSelectedItem: (value: string | null) => void;
|
||||
handleScroll?: () => void;
|
||||
activeScope: TagKeys;
|
||||
};
|
||||
|
||||
export const ItemsSelectionView: FC<ItemsSelectionViewProps> = ({
|
||||
items,
|
||||
selectedItemId,
|
||||
setSelectedItem,
|
||||
handleScroll,
|
||||
activeScope,
|
||||
}) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "20px",
|
||||
flexGrow: 1,
|
||||
width: "100%",
|
||||
height: "346px",
|
||||
}}
|
||||
>
|
||||
<CustomRadioGroup
|
||||
items={items}
|
||||
selectedItemId={selectedItemId}
|
||||
setSelectedItem={setSelectedItem}
|
||||
handleScroll={handleScroll}
|
||||
activeScope={activeScope}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,53 @@
|
||||
import { CustomRadioGroup } from "@/components/CustomRadioGroup/CustomRadioGroup"
|
||||
import { Box, Typography } from "@mui/material"
|
||||
import { MinifiedData } from "../types";
|
||||
|
||||
interface Props {
|
||||
items: MinifiedData[];
|
||||
fieldsItems: MinifiedData[];
|
||||
currentField: string;
|
||||
currentQuestion: string;
|
||||
setCurrentField: (value: string) => void;
|
||||
setCurrentQuestion: (value: string) => void;
|
||||
}
|
||||
export const NewFields = ({
|
||||
items,
|
||||
currentQuestion,
|
||||
setCurrentQuestion,
|
||||
}: Props) => {
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "inline-flex",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
height: "300px"
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
fontWeight: 400,
|
||||
lineHeight: "18.96px",
|
||||
textAlign: "left",
|
||||
color: "#9A9AAF",
|
||||
m: "15px 0 10px",
|
||||
}}
|
||||
>Выберите вопрос для поля. Название поля настроится автоматически</Typography>
|
||||
<CustomRadioGroup
|
||||
items={items}
|
||||
selectedItemId={currentQuestion}
|
||||
setSelectedItem={setCurrentQuestion}
|
||||
handleScroll={() => { }}
|
||||
activeScope={undefined}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
import { FC } from "react"
|
||||
import { Button, Typography, useTheme, Box } from "@mui/material"
|
||||
import { removeBitrixAccount } from "@/api/bitrixIntegration";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
|
||||
|
||||
interface Props {
|
||||
stopThisPage: () => void;
|
||||
handleCloseModal: () => void;
|
||||
|
||||
}
|
||||
|
||||
export const RemoveAccount: FC<Props> = ({
|
||||
stopThisPage,
|
||||
handleCloseModal,
|
||||
|
||||
}: Props) => {
|
||||
const theme = useTheme();
|
||||
const removeAccount = async () => {
|
||||
const [, error] = await removeBitrixAccount()
|
||||
|
||||
if (error) {
|
||||
enqueueSnackbar(error)
|
||||
} else {
|
||||
handleCloseModal()
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
mt: "30px"
|
||||
}}
|
||||
>
|
||||
<Typography textAlign="center">
|
||||
Вы хотите сменить аккаунт?
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-evenly",
|
||||
flexWrap: "wrap",
|
||||
margin: "30px auto",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
width: "150px",
|
||||
mb: "15px"
|
||||
}}
|
||||
onClick={stopThisPage}
|
||||
>отмена</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
width: "150px",
|
||||
mb: "15px"
|
||||
}}
|
||||
onClick={removeAccount}
|
||||
>сменить</Button>
|
||||
</Box>
|
||||
</Box >
|
||||
)
|
||||
}
|
||||
38
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/SettingsBlock/SettingItem/ResponsiblePerson/ResponsiblePerson.tsx
Normal file
38
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/SettingsBlock/SettingItem/ResponsiblePerson/ResponsiblePerson.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import { Typography, useTheme } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
|
||||
type ResponsiblePersonProps = {
|
||||
performer: string | null;
|
||||
};
|
||||
|
||||
export const ResponsiblePerson: FC<ResponsiblePersonProps> = ({
|
||||
performer,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "18px",
|
||||
fontWeight: 400,
|
||||
margin: "10px 8px 0 0",
|
||||
}}
|
||||
display={"inline-block"}
|
||||
>
|
||||
Ответственный за сделку:
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey3.main,
|
||||
fontSize: "18px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
display={"inline"}
|
||||
>
|
||||
{performer ? performer : "Не выбран"}
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
};
|
||||
27
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/SettingsBlock/SettingItem/SelectedParameter/SelectedParameter.tsx
Normal file
27
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/SettingsBlock/SettingItem/SelectedParameter/SelectedParameter.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { Typography, useTheme } from "@mui/material";
|
||||
import Box from "@mui/material/Box";
|
||||
import { FC } from "react";
|
||||
|
||||
type SelectedParameterProps = {
|
||||
parameter: string | null;
|
||||
};
|
||||
|
||||
export const SelectedParameter: FC<SelectedParameterProps> = ({
|
||||
parameter,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
padding: "15px 20px",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
borderRadius: "12px",
|
||||
marginTop: "10px",
|
||||
}}
|
||||
>
|
||||
<Typography>{parameter ? parameter : "Не выбрано"}</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
152
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/SettingsBlock/SettingItem/SettingItem.tsx
Normal file
152
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/SettingsBlock/SettingItem/SettingItem.tsx
Normal file
@ -0,0 +1,152 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import { FC, useMemo } from "react";
|
||||
import { Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { SettingItemHeader } from "./SettingItemHeader/SettingItemHeader";
|
||||
import { ResponsiblePerson } from "./ResponsiblePerson/ResponsiblePerson";
|
||||
import { SelectedParameter } from "./SelectedParameter/SelectedParameter";
|
||||
import { SelectedQuestions, SelectedTags } from "../../types";
|
||||
|
||||
type SettingItemProps = {
|
||||
step: number;
|
||||
title: string;
|
||||
setStep: (step: number) => void;
|
||||
selectedFunnelPerformer: string | null;
|
||||
selectedFunnel: string | null;
|
||||
selectedStagePerformer: string | null;
|
||||
selectedDealUser: string | null;
|
||||
selectedStage: string | null;
|
||||
selectedQuestions: SelectedQuestions;
|
||||
leadFlag: boolean
|
||||
};
|
||||
|
||||
export const SettingItem: FC<SettingItemProps> = ({
|
||||
step,
|
||||
title,
|
||||
setStep,
|
||||
selectedFunnelPerformer,
|
||||
selectedFunnel,
|
||||
selectedStagePerformer,
|
||||
selectedDealUser,
|
||||
selectedStage,
|
||||
selectedQuestions,
|
||||
leadFlag
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
if (step === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const SettingsContent = useMemo(() => {
|
||||
if (step === 1) {
|
||||
return (
|
||||
<>
|
||||
<ResponsiblePerson performer={selectedDealUser} />
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "18px",
|
||||
fontWeight: 400,
|
||||
margin: "10px 8px 0 0",
|
||||
}}
|
||||
display={"inline-block"}
|
||||
>
|
||||
Выбранный этап сделки
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey3.main,
|
||||
fontSize: "18px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
display={"inline"}
|
||||
>
|
||||
{leadFlag ? "Лид сделки" : "Дил сделки"}
|
||||
</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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (step === 5) {
|
||||
const isFilled = Object.values(selectedQuestions).some((array) => array.length > 0);
|
||||
const status = isFilled ? "Заполнено" : "Не заполнено";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "18px",
|
||||
fontWeight: 400,
|
||||
margin: "10px 8px 0 0",
|
||||
}}
|
||||
display={"inline-block"}
|
||||
>
|
||||
Статус:
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey3.main,
|
||||
fontSize: "18px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
display={"inline"}
|
||||
>
|
||||
{status}
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}, [
|
||||
step,
|
||||
selectedFunnelPerformer,
|
||||
selectedFunnel,
|
||||
selectedStagePerformer,
|
||||
selectedDealUser,
|
||||
selectedStage,
|
||||
selectedQuestions,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
padding: "20px 0",
|
||||
borderTop: `1px solid ${theme.palette.background.default}`,
|
||||
}}
|
||||
>
|
||||
<SettingItemHeader
|
||||
title={title}
|
||||
step={step}
|
||||
setStep={() => setStep(step)}
|
||||
/>
|
||||
<Box>{SettingsContent}</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
57
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/SettingsBlock/SettingItem/SettingItemHeader/SettingItemHeader.tsx
Normal file
57
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/SettingsBlock/SettingItem/SettingItemHeader/SettingItemHeader.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import { IconButton, Typography, useTheme } from "@mui/material";
|
||||
import EditPencil from "@icons/EditPencil";
|
||||
import { FC } from "react";
|
||||
|
||||
type SettingItemHeaderProps = {
|
||||
title: string;
|
||||
step: number;
|
||||
setStep: () => void;
|
||||
};
|
||||
|
||||
export const SettingItemHeader: FC<SettingItemHeaderProps> = ({
|
||||
title,
|
||||
step,
|
||||
setStep,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "14px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
{step} этап
|
||||
</Typography>
|
||||
<IconButton onClick={setStep}>
|
||||
<EditPencil
|
||||
color={theme.palette.brightPurple.main}
|
||||
width={"18px"}
|
||||
height={"18px"}
|
||||
/>
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey3.main,
|
||||
fontSize: "18px",
|
||||
fontWeight: 500,
|
||||
lineHeight: "1",
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,93 @@
|
||||
import { FC } from "react";
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { StepButtonsBlock } from "../StepButtonsBlock";
|
||||
import { SettingItem } from "./SettingItem/SettingItem";
|
||||
import { SelectedQuestions, SelectedTags } from "../types";
|
||||
|
||||
type AmoSettingsBlockProps = {
|
||||
stepTitles: string[];
|
||||
selectedFunnel: string | null;
|
||||
selectedStage: string | null;
|
||||
selectedDealUser: string | null;
|
||||
selectedQuestions: SelectedQuestions;
|
||||
toBack: () => void
|
||||
setStep: (step: number) => void
|
||||
leadFlag: boolean
|
||||
};
|
||||
|
||||
export const SettingsBlock: FC<AmoSettingsBlockProps> = ({
|
||||
stepTitles,
|
||||
selectedFunnel,
|
||||
selectedDealUser,
|
||||
selectedStage,
|
||||
selectedQuestions,
|
||||
// selectedTags,
|
||||
toBack,
|
||||
setStep,
|
||||
leadFlag,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
|
||||
return (
|
||||
<Box sx={{ flexGrow: 1, width: "100%", height: "100%"}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "24px",
|
||||
fontWeight: 500,
|
||||
lineHeight: "28.44px"
|
||||
|
||||
}}
|
||||
>
|
||||
Мои настройки
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "20px",
|
||||
width: "100%",
|
||||
minHheight: "440px",
|
||||
maxHeight: "90%",
|
||||
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
|
||||
leadFlag={leadFlag}
|
||||
step={index+1}
|
||||
title={title}
|
||||
selectedDealUser={selectedDealUser}
|
||||
selectedFunnel={selectedFunnel}
|
||||
selectedStage={selectedStage}
|
||||
selectedQuestions={selectedQuestions}
|
||||
|
||||
setStep={setStep}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
alignSelf: "end",
|
||||
}}
|
||||
>
|
||||
<StepButtonsBlock
|
||||
onSmallBtnClick={toBack}
|
||||
isLargeBtnMissing={true}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,79 @@
|
||||
import { Box, Button, useTheme } from "@mui/material";
|
||||
import ArrowLeft from "@icons/questionsPage/arrowLeft";
|
||||
import { FC } from "react";
|
||||
|
||||
type StepButtonsBlockProps = {
|
||||
onSmallBtnClick?: () => void;
|
||||
onLargeBtnClick?: () => void;
|
||||
isSmallBtnMissing?: boolean;
|
||||
isLargeBtnMissing?: boolean;
|
||||
isSmallBtnDisabled?: boolean;
|
||||
isLargeBtnDisabled?: boolean;
|
||||
smallBtnText?: string;
|
||||
largeBtnText?: string;
|
||||
largeBtnType?: "button" | "submit" | "reset";
|
||||
};
|
||||
|
||||
export const StepButtonsBlock: FC<StepButtonsBlockProps> = ({
|
||||
onSmallBtnClick,
|
||||
onLargeBtnClick,
|
||||
isSmallBtnMissing,
|
||||
isLargeBtnMissing,
|
||||
smallBtnText,
|
||||
largeBtnText,
|
||||
isSmallBtnDisabled,
|
||||
isLargeBtnDisabled,
|
||||
largeBtnType,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "end",
|
||||
alignItems: "end",
|
||||
gap: "10px",
|
||||
mt: "20px"
|
||||
}}
|
||||
>
|
||||
{isSmallBtnMissing || (
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
padding: "10px 20px",
|
||||
borderRadius: "8px",
|
||||
height: "44px",
|
||||
color: theme.palette.brightPurple.main,
|
||||
}}
|
||||
data-cy="back-button"
|
||||
disabled={isSmallBtnDisabled}
|
||||
onClick={onSmallBtnClick}
|
||||
>
|
||||
{smallBtnText ? (
|
||||
smallBtnText
|
||||
) : (
|
||||
<ArrowLeft color={theme.palette.brightPurple.main} />
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
{isLargeBtnMissing || (
|
||||
<Button
|
||||
data-cy="next-step"
|
||||
variant="contained"
|
||||
disabled={isLargeBtnDisabled}
|
||||
type={largeBtnType ? largeBtnType : "button"}
|
||||
sx={{
|
||||
height: "44px",
|
||||
padding: "10px 20px",
|
||||
borderRadius: "8px",
|
||||
background: theme.palette.brightPurple.main,
|
||||
fontSize: "18px",
|
||||
}}
|
||||
onClick={onLargeBtnClick}
|
||||
>
|
||||
{largeBtnText ? largeBtnText : "Далее"}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,456 @@
|
||||
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 { BitrixLogin } from "./BitrixLogin";
|
||||
import { Pipelines } from "./Pipelines";
|
||||
import { PipelineSteps } from "./PipelineSteps";
|
||||
import { DealPerformers } from "./DealPerformers";
|
||||
import { СhoosePerson } from "./СhoosePerson";
|
||||
import { BitrixQuestions } from "./Questions/BitrixQuestions";
|
||||
import { ModalTitle } from "./ModalTitle";
|
||||
import { SettingsBlock } from "./SettingsBlock/SettingsBlock";
|
||||
import { AccountInfo } from "./AccountInfo";
|
||||
import { MinifiedData, QuestionKeys, TagKeys, TagQuestionHC } from "./types";
|
||||
import { Quiz } from "@/model/quiz/quiz";
|
||||
import { AccountResponse, setIntegrationRules, updateIntegrationRules } from "@/api/bitrixIntegration";
|
||||
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: () => void;
|
||||
setPageOfPipelinesSteps: () => void;
|
||||
setPageOfUsers: () => void;
|
||||
setPageOfTags: () => void;
|
||||
setPageOfFields: () => void;
|
||||
setSelectedCurrentFields: any;
|
||||
handleCloseModal: any;
|
||||
leadFlag: boolean;
|
||||
leadFlagHC: (s: boolean) => void;
|
||||
}
|
||||
|
||||
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,
|
||||
setPageOfFields,
|
||||
setSelectedCurrentFields,
|
||||
handleCloseModal,
|
||||
leadFlag,
|
||||
leadFlagHC,
|
||||
fullArrayOfPipelinesSteps,
|
||||
}: Props) => {
|
||||
const [step, setStep] = useState(0);
|
||||
const [specialPage, setSpecialPage] = useState<
|
||||
"deleteCell" | "removeAccount" | "settingsBlock" | "accountInfo" | "bitrixLogin" | ""
|
||||
>(accountInfo ? "accountInfo" : "bitrixLogin");
|
||||
const [openDelete, setOpenDelete] = useState<TagQuestionHC | null>(null);
|
||||
|
||||
const startDeleteTagQuestion = (itemForDelete) => {
|
||||
setOpenDelete(itemForDelete);
|
||||
setSpecialPage("deleteCell");
|
||||
};
|
||||
|
||||
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) => {
|
||||
return {
|
||||
...prevState,
|
||||
[scope]: [...prevState[scope as TagKeys], id],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (type === "question") {
|
||||
const q = questions.find((e) => e.backendId === Number(id));
|
||||
setSelectedQuestions((prevState) => {
|
||||
return {
|
||||
...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
|
||||
);
|
||||
setSelectedQuestions(newArray);
|
||||
setSelectedCurrentFields(selectedCurrentFields.filter((e) => e.id !== openDelete.id));
|
||||
}
|
||||
setOpenDelete(null);
|
||||
closeSpecialPage();
|
||||
};
|
||||
|
||||
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: selectedDealUser,
|
||||
LeadFlag: leadFlag,
|
||||
|
||||
StageID: "",
|
||||
SourceID: "",
|
||||
StatusID: "",
|
||||
// FieldsRule: questionsBackend,
|
||||
// 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 = {
|
||||
lead: { QuestionID: {} },
|
||||
deal: { QuestionID: {} },
|
||||
company: { QuestionID: {} },
|
||||
contact: {
|
||||
QuestionID: {},
|
||||
ContactRuleMap: {},
|
||||
},
|
||||
// CRM_INVOICE: { QuestionID: {} },
|
||||
// CRM_SMART_INVOICE: { QuestionID: {} },
|
||||
// CRM_QUOTE: { QuestionID: {} },
|
||||
// CRM_REQUISITE: { QuestionID: {} },
|
||||
};
|
||||
|
||||
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.bitrixId);
|
||||
} else {
|
||||
FieldsRule[data.entity].QuestionID[data.id] = Number(data.bitrixId) || 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 = [
|
||||
{
|
||||
isSettingsAvailable: true,
|
||||
component: (
|
||||
<СhoosePerson
|
||||
leadFlag={leadFlag}
|
||||
leadFlagHC={leadFlagHC}
|
||||
handlePrevStep={() => setSpecialPage("accountInfo")}
|
||||
handleNextStep={handleNextStep}
|
||||
titleProps={{
|
||||
step: step + 2,
|
||||
title: "Выбор этапа взаимодействия",
|
||||
desc: "",
|
||||
toSettings: () => setSpecialPage("settingsBlock"),
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
isSettingsAvailable: true,
|
||||
component: (
|
||||
<Pipelines
|
||||
users={arrayOfUsers}
|
||||
pipelines={arrayOfPipelines}
|
||||
handlePrevStep={handlePrevStep}
|
||||
handleNextStep={handleNextStep}
|
||||
selectedDealUser={selectedDealUser}
|
||||
setSelectedDealPerformer={setSelectedDealPerformer}
|
||||
selectedPipeline={selectedPipeline}
|
||||
setSelectedPipeline={setSelectedPipeline}
|
||||
titleProps={{
|
||||
step: step + 2,
|
||||
title: "Выбор воронки",
|
||||
desc: "На этом этапе вы можете выбрать нужную воронку и ответственного за сделку",
|
||||
toSettings: () => setSpecialPage("settingsBlock"),
|
||||
}}
|
||||
onScroll={setPageOfPipelines}
|
||||
onScrollUsers={setPageOfUsers}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
isSettingsAvailable: true,
|
||||
component: (
|
||||
<PipelineSteps
|
||||
users={arrayOfUsers}
|
||||
selectedDealUser={selectedDealUser}
|
||||
selectedStep={selectedPipelineStep}
|
||||
steps={arrayOfPipelinesSteps}
|
||||
setSelectedDealPerformer={setSelectedDealPerformer}
|
||||
setSelectedStep={setSelectedPipelineStep}
|
||||
handlePrevStep={handlePrevStep}
|
||||
handleNextStep={handleNextStep}
|
||||
titleProps={{
|
||||
step: step + 2,
|
||||
title: "Выбор этапа воронки",
|
||||
desc: "На этом этапе вы можете выбрать нужный этап и ответственного за сделку",
|
||||
toSettings: () => setSpecialPage("settingsBlock"),
|
||||
}}
|
||||
onScroll={setPageOfPipelinesSteps}
|
||||
onScrollUsers={setPageOfUsers}
|
||||
leadFlag={leadFlag}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
isSettingsAvailable: true,
|
||||
component: (
|
||||
<DealPerformers
|
||||
handlePrevStep={handlePrevStep}
|
||||
handleNextStep={handleNextStep}
|
||||
users={arrayOfUsers}
|
||||
selectedDealUser={selectedDealUser}
|
||||
setSelectedDealPerformer={setSelectedDealPerformer}
|
||||
titleProps={{
|
||||
step: step + 2,
|
||||
title: "Сделка",
|
||||
desc: "На этом этапе вы можете выбрать ответственного за сделку",
|
||||
toSettings: () => setSpecialPage("settingsBlock"),
|
||||
}}
|
||||
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,
|
||||
component: (
|
||||
<BitrixQuestions
|
||||
setSelectedCurrentFields={setSelectedCurrentFields}
|
||||
fieldsItems={arrayOfFields}
|
||||
selectedCurrentFields={selectedCurrentFields}
|
||||
questionsItems={minifiedQuestions}
|
||||
selectedQuestions={selectedQuestions}
|
||||
openDelete={startDeleteTagQuestion}
|
||||
handleAddQuestion={handleAddTagQuestion}
|
||||
handlePrevStep={handlePrevStep}
|
||||
handleNextStep={handleSave}
|
||||
FieldsAllowedFC={FieldsAllowedFC}
|
||||
titleProps={{
|
||||
step: step + 2,
|
||||
title: "Соотнесение вопросов и сущностей",
|
||||
toSettings: () => setSpecialPage("settingsBlock"),
|
||||
}}
|
||||
onScroll={setPageOfFields}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const stepTitles = steps.map((step) => step.title);
|
||||
|
||||
switch (specialPage) {
|
||||
case "deleteCell":
|
||||
return (
|
||||
<DeleteTagQuestion
|
||||
close={closeSpecialPage}
|
||||
deleteItem={handleDeleteTagQuestion}
|
||||
/>
|
||||
);
|
||||
case "removeAccount":
|
||||
return (
|
||||
<RemoveAccount
|
||||
handleCloseModal={handleCloseModal}
|
||||
stopThisPage={closeSpecialPage}
|
||||
/>
|
||||
);
|
||||
case "settingsBlock":
|
||||
return (
|
||||
<SettingsBlock
|
||||
stepTitles={stepTitles}
|
||||
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}
|
||||
toBack={() => closeSpecialPage()}
|
||||
setStep={(step: number) => {
|
||||
closeSpecialPage();
|
||||
setStep(step - 1);
|
||||
}}
|
||||
leadFlag={leadFlag}
|
||||
/>
|
||||
);
|
||||
case "bitrixLogin":
|
||||
return <BitrixLogin handleNextStep={handleNextStep} />;
|
||||
case "accountInfo":
|
||||
return (
|
||||
<AccountInfo
|
||||
handleNextStep={() => closeSpecialPage()}
|
||||
accountInfo={accountInfo}
|
||||
toChangeAccount={() => setSpecialPage("removeAccount")}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
{steps[step].component}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,158 @@
|
||||
import { FC, useState } from "react";
|
||||
import { Box, Button, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { ItemsSelectionView } from "../Questions/ItemsSelectionView/ItemsSelectionView";
|
||||
import { TagsDetailsView } from "./TagsDetailsView/TagsDetailsView";
|
||||
import { MinifiedData, QuestionKeys, SelectedTags, TagKeys, TagQuestionHC } from "../types";
|
||||
import { DataConstrictor } from "../Components/DataConstrictor";
|
||||
import { ModalTitle } from "../ModalTitle";
|
||||
import { StepButtonsBlock } from "../StepButtonsBlock";
|
||||
import { resetBitrixTagsFields } from "../useBitrixIntegration";
|
||||
|
||||
type Props = {
|
||||
tagsItems: MinifiedData[] | [];
|
||||
selectedTags: SelectedTags;
|
||||
handleAddTag: (scope: QuestionKeys | TagKeys, id: string, type: "question" | "tag") => void;
|
||||
openDelete: (data: TagQuestionHC) => void;
|
||||
handlePrevStep: () => void;
|
||||
handleNextStep: () => void;
|
||||
titleProps: {
|
||||
step: number;
|
||||
title: string;
|
||||
desc: string;
|
||||
toSettings: () => void;
|
||||
}
|
||||
onScroll: () => void;
|
||||
|
||||
};
|
||||
|
||||
export const BitrixTags: FC<Props> = ({
|
||||
tagsItems,
|
||||
selectedTags,
|
||||
handleAddTag,
|
||||
openDelete,
|
||||
handlePrevStep,
|
||||
handleNextStep,
|
||||
onScroll,
|
||||
titleProps,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
const [sortedTagsItems, setSortedTagsItems] = useState<MinifiedData[] | []>(tagsItems);
|
||||
|
||||
const [isSelection, setIsSelection] = useState<boolean>(false);
|
||||
const [activeScope, setActiveScope] = useState<TagKeys | null>(null);
|
||||
const [selectedTag, setSelectedTag] = useState<string | null>(null);
|
||||
|
||||
const handleAdd = () => {
|
||||
if (activeScope === null || selectedTag === null) return;
|
||||
setActiveScope(null);
|
||||
handleAddTag(activeScope, selectedTag, "tag");
|
||||
};
|
||||
const handleDelete = (id: string, scope: TagKeys) => {
|
||||
openDelete({
|
||||
id,
|
||||
scope,
|
||||
type: "tag",
|
||||
});
|
||||
};
|
||||
const startConstrictor = (substr: string) => {
|
||||
const a = tagsItems.filter((mData) => mData.title.toLowerCase().startsWith(substr.toLowerCase()))
|
||||
setSortedTagsItems(a);
|
||||
}
|
||||
|
||||
const [blockButton, setBlockButton] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
height: "calc( 100% - 70px )",
|
||||
overflow: "auto"
|
||||
}}
|
||||
>
|
||||
<ModalTitle
|
||||
{...titleProps}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
{isSelection && activeScope !== null ? (
|
||||
// Здесь выбираем элемент в табличку
|
||||
<>
|
||||
<Button
|
||||
disabled={blockButton}
|
||||
onClick={() => {
|
||||
setBlockButton(true)
|
||||
setTimeout(() => setBlockButton(false), 20000)
|
||||
resetBitrixTagsFields()
|
||||
}}
|
||||
sx={{
|
||||
width: !isMobile ? "250px" : "auto",
|
||||
borderRadius: "50px",
|
||||
p: "8px 20px",
|
||||
mr: "10px",
|
||||
fontSize: "16px",
|
||||
border: "#7E2AEA",
|
||||
bgcolor: "#7E2AEA1A",
|
||||
color: "#7E2AEA",
|
||||
}}
|
||||
>Обновить теги и сущности</Button>
|
||||
<DataConstrictor
|
||||
isError={sortedTagsItems.length === 0}
|
||||
constrictor={startConstrictor}
|
||||
/>
|
||||
<ItemsSelectionView
|
||||
items={sortedTagsItems}
|
||||
selectedItemId={selectedTag}
|
||||
setSelectedItem={setSelectedTag}
|
||||
handleScroll={onScroll}
|
||||
activeScope={activeScope}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
// Табличка
|
||||
<TagsDetailsView
|
||||
items={tagsItems}
|
||||
setActiveScope={setActiveScope}
|
||||
selectedTags={selectedTags}
|
||||
setIsSelection={setIsSelection}
|
||||
deleteHC={handleDelete}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
alignSelf: "end",
|
||||
}}
|
||||
>
|
||||
|
||||
{
|
||||
isSelection && activeScope !== null ?
|
||||
<StepButtonsBlock
|
||||
onLargeBtnClick={() => {
|
||||
handleAdd();
|
||||
setActiveScope(null);
|
||||
setIsSelection(false);
|
||||
}}
|
||||
largeBtnText={"Добавить"}
|
||||
onSmallBtnClick={() => {
|
||||
setActiveScope(null);
|
||||
setIsSelection(false);
|
||||
}}
|
||||
smallBtnText={"Отменить"}
|
||||
/>
|
||||
:
|
||||
<StepButtonsBlock
|
||||
onSmallBtnClick={handlePrevStep}
|
||||
onLargeBtnClick={handleNextStep}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
89
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/Tags/TagsDetailsView/TagsDetailsView.tsx
Normal file
89
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/Tags/TagsDetailsView/TagsDetailsView.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { StepButtonsBlock } from "../../StepButtonsBlock";
|
||||
import { FC } from "react";
|
||||
import { Item } from "../../Questions/Item/Item";
|
||||
import { MinifiedData, SelectedTags, TagKeys } from "../../types";
|
||||
|
||||
type TagsDetailsViewProps = {
|
||||
items: MinifiedData[];
|
||||
setIsSelection: (value: boolean) => void;
|
||||
setActiveScope: (value: TagKeys | null) => void;
|
||||
selectedTags: SelectedTags;
|
||||
deleteHC: (id: string, scope: TagKeys) => void;
|
||||
};
|
||||
|
||||
export const TagsDetailsView: FC<TagsDetailsViewProps> = ({
|
||||
items,
|
||||
setActiveScope,
|
||||
selectedTags,
|
||||
setIsSelection,
|
||||
deleteHC,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "15px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
maxHeight: "380px",
|
||||
flexGrow: 1,
|
||||
borderRadius: "10px",
|
||||
padding: "10px",
|
||||
boxShadow: "0 0 20px rgba(0, 0, 0, 0.15)",
|
||||
display: "flex",
|
||||
flexDirection: isMobile ? "column" : "row",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
p: isMobile ? "0" : "0 40px",
|
||||
m: isMobile ? "5px auto 20px" : "0",
|
||||
borderRight: isMobile ? "none" : `1px solid ${theme.palette.background.default}`,
|
||||
height: isMobile ? "auto" : "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ fontSize: "14px", color: theme.palette.grey2.main }}>Результат</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
overflowY: "auto",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "start",
|
||||
}}
|
||||
>
|
||||
{selectedTags &&
|
||||
Object.keys(selectedTags).map((item) => (
|
||||
<Item
|
||||
key={item}
|
||||
items={items}
|
||||
title={item as TagKeys}
|
||||
onAddBtnClick={() => {
|
||||
setIsSelection(true);
|
||||
setActiveScope(item as TagKeys);
|
||||
}}
|
||||
data={selectedTags}
|
||||
deleteHC={deleteHC}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
178
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/index.tsx
Normal file
178
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/index.tsx
Normal file
@ -0,0 +1,178 @@
|
||||
import { FC, 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 CloseIcon from "@mui/icons-material/Close";
|
||||
|
||||
import { useBitrixIntegration } from "./useBitrixIntegration";
|
||||
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 BitrixModal: 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,
|
||||
setPageOfFields,
|
||||
setSelectedCurrentFields,
|
||||
leadFlag,
|
||||
leadFlagHC,
|
||||
fullArrayOfPipelinesSteps
|
||||
} = useBitrixIntegration({
|
||||
quizID: quiz.backendId,
|
||||
isModalOpen,
|
||||
isTryRemoveAccount,
|
||||
questions
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isModalOpen}
|
||||
onClose={handleCloseModal}
|
||||
fullWidth
|
||||
// fullScreen={isMobile}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
maxWidth: isTablet ? "100%" : "919px",
|
||||
height: "658px",
|
||||
borderRadius: "12px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<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>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
padding: "15px 20px 15px",
|
||||
flexGrow: 1,
|
||||
height: "100%",
|
||||
overflow: "auto"
|
||||
}}
|
||||
>
|
||||
{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}
|
||||
setPageOfFields={setPageOfFields}
|
||||
setSelectedCurrentFields={setSelectedCurrentFields}
|
||||
handleCloseModal={handleCloseModal}
|
||||
leadFlag={leadFlag}
|
||||
leadFlagHC={leadFlagHC}
|
||||
fullArrayOfPipelinesSteps={fullArrayOfPipelinesSteps}
|
||||
/>
|
||||
}
|
||||
</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));
|
||||
}
|
||||
34
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/types.ts
Normal file
34
src/pages/IntegrationsPage/IntegrationsModal/Bitrix/types.ts
Normal file
@ -0,0 +1,34 @@
|
||||
export type TagKeys = "lead"
|
||||
| "company"
|
||||
| "contact"
|
||||
| "deal"
|
||||
| "CRM_INVOICE"
|
||||
| "CRM_SMART_INVOICE"
|
||||
| "CRM_QUOTE"
|
||||
| "CRM_REQUISITE";
|
||||
|
||||
export type SelectedTags = Record<TagKeys, number[]>;
|
||||
|
||||
export type QuestionKeys = "lead"
|
||||
| "company"
|
||||
| "contact"
|
||||
| "deal"
|
||||
| "CRM_INVOICE"
|
||||
| "CRM_SMART_INVOICE"
|
||||
| "CRM_QUOTE"
|
||||
| "CRM_REQUISITE";
|
||||
export type SelectedQuestions = Record<QuestionKeys, MinifiedData[]>;
|
||||
|
||||
export type MinifiedData = {
|
||||
id: string;
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
entity?: TagKeys;
|
||||
amoId?: string;
|
||||
};
|
||||
|
||||
export type TagQuestionHC = {
|
||||
scope: QuestionKeys | TagKeys;
|
||||
id: string;
|
||||
type: "question" | "tag";
|
||||
};
|
||||
@ -0,0 +1,444 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import type { TagKeys, SelectedTags, QuestionKeys, SelectedQuestions, MinifiedData } from "./types";
|
||||
import {
|
||||
AccountResponse,
|
||||
getIntegrationRules,
|
||||
getPipelines,
|
||||
getSteps,
|
||||
getTags,
|
||||
getUsers,
|
||||
getAccount,
|
||||
FieldsRule,
|
||||
getFields,
|
||||
connectBitrix,
|
||||
} from "@/api/bitrixIntegration";
|
||||
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
import { UntypedQuizQuestion } from "@/model/questionTypes/shared";
|
||||
|
||||
const SIZE = 25;
|
||||
|
||||
interface Props {
|
||||
isModalOpen: boolean;
|
||||
isTryRemoveAccount: boolean;
|
||||
quizID: number;
|
||||
questions: (AnyTypedQuizQuestion | UntypedQuizQuestion)[]
|
||||
}
|
||||
|
||||
const FCTranslate = {
|
||||
"name": "имя",
|
||||
"email": "почта",
|
||||
"phone": "телефон",
|
||||
"text": "номер",
|
||||
"address": "адрес",
|
||||
}
|
||||
|
||||
let isReadyGetPipeline = true;
|
||||
let isReadyGetPipelineStep = true;
|
||||
let isReadyGetUsers = true;
|
||||
let isReadyGetTags = true;
|
||||
let isReadyGetFields = true;
|
||||
|
||||
export const useBitrixIntegration = ({ isModalOpen, isTryRemoveAccount, quizID, questions }: Props) => {
|
||||
const [isLoadingPage, setIsLoadingPage] = useState<boolean>(true);
|
||||
const [firstRules, setFirstRules] = useState<boolean>(false);
|
||||
const [accountInfo, setAccountInfo] = useState<AccountResponse | null>(null);
|
||||
|
||||
const [leadFlag, setLeadFlag] = useState<boolean>(false);
|
||||
|
||||
const [arrayOfPipelines, setArrayOfPipelines] = useState<MinifiedData[]>([]);
|
||||
const [arrayOfPipelinesSteps, setArrayOfPipelinesSteps] = useState<MinifiedData[]>([]);
|
||||
const [fullArrayOfPipelinesSteps, setFullArrayOfPipelinesSteps] = useState<any[]>([]);
|
||||
const [arrayOfUsers, setArrayOfUsers] = useState<MinifiedData[]>([]);
|
||||
const [arrayOfTags, setArrayOfTags] = useState<MinifiedData[]>([]);
|
||||
const [arrayOfFields, setArrayOfFields] = useState<MinifiedData[]>([]);
|
||||
|
||||
const [selectedPipeline, setSelectedPipeline] = useState<string | null>(null);
|
||||
const [selectedPipelineStep, setSelectedPipelineStep] = useState<string | null>(null);
|
||||
const [selectedDealUser, setSelectedDealPerformer] = useState<string | null>(null);
|
||||
const [selectedCurrentFields, setSelectedCurrentFields] = useState<MinifiedData[]>([]);
|
||||
|
||||
const [questionsBackend, setQuestionsBackend] = useState<FieldsRule>({} as FieldsRule);
|
||||
const [selectedTags, setSelectedTags] = useState<SelectedTags>({
|
||||
Lead: [],
|
||||
Contact: [],
|
||||
Company: [],
|
||||
Customer: [],
|
||||
});
|
||||
const [selectedQuestions, setSelectedQuestions] = useState<SelectedQuestions>({
|
||||
lead: [],
|
||||
company: [],
|
||||
contact: [],
|
||||
deal: [],
|
||||
CRM_INVOICE: [],
|
||||
CRM_SMART_INVOICE: [],
|
||||
CRM_QUOTE: [],
|
||||
CRM_REQUISITE: [],
|
||||
});
|
||||
|
||||
const [pageOfPipelines, setPageOfPipelines] = useState(1);
|
||||
const [pageOfPipelinesSteps, setPageOfPipelinesSteps] = useState(1);
|
||||
const [pageOfUsers, setPageOfUsers] = useState(1);
|
||||
const [pageOfTags, setPageOfTags] = useState(1);
|
||||
const [pageOfFields, setPageOfFields] = useState(1);
|
||||
|
||||
const leadFlagHC = (s:boolean) => {
|
||||
setLeadFlag(s)
|
||||
};
|
||||
const selectedPipelineHC = (id:string | null) => {
|
||||
setSelectedPipeline(id);
|
||||
isReadyGetPipelineStep = true;
|
||||
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(() => {
|
||||
const fetchAccountRules = async () => {
|
||||
setIsLoadingPage(true);
|
||||
const [account, accountError] = await getAccount();
|
||||
|
||||
if (accountError) {
|
||||
if (!accountError.includes("Not Found")) enqueueSnackbar(accountError);
|
||||
setAccountInfo(null);
|
||||
}
|
||||
if (account) {
|
||||
setAccountInfo(account);
|
||||
}
|
||||
const [settingsResponse, rulesError] = await getIntegrationRules(quizID.toString());
|
||||
|
||||
if (rulesError) {
|
||||
if (rulesError === "first") setFirstRules(true);
|
||||
if (!rulesError.includes("Not Found") && !rulesError.includes("first")) enqueueSnackbar(rulesError);
|
||||
}
|
||||
if (settingsResponse) {
|
||||
if (settingsResponse.PipelineID) selectedPipelineHC(settingsResponse.PipelineID.toString());
|
||||
if (settingsResponse.StepID) setSelectedPipelineStep(settingsResponse.StepID.toString());
|
||||
if (settingsResponse.PerformerID) setSelectedDealPerformer(settingsResponse.PerformerID.toString());
|
||||
|
||||
if (Boolean(settingsResponse.FieldsRule) && Object.keys(settingsResponse?.FieldsRule).length > 0) {
|
||||
const gottenQuestions = { ...selectedQuestions };
|
||||
setQuestionsBackend(settingsResponse.FieldsRule);
|
||||
|
||||
for (let key in settingsResponse.FieldsRule) {
|
||||
if (
|
||||
settingsResponse.FieldsRule[key as QuestionKeys] !== null
|
||||
) {
|
||||
const gottenList = settingsResponse.FieldsRule[key as QuestionKeys];
|
||||
|
||||
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] = []
|
||||
|
||||
gottenQuestions[key as QuestionKeys].push({
|
||||
id: qId,
|
||||
title: q.title,
|
||||
entity: key,
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
if (key === "Contact") {
|
||||
const MAP = settingsResponse.FieldsRule[key as QuestionKeys].ContactRuleMap
|
||||
|
||||
const list = []
|
||||
for (let key in MAP) {
|
||||
list.push({
|
||||
id: key,
|
||||
title: FCTranslate[key],
|
||||
entity: "Contact",
|
||||
bitrixId: MAP[key].toString(),
|
||||
})
|
||||
}
|
||||
setSelectedCurrentFields(list)
|
||||
}
|
||||
}
|
||||
}
|
||||
setSelectedQuestions(gottenQuestions);
|
||||
}
|
||||
|
||||
if (Boolean(settingsResponse.TagsToAdd) && Object.keys(settingsResponse.TagsToAdd).length > 0) {
|
||||
const gottenTags = { ...selectedTags };
|
||||
|
||||
for (let key in settingsResponse.TagsToAdd) {
|
||||
const gottenList = settingsResponse.TagsToAdd[key as TagKeys];
|
||||
if (gottenList !== null && Array.isArray(gottenList)) {
|
||||
gottenTags[key as TagKeys] = gottenList.map((e) => e.toString());
|
||||
}
|
||||
}
|
||||
setSelectedTags(gottenTags);
|
||||
}
|
||||
setFirstRules(false);
|
||||
}
|
||||
setIsLoadingPage(false);
|
||||
};
|
||||
|
||||
fetchAccountRules();
|
||||
|
||||
}, [isModalOpen, isTryRemoveAccount]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const transletedQuestions = {...selectedQuestions}
|
||||
|
||||
Object.keys(selectedQuestions)?.forEach((column) => {
|
||||
selectedQuestions[column].forEach((minifiedData) => {
|
||||
const q = questions.find(e => e.backendId === Number(minifiedData.id)) || {};
|
||||
transletedQuestions[column].push({
|
||||
...minifiedData,
|
||||
title: q.title || transletedQuestions[column].title
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
setSelectedQuestions(transletedQuestions)
|
||||
}, [questions])
|
||||
|
||||
useEffect(() => {
|
||||
if (isReadyGetPipeline) {
|
||||
getPipelines({
|
||||
page: pageOfPipelines,
|
||||
size: SIZE,
|
||||
}).then(([response]) => {
|
||||
if (response && response.items !== null) {
|
||||
const minifiedPipelines: MinifiedData[] = [];
|
||||
|
||||
response.items.forEach((step) => {
|
||||
minifiedPipelines.push({
|
||||
id: step.bitrixID.toString(),
|
||||
title: step.name || "Нет названия",
|
||||
});
|
||||
});
|
||||
setArrayOfPipelines((prevItems) => [...prevItems, ...minifiedPipelines]);
|
||||
setPageOfPipelinesSteps(1);
|
||||
} else {
|
||||
isReadyGetPipeline = false
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [pageOfPipelines]);
|
||||
useEffect(() => {
|
||||
if (isReadyGetPipelineStep && selectedPipeline !== null) {
|
||||
const oldData = pageOfPipelinesSteps === 1 ? [] : arrayOfPipelinesSteps;
|
||||
const oldFullData = pageOfPipelinesSteps === 1 ? [] : fullArrayOfPipelinesSteps;
|
||||
|
||||
getSteps({
|
||||
page: pageOfPipelinesSteps,
|
||||
size: SIZE,
|
||||
pipelineId: Number(selectedPipeline),
|
||||
}).then(([response]) => {
|
||||
if (response && response.items !== null) {
|
||||
// Фильтруем только нужные элементы
|
||||
const filteredItems = response.items.filter(item =>
|
||||
item.entityID === "STATUS" ||
|
||||
item.entityID === "SOURCE" ||
|
||||
(typeof item.entityID === 'string' && item.entityID.startsWith("DEAL_STAGE"))
|
||||
);
|
||||
|
||||
// Минифицируем отфильтрованные данные
|
||||
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(() => {
|
||||
if (isReadyGetUsers) {
|
||||
getUsers({
|
||||
page: pageOfUsers,
|
||||
size: SIZE,
|
||||
}).then(([response]) => {
|
||||
if (response && response.items !== null) {
|
||||
const minifiedUsers: MinifiedData[] = [];
|
||||
|
||||
response.items.forEach((step) => {
|
||||
minifiedUsers.push({
|
||||
id: step.bitrixUserID.toString(),
|
||||
title: step.name || "Нет имени",
|
||||
});
|
||||
});
|
||||
setArrayOfUsers((prevItems) => [...prevItems, ...minifiedUsers]);
|
||||
} else {
|
||||
isReadyGetUsers = false
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [pageOfUsers]);
|
||||
// useEffect(() => {
|
||||
// if (isReadyGetTags) {
|
||||
// getTags({
|
||||
// page: pageOfTags,
|
||||
// size: SIZE,
|
||||
// }).then(([response]) => {
|
||||
// if (response && response.items !== null) {
|
||||
// const minifiedTags: MinifiedData[] = [];
|
||||
|
||||
// response.items.forEach((step) => {
|
||||
// minifiedTags.push({
|
||||
// id: step.BitrixID.toString(),
|
||||
// title: step.Name,
|
||||
// entity:
|
||||
// step.Entity === "leads"
|
||||
// ? "Lead"
|
||||
// : step.Entity === "contacts"
|
||||
// ? "Contact"
|
||||
// : step.Entity === "companies"
|
||||
// ? "Company"
|
||||
// : "Customer",
|
||||
// });
|
||||
// });
|
||||
// setArrayOfTags((prevItems) => [...prevItems, ...minifiedTags]);
|
||||
// } else {
|
||||
// isReadyGetTags = false
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }, [pageOfTags]);
|
||||
useEffect(() => {
|
||||
if (isReadyGetFields) {
|
||||
getFields({
|
||||
page: pageOfFields,
|
||||
size: 1000,
|
||||
}).then(([response]) => {
|
||||
if (response && response.items !== null) {
|
||||
const minifiedTags: MinifiedData[] = [];
|
||||
|
||||
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({
|
||||
id: field.bitrixID.toString(),
|
||||
title: field.editFromLabel,
|
||||
entity: entity,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
setArrayOfFields((prevItems) => [...prevItems, ...minifiedTags]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
isReadyGetFields = false
|
||||
}
|
||||
}, [pageOfFields]);
|
||||
useEffect(() => () => {
|
||||
isReadyGetPipeline = true;
|
||||
isReadyGetPipelineStep = true;
|
||||
isReadyGetUsers = true;
|
||||
isReadyGetTags = true;
|
||||
isReadyGetFields = true;
|
||||
}, [])
|
||||
|
||||
return {
|
||||
isLoadingPage,
|
||||
firstRules,
|
||||
accountInfo,
|
||||
arrayOfPipelines,
|
||||
arrayOfPipelinesSteps,
|
||||
arrayOfUsers,
|
||||
arrayOfTags,
|
||||
arrayOfFields,
|
||||
selectedPipeline,
|
||||
setSelectedPipeline: selectedPipelineHC,
|
||||
selectedCurrentFields,
|
||||
selectedPipelineStep,
|
||||
setSelectedPipelineStep,
|
||||
selectedDealUser,
|
||||
setSelectedDealPerformer,
|
||||
questionsBackend,
|
||||
selectedTags,
|
||||
setSelectedTags,
|
||||
selectedQuestions,
|
||||
setSelectedQuestions,
|
||||
setPageOfPipelines: () => setPageOfPipelines(old => old + 1),
|
||||
setPageOfPipelinesSteps: () => setPageOfPipelinesSteps(old => old + 1),
|
||||
setPageOfUsers: () => setPageOfUsers(old => old + 1),
|
||||
setPageOfTags: () => setPageOfTags(old => old + 1),
|
||||
setPageOfFields: () => setPageOfFields(old => old + 1),
|
||||
setSelectedCurrentFields,
|
||||
leadFlag,
|
||||
leadFlagHC,
|
||||
fullArrayOfPipelinesSteps
|
||||
};
|
||||
};
|
||||
|
||||
import { makeRequest } from "@frontend/kitui";
|
||||
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz/bitrix`;
|
||||
|
||||
export const resetBitrixTagsFields = async () => {
|
||||
let success = true
|
||||
//Fields
|
||||
try {
|
||||
await makeRequest({
|
||||
method: "PATCH",
|
||||
url: `${API_URL}/fields`,
|
||||
});
|
||||
} catch (nativeError) {
|
||||
success = false
|
||||
}
|
||||
//Tags
|
||||
try {
|
||||
await makeRequest({
|
||||
method: "PATCH",
|
||||
url: `${API_URL}/tags`,
|
||||
});
|
||||
} catch (nativeError) {
|
||||
success = false
|
||||
}
|
||||
if (success) {
|
||||
enqueueSnackbar("Данные обновятся через 5-30 минут")
|
||||
} else {
|
||||
enqueueSnackbar("Не удалось обновить данные")
|
||||
}
|
||||
}
|
||||
@ -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 { getLeadTargetsByQuiz, LeadTargetModel } from "@/api/leadtarget";
|
||||
import { QuizMetricType } from "@model/quizSettings";
|
||||
import { makeRequest } from "@frontend/kitui";
|
||||
|
||||
interface IntegrationsPageProps {
|
||||
heightSidebar: number;
|
||||
@ -27,9 +28,8 @@ export const IntegrationsPage = ({
|
||||
keyof typeof QuizMetricType | null
|
||||
>(null);
|
||||
|
||||
const [isAmoCrmModalOpen, setIsAmoCrmModalOpen] = useState<boolean>(false);
|
||||
const [isZapierModalOpen, setIsZapierModalOpen] = useState<boolean>(false);
|
||||
const [isPostbackModalOpen, setIsPostbackModalOpen] = useState<boolean>(false);
|
||||
|
||||
|
||||
const [leadTargetsLoaded, setLeadTargetsLoaded] = useState<boolean>(false);
|
||||
const [leadTargets, setLeadTargets] = useState<LeadTargetModel[] | null>(null);
|
||||
const [zapierTarget, setZapierTarget] = useState<LeadTargetModel | null>(null);
|
||||
@ -57,6 +57,24 @@ export const IntegrationsPage = ({
|
||||
load();
|
||||
}, [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 () => {
|
||||
if (!quiz?.id) return;
|
||||
const [items] = await getLeadTargetsByQuiz(quiz.backendId);
|
||||
@ -76,15 +94,7 @@ export const IntegrationsPage = ({
|
||||
const handleCloseModal = () => {
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
const handleCloseAmoSRMModal = () => {
|
||||
setIsAmoCrmModalOpen(false);
|
||||
};
|
||||
const handleCloseZapierModal = () => {
|
||||
setIsZapierModalOpen(false);
|
||||
};
|
||||
const handleClosePostbackModal = () => {
|
||||
setIsPostbackModalOpen(false);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -111,15 +121,6 @@ export const IntegrationsPage = ({
|
||||
setCompanyName={setCompanyName}
|
||||
isModalOpen={isModalOpen}
|
||||
handleCloseModal={handleCloseModal}
|
||||
setIsAmoCrmModalOpen={setIsAmoCrmModalOpen}
|
||||
isAmoCrmModalOpen={isAmoCrmModalOpen}
|
||||
handleCloseAmoSRMModal={handleCloseAmoSRMModal}
|
||||
setIsZapierModalOpen={setIsZapierModalOpen}
|
||||
isZapierModalOpen={isZapierModalOpen}
|
||||
handleCloseZapierModal={handleCloseZapierModal}
|
||||
setIsPostbackModalOpen={setIsPostbackModalOpen}
|
||||
isPostbackModalOpen={isPostbackModalOpen}
|
||||
handleClosePostbackModal={handleClosePostbackModal}
|
||||
zapierTarget={zapierTarget}
|
||||
postbackTarget={postbackTarget}
|
||||
/>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
import React, { FC, lazy, Suspense } from "react";
|
||||
import React, { FC, lazy, Suspense, useState } from "react";
|
||||
import { ServiceButton } from "./buttons/ServiceButton";
|
||||
import { ZapierButton } from "./buttons/ZapierButton";
|
||||
import { PostbackButton } from "./buttons/PostbackButton";
|
||||
@ -21,6 +21,11 @@ const AmoCRMModal = lazy(() =>
|
||||
default: module.AmoCRMModal,
|
||||
}))
|
||||
);
|
||||
const BitrixModal = lazy(() =>
|
||||
import("../IntegrationsModal/Bitrix").then((module) => ({
|
||||
default: module.BitrixModal,
|
||||
}))
|
||||
);
|
||||
|
||||
const ZapierModal = lazy(() =>
|
||||
import("../IntegrationsModal/Zapier").then((module) => ({
|
||||
@ -40,15 +45,6 @@ type PartnersBoardProps = {
|
||||
setCompanyName: (value: keyof typeof QuizMetricType) => void;
|
||||
isModalOpen: boolean;
|
||||
handleCloseModal: () => void;
|
||||
setIsAmoCrmModalOpen: (value: boolean) => void;
|
||||
isAmoCrmModalOpen: boolean;
|
||||
handleCloseAmoSRMModal: () => void;
|
||||
setIsZapierModalOpen: (value: boolean) => void;
|
||||
isZapierModalOpen: boolean;
|
||||
handleCloseZapierModal: () => void;
|
||||
setIsPostbackModalOpen: (value: boolean) => void;
|
||||
isPostbackModalOpen: boolean;
|
||||
handleClosePostbackModal: () => void;
|
||||
zapierTarget?: LeadTargetModel | null;
|
||||
postbackTarget?: LeadTargetModel | null;
|
||||
};
|
||||
@ -59,21 +55,22 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
|
||||
handleCloseModal,
|
||||
companyName,
|
||||
setCompanyName,
|
||||
setIsAmoCrmModalOpen,
|
||||
isAmoCrmModalOpen,
|
||||
handleCloseAmoSRMModal,
|
||||
setIsZapierModalOpen,
|
||||
isZapierModalOpen,
|
||||
handleCloseZapierModal,
|
||||
setIsPostbackModalOpen,
|
||||
isPostbackModalOpen,
|
||||
handleClosePostbackModal,
|
||||
zapierTarget,
|
||||
postbackTarget,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const quiz = useCurrentQuiz();
|
||||
|
||||
const [isAmoCrmModalOpen, setIsAmoCrmModalOpen] = useState<boolean>(false);
|
||||
const handleCloseAmoSRMModal = () => setIsAmoCrmModalOpen(false);
|
||||
const [isBitrixModalOpen, setIsBitrixModalOpen] = useState<boolean>(false);
|
||||
const handleCloseBirixModal = () => setIsBitrixModalOpen(false);
|
||||
|
||||
const [isZapierModalOpen, setIsZapierModalOpen] = useState<boolean>(false);
|
||||
const handleCloseZapierModal = () => setIsZapierModalOpen(false);
|
||||
const [isPostbackModalOpen, setIsPostbackModalOpen] = useState<boolean>(false);
|
||||
const handleClosePostbackModal = () => setIsPostbackModalOpen(false);
|
||||
|
||||
const sectionTitleStyles = {
|
||||
textAlign: { xs: "start", sm: "start", md: "start" } as const,
|
||||
lineHeight: "1",
|
||||
@ -116,6 +113,12 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
|
||||
setCompanyName={setCompanyName}
|
||||
name={"amoCRM"}
|
||||
/>
|
||||
<ServiceButton
|
||||
logo={"Bitrix"}
|
||||
setIsModalOpen={setIsBitrixModalOpen}
|
||||
setCompanyName={setCompanyName}
|
||||
name={"Bitrix"}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Typography variant="h6" sx={sectionTitleStyles}>
|
||||
@ -137,7 +140,7 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Typography variant="h6" sx={sectionTitleStyles}>
|
||||
<Typography variant="h6" sx={sectionTitleStyles}>
|
||||
Автоматизация
|
||||
</Typography>
|
||||
<Box sx={containerStyles}>
|
||||
@ -149,7 +152,7 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
|
||||
setIsModalOpen={setIsPostbackModalOpen}
|
||||
setCompanyName={setCompanyName}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{companyName && (
|
||||
@ -171,6 +174,16 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
{companyName && isBitrixModalOpen && (
|
||||
<Suspense>
|
||||
<BitrixModal
|
||||
isModalOpen={isBitrixModalOpen}
|
||||
handleCloseModal={handleCloseBirixModal}
|
||||
companyName={companyName}
|
||||
quiz={quiz!}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
{companyName && isZapierModalOpen && (
|
||||
<Suspense>
|
||||
<ZapierModal
|
||||
|
||||
@ -6,7 +6,7 @@ import { IntegrationButton } from "./IntegrationButton";
|
||||
type PartnerItemProps = {
|
||||
setIsModalOpen: (value: boolean) => void;
|
||||
setCompanyName: (value: keyof typeof QuizMetricType) => void;
|
||||
logo?: JSX.Element;
|
||||
logo?: JSX.Element | string;
|
||||
title?: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
@ -120,7 +120,6 @@ const OverTime = () => {
|
||||
if (rafId) cancelAnimationFrame(rafId);
|
||||
if (timerId) clearInterval(timerId);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [enabled, (quiz as any)?.config?.overTime?.endsAt]);
|
||||
|
||||
// Синхронизация, если config.overTime обновился извне
|
||||
|
||||
Loading…
Reference in New Issue
Block a user