api файл и отображение списка
This commit is contained in:
parent
6273e62e66
commit
c2d79c04cc
577
api-docs.html
Normal file
577
api-docs.html
Normal file
@ -0,0 +1,577 @@
|
|||||||
|
<!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>
|
106
src/api/auditory.ts
Normal file
106
src/api/auditory.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { makeRequest } from "@frontend/kitui";
|
||||||
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz`;
|
||||||
|
|
||||||
|
// Types
|
||||||
|
export interface AuditoryItem {
|
||||||
|
id: number;
|
||||||
|
quiz_id: number;
|
||||||
|
sex: boolean;
|
||||||
|
age: string;
|
||||||
|
deleted: boolean;
|
||||||
|
created_at: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuditoryResponse {
|
||||||
|
data: AuditoryItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request Types
|
||||||
|
export interface AuditoryGetRequest {
|
||||||
|
quizId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuditoryDeleteRequest {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuditoryAddRequest {
|
||||||
|
sex: boolean;
|
||||||
|
age: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameters
|
||||||
|
export interface AuditoryGetParams {
|
||||||
|
quizId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuditoryDeleteParams {
|
||||||
|
quizId: number;
|
||||||
|
auditoryId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuditoryAddParams {
|
||||||
|
quizId: number;
|
||||||
|
body: AuditoryAddRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// API calls
|
||||||
|
export const auditoryGet = async ({ quizId }: AuditoryGetParams): Promise<[AuditoryResponse | null, string?]> => {
|
||||||
|
if (!quizId) {
|
||||||
|
return [null, "Quiz ID is required"];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await makeRequest<AuditoryGetRequest, AuditoryResponse>({
|
||||||
|
url: `${API_URL}/quiz/${quizId}/auditory`,
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
return [response];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Не удалось получить аудиторию. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const auditoryDelete = async ({ quizId, auditoryId }: AuditoryDeleteParams): Promise<[AuditoryResponse | null, string?]> => {
|
||||||
|
if (!quizId || !auditoryId) {
|
||||||
|
return [null, "Quiz ID and Auditory ID are required"];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await makeRequest<AuditoryDeleteRequest, AuditoryResponse>({
|
||||||
|
url: `${API_URL}/quiz/${quizId}/auditory`,
|
||||||
|
body: {
|
||||||
|
id: auditoryId
|
||||||
|
},
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
|
||||||
|
return [response];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Не удалось удалить аудиторию. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const auditoryAdd = async ({ quizId, body }: AuditoryAddParams): Promise<[AuditoryResponse | null, string?]> => {
|
||||||
|
if (!quizId) {
|
||||||
|
return [null, "Quiz ID is required"];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await makeRequest<AuditoryAddRequest, AuditoryResponse>({
|
||||||
|
url: `${API_URL}/quiz/${quizId}/auditory`,
|
||||||
|
body,
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
|
||||||
|
return [response];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Не удалось добавить аудиторию. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
114
src/pages/PersonalizationAI/AuditoryList.tsx
Normal file
114
src/pages/PersonalizationAI/AuditoryList.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import { auditoryGet, AuditoryResponse, AuditoryItem } from "@/api/auditory";
|
||||||
|
import ArrowDownIcon from "@/assets/icons/ArrowDownIcon";
|
||||||
|
import CopyIcon from "@/assets/icons/CopyIcon";
|
||||||
|
import { useCurrentQuiz } from "@/stores/quizes/hooks";
|
||||||
|
import { InfoPopover } from "@/ui_kit/InfoPopover";
|
||||||
|
import { useDomainDefine } from "@/utils/hooks/useDomainDefine";
|
||||||
|
import { Box, Collapse, IconButton, List, ListItem, Typography, useTheme } from "@mui/material";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const PURPLE = "#7E2AEA";
|
||||||
|
|
||||||
|
export const AuditoryList = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const quiz = useCurrentQuiz();
|
||||||
|
const { isTestServer } = useDomainDefine();
|
||||||
|
const [linksOpen, setLinksOpen] = useState(true);
|
||||||
|
const [auditory, setAuditory] = useState<AuditoryItem[]>([]);
|
||||||
|
|
||||||
|
const handleCopy = (text: string) => {
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
if (quiz?.backendId) {
|
||||||
|
const [result, error] = await auditoryGet({ quizId: quiz.backendId });
|
||||||
|
console.log("result-___---_------__---__-__---_------__---__-__---_------__---__-__---_------__---__-____--__")
|
||||||
|
console.log(result)
|
||||||
|
if (result) {
|
||||||
|
setAuditory(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [quiz]);
|
||||||
|
|
||||||
|
console.log("auditory-___---_auditory__---__-__auditory_------__---__-__---_------__---__-__---_------__---__-____--__")
|
||||||
|
console.log(auditory)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={{
|
||||||
|
maxWidth: "796px",
|
||||||
|
bgcolor: "#fff",
|
||||||
|
borderRadius: "12px",
|
||||||
|
p: "20px",
|
||||||
|
boxShadow: "0px 4px 32px 0px #7E2AEA14",
|
||||||
|
mt: "24px"
|
||||||
|
}}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<Typography sx={{ fontSize: "18px", fontWeight: 500, color: theme.palette.grey3.main }}>
|
||||||
|
Ваши сохраненные ссылки
|
||||||
|
</Typography>
|
||||||
|
<IconButton
|
||||||
|
sx={{ cursor: 'pointer', color: PURPLE, display: 'flex', alignItems: 'center', transition: 'transform 0.2s', transform: linksOpen ? 'rotate(0deg)' : 'rotate(180deg)' }}
|
||||||
|
onClick={() => setLinksOpen((prev) => !prev)}
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<ArrowDownIcon style={{ width: "18px", height: "18px" }} />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
<Collapse in={linksOpen} timeout="auto" unmountOnExit sx={{ mt: "3px" }}>
|
||||||
|
<List sx={{ gap: '8px', p: 0, m: 0 }}>
|
||||||
|
{auditory.map((item, idx) => {
|
||||||
|
const linkText = `${isTestServer ? "https://s.hbpn.link/" : "https://hbpn.link/"}?_paud=${item.id}`;
|
||||||
|
console.log(item)
|
||||||
|
return (
|
||||||
|
<ListItem
|
||||||
|
key={idx}
|
||||||
|
disablePadding
|
||||||
|
sx={{
|
||||||
|
bgcolor: "#F2F3F7",
|
||||||
|
borderRadius: "10px",
|
||||||
|
p: "13px 14px 13px 20px",
|
||||||
|
mb: "8px",
|
||||||
|
maxWidth: "756px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
transition: 'background 0.2s, border 0.2s',
|
||||||
|
'& .MuiListItemSecondaryAction-root': {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '12px',
|
||||||
|
width: "60px",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
secondaryAction={
|
||||||
|
<>
|
||||||
|
<IconButton edge="end" aria-label="info" sx={{ color: PURPLE, p: 0, width: 18, height: 18 }}>
|
||||||
|
<InfoPopover />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
edge="end"
|
||||||
|
aria-label="copy"
|
||||||
|
sx={{ color: PURPLE, p: 0, width: 18, height: 18, marginRight: "-2px" }}
|
||||||
|
onClick={() => handleCopy(linkText)}
|
||||||
|
>
|
||||||
|
<CopyIcon color={PURPLE} />
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography sx={{ color: 'black', fontWeight: 400, fontSize: "16px" }}>
|
||||||
|
{linkText}
|
||||||
|
</Typography>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
</Collapse>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -2,24 +2,20 @@ import { Box, Container, Typography, TextField, Button, List, ListItem, IconButt
|
|||||||
import { InfoPopover } from '@ui_kit/InfoPopover';
|
import { InfoPopover } from '@ui_kit/InfoPopover';
|
||||||
import CopyIcon from "@/assets/icons/CopyIcon";
|
import CopyIcon from "@/assets/icons/CopyIcon";
|
||||||
import GenderAndAgeSelector from "./GenderAndAgeSelector";
|
import GenderAndAgeSelector from "./GenderAndAgeSelector";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
import Collapse from '@mui/material/Collapse';
|
import Collapse from '@mui/material/Collapse';
|
||||||
import { ArrowDownIcon } from "../../assets/icons/questionsPage/ArrowDownIcon";
|
import { ArrowDownIcon } from "../../assets/icons/questionsPage/ArrowDownIcon";
|
||||||
import { useTheme } from "@mui/material";
|
import { useTheme } from "@mui/material";
|
||||||
|
import { auditoryAdd, auditoryGet } from "@/api/auditory";
|
||||||
|
import { useCurrentQuiz } from "@/stores/quizes/hooks";
|
||||||
|
import { AuditoryList } from "./AuditoryList";
|
||||||
|
|
||||||
const PURPLE = "#7E2AEA";
|
const PURPLE = "#7E2AEA";
|
||||||
const GREY_TEXT = "#A0A0A0";
|
|
||||||
const GREY_BORDER = "#E0E0E0";
|
|
||||||
const GREY_ICON = "#B0B0B0";
|
|
||||||
const BLOCK_RADIUS = "16px";
|
|
||||||
const BLOCK_PX = "32px";
|
|
||||||
const BLOCK_PY = "24px";
|
|
||||||
|
|
||||||
export default function PersonalizationAI() {
|
export default function PersonalizationAI() {
|
||||||
const [gender, setGender] = useState('');
|
|
||||||
const [linksOpen, setLinksOpen] = useState(true);
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const [gender, setGender] = useState('');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container id="PersonalizationAI" maxWidth={false} sx={{ minHeight: "100%", p: "20px" }}>
|
<Container id="PersonalizationAI" maxWidth={false} sx={{ minHeight: "100%", p: "20px" }}>
|
||||||
@ -47,9 +43,6 @@ export default function PersonalizationAI() {
|
|||||||
}}>
|
}}>
|
||||||
<GenderAndAgeSelector gender={gender} setGender={setGender} />
|
<GenderAndAgeSelector gender={gender} setGender={setGender} />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Ссылка */}
|
{/* Ссылка */}
|
||||||
<Box sx={{ mt: "34px" }}>
|
<Box sx={{ mt: "34px" }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||||
@ -95,73 +88,8 @@ export default function PersonalizationAI() {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Второй белый блок */}
|
<AuditoryList />
|
||||||
<Box sx={{
|
|
||||||
maxWidth: "796px",
|
|
||||||
bgcolor: "#fff",
|
|
||||||
borderRadius: "12px",
|
|
||||||
p: "20px",
|
|
||||||
boxShadow: "0px 4px 32px 0px #7E2AEA14",
|
|
||||||
mt: "24px"
|
|
||||||
}}>
|
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
||||||
<Typography sx={{ fontSize: "18px", fontWeight: 500, color: theme.palette.grey3.main }}>
|
|
||||||
Ваши сохраненные ссылки
|
|
||||||
</Typography>
|
|
||||||
<IconButton
|
|
||||||
sx={{ cursor: 'pointer', color: PURPLE, display: 'flex', alignItems: 'center', transition: 'transform 0.2s', transform: linksOpen ? 'rotate(0deg)' : 'rotate(180deg)' }}
|
|
||||||
onClick={() => setLinksOpen((prev) => !prev)}
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
<ArrowDownIcon style={{ width: "18px", height: "18px" }} />
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
<Collapse in={linksOpen} timeout="auto" unmountOnExit sx={{ mt: "3px" }}>
|
|
||||||
<List sx={{ gap: '8px', p: 0, m: 0 }}>
|
|
||||||
{[1, 2, 3, 4, 5].map((_, idx) => (
|
|
||||||
<ListItem
|
|
||||||
key={idx}
|
|
||||||
disablePadding
|
|
||||||
sx={{
|
|
||||||
bgcolor: "#F2F3F7",
|
|
||||||
borderRadius: "10px",
|
|
||||||
p: "13px 14px 13px 20px",
|
|
||||||
mb: "8px",
|
|
||||||
maxWidth: "756px",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
transition: 'background 0.2s, border 0.2s',
|
|
||||||
'& .MuiListItemSecondaryAction-root': {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '12px',
|
|
||||||
width: "60px",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
secondaryAction={
|
|
||||||
<>
|
|
||||||
<IconButton edge="end" aria-label="info" sx={{ color: PURPLE, p: 0, width: 18, height: 18 }}>
|
|
||||||
<InfoPopover />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton edge="end" aria-label="copy" sx={{ color: PURPLE, p: 0, width: 18, height: 18, marginRight: "-2px" }}>
|
|
||||||
|
|
||||||
<CopyIcon
|
|
||||||
color={PURPLE}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Typography sx={{ color: 'black', fontWeight: 400, fontSize: "16px" }}>
|
|
||||||
linkexample.ru
|
|
||||||
</Typography>
|
|
||||||
</ListItem>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
</Collapse>
|
|
||||||
</Box>
|
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user