Initial commit
This commit is contained in:
35
templates/admin/features.html
Normal file
35
templates/admin/features.html
Normal file
@@ -0,0 +1,35 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<h2 class="mb-4">Вопросы категории: <span class="text-primary">{{ product.name }}</span></h2>
|
||||
|
||||
<form method="post" class="mb-4">
|
||||
<div class="row g-2">
|
||||
|
||||
<div class="col-md-10">
|
||||
<textarea name="question_text" class="form-control" placeholder="Текст вопроса" rows="1" required></textarea>
|
||||
</div>
|
||||
<div class="col-md-2 d-grid">
|
||||
<button type="submit" class="btn btn-success">➕ Добавить</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<ul class="list-group shadow-sm">
|
||||
{% for feature in features %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong>{{ feature.name }}</strong><br>
|
||||
<small class="text-muted">{{ feature.question_text }}</small>
|
||||
</div>
|
||||
<form method="post" action="{{ url_for('delete_feature', feature_id=feature.id, product_id=product.id) }}"
|
||||
onsubmit="return confirm('Удалить вопрос «{{ feature.name }}»?');">
|
||||
<button class="btn btn-sm btn-outline-danger">🗑 Удалить</button>
|
||||
</form>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="list-group-item text-center text-muted">Нет добавленных вопросов</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<a href="{{ url_for('manage_products', survey_id=product.survey_type.id) }}" class="btn btn-link mt-4">← Назад к категориям</a>
|
||||
{% endblock %}
|
||||
33
templates/admin/platforms_by_survey.html
Normal file
33
templates/admin/platforms_by_survey.html
Normal file
@@ -0,0 +1,33 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<h2 class="mb-4">Платформы опроса: {{ survey.name }}</h2>
|
||||
|
||||
<form method="post" class="mb-4">
|
||||
<div class="input-group shadow-sm">
|
||||
<input type="text" name="name" class="form-control" placeholder="Название платформы" required>
|
||||
<button type="submit" class="btn btn-success">➕ Добавить</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="list-group shadow-sm">
|
||||
{% for platform in platforms %}
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<strong>{{ platform.name }}</strong>
|
||||
<form method="post"
|
||||
action="{{ url_for('delete_platform_by_survey', survey_id=survey.id, platform_id=platform.id) }}"
|
||||
onsubmit="return confirm('Удалить платформу «{{ platform.name }}»?');">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">🗑</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="list-group-item text-muted text-center">Платформ пока нет</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="mt-4 d-flex justify-content-between">
|
||||
<a href="{{ url_for('manage_support_by_survey', survey_id=survey.id) }}" class="btn btn-outline-secondary">
|
||||
⚙ Управление поддержкой функциональности
|
||||
</a>
|
||||
<a href="{{ url_for('manage_surveys') }}" class="btn btn-link">← Назад к опросам</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
32
templates/admin/products.html
Normal file
32
templates/admin/products.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<h2 class="mb-4">Категории опроса: <span class="text-primary">{{ survey.name }}</span></h2>
|
||||
|
||||
<form method="post" class="mb-4">
|
||||
<div class="input-group shadow-sm">
|
||||
<input type="text" name="name" class="form-control" placeholder="Название категории (например: Базовые возможности)" required>
|
||||
<button type="submit" class="btn btn-success">➕ Добавить</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="list-group shadow-sm">
|
||||
{% for product in products %}
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong>{{ product.name }}</strong>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{{ url_for('manage_features', product_id=product.id) }}" class="btn btn-sm btn-outline-primary">Вопросы</a>
|
||||
<form method="post" action="{{ url_for('delete_product', product_id=product.id, survey_id=survey.id) }}"
|
||||
onsubmit="return confirm('Удалить категорию «{{ product.name }}»?');">
|
||||
<button class="btn btn-sm btn-outline-danger">🗑</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="list-group-item text-muted text-center">Нет категорий</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<a href="{{ url_for('manage_surveys') }}" class="btn btn-link mt-4">← Назад к опросникам</a>
|
||||
{% endblock %}
|
||||
37
templates/admin/support.html
Normal file
37
templates/admin/support.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<h2 class="mb-4">Матрица поддержки: {{ survey.name }}</h2>
|
||||
|
||||
<form method="post">
|
||||
<table class="table table-bordered table-sm align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Функция</th>
|
||||
{% for platform in platforms %}
|
||||
<th class="text-center">{{ platform.name }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for feature in features %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ feature.name }}</strong><br>
|
||||
<small>{{ feature.question_text }}</small>
|
||||
</td>
|
||||
{% for platform in platforms %}
|
||||
<td class="text-center">
|
||||
<input type="checkbox"
|
||||
name="support_{{ platform.id }}_{{ feature.id }}"
|
||||
{% if support[platform.id][feature.id] %}checked{% endif %}>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="submit" class="btn btn-success mt-3">Сохранить</button>
|
||||
</form>
|
||||
|
||||
<a href="{{ url_for('manage_platforms_by_survey', survey_id=survey.id) }}" class="btn btn-link mt-4">← Назад к платформам</a>
|
||||
{% endblock %}
|
||||
48
templates/admin/support_by_survey.html
Normal file
48
templates/admin/support_by_survey.html
Normal file
@@ -0,0 +1,48 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="mb-0">Матрица поддержки: {{ survey.name }}</h2>
|
||||
<a href="{{ url_for('manage_platforms_by_survey', survey_id=survey.id) }}" class="btn btn-outline-secondary">← Назад к платформам</a>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover align-middle">
|
||||
<thead class="table-dark text-center">
|
||||
<tr>
|
||||
<th class="text-start">Функция</th>
|
||||
{% for platform in platforms %}
|
||||
<th>{{ platform.name }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for feature in features %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ feature.question_text }}</strong><br>
|
||||
<!-- <span class="text-muted small">{{ feature.question_text }}</span>-->
|
||||
</td>
|
||||
{% for platform in platforms %}
|
||||
<td class="text-center">
|
||||
<div class="form-check form-switch d-flex justify-content-center">
|
||||
<input class="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
name="support_{{ platform.id }}_{{ feature.id }}"
|
||||
{% if support[platform.id][feature.id] %}checked{% endif %}>
|
||||
</div>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<button type="submit" class="btn btn-primary">💾 Сохранить</button>
|
||||
<a href="{{ url_for('choose_survey') }}" class="btn btn-link">На главную</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
BIN
templates/admin/survey.db
Normal file
BIN
templates/admin/survey.db
Normal file
Binary file not shown.
51
templates/admin/surveys.html
Normal file
51
templates/admin/surveys.html
Normal file
@@ -0,0 +1,51 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<h2 class="mb-4">Опросники</h2>
|
||||
|
||||
<form method="post" class="mb-4">
|
||||
<div class="input-group">
|
||||
<input type="text" name="name" class="form-control" placeholder="Название опроса" required>
|
||||
<button type="submit" class="btn btn-primary">Создать</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% for survey in surveys %}
|
||||
<div class="card mb-4 p-3">
|
||||
<h5>{{ survey.name }}</h5>
|
||||
|
||||
<!-- Старая админская панель -->
|
||||
<div class="btn-group mb-3">
|
||||
<a href="{{ url_for('manage_products', survey_id=survey.id) }}" class="btn btn-sm btn-outline-secondary">Категории и вопросы</a>
|
||||
<a href="{{ url_for('manage_platforms_by_survey', survey_id=survey.id) }}" class="btn btn-sm btn-outline-primary">Продукты</a>
|
||||
<a href="{{ url_for('manage_support_by_survey', survey_id=survey.id) }}" class="btn btn-sm btn-outline-success">Матрица</a>
|
||||
<form method="post" action="{{ url_for('delete_survey', survey_id=survey.id) }}" onsubmit="return confirm('Удалить опрос {{ survey.name }}?')">
|
||||
<button type="submit" class="btn btn-sm btn-danger">Удалить</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Назначения пользователям -->
|
||||
{% if current_user.is_admin %}
|
||||
<!-- Назначения пользователям (только для админа) -->
|
||||
<form method="POST" action="{{ url_for('assign_bulk_surveys') }}">
|
||||
<input type="hidden" name="survey_id" value="{{ survey.id }}">
|
||||
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-4 g-2">
|
||||
{% for user in users %}
|
||||
<div class="form-check col">
|
||||
<input class="form-check-input" type="checkbox" name="user_ids" value="{{ user.id }}"
|
||||
id="user{{ user.id }}_survey{{ survey.id }}"
|
||||
{% if user.id in assignments[survey.id] %}checked{% endif %}>
|
||||
<label class="form-check-label" for="user{{ user.id }}_survey{{ survey.id }}">
|
||||
{{ user.username }} ({{ user.full_name or "без ФИО" }})
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<button class="btn btn-sm btn-primary">Сохранить назначения</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
61
templates/choose_survey.html
Normal file
61
templates/choose_survey.html
Normal file
@@ -0,0 +1,61 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2>Доступные опросы</h2>
|
||||
|
||||
{% if current_user.is_admin %}
|
||||
{% for survey in surveys %}
|
||||
<div class="card mb-4 p-3">
|
||||
<h5>{{ survey.name }}</h5>
|
||||
|
||||
<!-- Кнопка пройти -->
|
||||
<a href="{{ url_for('show_quiz', survey_id=survey.id) }}" class="btn btn-sm btn-primary mb-2">Пройти</a>
|
||||
|
||||
<!-- Расшаривание пользователям -->
|
||||
<form method="POST" action="{{ url_for('assign_user_survey_bulk') }}">
|
||||
<input type="hidden" name="survey_id" value="{{ survey.id }}">
|
||||
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-4 g-2">
|
||||
{% for user in users %}
|
||||
<div class="form-check col">
|
||||
<input class="form-check-input" type="checkbox" name="user_ids"
|
||||
value="{{ user.id }}"
|
||||
id="survey{{ survey.id }}_user{{ user.id }}"
|
||||
{% if survey.id in assignments and user.id in assignments[survey.id] %}checked{% endif %}>
|
||||
<label class="form-check-label" for="survey{{ survey.id }}_user{{ user.id }}">
|
||||
{{ user.username }} ({{ user.full_name or "без ФИО" }})
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<button class="btn btn-sm btn-warning">Сохранить назначения</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for us in surveys %}
|
||||
<div class="list-group mb-2">
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<div class="fw-bold">{{ us.survey_type.name }}</div>
|
||||
<div class="btn-group">
|
||||
<a href="{{ url_for('show_quiz', survey_id=us.survey_type.id) }}" class="btn btn-sm btn-primary">Пройти</a>
|
||||
<button class="btn btn-sm btn-outline-secondary" data-bs-toggle="collapse" data-bs-target="#send{{ us.id }}">Отправить</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapse p-3 border rounded" id="send{{ us.id }}">
|
||||
<form id="send-survey-form" method="POST" action="{{ url_for('send_survey', survey_id=us.id) }}">
|
||||
<input type="email" name="email" class="form-control mb-2" placeholder="Email" required>
|
||||
<div class="form-check"><input class="form-check-input" type="checkbox" name="show_result"> Показывать результат</div>
|
||||
<div class="form-check"><input class="form-check-input" type="checkbox" name="ask_full_name"> Указать ФИО</div>
|
||||
<div class="form-check"><input class="form-check-input" type="checkbox" name="ask_phone"> Указать телефон</div>
|
||||
<div class="form-check"><input class="form-check-input" type="checkbox" name="ask_organization"> Указать организацию</div>
|
||||
<button class="btn btn-success btn-sm mt-2">Отправить</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
61
templates/invite/form.html
Normal file
61
templates/invite/form.html
Normal file
@@ -0,0 +1,61 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<div class="container my-5">
|
||||
<h2 class="text-center mb-4">Опрос: {{ survey.survey_type.name }}</h2>
|
||||
|
||||
<form method="POST">
|
||||
{% if invite.ask_full_name or invite.ask_phone or invite.ask_organization %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-info text-white fw-bold">
|
||||
Информация о респонденте
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if invite.ask_full_name %}
|
||||
<div class="mb-3">
|
||||
<label for="full_name" class="form-label">ФИО</label>
|
||||
<input type="text" name="full_name" id="full_name" class="form-control" required>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if invite.ask_phone %}
|
||||
<div class="mb-3">
|
||||
<label for="phone" class="form-label">Телефон</label>
|
||||
<input type="text" name="phone" id="phone" class="form-control" required>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if invite.ask_organization %}
|
||||
<div class="mb-3">
|
||||
<label for="organization" class="form-label">Наименование организации</label>
|
||||
<input type="text" name="organization" id="organization" class="form-control" required>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% for group, items in questions.items() %}
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header bg-primary text-white fw-bold">
|
||||
{{ group }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% for key, text in items.items() %}
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" name="{{ key }}" id="{{ key }}">
|
||||
<label class="form-check-label" for="{{ key }}">{{ text }}</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="mb-4">
|
||||
<label for="comment" class="form-label">Комментарий</label>
|
||||
<textarea name="comment" id="comment" class="form-control" rows="4" placeholder="Оставьте ваш комментарий..."></textarea>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<button type="submit" class="btn btn-primary btn-lg" id="submitBtn">
|
||||
Отправить
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
44
templates/invite/result.html
Normal file
44
templates/invite/result.html
Normal file
@@ -0,0 +1,44 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2 class="text-center mb-4">Результат сравнения платформ</h2>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<h5 class="mb-3">Процент соответствия платформ:</h5>
|
||||
<ul class="list-group mb-4">
|
||||
{% for platform, score in scores.items() %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
{{ platform }}
|
||||
<span class="badge bg-primary rounded-pill">{{ score }}%</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<h5 class="mb-3">Неподдерживаемые функции:</h5>
|
||||
{% for platform, features in unsupported_features.items() %}
|
||||
<div class="mb-3">
|
||||
<strong>{{ platform }}:</strong>
|
||||
{% if features %}
|
||||
<ul class="mt-1">
|
||||
{% for f in features %}
|
||||
<li>{{ f }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-success mb-0">Все функции поддерживаются</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="text-end">
|
||||
<a href="https://mont.ru" class="btn btn-outline-secondary mt-4">Перейти на сайт МОНТ</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
34
templates/invite/thankyou.html
Normal file
34
templates/invite/thankyou.html
Normal file
@@ -0,0 +1,34 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<div class="container mt-5">
|
||||
<div class="text-center">
|
||||
<h2 class="text-success mb-3">Спасибо за участие!</h2>
|
||||
<p class="lead">Ваши ответы сохранены.</p>
|
||||
|
||||
{% if show_result %}
|
||||
<hr>
|
||||
<h4>Результаты:</h4>
|
||||
<ul class="list-group mb-3">
|
||||
{% for platform, percent in scores.items() %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
{{ platform }}
|
||||
<span class="badge bg-primary rounded-pill">{{ percent }}%</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<h5 class="text-danger">Неподдерживаемые функции:</h5>
|
||||
{% for platform, feature_list in unsupported.items() %}
|
||||
{% if feature_list %}
|
||||
<p><strong>{{ platform }}:</strong></p>
|
||||
<ul>
|
||||
{% for text in feature_list %}
|
||||
<li>{{ text }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
106
templates/layout.html
Normal file
106
templates/layout.html
Normal file
@@ -0,0 +1,106 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ title or "Админка" }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
.navbar {
|
||||
background-color: #e9f6ff !important;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0 0 10px 10px;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
|
||||
}
|
||||
.navbar-brand {
|
||||
font-weight: 600;
|
||||
font-size: 1.3rem;
|
||||
color: #000 !important;
|
||||
}
|
||||
.btn {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
.container {
|
||||
max-width: 960px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center" href="{{ url_for('choose_survey') }}">
|
||||
<img src="{{ url_for('static', filename='mont.png') }}" height="40" class="me-2">
|
||||
<span>Опросник</span>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarNav" aria-controls="navbarNav"
|
||||
aria-expanded="false" aria-label="Меню">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse justify-content-end" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link{% if request.path.startswith('/quiz') %} active{% endif %}" href="{{ url_for('choose_survey') }}">Опросы</a>
|
||||
</li>
|
||||
{% if current_user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link{% if request.path.startswith('/admin') %} active{% endif %}" href="{{ url_for('manage_surveys') }}">Админка</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link{% if request.path.startswith('/dashboard') %} active{% endif %}" href="{{ url_for('dashboard') }}">Отчёты</a>
|
||||
</li>
|
||||
<li class="nav-item d-flex align-items-center text-dark ms-3">
|
||||
<i class="bi bi-person-circle me-1"></i> {{ current_user.full_name or current_user.username }}
|
||||
{% if current_user.is_admin %}<span class="badge bg-danger ms-2">Админ</span>{% endif %}
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('logout') }}">Выйти</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link{% if request.path.startswith('/login') %} active{% endif %}" href="{{ url_for('login') }}">Вход</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container my-5">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
function showLoading(form) {
|
||||
const button = form.querySelector('button[type="submit"]');
|
||||
button.disabled = true;
|
||||
button.innerHTML = `<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>Отправка...`;
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const form = document.querySelector('form');
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
|
||||
if (form && submitBtn) {
|
||||
form.addEventListener('submit', function () {
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '⏳ Отправка...';
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
18
templates/login.html
Normal file
18
templates/login.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2>Вход</h2>
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Имя пользователя</label>
|
||||
<input type="text" name="username" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Пароль</label>
|
||||
<input type="password" name="password" class="form-control" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Войти</button>
|
||||
<a href="{{ url_for('register') }}" class="btn btn-link">Регистрация</a>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
27
templates/quiz.html
Normal file
27
templates/quiz.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<h2 class="mb-4 text-center">Опрос: {{ survey.name }}</h2>
|
||||
|
||||
<form method="post" action="{{ url_for('show_quiz', survey_id=survey.id) }}">
|
||||
{% for category, features in questions.items() %}
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header bg-primary text-white fw-semibold fs-5">
|
||||
{{ category }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% for feature in features %}
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" value="1" name="feature_{{ feature.id }}" id="f{{ feature.id }}">
|
||||
<label class="form-check-label" for="f{{ feature.id }}">
|
||||
{{ feature.question_text }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-success btn-lg" id="submitBtn">Отправить</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
26
templates/register.html
Normal file
26
templates/register.html
Normal file
@@ -0,0 +1,26 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2>Регистрация</h2>
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Имя пользователя</label>
|
||||
<input type="text" name="username" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">ФИО</label>
|
||||
<input type="text" name="full_name" class="form-control">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Email</label>
|
||||
<input type="email" name="email" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Пароль</label>
|
||||
<input type="password" name="password" class="form-control" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Зарегистрироваться</button>
|
||||
<a href="{{ url_for('login') }}" class="btn btn-link">Уже есть аккаунт?</a>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
132
templates/result.html
Normal file
132
templates/result.html
Normal file
@@ -0,0 +1,132 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
|
||||
<div class="text-center mb-5">
|
||||
<h1 class="fw-bold">Результаты выбора платформы</h1>
|
||||
</div>
|
||||
|
||||
<div class="results-container">
|
||||
<div class="card shadow-sm mb-4 p-4">
|
||||
<canvas id="platformChart" height="100"></canvas>
|
||||
</div>
|
||||
|
||||
{% for platform, score in scores.items() %}
|
||||
<div class="card mb-3 shadow-sm">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title text-primary mb-2">{{ platform }} — {{ "%.2f"|format(score) }}%</h4>
|
||||
|
||||
{% if unsupported_features[platform] %}
|
||||
<p class="text-danger fw-semibold mb-1">Не поддерживается:</p>
|
||||
<ul class="text-danger mb-0">
|
||||
{% for feature in unsupported_features[platform] %}
|
||||
<li>{{ feature }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-success mb-0">Все необходимые функции поддерживаются ✅</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<button class="btn btn-outline-primary btn-lg" onclick="downloadPDF()">Скачать в PDF</button>
|
||||
<a class="btn btn-link mt-3 d-block" href="{{ url_for('choose_survey') }}">← Вернуться к выбору</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/jspdf.roboto.js') }}"></script>
|
||||
|
||||
<script>
|
||||
const labels = {{ chart_labels|tojson }};
|
||||
const data = {{ chart_data|tojson }};
|
||||
const scores = {{ scores|tojson }};
|
||||
const unsupported = {{ unsupported_features|tojson }};
|
||||
|
||||
const ctx = document.getElementById('platformChart').getContext('2d');
|
||||
const platformChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: 'Совпадение (%)',
|
||||
data: data,
|
||||
backgroundColor: 'rgba(52, 152, 219, 0.6)',
|
||||
borderColor: 'rgba(52, 152, 219, 1)',
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
max: 100,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Процент совпадения'
|
||||
}
|
||||
},
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Платформы'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function downloadPDF() {
|
||||
const { jsPDF } = window.jspdf;
|
||||
const doc = new jsPDF();
|
||||
|
||||
doc.addFileToVFS("Roboto.ttf", roboto_base64);
|
||||
doc.addFont("Roboto.ttf", "Roboto", "normal");
|
||||
doc.setFont("Roboto");
|
||||
doc.setFontSize(18);
|
||||
doc.text("Результаты выбора платформы", 10, 15);
|
||||
|
||||
const chartCanvas = document.getElementById('platformChart');
|
||||
const chartImage = await html2canvas(chartCanvas);
|
||||
const chartDataUrl = chartImage.toDataURL('image/png');
|
||||
doc.addImage(chartDataUrl, 'PNG', 10, 25, 180, 90);
|
||||
|
||||
let y = 120;
|
||||
doc.setFontSize(12);
|
||||
|
||||
for (let platform of labels) {
|
||||
const score = scores[platform].toFixed(2);
|
||||
doc.text(`${platform}: ${score}%`, 10, y);
|
||||
y += 8;
|
||||
|
||||
const unsupp = unsupported[platform];
|
||||
if (unsupp && unsupp.length) {
|
||||
doc.text("Не поддерживается:", 12, y);
|
||||
y += 8;
|
||||
for (let f of unsupp) {
|
||||
doc.text(`- ${f}`, 16, y);
|
||||
y += 6;
|
||||
if (y > 270) {
|
||||
doc.addPage();
|
||||
y = 20;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
doc.text("✔ Все необходимые функции поддерживаются", 12, y);
|
||||
y += 10;
|
||||
}
|
||||
}
|
||||
|
||||
doc.save("Результаты_платформы.pdf");
|
||||
}
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
88
templates/user/dashboard.html
Normal file
88
templates/user/dashboard.html
Normal file
@@ -0,0 +1,88 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2>Мои отправленные опросы</h2>
|
||||
|
||||
{% if invites %}
|
||||
{% for invite in invites %}
|
||||
<div class="card mb-3 border {% if invite.responded %}border-success bg-light{% else %}border-danger bg-light{% endif %}">
|
||||
<div class="card-body d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong>{{ invite.survey.survey_type.name }}</strong><br>
|
||||
<small class="text-muted">Отправлено: {{ invite.sent_at.strftime('%d.%m.%Y %H:%M') }}</small><br>
|
||||
<small>Кому: {{ invite.recipient_email }}</small><br>
|
||||
|
||||
{% if current_user.is_admin %}
|
||||
<small class="text-muted">Отправитель: {{ invite.survey.user.full_name }} ({{ invite.survey.user.email }})</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="btn-group">
|
||||
{% if invite.responded %}
|
||||
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="collapse" data-bs-target="#r{{ invite.id }}">
|
||||
Показать результат
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if not invite.responded %}
|
||||
<form method="POST" action="{{ url_for('send_survey', survey_id=invite.survey.id) }}">
|
||||
<input type="hidden" name="email" value="{{ invite.recipient_email }}">
|
||||
<input type="hidden" name="show_result" value="{{ 1 if invite.show_result else 0 }}">
|
||||
<input type="hidden" name="ask_full_name" value="{{ 1 if invite.ask_full_name else 0 }}">
|
||||
<input type="hidden" name="ask_phone" value="{{ 1 if invite.ask_phone else 0 }}">
|
||||
<input type="hidden" name="ask_organization" value="{{ 1 if invite.ask_organization else 0 }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline-warning">Отправить повторно</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<form method="POST" action="{{ url_for('delete_invite', invite_id=invite.id) }}" onsubmit="return confirm('Удалить приглашение?')">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">Удалить</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="collapse" id="r{{ invite.id }}">
|
||||
<div class="card-body">
|
||||
{% if invite.responded and results[invite.id] %}
|
||||
{% set result = results[invite.id] %}
|
||||
{% set scores = result.platform_scores | from_json %}
|
||||
{% set unsupported = result.unsupported | from_json %}
|
||||
|
||||
<p><strong>ФИО:</strong> {{ result.full_name or '—' }}</p>
|
||||
<p><strong>Телефон:</strong> {{ result.phone or '—' }}</p>
|
||||
<p><strong>Организация:</strong> {{ result.organization or '—' }}</p>
|
||||
<p><strong>Получен:</strong> {{ result.submitted_at.strftime('%d.%m.%Y %H:%M') }}</p>
|
||||
|
||||
<hr>
|
||||
<p><strong>Процент соответствия платформ:</strong></p>
|
||||
<ul>
|
||||
{% for p, percent in scores.items() %}
|
||||
<li>{{ p }}: {{ percent }}%</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<p><strong>Неподдерживаемые функции:</strong></p>
|
||||
<ul>
|
||||
{% for p, items in unsupported.items() %}
|
||||
<li>{{ p }}:
|
||||
{% if items %}
|
||||
{{ items | join(", ") }}
|
||||
{% else %}
|
||||
все поддерживаются
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p><strong>Комментарий:</strong><br>{{ results[invite.id].comment or "—" }}</p>
|
||||
|
||||
{% else %}
|
||||
<p class="text-danger">Ответ ещё не получен.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>Вы ещё не отправляли опросы.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user