Many fixes

This commit is contained in:
root
2026-01-16 21:58:12 +03:00
parent b810a17649
commit 77f76c8c28
45 changed files with 3392 additions and 1243 deletions
@@ -0,0 +1,209 @@
{% extends 'superadmin/layout.twig' %}
{% block title %}Добавить подписку - Суперадмин{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Добавить подписку</h1>
<a href="{{ base_url('/superadmin/subscriptions') }}" class="btn btn-outline-secondary">
<i class="fa-solid fa-arrow-left"></i> Назад к списку
</a>
</div>
<div class="card">
<div class="card-body">
<form action="{{ base_url('/superadmin/subscriptions/store') }}" method="post" class="row g-3">
{{ csrf_field()|raw }}
<div class="col-md-6">
<label class="form-label">Организация *</label>
<div class="autocomplete-wrapper">
<input type="text" class="form-control autocomplete-input" placeholder="Начните вводить название организации..."
data-url="{{ base_url('/superadmin/organizations/search') }}" autocomplete="off">
<input type="hidden" name="organization_id" class="autocomplete-value" value="">
<div class="autocomplete-dropdown"></div>
</div>
</div>
<div class="col-md-6">
<label class="form-label">Модуль *</label>
<select name="module_code" class="form-select" required>
<option value="">Выберите модуль...</option>
{% for code, module in modules %}
<option value="{{ code }}">{{ module.name }} - {{ module.price_monthly }} руб/мес</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<label class="form-label">Количество дней</label>
<input type="number" name="duration_days" class="form-control" value="30" min="0" placeholder="0 - бессрочно">
<div class="form-text">0 - подписка без срока истечения</div>
</div>
<div class="col-md-4">
<label class="form-label">Статус</label>
<select name="status" class="form-select">
<option value="active">Активна</option>
<option value="trial">Триал</option>
<option value="expired">Истекла</option>
<option value="cancelled">Отменена</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Истекает</label>
<input type="text" class="form-control" disabled value="Будет рассчитано автоматически">
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">
<i class="fa-solid fa-plus"></i> Создать подписку
</button>
</div>
</form>
</div>
</div>
<style>
.autocomplete-wrapper {
position: relative;
}
.autocomplete-input {
width: 100%;
}
.autocomplete-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: #fff;
border: 1px solid #ddd;
border-top: none;
border-radius: 0 0 5px 5px;
max-height: 250px;
overflow-y: auto;
display: none;
z-index: 1000;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.autocomplete-dropdown.active {
display: block;
}
.autocomplete-item {
padding: 12px 15px;
cursor: pointer;
border-bottom: 1px solid #eee;
transition: background-color 0.2s;
}
.autocomplete-item:hover,
.autocomplete-item.selected {
background-color: #f8f9fa;
}
.autocomplete-item:last-child {
border-bottom: none;
}
.autocomplete-empty {
padding: 12px 15px;
color: #6c757d;
text-align: center;
}
</style>
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const input = document.querySelector('.autocomplete-input');
const hiddenInput = document.querySelector('.autocomplete-value');
const dropdown = document.querySelector('.autocomplete-dropdown');
let timeout = null;
input.addEventListener('input', function() {
clearTimeout(timeout);
const value = this.value.trim();
if (value.length < 2) {
dropdown.classList.remove('active');
hiddenInput.value = '';
return;
}
timeout = setTimeout(() => {
fetch(this.dataset.url + '?q=' + encodeURIComponent(value))
.then(response => response.json())
.then(data => {
dropdown.innerHTML = '';
if (data.results && data.results.length > 0) {
data.results.forEach(item => {
const div = document.createElement('div');
div.className = 'autocomplete-item';
div.textContent = item.text;
div.dataset.id = item.id;
div.addEventListener('click', function() {
input.value = this.textContent;
hiddenInput.value = this.dataset.id;
dropdown.classList.remove('active');
});
dropdown.appendChild(div);
});
} else {
const div = document.createElement('div');
div.className = 'autocomplete-empty';
div.textContent = 'Организации не найдены';
dropdown.appendChild(div);
}
dropdown.classList.add('active');
})
.catch(error => {
console.error('Error:', error);
});
}, 300);
});
document.addEventListener('click', function(e) {
if (!e.target.closest('.autocomplete-wrapper')) {
dropdown.classList.remove('active');
}
});
input.addEventListener('focus', function() {
if (this.value.trim().length >= 2) {
this.dispatchEvent(new Event('input'));
}
});
input.addEventListener('keydown', function(e) {
const items = dropdown.querySelectorAll('.autocomplete-item');
const selected = dropdown.querySelector('.selected');
let index = Array.from(items).indexOf(selected);
if (e.key === 'ArrowDown') {
e.preventDefault();
if (items.length > 0) {
if (index < items.length - 1) {
if (selected) selected.classList.remove('selected');
items[index + 1].classList.add('selected');
}
}
} else if (e.key === 'ArrowUp') {
e.preventDefault();
if (index > 0) {
if (selected) selected.classList.remove('selected');
items[index - 1].classList.add('selected');
}
} else if (e.key === 'Enter' && selected) {
e.preventDefault();
input.value = selected.textContent;
hiddenInput.value = selected.dataset.id;
dropdown.classList.remove('active');
} else if (e.key === 'Escape') {
dropdown.classList.remove('active');
}
});
});
</script>
{% endblock %}
@@ -0,0 +1,63 @@
{% extends 'superadmin/layout.twig' %}
{% block title %}Подписки - Суперадмин{% endblock %}
{% block styles %}
<link rel="stylesheet" href="/assets/css/modules/data-table.css">
{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Управление подписками</h1>
<a href="{{ base_url('/superadmin/subscriptions/create') }}" class="btn btn-primary">
<i class="fa-solid fa-plus"></i> Добавить подписку
</a>
</div>
{% if session.success %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ session.success }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
{% if session.error %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
{{ session.error }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<div class="card">
<div class="card-body">
{{ tableHtml|raw }}
{# CSRF токен для AJAX запросов #}
{{ csrf_field()|raw }}
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="{{ site_url('/assets/js/modules/DataTable.js') }}"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.data-table').forEach(function(container) {
const id = container.id;
const url = container.dataset.url;
const perPage = parseInt(container.dataset.perPage) || 10;
if (window.dataTables && window.dataTables[id]) {
return;
}
const table = new DataTable(id, {
url: url,
perPage: perPage
});
window.dataTables = window.dataTables || {};
window.dataTables[id] = table;
});
});
</script>
{% endblock %}