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.                     <div class="p-2">
  75.                         {% for status, itemsByStatusCountItem in itemsByStatusCount %}
  76.                             <div>
  77.                                 <label>
  78.                                     <input type="checkbox" data-calc-item-name="{{ status }}" data-calc-item-value="{{ itemsByStatusCountItem.count }}">
  79.                                     Всего со статусом <b>"{{ itemsByStatusCountItem.statusText }}"</b>: {{ itemsByStatusCountItem.count }}
  80.                                 </label>
  81.                             </div>
  82.                         {% endfor %}
  83.                     </div>
  84.                     <div class="p-2">
  85.                         {% for level, itemsByStudentLevelCountItem in itemsByStudentLevelCount %}
  86.                             <div>
  87.                                 <label>
  88.                                     <input type="checkbox" data-calc-item-name="level_{{ level }}" data-calc-item-value="{{ itemsByStudentLevelCountItem.count }}">
  89.                                     Всего c уровнем <b>"{{ itemsByStudentLevelCountItem.level }}"</b>: {{ itemsByStudentLevelCountItem.count }}
  90.                                 </label>
  91.                             </div>
  92.                         {% endfor %}
  93.                     </div>
  94.                 </div>
  95.                 <div class="p-2">
  96.                     <div>Всего учеников: {{ allStudentsCount }}</div>
  97.                     <div>Всего числящихся учеников: {{ itemsWithCanSendCampaignStatusCount }}</div>
  98.                     <div>Всего подписчиков: {{ itemsByStatusCount['subscriber_virtual_status']['count'] }}</div>
  99.                     <div>Всего выбывших: {{ allLeavedStudentsCount }}</div>
  100.                     <br>
  101.                     <div>
  102.                         Калькулятор: <span class="calc-result"><b>выберите значения</b></span>
  103.                     </div>
  104.                 </div>
  105.             </div>
  106.             <br>
  107. {#            <div>#}
  108. {#                Тексты писем:#}
  109. {#            </div>#}
  110. {#            <script>#}
  111. {#                let msg = null;#}
  112. {#            </script>#}
  113. {#            {% set i = -1 %}#}
  114. {#            {% set emails = "hh099@ya.ru\n" %}#}
  115. {#            {% for item in items %}#}
  116. {#                {% set i = i + 1 %}#}
  117. {#                {% if item.virtualStudentStatus != "subscriber" %}#}
  118. {#                    <div>#}
  119. {#                        <button class="copy-to-buffer" data-name="{{ item.name }}"#}
  120. {#                            data-email="{{ item.email }}"#}
  121. {#                        >{{ i + 1 }} - id: {{ item.id}} - {{ item.name }} {{ item.email }} - {{ item.virtualStudentStatus }}</button>#}
  122. {#                    </div>#}
  123. {#                    <br>#}
  124. {#                    {% if item.id >= 328 and item.email %}#}
  125. {#                        {% set emails = emails ~ item.email ~ "\n" %}#}
  126. {#                    {% endif %}#}
  127. {#                {% endif %}#}
  128. {#            {% endfor %}#}
  129. {#            <textarea id="emails-textarea" class="form-control" rows="10" placeholder="Сюда будут скопированы все email обучающихся, не являющихся подписчиками, при нажатии на кнопку выше">{{ emails }}</textarea>#}
  130. {#            <script>#}
  131. {#                $('.copy-to-buffer').click(function () {#}
  132. {#                    let btn = $(this)[0];#}
  133. {#                    let name = $(this).data('name');#}
  134. {#                    let email = $(this).data('email');#}
  135. {#                    let msg = `${name}, добрый день! Отправляю информацию чтобы Телеграм работал.#}
  136. {#Ссылка для телефонов Android (Samsung, Xiaomi, Huawei и др.):#}
  137. {#https://play.google.com/store/apps/details?id=com.adguard.vpn#}
  138. {#Программа для компьютера Windows AdGuard прикреплена к письму.`;#}
  139. {#                    msg = email;#}
  140. {#                    navigator.clipboard.writeText(msg).then(() => {#}
  141. {#                        // alert('Текст скопирован в буфер обмена');#}
  142. {#                        //change button class to primarey#}
  143. {#                        $(btn).removeClass('btn-secondary').addClass('btn-primary');#}
  144. {#                    }).catch(err => {#}
  145. {#                        alert('Ошибка при копировании текста: ' + err);#}
  146. {#                    });#}
  147. {#                });#}
  148. {#            </script>#}
  149.             <div class="mb-4 d-flex justify-content-end">
  150.                 <button id="compare-table-btn" class="btn btn-primary mb-3">Поиск по списку ФИО</button>
  151.             </div>
  152.             <div class="table-responsive">
  153.                 <table class="table table-bordered" id="students-data-table">
  154.                     <thead>
  155.                     <tr>
  156.                         <th data-table-type="num">Номер п/п</th>
  157.                         <th>Имя</th>
  158.                         <th>Город</th>
  159.                         <th data-table-dropdown-id="cityRegionDropdown"
  160.                             data-table-badge-id="cityRegionBadge"
  161.                             data-table-dropdown-text-for-all="Все области">Область</th>
  162.                         <th>Телефон</th>
  163.                         <th>E-mail</th>
  164.                         <th data-table-dropdown-id="levelDropdown"
  165.                             data-table-badge-id="levelBadge"
  166.                             data-table-dropdown-text-for-all="Все уровни">Уровень</th>
  167.                         <th data-export-exclude-col>Статус</th>
  168.                         <th data-table-hide-col data-table-dropdown-id="statusDropdown"
  169.                             data-table-badge-id="statusBadge"
  170.                             data-table-dropdown-text-for-all="Все статусы">Статус (текст)</th>
  171.                         <th data-export-exclude-col>Фото</th>
  172.                         <th data-table-type="date-d-m-Y-asc">Дата последнего прохождния курса уровня</th>
  173.                         <th>Комментарий</th>
  174.                         {# Для каждого календарного события добавляем скрытый столбец (значение Да/Нет). #}
  175.                         {% for ceId, ce in calendarEvents %}
  176.                             <th data-table-hide-col
  177.                                 data-table-dropdown-id="calendarEvent_{{ ceId }}Dropdown"
  178.                                 data-table-badge-id="calendarEvent_{{ ceId }}Badge"
  179.                                 data-table-dropdown-text-for-all="Не выбрано"
  180.                                 data-export-exclude-col>{{ ce.getText() }}</th>
  181.                         {% endfor %}
  182.                         <th data-table-hide-col data-export-exclude-col>Идентификатор пользователя Telegram</th>
  183.                         <th data-table-hide-col data-export-exclude-col
  184.                             data-table-dropdown-id="telegramUserIdSetDropdown"
  185.                             data-table-badge-id="telegramUserIdSetBadge"
  186.                             data-table-dropdown-text-for-all="Не выбрано"
  187.                         >Идентификатор пользователя Telegram установлен (да/нет)</th>
  188.                         <th>Сведения</th>
  189.                         <th data-export-exclude-col>Действия</th>
  190.                         <th data-table-type="num" data-table-hide-col data-table-col-id="id">#</th>
  191.                         <th data-table-hide-col>Дополнить комментарий учащегося</th>
  192.                     </tr>
  193.                     </thead>
  194.                     {# <tfoot>
  195.                     <tr>
  196.                         <th>Имя</th>
  197.                         <th>Уровень</th>
  198.                     </tr>
  199.                 </tfoot> #}
  200.                     <tbody>
  201.                     {% set studentIndex = -1 %}
  202.                     {% for item in items %}
  203.                         {% set studentIndex = studentIndex + 1 %}
  204.                         {% set earnLevelCandidate = earnLevelCandidates[item.id] %}
  205.                         <tr>
  206.                             <td>{{ studentIndex + 1 }}</td>
  207.                             <td>{{ getFirstLastNameWithOld(item, false, true) }}</td>
  208.                             <td>{{ item.city ? item.city.name : "город не указан" }}</td>
  209.                             <td>{{ item.city and item.city.region ? item.city.region.name : "область не указана" }}</td>
  210.                             <td>{{ item.phone }}</td>
  211.                             <td>{{ item.email }}</td>
  212.                             <td>{{ item.student and item.student.level > -1 ? item.student.level : "нет уровня" }}</td>
  213.                             <td>
  214.                                 <span href="#" class="btn btn-{{ item.virtualStudentStatusCssClass }} btn-icon-split w-max cursor-default">
  215.                                     <span class="icon text-white-50">
  216.                                         <i class="fas fa-tag"></i>
  217.                                     </span>
  218.                                     <span class="text">{{ item.virtualStudentStatusText }}</span>
  219.                                 </span>
  220.                             </td>
  221.                             <td>{{ item.virtualStudentStatusText }}</td>
  222.                             <td>
  223. {#                                {% if item.image %}#}
  224. {#                                    <img src="{{ path('image_view', {id: item.image.id}) }}"#}
  225. {#                                         alt="Фото {{ item.name }}"#}
  226. {#                                         class="cursor-pointer gallery-trigger"#}
  227. {#                                         style="max-width: 100px; max-height: 100px;"#}
  228. {#                                         data-gallery-index="{{ studentIndex }}"#}
  229. {#                                         data-gallery-modal-id="studentsPhotoGalleryModal"#}
  230. {#                                    >#}
  231. {#                                {% else %}#}
  232. {#                                    –#}
  233. {#                                {% endif %}#}
  234.                             </td>
  235.                             <td>
  236.                                 {% set outputEarnLevelCandidate = item.student and earnLevelCandidate and earnLevelCandidate.calendarEvent.earnLevel == item.student.level %}
  237.                                 {% if outputEarnLevelCandidate %}
  238.                                     {{ earnLevelCandidate.calendarEvent.startDate|date('d.m.Y') }}
  239.                                 {% else %}
  240.                                     –
  241.                                 {% endif %}
  242.                             </td>
  243.                             <td>{{ item.comment ?: "–" }}</td>
  244.                             {# Для каждого события выводим 'Да' если есть кандидат для пары студент-событие, иначе 'Нет' #}
  245.                             {% for ceId, ce in calendarEvents %}
  246.                                 {% set registered = item.student and ((candidatesByStudentAndCalendarEvent[item.student.id] is defined) and (candidatesByStudentAndCalendarEvent[item.student.id][(ceId)] is defined)) %}
  247.                                 <td>{{ registered ? 'Да' : 'Нет' }}</td>
  248.                             {% endfor %}
  249.                             <td>{{ item.telegramUserId ?: "–" }}</td>
  250.                             <td>
  251.                                 {% set statusAdditon = item.canSendTelegramCampaigns ? "" : " (Рассылка Telegram отключена!)" %}
  252.                                 {{ item.telegramUserId ? 'Да' ~ statusAdditon : 'Нет' ~ statusAdditon }}
  253.                             </td>
  254.                             <td>
  255.                                 <b>Дата рождения</b>: {{ item.birthday ? item.birthday|date('d.m.Y') : "–" }}
  256.                                 <br><br>
  257.                                 <b>Род деятельности</b>: {{ item.job ? item.job.name : "–" }}
  258.                             </td>
  259.                             <td>
  260.                                 <a title="Редактировать" href="{{ path('moderator_learner_edit', {id: item.id}) }}" class="btn btn-primary btn-circle">
  261.                                     <i class="fas fa-edit"></i>
  262.                                 </a>
  263.                                 {% if getUser() and getUser().getSettings().isCanEditStudents() %}
  264.                                     <a title="Удалить" href="javascript:void(0);" class="btn btn-danger btn-circle"
  265.                                        onclick='(async ()=>{
  266.                                                let msg = "Удалить учащегося {{ (item.name)|e('js') }}?";
  267.                                            let answer = await showWindowModal("Подтверждение", msg, "Удалить", "Отмена");
  268.                                            if (answer === MODAL_WINDOW_BUTTON.BUTTON_1) {
  269.                                                window.location = "{{ path('moderator_student_delete', {id: item.id}) }}";
  270.                                                }
  271.                                                })()'>
  272.                                         <i class="fas fa-trash"></i>
  273.                                     </a>
  274.                                 {% endif %}
  275.                             </td>
  276.                             <td>{{ item.id }}</td>
  277.                             <td></td>
  278.                         </tr>
  279.                     {% endfor %}
  280.                     </tbody>
  281.                 </table>
  282.                 {% set galleryItems = items %}
  283.                 {% embed 'components/image_carousel_modal/image_carousel_modal.html.twig' with { options: {
  284.                     modalId: 'studentsPhotoGalleryModal',
  285.                     modalTitle: 'Галерея учащихся',
  286.                     elementClickTriggerSelector: '#students-data-table img.gallery-trigger',
  287.                 } } %}
  288.                     {% block slides %}
  289.                         {% set galleryIndex = -1 %}
  290.                         {% for galleryItem in galleryItems %}
  291.                             {% set galleryIndex = galleryIndex + 1 %}
  292. {#                            {% set slideText = 'Фото: ' ~ galleryItem.name %}#}
  293. {#                            {% embed 'components/image_carousel_modal/gallery_slide.html.twig' with { options: {#}
  294. {#                                imageIndex: galleryIndex,#}
  295. {#                                imageUrl: galleryItem.image ? path('image_view', {id: galleryItem.image.id}),#}
  296. {#                                attrs: 'data-student-id="' ~ galleryItem.id ~ '"',#}
  297. {#                            } } %}#}
  298. {#                                {% block text %}#}
  299. {#                                    <div><b>Имя:</b> {{ galleryItem.name }}</div>#}
  300. {#                                    <div><b>Город:</b> {{ galleryItem.city ? galleryItem.cityText : '–' }}</div>#}
  301. {#                                    <div><b>Уровень:</b> {{ galleryItem.student ? galleryItem.student.levelText : "нет уровня" }}</div>#}
  302. {#                                    <div class="mb-0"><b>Телефон:</b> {{ galleryItem.phone ?: '–' }}</div>#}
  303. {#                                    <div class="mb-1"><b>E-mail:</b> {{ galleryItem.email ?: '–' }}</div>#}
  304. {#                                {% endblock %}#}
  305. {#                            {% endembed %}#}
  306.                         {% endfor %}
  307.                     {% endblock %}
  308.                 {% endembed %}
  309.                 {#                {% if getUser() and (getUser().getSettings().canUseTestFeatures()) %}#}
  310.                 <div class="mt-4 d-flex">
  311.                     <button id="download-csv-btn" class="btn btn-primary">Скачать таблицу</button>
  312.                 </div>
  313. {#                {% endif %}#}
  314.             </div>
  315.         </div>
  316.     </div>
  317. {% endblock %}
  318. {% block addJs %}
  319.     <script>
  320.         let studentsDataTableOptions =  {
  321.             // "scrollY": "500px",
  322.             "lengthMenu": [[10, 20, 50, 100, 200, 300, 500, 700, -1], [10, 20, 50, 100, 200, 300, 500, 700, "Все"]],
  323.             drawRowNumbersForColIndex: 0,
  324.         };
  325.         let table = new CustomDataTable('#students-data-table', studentsDataTableOptions);
  326.         table.initDataTable();
  327.         // compare table button - collect filtered table data and run compare
  328.         $('#compare-table-btn').click(async () => {
  329.             let result = await showTableCompareModal();
  330.             if (result !== null) {
  331.                 let compareList = result.compareList;
  332.                 let tableData = table.getAllFilteredTableData();
  333.                 let tableList = "";
  334.                 for (let i = 0; i < tableData.length; i++) {
  335.                     tableList += tableData[i][1] + "\n";
  336.                 }
  337.                 let compareResult = await compareData(tableList, compareList);
  338.                 const modalContent = getCompareTableHtml(tableData, compareResult, "students-data-table",
  339.                     "#students-table-filters", studentsDataTableOptions);
  340.                 let answer = await showWindowModal('Результаты поиска в таблице по списку ФИО', modalContent, "Ок", "Отмена",
  341.                     "max-width: 95%;");
  342.             }
  343.         });
  344.         $('#download-csv-btn').click(() => {
  345.             table.downloadCsv()
  346.         });
  347.         let galleries = [];
  348.         $('.gallery-modal').each(function() {
  349.             let modalId = $(this).attr('id');
  350.             let options = {};
  351.             if ($(this).is("#studentsPhotoGalleryModal")) {
  352.                 options = {
  353.                     ...options,
  354.                     slideIdAttrName: 'data-student-id',
  355.                     dataTableInstance: table,
  356.                 }
  357.             }
  358.             let gallery = new GalleryCarousel(modalId, options);
  359.             galleries.push(gallery);
  360.         });
  361.         function updateCalc() {
  362.             const $span = $('.calc-result');
  363.             const $inputs = $('input[data-calc-item-name]');
  364.             let total = 0;
  365.             let anyChecked = false;
  366.             $inputs.each(function() {
  367.                 const $el = $(this);
  368.                 if ($el.is(':checked')) {
  369.                     anyChecked = true;
  370.                     const raw = $el.attr('data-calc-item-value');
  371.                     const n = Number(String(raw).replace(/[^0-9.-]+/g, ''));
  372.                     if (!isNaN(n)) total += n;
  373.                 }
  374.             });
  375.             if (!anyChecked) {
  376.                 $span.html('<b>выберите значения</b>');
  377.             } else {
  378.                 $span.html('<b>' + total + '</b>');
  379.             }
  380.         }
  381.         // Привязываем событие и инициализируем при загрузке
  382.         $(document).ready(function() {
  383.             $(document).on('change', 'input[data-calc-item-name]', updateCalc);
  384.             updateCalc();
  385.         });
  386.     </script>
  387. {% endblock %}