first commit

This commit is contained in:
root
2026-01-10 16:16:39 +03:00
commit 5f5a28e292
154 changed files with 21454 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
# Disable directory browsing
Options -Indexes
# ----------------------------------------------------------------------
# Rewrite engine
# ----------------------------------------------------------------------
# Turning on the rewrite engine is necessary for the following rules and features.
# FollowSymLinks must be enabled for this to work.
<IfModule mod_rewrite.c>
Options +FollowSymlinks
RewriteEngine On
# If you installed CodeIgniter in a subfolder, you will need to
# change the following line to match the subfolder you need.
# http://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase
# RewriteBase /
# Redirect Trailing Slashes...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Rewrite "www.example.com -> example.com"
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
# Checks to see if the user is attempting to access a valid file,
# such as an image or css document, if this isn't true it sends the
# request to the front controller, index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([\s\S]*)$ index.php/$1 [L,NC,QSA]
# Ensure Authorization header is passed along
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
</IfModule>
<IfModule !mod_rewrite.c>
# If we don't have mod_rewrite installed, all 404's
# can be sent to index.php, and everything works as normal.
ErrorDocument 404 index.php
</IfModule>
# Disable server signature start
ServerSignature Off
# Disable server signature end
+9
View File
File diff suppressed because one or more lines are too long
+111
View File
@@ -0,0 +1,111 @@
/* Основные стили для сайдбара */
#wrapper {
display: flex;
min-height: 100vh;
}
#sidebar-wrapper {
min-height: 100vh;
margin-left: 0;
width: 250px;
transition: margin-left 0.3s ease, width 0.3s ease;
flex-shrink: 0;
}
#page-content-wrapper {
flex: 1;
min-width: 0;
transition: margin-left 0.3s ease;
}
/* Стили для свернутого сайдбара */
body.sb-sidenav-toggled #sidebar-wrapper {
margin-left: -250px;
width: 0;
}
body.sb-sidenav-toggled #page-content-wrapper {
margin-left: 0;
}
/* Стили для активных пунктов меню */
.sidebar-link {
transition: all 0.2s ease;
border-left: 3px solid transparent;
}
.sidebar-link:hover {
background-color: rgba(13, 110, 253, 0.05);
border-left-color: rgba(13, 110, 253, 0.3);
}
.sidebar-link.active {
background-color: rgba(13, 110, 253, 0.1);
border-left-color: var(--bs-primary, #0d6efd);
color: var(--bs-primary, #0d6efd) !important;
font-weight: 500;
}
.sidebar-link.active i {
color: var(--bs-primary, #0d6efd) !important;
}
.sidebar-link.disabled {
opacity: 0.5;
cursor: not-allowed;
background-color: transparent !important;
}
.sidebar-link.disabled:hover {
border-left-color: transparent;
}
/* Адаптивность для мобильных */
@media (max-width: 768px) {
#sidebar-wrapper {
margin-left: -250px;
width: 0;
}
#page-content-wrapper {
margin-left: 0;
}
body.sb-sidenav-toggled #sidebar-wrapper {
margin-left: 0;
width: 250px;
z-index: 1050;
position: fixed;
height: 100vh;
}
body.sb-sidenav-toggled #page-content-wrapper {
margin-left: 0;
}
body.sb-sidenav-toggled::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1049;
}
}
/* Иконка бургера - анимация */
#sidebarToggle .fa-bars {
transition: transform 0.3s ease;
}
body.sb-sidenav-toggled #sidebarToggle .fa-bars {
transform: rotate(90deg);
}
/* Стили для заголовка сайдбара */
.sidebar-heading {
font-weight: 600;
font-size: 1.1rem;
}
File diff suppressed because one or more lines are too long
+354
View File
@@ -0,0 +1,354 @@
/**
* DataTable - Универсальные стили для интерактивных таблиц
*
* Подключение: <link rel="stylesheet" href="/css/components/data-table.css">
*/
/* Основной контейнер таблицы */
.data-table {
width: 100%;
}
/* Интерактивные заголовки */
.data-table thead th {
vertical-align: middle;
white-space: nowrap;
padding: 0.5rem 0.75rem;
position: relative;
}
.data-table .header-content {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
min-width: 0;
position: relative;
}
/* Текст заголовка */
.data-table .header-text {
cursor: pointer;
flex: 1 1 auto;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
transition: color 0.2s ease;
white-space: nowrap;
}
.data-table .header-text:hover {
color: var(--bs-primary, #0d6efd);
}
/* Иконки в заголовке */
.data-table .search-trigger,
.data-table .sort-icon {
flex-shrink: 0;
}
.data-table .search-trigger {
cursor: pointer;
opacity: 0.5;
transition: opacity 0.2s ease, color 0.2s ease;
font-size: 0.875rem;
}
.data-table .search-trigger:hover {
opacity: 1;
color: var(--bs-primary, #0d6efd);
}
.data-table .sort-icon {
cursor: pointer;
transition: color 0.2s ease;
font-size: 0.875rem;
}
.data-table .sort-icon:hover {
color: var(--bs-primary, #0d6efd) !important;
}
.data-table .sort-icon.active {
color: var(--bs-primary, #0d6efd) !important;
}
/* Поле ввода - абсолютное позиционирование */
.data-table .header-search-input {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
font-size: 0.875rem;
padding: 0.25rem 0.5rem;
border: 1px solid transparent;
border-radius: 0.25rem;
background-color: #fff;
box-shadow: 0 0 0 2px rgba(var(--bs-primary-rgb, 13, 110, 253), 0.25);
z-index: 10;
display: none;
}
.data-table .header-search-input.visible {
display: block;
}
/* Скрываем элементы заголовка при активном поиске */
.data-table .header-search-input.visible ~ .search-trigger,
.data-table .header-search-input.visible ~ .header-text,
.data-table .header-search-input.visible ~ .sort-icon {
visibility: hidden;
}
/* Пагинация - исправленная структура */
.data-table .pagination-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: nowrap;
gap: 1rem;
padding: 0.75rem 1rem;
border-top: 1px solid var(--bs-border-color, #dee2e6);
width: 100%;
margin: 0;
background-color: #fff;
border-bottom-left-radius: 0.375rem;
border-bottom-right-radius: 0.375rem;
}
/* Информация слева */
.data-table .pagination-info {
font-size: 0.875rem;
color: var(--bs-secondary-color, #6c757d);
white-space: nowrap;
flex-shrink: 0;
margin: 0;
}
/* Навигация по центру */
.data-table .pagination-wrapper > nav.pagination {
flex: 1 1 auto;
display: flex;
justify-content: center;
margin: 0;
}
.data-table .pagination-wrapper > nav.pagination .pagination {
margin-bottom: 0;
flex-wrap: nowrap;
}
/* Селектор справа */
.data-table .per-page-selector {
display: flex;
align-items: center;
gap: 0.5rem;
flex-shrink: 0;
white-space: nowrap;
margin: 0;
}
/* Компактный селектор */
.data-table .per-page-selector .per-page-select,
.data-table .per-page-selector select {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 0.5rem center;
background-size: 16px 12px;
padding-right: 2rem !important;
width: 80px !important;
font-size: 0.875rem !important;
padding: 0.25rem 0.5rem !important;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
background-color: #fff;
cursor: pointer;
margin: 0;
}
/* Убираем отступы у ячейки футера */
.data-table tfoot td {
padding: 0 !important;
border-top: 1px solid var(--bs-border-color, #dee2e6);
}
/* Убираем границы у строки футера */
.data-table tfoot tr {
border: none;
}
/* Кнопки пагинации */
.data-table .page-link {
display: flex;
align-items: center;
justify-content: center;
min-width: 2rem;
height: 2rem;
padding: 0 0.5rem;
border-radius: 0.375rem;
font-size: 0.875rem;
text-decoration: none;
transition: all 0.2s ease;
margin: 0 0.125rem;
}
.data-table .page-link:hover:not(.disabled):not(.active) {
background-color: var(--bs-primary-bg-subtle, #e9ecef);
}
.data-table .page-link.active {
background-color: var(--bs-primary, #0d6efd);
color: #fff;
border-color: var(--bs-primary, #0d6efd);
}
.data-table .page-link.disabled {
color: var(--bs-secondary-color, #6c757d);
pointer-events: none;
opacity: 0.5;
}
/* Контейнер селектора не должен растягиваться */
.data-table .per-page-selector {
width: auto !important;
max-width: none !important;
flex-shrink: 0;
}
.data-table .per-page-selector label {
font-size: 0.875rem;
color: var(--bs-secondary-color, #6c757d);
margin-bottom: 0;
white-space: nowrap;
}
/* Стили для ячеек с действиями */
.data-table .actions-cell {
white-space: nowrap;
text-align: end;
}
.data-table .action-btn {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
border-radius: 0.25rem;
margin-left: 0.25rem;
}
/* Hover эффекты для строк */
.data-table tbody tr {
transition: background-color 0.15s ease;
}
.data-table tbody tr:hover {
background-color: var(--bs-table-hover-bg, rgba(0, 0, 0, 0.02));
}
/* Статус активной сортировки в заголовке */
.data-table th.sorted-asc .sort-icon::before {
content: '\f160';
font-weight: 900;
}
.data-table th.sorted-desc .sort-icon::before {
content: '\f161';
font-weight: 900;
}
/* Анимация появления */
@keyframes data-table-fade-in {
from {
opacity: 0;
transform: translateY(-2px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.data-table .header-search-input {
animation: data-table-fade-in 0.15s ease-in-out;
}
/* Стили для загрузки */
.data-table .loading {
position: relative;
pointer-events: none;
}
.data-table .loading::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.7);
z-index: 1;
}
/* Пустое состояние */
.data-table .empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--bs-secondary-color, #6c757d);
}
.data-table .empty-state i {
font-size: 3rem;
opacity: 0.3;
margin-bottom: 1rem;
}
.data-table .empty-state h5 {
margin-bottom: 0.5rem;
}
.data-table .empty-state p {
margin-bottom: 0;
opacity: 0.8;
}
/* Адаптивность для мобильных */
@media (max-width: 768px) {
.data-table .header-content {
gap: 4px;
}
.data-table .header-text {
font-size: 0.875rem;
}
.data-table .search-trigger,
.data-table .sort-icon {
font-size: 0.875rem;
}
.data-table .pagination-wrapper {
flex-wrap: wrap;
justify-content: center;
gap: 0.5rem;
padding: 0.5rem;
}
.data-table .pagination-info,
.data-table .per-page-selector {
flex: 1 1 100%;
justify-content: center;
text-align: center;
}
.data-table .per-page-selector {
justify-content: center;
}
}
/* Исправляем Bootstrap стили для пагинации внутри таблицы */
.table .pagination {
margin-bottom: 0;
}
+65
View File
@@ -0,0 +1,65 @@
// Скрипт тоггла сайдбара с улучшенной обработкой
document.addEventListener("DOMContentLoaded", function () {
const sidebarToggle = document.getElementById('sidebarToggle');
const sidebarWrapper = document.getElementById('sidebar-wrapper');
const body = document.body;
if (sidebarToggle) {
// Обработчик клика
sidebarToggle.addEventListener('click', function(event) {
event.preventDefault();
event.stopPropagation();
// Переключаем класс
body.classList.toggle('sb-sidenav-toggled');
// Сохраняем состояние в localStorage
const isToggled = body.classList.contains('sb-sidenav-toggled');
localStorage.setItem('sidebar-toggled', isToggled);
});
// Закрытие сайдбара при клике вне его (для мобильных)
document.addEventListener('click', function(event) {
if (window.innerWidth <= 768 &&
body.classList.contains('sb-sidenav-toggled') &&
!sidebarWrapper.contains(event.target) &&
event.target !== sidebarToggle &&
!sidebarToggle.contains(event.target)) {
body.classList.remove('sb-sidenav-toggled');
localStorage.setItem('sidebar-toggled', false);
}
});
// Восстановление состояния из localStorage
const sidebarToggled = localStorage.getItem('sidebar-toggled');
if (sidebarToggled === 'true' && window.innerWidth > 768) {
body.classList.add('sb-sidenav-toggled');
}
}
// Адаптивное поведение при изменении размера окна
window.addEventListener('resize', function() {
if (window.innerWidth > 768) {
// На десктопе всегда показываем сайдбар
body.classList.remove('sb-sidenav-toggled');
}
});
// Подсветка активного пункта меню при загрузке
highlightActiveMenuItem();
function highlightActiveMenuItem() {
const currentPath = window.location.pathname;
const menuItems = document.querySelectorAll('.sidebar-link');
menuItems.forEach(item => {
const href = item.getAttribute('href');
if (href && href !== '#' && currentPath.includes(href.replace(baseUrl, ''))) {
item.classList.add('active');
}
});
}
// Базовая функция для получения base URL
const baseUrl = '{{ base_url("/") }}'.replace(/\/+$/, '');
});
File diff suppressed because one or more lines are too long
+539
View File
@@ -0,0 +1,539 @@
/**
* DataTable - Универсальный модуль для интерактивных таблиц
*
* Функциональность:
* - AJAX-загрузка данных
* - Сортировка по столбцам
* - Поиск по столбцам (click-to-search)
* - Пагинация
* - Состояние URL (back/forward навигация)
* - Сохранение состояния поиска между запросами
*/
class DataTable {
/**
* @param {string} containerId - ID контейнера таблицы
* @param {Object} options - Конфигурация таблицы
*/
constructor(containerId, options = {}) {
this.containerId = containerId;
this.options = {
url: options.url || '/api/table',
perPage: options.perPage || 10,
debounceTime: options.debounceTime || 300,
preserveSearchOnSort: options.preserveSearchOnSort !== false,
...options
};
// Состояние таблицы
this.state = {
page: 1,
perPage: this.options.perPage,
sort: '',
order: 'asc',
filters: {}
};
this.searchTimeout = null;
// Инициализация с задержкой для уверенности что DOM готов
this.initWithDOM();
}
/**
* Инициализация с проверкой доступности DOM
*/
initWithDOM() {
// Пробуем найти контейнер
this.container = document.getElementById(this.containerId);
if (!this.container) {
// Контейнер не найден - пробуем через setTimeout
setTimeout(() => {
this.container = document.getElementById(this.containerId);
if (this.container) {
this.init();
} else {
// Контейнер всё ещё не найден, это нормально для AJAX-обновлений
// Не показываем ошибку в консоли
}
}, 0);
return;
}
this.init();
}
/**
* Инициализация таблицы
*/
init() {
// Проверяем, не инициализирован ли уже этот экземпляр
if (this.container.dataset.datatableInitialized) {
return;
}
this.container.dataset.datatableInitialized = 'true';
// Проверяем, есть ли уже данные в таблице (рендер на сервере)
const tbody = this.container.querySelector('tbody');
const existingRows = tbody ? tbody.querySelectorAll('tr') : [];
// Если есть строки в tbody и это не строка загрузки - данные уже есть
if (existingRows.length > 0) {
const hasDataRow = Array.from(existingRows).some(tr => !tr.classList.contains('loading'));
if (hasDataRow) {
// Данные уже есть на странице - не делаем AJAX запрос
this.bindEvents();
return;
}
}
// Если tbody пустой или содержит только строку загрузки - делаем AJAX запрос
this.bindEvents();
this.loadData();
}
/**
* Привязка событий
*/
bindEvents() {
// Делегирование кликов на уровне контейнера
this.container.addEventListener('click', (e) => this.handleClick(e));
// Делегирование ввода в поля поиска
this.container.addEventListener('input', (e) => this.handleInput(e));
// Обработка Enter в полях поиска
this.container.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && e.target.matches('[data-search-input]')) {
e.preventDefault();
e.target.blur();
}
});
// Обработка изменения количества записей на странице
this.container.addEventListener('change', (e) => {
if (e.target.matches('[id^="per-page-select-"]')) {
const value = e.target.value;
this.setPerPage(value);
}
});
}
/**
* Обработка кликов
*/
handleClick(e) {
// Клик по иконке сортировки
const sortIcon = e.target.closest('[data-sort]');
if (sortIcon) {
e.stopPropagation();
const column = sortIcon.dataset.sort;
this.toggleSort(column);
return;
}
// Клик по иконке поиска
const searchTrigger = e.target.closest('[data-search-trigger]');
if (searchTrigger) {
e.stopPropagation();
const th = searchTrigger.closest('th');
this.openSearch(th);
return;
}
// Клик по тексту заголовка
const headerText = e.target.closest('[data-header-text]');
if (headerText) {
e.stopPropagation();
const th = headerText.closest('th');
this.openSearch(th);
return;
}
// Клик по ссылке пагинации
const pageLink = e.target.closest('[data-page]');
if (pageLink && !e.target.closest('.disabled') && !e.target.closest('.active')) {
e.preventDefault();
const page = parseInt(pageLink.dataset.page);
if (page > 0) {
this.state.page = page;
this.loadData();
}
return;
}
// Клик по кнопке "предыдущая/следующая" страница
const navLink = e.target.closest('[data-nav-page]');
if (navLink && !e.target.closest('.disabled')) {
e.preventDefault();
const direction = navLink.dataset.navPage;
this.state.page = direction === 'prev'
? Math.max(1, this.state.page - 1)
: this.state.page + 1;
this.loadData();
return;
}
// Клик вне заголовков таблицы - закрываем все поисковые поля
if (!e.target.closest('thead')) {
this.closeAllSearches();
}
}
/**
* Обработка ввода в поля поиска
*/
handleInput(e) {
if (e.target.matches('[data-search-input]')) {
const column = e.target.dataset.searchInput;
this.debouncedSearch(column, e.target.value);
}
}
/**
* Debounced поиск
*/
debouncedSearch(column, value) {
clearTimeout(this.searchTimeout);
this.searchTimeout = setTimeout(() => {
this.state.filters[column] = value;
this.state.page = 1;
this.loadData();
}, this.options.debounceTime);
}
/**
* Переключение сортировки
*/
toggleSort(column) {
if (this.state.sort === column) {
this.state.order = this.state.order === 'asc' ? 'desc' : 'asc';
} else {
this.state.sort = column;
this.state.order = 'desc';
}
this.loadData();
}
/**
* Открытие поля поиска
*/
openSearch(th) {
const headerText = th.querySelector('[data-header-text]');
const searchInput = th.querySelector('[data-search-input]');
if (headerText && searchInput) {
headerText.style.display = 'none';
searchInput.style.display = 'inline';
searchInput.focus();
}
}
/**
* Закрытие всех полей поиска
*/
closeAllSearches() {
const inputs = this.container.querySelectorAll('[data-search-input]');
inputs.forEach(input => {
const th = input.closest('th');
const headerText = th.querySelector('[data-header-text]');
if (headerText) {
if (input.value.trim()) {
// Если есть значение - показываем заголовок, скрываем input
input.style.display = 'none';
headerText.style.display = 'inline';
} else {
// Если пусто - также скрываем input и показываем заголовок
input.style.display = 'none';
headerText.style.display = 'inline';
}
}
});
}
/**
* Получение CSRF токена из cookie CodeIgniter 4
*/
getCsrfToken() {
// Сначала пробуем из data-атрибутов контейнера (если заданы)
if (this.container.dataset.csrfToken) {
return this.container.dataset.csrfToken;
}
// Пробуем из скрытого input внутри контейнера
const csrfInput = this.container.querySelector('input[name*="csrf"]');
if (csrfInput) {
return csrfInput.value;
}
// Пробуем из скрытого input на всей странице
const globalCsrfInput = document.querySelector('input[name*="csrf"]');
if (globalCsrfInput) {
return globalCsrfInput.value;
}
// Пробуем из cookie - ищем по паттерну
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
const [name, value] = cookie.trim().split('=');
// Ищем cookie, содержащий 'csrf' в имени
if (name.toLowerCase().includes('csrf')) {
return decodeURIComponent(value);
}
}
console.warn('CSRF token not found');
return '';
}
/**
* Получение имени CSRF параметра
*/
getCsrfTokenName() {
// Из data-атрибута
if (this.container.dataset.csrfTokenName) {
return this.container.dataset.csrfTokenName;
}
// Из скрытого input внутри контейнера
const csrfInput = this.container.querySelector('input[name*="csrf"]');
if (csrfInput) {
return csrfInput.name;
}
// Из скрытого input на странице
const globalCsrfInput = document.querySelector('input[name*="csrf"]');
if (globalCsrfInput) {
return globalCsrfInput.name;
}
// Значения по умолчанию
return 'csrf_test_name';
}
/**
* Обновление CSRF токена из ответа сервера
*/
updateCsrfToken(response) {
// CodeIgniter 4 может возвращать новый токен в заголовке
const csrfHeader = response.headers.get('X-CSRF-TOKEN');
if (csrfHeader) {
// Обновляем все найденные CSRF inputs
document.querySelectorAll('input[name*="csrf"]').forEach(input => {
input.value = csrfHeader;
});
// Также обновляем data-атрибут контейнера
this.container.dataset.csrfToken = csrfHeader;
}
}
/**
* Загрузка данных таблицы
*/
async loadData() {
const params = this.buildParams();
const csrfToken = this.getCsrfToken();
const csrfTokenName = this.getCsrfTokenName();
// Добавляем CSRF токен в параметры запроса
const url = `${this.options.url}?${params}&${csrfTokenName}=${encodeURIComponent(csrfToken)}`;
// Показываем лоадер в tbody
const tableBody = this.container.querySelector('tbody');
if (tableBody) {
tableBody.innerHTML = `
<tr>
<td colspan="100" class="text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Загрузка...</span>
</div>
</td>
</tr>
`;
}
try {
const response = await fetch(url, {
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': csrfToken
}
});
// Обновляем CSRF токен из ответа
this.updateCsrfToken(response);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const html = await response.text();
this.updateTable(html);
} catch (error) {
console.error('DataTable: Ошибка загрузки данных:', error);
this.showError();
}
}
/**
* Обновление таблицы
* Парсит HTML и заменяет только tbody/tfoot, сохраняя thead
*/
updateTable(html) {
// Сохраняем состояние полей поиска
const searchInputs = this.container.querySelectorAll('[data-search-input]');
const searchStates = {};
searchInputs.forEach(input => {
searchStates[input.dataset.searchInput] = input.value;
});
// Ищем tbody и tfoot в ответе
const tbodyMatch = html.match(/<tbody[^>]*>[\s\S]*?<\/tbody>/i);
const tfootMatch = html.match(/<tfoot[^>]*>[\s\S]*?<\/tfoot>/i);
const oldTableBody = this.container.querySelector('tbody');
const oldTableFooter = this.container.querySelector('tfoot');
const table = this.container.querySelector('table');
if (tbodyMatch && oldTableBody && table) {
// Извлекаем содержимое tbody (без тегов tbody)
const tbodyContent = tbodyMatch[0].replace(/<\/?tbody[^>]*>/gi, '');
// Очищаем старый tbody и заполняем новым содержимым
oldTableBody.innerHTML = tbodyContent;
// Обновляем tfoot
if (tfootMatch) {
const tfootContent = tfootMatch[0].replace(/<\/?tfoot[^>]*>/gi, '');
if (oldTableFooter) {
oldTableFooter.innerHTML = tfootContent;
} else {
const newTfoot = document.createElement('tfoot');
newTfoot.innerHTML = tfootContent;
table.appendChild(newTfoot);
}
} else if (oldTableFooter) {
oldTableFooter.remove();
}
} else {
// Нет tbody или нет table - полная замена контейнера
this.container.innerHTML = html;
}
// Восстанавливаем состояние полей поиска
this.restoreSearchStates(searchStates);
// Обновляем URL
this.updateURL();
}
/**
* Восстановление состояния полей поиска
*/
restoreSearchStates(savedStates = null) {
const inputs = this.container.querySelectorAll('[data-search-input]');
inputs.forEach(input => {
const column = input.dataset.searchInput;
const value = savedStates && savedStates[column] !== undefined
? savedStates[column]
: input.value.trim();
const th = input.closest('th');
const headerText = th.querySelector('[data-header-text]');
if (headerText) {
if (value) {
headerText.style.display = 'none';
input.style.display = 'inline';
input.value = value;
} else {
input.style.display = 'none';
headerText.style.display = 'inline';
}
}
});
}
/**
* Обновление URL
*/
updateURL() {
const params = this.buildParams();
// Убираем существующие параметры из URL и добавляем новые
const baseUrl = this.options.url.split('?')[0];
const url = `${baseUrl}?${params}`;
window.history.pushState(this.state, '', url);
}
/**
* Формирование параметров запроса
*/
buildParams() {
const params = new URLSearchParams();
params.set('page', this.state.page);
params.set('perPage', this.state.perPage);
if (this.state.sort) {
params.set('sort', this.state.sort);
params.set('order', this.state.order);
}
Object.entries(this.state.filters).forEach(([key, value]) => {
if (value && value.trim()) {
params.set(`filters[${key}]`, value);
}
});
return params.toString();
}
/**
* Показать ошибку загрузки
*/
showError() {
const tableBody = this.container.querySelector('tbody');
if (tableBody) {
tableBody.innerHTML = `
<tr>
<td colspan="100" class="alert alert-danger m-3">
Ошибка загрузки данных. Пожалуйста, обновите страницу.
</td>
</tr>
`;
}
}
/**
* Установка параметров фильтрации извне
*/
setFilter(column, value) {
this.state.filters[column] = value;
this.state.page = 1;
this.loadData();
}
/**
* Установка количества записей на странице
*/
setPerPage(value) {
this.state.perPage = parseInt(value);
this.state.page = 1;
this.loadData();
}
/**
* Переход на страницу
*/
goToPage(page) {
this.state.page = Math.max(1, parseInt(page));
this.loadData();
}
}
// Экспорт для глобального использования
window.DataTable = DataTable;
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

+59
View File
@@ -0,0 +1,59 @@
<?php
use CodeIgniter\Boot;
use Config\Paths;
/*
*---------------------------------------------------------------
* CHECK PHP VERSION
*---------------------------------------------------------------
*/
$minPhpVersion = '8.1'; // If you update this, don't forget to update `spark`.
if (version_compare(PHP_VERSION, $minPhpVersion, '<')) {
$message = sprintf(
'Your PHP version must be %s or higher to run CodeIgniter. Current version: %s',
$minPhpVersion,
PHP_VERSION,
);
header('HTTP/1.1 503 Service Unavailable.', true, 503);
echo $message;
exit(1);
}
/*
*---------------------------------------------------------------
* SET THE CURRENT DIRECTORY
*---------------------------------------------------------------
*/
// Path to the front controller (this file)
define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR);
// Ensure the current directory is pointing to the front controller's directory
if (getcwd() . DIRECTORY_SEPARATOR !== FCPATH) {
chdir(FCPATH);
}
/*
*---------------------------------------------------------------
* BOOTSTRAP THE APPLICATION
*---------------------------------------------------------------
* This process sets up the path constants, loads and registers
* our autoloader, along with Composer's, loads our constants
* and fires up an environment-specific bootstrapping.
*/
// LOAD OUR PATHS CONFIG FILE
// This is the line that might need to be changed, depending on your folder structure.
require FCPATH . '../app/Config/Paths.php';
// ^^^ Change this line if you move your application folder
$paths = new Paths();
// LOAD THE FRAMEWORK BOOTSTRAP FILE
require $paths->systemDirectory . '/Boot.php';
exit(Boot::bootWeb($paths));
+2
View File
@@ -0,0 +1,2 @@
User-agent: *
Disallow: