templates/admin/student/learners.html.twig line 1

Open in your IDE?
  1. {% extends 'admin/base.html.twig' %}
  2. {% block title %}Учищиеся{% endblock %}
  3. {% block content %}
  4.     <!-- Page Heading -->
  5.     <h1 class="h3 mb-2 text-gray-800">Просмотр учащихся</h1>
  6.     {# <p class="mb-4">DataTables is a third party plugin that is used to generate the demo table below.
  7.     For more information about DataTables, please visit the <a target="_blank"
  8.         href="https://datatables.net">official DataTables documentation</a>.</p> #}
  9.     <!-- DataTales Example -->
  10.     <div class="card shadow mb-4 mt-4">
  11.         <div class="card-header py-3">
  12.             <h6 class="m-0 font-weight-bold text-primary">Учащиеся</h6>
  13.         </div>
  14.         <div class="card-body">
  15.             {% if getUser() and getUser().getSettings().isCanEditStudents() %}
  16.                 {% include 'components/add_item_button.html.twig' with { options: {
  17.                     url: path('moderator_learner_edit'),
  18.                     title: 'Новый учащийся'
  19.                 } } %}
  20.             {% endif %}
  21.             <div id="students-table-filters" class="mb-4 d-flex align-items-center" style="gap: 20px; flex-wrap: wrap;">
  22.                 {% include 'components/table_filter.html.twig' with { options: {
  23.                     labelText: 'Уровень',
  24.                     dropdownIdPrefix: 'level',
  25.                     allItemsButtonText: 'Все уровни',
  26.                 }} %}
  27.                 {% include 'components/table_filter.html.twig' with { options: {
  28.                     labelText: 'Статус',
  29.                     dropdownIdPrefix: 'status',
  30.                     allItemsButtonText: 'Все статусы',
  31.                 }} %}
  32.                 {% include 'components/table_filter.html.twig' with { options: {
  33.                     labelText: 'Контакт Telegram найден',
  34.                     dropdownIdPrefix: 'telegramUserIdSet',
  35.                     allItemsButtonText: 'Не выбрано',
  36.                 }} %}
  37.                 {# Добавлен фильтр по области (city.region) #}
  38.                 {% include 'components/table_filter.html.twig' with { options: {
  39.                     labelText: 'Область',
  40.                     dropdownIdPrefix: 'cityRegion',
  41.                     allItemsButtonText: 'Все области',
  42.                 }} %}
  43.                 {% if getUser() and getUser().settings.canUseTestFeatures %}
  44. {#                    <div>#}
  45. {#                        {% embed 'components/dropdown.html.twig' with { options: {#}
  46. {#                            labelAttrs: 'style="display: block"',#}
  47. {#                            dropdownMenuAttrs: 'style="width: 500px; max-height: 600px; overflow-y: auto;"',#}
  48. {#                            defaultItemText: "Выбрать",#}
  49. {#                            labelText: "Зарегистрирован на мероприятие:",#}
  50. {#                            buttonClass: 'btn-primary btn',#}
  51. {#                            preventDropdownClose: true,#}
  52. {#                        } } %}#}
  53. {#                            {% block beforeDropdownItems %}#}
  54. {#                                <li class="px-3 py-2">#}
  55.                                     {# Перечисляем фильтры для каждого календарного события: статичные элементы Да/Нет #}
  56.                                         {% for ceId, ce in calendarEvents %}
  57.                                             {% include 'components/table_filter.html.twig' with { options: {
  58.                                                 labelText: 'Зарегистрирован: ' ~ ce.getText(),
  59.                                                 dropdownIdPrefix: 'calendarEvent_' ~ ceId,
  60.                                                 allItemsButtonText: 'Не выбрано',
  61.                                                 multipleSelect: false,
  62.                                                 itemsHtml: '<a class="dropdown-item" href="#" data-filter="Да">Да</a><a class="dropdown-item" href="#" data-filter="Нет">Нет</a>',
  63.                                             }} %}
  64. {#                                            <br>#}
  65.                                         {% endfor %}
  66. {#                                {% endblock %}#}
  67. {#                            {% endembed %}#}
  68.                         {% endif %}
  69. {#                    </div>#}
  70.             </div>
  71.             <div class="border p-3 rounded">
  72.                 <h4>Общая статистика:</h4>
  73.                 <div class="d-flex"
  74.                      data-set-checked-down-to-container data-target="[data-calc-item-name]"
  75.                 >
  76.                     <div class="p-2">
  77.                         <div>
  78.                             <label>
  79.                                 <input type="checkbox" data-toggle="check-all"
  80.                                        data-target="[data-calc-item-name]"
  81.                                 > Все
  82.                             </label>
  83.                         </div>
  84.                         {% for status, itemsByStatusCountItem in itemsByStatusCount %}
  85.                             <div>
  86.                                 <label>
  87.                                     <input type="checkbox" data-calc-item-name="{{ status }}" data-calc-item-value="{{ itemsByStatusCountItem.count }}">
  88.                                     Всего со статусом <b>"{{ itemsByStatusCountItem.statusText }}"</b>: {{ itemsByStatusCountItem.count }}
  89.                                 </label>
  90.                             </div>
  91.                         {% endfor %}
  92.                     </div>
  93.                     <div class="p-2">
  94.                         {% for level, itemsByStudentLevelCountItem in itemsByStudentLevelCount %}
  95.                             <div>
  96.                                 <label>
  97.                                     <input type="checkbox" data-calc-item-name="level_{{ level }}" data-calc-item-value="{{ itemsByStudentLevelCountItem.count }}">
  98.                                     Всего c уровнем <b>"{{ itemsByStudentLevelCountItem.level }}"</b>: {{ itemsByStudentLevelCountItem.count }}
  99.                                 </label>
  100.                             </div>
  101.                         {% endfor %}
  102.                     </div>
  103.                 </div>
  104.                 <div class="p-2">
  105.                     <div>Всего учеников: {{ allStudentsCount }}</div>
  106.                     <div>Всего подписчиков: {{ allSubscribersCount }}</div>
  107.                     <div>Всего выбывших: {{ allLeavedStudentsCount }}</div>
  108.                     <br>
  109.                     <div>
  110.                         Калькулятор: <span class="calc-result"><b>выберите значения</b></span>
  111.                     </div>
  112.                 </div>
  113.             </div>
  114.             <br>
  115. {#            <div>#}
  116. {#                Тексты писем:#}
  117. {#            </div>#}
  118. {#            <script>#}
  119. {#                let msg = null;#}
  120. {#            </script>#}
  121. {#            {% set i = -1 %}#}
  122. {#            {% set emails = "hh099@ya.ru\n" %}#}
  123. {#            {% for item in items %}#}
  124. {#                {% set i = i + 1 %}#}
  125. {#                {% if item.studentStatus != "subscriber" %}#}
  126. {#                    <div>#}
  127. {#                        <button class="copy-to-buffer" data-name="{{ item.name }}"#}
  128. {#                            data-email="{{ item.email }}"#}
  129. {#                        >{{ i + 1 }} - id: {{ item.id}} - {{ item.name }} {{ item.email }} - {{ item.studentStatus }}</button>#}
  130. {#                    </div>#}
  131. {#                    <br>#}
  132. {#                    {% if item.id >= 328 and item.email %}#}
  133. {#                        {% set emails = emails ~ item.email ~ "\n" %}#}
  134. {#                    {% endif %}#}
  135. {#                {% endif %}#}
  136. {#            {% endfor %}#}
  137. {#            <textarea id="emails-textarea" class="form-control" rows="10" placeholder="Сюда будут скопированы все email обучающихся, не являющихся подписчиками, при нажатии на кнопку выше">{{ emails }}</textarea>#}
  138. {#            <script>#}
  139. {#                $('.copy-to-buffer').click(function () {#}
  140. {#                    let btn = $(this)[0];#}
  141. {#                    let name = $(this).data('name');#}
  142. {#                    let email = $(this).data('email');#}
  143. {#                    let msg = `${name}, добрый день! Отправляю информацию чтобы Телеграм работал.#}
  144. {#Ссылка для телефонов Android (Samsung, Xiaomi, Huawei и др.):#}
  145. {#https://play.google.com/store/apps/details?id=com.adguard.vpn#}
  146. {#Программа для компьютера Windows AdGuard прикреплена к письму.`;#}
  147. {#                    msg = email;#}
  148. {#                    navigator.clipboard.writeText(msg).then(() => {#}
  149. {#                        // alert('Текст скопирован в буфер обмена');#}
  150. {#                        //change button class to primarey#}
  151. {#                        $(btn).removeClass('btn-secondary').addClass('btn-primary');#}
  152. {#                    }).catch(err => {#}
  153. {#                        alert('Ошибка при копировании текста: ' + err);#}
  154. {#                    });#}
  155. {#                });#}
  156. {#            </script>#}
  157.             <div class="mb-4 d-flex justify-content-end">
  158.                 <button id="compare-table-btn" class="btn btn-primary mb-3">Поиск по списку ФИО</button>
  159.             </div>
  160.             <div class="table-responsive">
  161.                 <table class="table table-bordered" id="students-data-table">
  162.                     <thead>
  163.                     <tr>
  164.                         <th data-table-type="num">Номер п/п</th>
  165.                         <th>Имя</th>
  166.                         <th>Город</th>
  167.                         <th data-table-dropdown-id="cityRegionDropdown"
  168.                             data-table-badge-id="cityRegionBadge"
  169.                             data-table-dropdown-text-for-all="Все области">Область</th>
  170.                         <th>Телефон</th>
  171.                         <th>E-mail</th>
  172.                         <th data-table-dropdown-id="levelDropdown"
  173.                             data-table-badge-id="levelBadge"
  174.                             data-table-dropdown-text-for-all="Все уровни">Уровень</th>
  175.                         <th data-export-exclude-col>Статус</th>
  176.                         <th data-table-hide-col data-table-dropdown-id="statusDropdown"
  177.                             data-table-badge-id="statusBadge"
  178.                             data-table-dropdown-text-for-all="Все статусы">Статус (текст)</th>
  179.                         <th data-export-exclude-col>Фото</th>
  180.                         <th data-table-type="date-d-m-Y-asc">Дата последнего прохождния курса уровня</th>
  181.                         <th>Комментарий</th>
  182.                         {# Для каждого календарного события добавляем скрытый столбец (значение Да/Нет). #}
  183.                         {% for ceId, ce in calendarEvents %}
  184.                             <th data-table-hide-col
  185.                                 data-table-dropdown-id="calendarEvent_{{ ceId }}Dropdown"
  186.                                 data-table-badge-id="calendarEvent_{{ ceId }}Badge"
  187.                                 data-table-dropdown-text-for-all="Не выбрано"
  188.                                 data-export-exclude-col>{{ ce.getText() }}</th>
  189.                         {% endfor %}
  190.                         <th data-table-hide-col data-export-exclude-col>Идентификатор пользователя Telegram</th>
  191.                         <th data-table-hide-col data-export-exclude-col
  192.                             data-table-dropdown-id="telegramUserIdSetDropdown"
  193.                             data-table-badge-id="telegramUserIdSetBadge"
  194.                             data-table-dropdown-text-for-all="Не выбрано"
  195.                         >Идентификатор пользователя Telegram установлен (да/нет)</th>
  196.                         <th data-table-hide-col>Отписался от рассылок (да/нет)</th>
  197.                         <th>Сведения</th>
  198.                         <th data-export-exclude-col>Действия</th>
  199.                         <th data-table-type="num" data-table-hide-col data-table-col-id="id">#</th>
  200.                         <th data-table-hide-col>Дополнить комментарий учащегося</th>
  201.                     </tr>
  202.                     </thead>
  203.                     {# <tfoot>
  204.                     <tr>
  205.                         <th>Имя</th>
  206.                         <th>Уровень</th>
  207.                     </tr>
  208.                 </tfoot> #}
  209.                     <tbody>
  210.                     {% set studentIndex = -1 %}
  211.                     {% for item in items %}
  212.                         {% set studentIndex = studentIndex + 1 %}
  213.                         {% set earnLevelCandidate = earnLevelCandidates[item.id] %}
  214.                         <tr>
  215.                             <td>{{ studentIndex + 1 }}</td>
  216.                             <td>{{ getFirstLastNameWithOld(item, false, true) }}</td>
  217.                             <td>{{ item.city ? item.city.name : "город не указан" }}</td>
  218.                             <td>{{ item.city and item.city.region ? item.city.region.name : "область не указана" }}</td>
  219.                             <td>{{ item.phone }}</td>
  220.                             <td>{{ item.email }}</td>
  221.                             <td>{{ item.getStudentLevelText }}</td>
  222.                             <td>
  223.                                 <span href="#" class="btn btn-{{ item.studentStatusCssClass }} btn-icon-split w-max cursor-default">
  224.                                     <span class="icon text-white-50">
  225.                                         <i class="fas fa-tag"></i>
  226.                                     </span>
  227.                                     <span class="text">{{ item.studentStatusText }}</span>
  228.                                 </span>
  229.                             </td>
  230.                             <td>{{ item.studentStatusText }}</td>
  231.                             <td>
  232. {#                                {% if item.image %}#}
  233. {#                                    <img src="{{ path('image_view', {id: item.image.id}) }}"#}
  234. {#                                         alt="Фото {{ item.name }}"#}
  235. {#                                         class="cursor-pointer gallery-trigger"#}
  236. {#                                         style="max-width: 100px; max-height: 100px;"#}
  237. {#                                         data-gallery-index="{{ studentIndex }}"#}
  238. {#                                         data-gallery-modal-id="studentsPhotoGalleryModal"#}
  239. {#                                    >#}
  240. {#                                {% else %}#}
  241. {#                                    –#}
  242. {#                                {% endif %}#}
  243.                             </td>
  244.                             <td>
  245.                                 {% set outputEarnLevelCandidate = item.student and earnLevelCandidate and earnLevelCandidate.calendarEvent.earnLevel == item.student.level %}
  246.                                 {% if outputEarnLevelCandidate %}
  247.                                     {{ earnLevelCandidate.calendarEvent.startDate|date('d.m.Y') }}
  248.                                 {% else %}
  249.                                     –
  250.                                 {% endif %}
  251.                             </td>
  252.                             <td>{{ item.comment ?: "–" }}</td>
  253.                             {# Для каждого события выводим 'Да' если есть кандидат для пары студент-событие, иначе 'Нет' #}
  254.                             {% for ceId, ce in calendarEvents %}
  255.                                 {% set registered = ((candidatesByPersonsAndCalendarEvent[item.id] is defined) and (candidatesByPersonsAndCalendarEvent[item.id][ceId] is defined)) %}
  256.                                 <td>{{ registered ? 'Да' : 'Нет' }}</td>
  257.                             {% endfor %}
  258.                             <td>{{ item.telegramUserId ?: "–" }}</td>
  259.                             <td>
  260.                                 {% set statusAdditon = item.canSendTelegramCampaigns ? "" : " (Рассылка Telegram отключена!)" %}
  261.                                 {{ item.telegramUserId ? 'Да' ~ statusAdditon : 'Нет' ~ statusAdditon }}
  262.                             </td>
  263.                             <td>
  264.                                 {% if item.isUnsubscribed %}
  265.                                     Да
  266.                                 {% else %}
  267.                                     Нет
  268.                                 {% endif %}
  269.                             </td>
  270.                             <td>
  271.                                 <b>Дата рождения</b>: {{ item.birthday ? item.birthday|date('d.m.Y') : "–" }}
  272.                                 <br><br>
  273.                                 <b>Род деятельности</b>: {{ item.job ? item.job.name : "–" }}
  274.                             </td>
  275.                             <td>
  276.                                 <a title="Редактировать" href="{{ path('moderator_learner_edit', {id: item.id}) }}" class="btn btn-primary btn-circle">
  277.                                     <i class="fas fa-edit"></i>
  278.                                 </a>
  279.                                 {% if getUser() and getUser().getSettings().isCanEditStudents() %}
  280.                                     <a title="Удалить" href="javascript:void(0);" class="btn btn-danger btn-circle"
  281.                                        onclick='(async ()=>{
  282.                                                let msg = "Удалить учащегося {{ (item.name)|e('js') }}?";
  283.                                            let answer = await showWindowModal("Подтверждение", msg, "Удалить", "Отмена");
  284.                                            if (answer === MODAL_WINDOW_BUTTON.BUTTON_1) {
  285.                                                window.location = "{{ path('moderator_student_delete', {id: item.id}) }}";
  286.                                                }
  287.                                                })()'>
  288.                                         <i class="fas fa-trash"></i>
  289.                                     </a>
  290.                                 {% endif %}
  291.                             </td>
  292.                             <td>{{ item.id }}</td>
  293.                             <td></td>
  294.                         </tr>
  295.                     {% endfor %}
  296.                     </tbody>
  297.                 </table>
  298.                 {% set galleryItems = items %}
  299.                 {% embed 'components/image_carousel_modal/image_carousel_modal.html.twig' with { options: {
  300.                     modalId: 'studentsPhotoGalleryModal',
  301.                     modalTitle: 'Галерея учащихся',
  302.                     elementClickTriggerSelector: '#students-data-table img.gallery-trigger',
  303.                 } } %}
  304.                     {% block slides %}
  305.                         {% set galleryIndex = -1 %}
  306.                         {% for galleryItem in galleryItems %}
  307.                             {% set galleryIndex = galleryIndex + 1 %}
  308. {#                            {% set slideText = 'Фото: ' ~ galleryItem.name %}#}
  309. {#                            {% embed 'components/image_carousel_modal/gallery_slide.html.twig' with { options: {#}
  310. {#                                imageIndex: galleryIndex,#}
  311. {#                                imageUrl: galleryItem.image ? path('image_view', {id: galleryItem.image.id}),#}
  312. {#                                attrs: 'data-student-id="' ~ galleryItem.id ~ '"',#}
  313. {#                            } } %}#}
  314. {#                                {% block text %}#}
  315. {#                                    <div><b>Имя:</b> {{ galleryItem.name }}</div>#}
  316. {#                                    <div><b>Город:</b> {{ galleryItem.city ? galleryItem.cityText : '–' }}</div>#}
  317. {#                                    <div><b>Уровень:</b> {{ galleryItem.student ? galleryItem.student.levelText : "нет уровня" }}</div>#}
  318. {#                                    <div class="mb-0"><b>Телефон:</b> {{ galleryItem.phone ?: '–' }}</div>#}
  319. {#                                    <div class="mb-1"><b>E-mail:</b> {{ galleryItem.email ?: '–' }}</div>#}
  320. {#                                {% endblock %}#}
  321. {#                            {% endembed %}#}
  322.                         {% endfor %}
  323.                     {% endblock %}
  324.                 {% endembed %}
  325.                 {#                {% if getUser() and (getUser().getSettings().canUseTestFeatures()) %}#}
  326.                 <div class="mt-4 d-flex">
  327.                     <button id="download-csv-btn" class="btn btn-primary">Скачать таблицу</button>
  328.                 </div>
  329. {#                {% endif %}#}
  330.             </div>
  331.         </div>
  332.     </div>
  333. {% endblock %}
  334. {% block addJs %}
  335.     <script>
  336.         let studentsDataTableOptions =  {
  337.             // "scrollY": "500px",
  338.             "lengthMenu": [[10, 20, 50, 100, 200, 300, 500, 700, -1], [10, 20, 50, 100, 200, 300, 500, 700, "Все"]],
  339.             drawRowNumbersForColIndex: 0,
  340.         };
  341.         let table = new CustomDataTable('#students-data-table', studentsDataTableOptions);
  342.         table.initDataTable();
  343.         // compare table button - collect filtered table data and run compare
  344.         $('#compare-table-btn').click(async () => {
  345.             let result = await showTableCompareModal();
  346.             if (result !== null) {
  347.                 let compareList = result.compareList;
  348.                 let tableData = table.getAllFilteredTableData();
  349.                 let tableList = "";
  350.                 for (let i = 0; i < tableData.length; i++) {
  351.                     tableList += tableData[i][1] + "\n";
  352.                 }
  353.                 let compareResult = await compareData(tableList, compareList);
  354.                 const modalContent = getCompareTableHtml(tableData, compareResult, "students-data-table",
  355.                     "#students-table-filters", studentsDataTableOptions);
  356.                 let answer = await showWindowModal('Результаты поиска в таблице по списку ФИО', modalContent, "Ок", "Отмена",
  357.                     "max-width: 95%;");
  358.             }
  359.         });
  360.         $('#download-csv-btn').click(() => {
  361.             table.downloadCsv()
  362.         });
  363.         let galleries = [];
  364.         $('.gallery-modal').each(function() {
  365.             let modalId = $(this).attr('id');
  366.             let options = {};
  367.             if ($(this).is("#studentsPhotoGalleryModal")) {
  368.                 options = {
  369.                     ...options,
  370.                     slideIdAttrName: 'data-student-id',
  371.                     dataTableInstance: table,
  372.                 }
  373.             }
  374.             let gallery = new GalleryCarousel(modalId, options);
  375.             galleries.push(gallery);
  376.         });
  377.         function updateCalc() {
  378.             const $span = $('.calc-result');
  379.             const $inputs = $('input[data-calc-item-name]');
  380.             let total = 0;
  381.             let anyChecked = false;
  382.             $inputs.each(function() {
  383.                 const $el = $(this);
  384.                 if ($el.is(':checked')) {
  385.                     anyChecked = true;
  386.                     const raw = $el.attr('data-calc-item-value');
  387.                     const n = Number(String(raw).replace(/[^0-9.-]+/g, ''));
  388.                     if (!isNaN(n)) total += n;
  389.                 }
  390.             });
  391.             if (!anyChecked) {
  392.                 $span.html('<b>выберите значения</b>');
  393.             } else {
  394.                 $span.html('<b>' + total + '</b>');
  395.             }
  396.         }
  397.         // Привязываем событие и инициализируем при загрузке
  398.         $(document).ready(function() {
  399.             $(document).on('change', 'input[data-calc-item-name]', updateCalc);
  400.             updateCalc();
  401.         });
  402.     </script>
  403. {% endblock %}