Many fixes
This commit is contained in:
@@ -101,7 +101,86 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Легенда #}
|
||||
{% block stylesheets %}
|
||||
<style>
|
||||
.calendar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.calendar .calendar-header {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(7, 1fr) !important;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.calendar .calendar-header-cell {
|
||||
padding: 0.75rem 0.5rem;
|
||||
font-weight: 500;
|
||||
color: #6b7280;
|
||||
font-size: 0.875rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.calendar .calendar-grid {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(7, 1fr) !important;
|
||||
}
|
||||
|
||||
.calendar .calendar-cell {
|
||||
min-height: 100px;
|
||||
padding: 0.5rem;
|
||||
border-right: 1px solid #e5e7eb;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.calendar .calendar-cell:nth-child(7n) {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.calendar .calendar-cell.bg-light {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
.calendar .calendar-cell-today {
|
||||
background-color: #eff6ff;
|
||||
}
|
||||
|
||||
.calendar .calendar-day-number {
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.calendar .calendar-events {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.calendar .calendar-event {
|
||||
display: block !important;
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
background-color: #f3f4f6;
|
||||
border-left: 3px solid #6b7280;
|
||||
border-radius: 0.25rem;
|
||||
text-decoration: none;
|
||||
color: #374151;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.calendar .calendar-event:hover {
|
||||
background-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.calendar .calendar-events-more {
|
||||
padding: 0.125rem 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% if showLegend|default(true) and (legend is defined or events is defined) %}
|
||||
<div class="card shadow-sm mt-4">
|
||||
<div class="card-body">
|
||||
|
||||
@@ -131,20 +131,42 @@ function handleDrop(e) {
|
||||
|
||||
if (itemId && newColumnId) {
|
||||
if (moveUrl) {
|
||||
// AJAX перемещение
|
||||
console.log('Moving deal:', itemId, 'to stage:', newColumnId);
|
||||
|
||||
// Находим перетаскиваемую карточку
|
||||
const draggedCard = document.querySelector(`.kanban-card[data-item-id="${itemId}"]`);
|
||||
const sourceColumn = draggedCard ? draggedCard.closest('.kanban-cards-container') : null;
|
||||
|
||||
// AJAX перемещение - base.js автоматически добавит CSRF заголовок
|
||||
fetch(moveUrl, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: 'item_id=' + itemId + '&column_id=' + newColumnId
|
||||
body: 'deal_id=' + itemId + '&stage_id=' + newColumnId
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
if (onMove) {
|
||||
window[onMove](itemId, newColumnId, data);
|
||||
} else if (draggedCard && sourceColumn) {
|
||||
// Перемещаем карточку в новую колонку
|
||||
const targetContainer = this;
|
||||
targetContainer.appendChild(draggedCard);
|
||||
|
||||
// Обновляем счётчики колонок
|
||||
updateColumnCounters(sourceColumn);
|
||||
updateColumnCounters(targetContainer);
|
||||
|
||||
// Анимация успешного перемещения
|
||||
draggedCard.style.transition = 'all 0.2s ease';
|
||||
draggedCard.style.transform = 'scale(1.02)';
|
||||
setTimeout(() => {
|
||||
draggedCard.style.transform = '';
|
||||
}, 200);
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
@@ -162,4 +184,22 @@ function handleDrop(e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновление счётчиков колонки (количество карточек и сумма)
|
||||
*/
|
||||
function updateColumnCounters(container) {
|
||||
const columnId = container.dataset.columnId;
|
||||
const cards = container.querySelectorAll('.kanban-card');
|
||||
const count = cards.length;
|
||||
|
||||
// Находим badge в заголовке колонки
|
||||
const column = container.closest('.kanban-column');
|
||||
if (column) {
|
||||
const badge = column.querySelector('.badge');
|
||||
if (badge) {
|
||||
badge.textContent = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -16,10 +16,21 @@
|
||||
<div class="col-md-6 col-lg-3 mb-4">
|
||||
<div class="card h-100 text-center p-3">
|
||||
<div class="card-body">
|
||||
<i class="fa-solid fa-users fa-3x text-primary mb-3"></i>
|
||||
<h5 class="card-title">CRM</h5>
|
||||
<i class="fa-solid fa-building fa-3x text-primary mb-3"></i>
|
||||
<h5 class="card-title">Клиенты</h5>
|
||||
<p class="card-text text-muted">Управление клиентами</p>
|
||||
<a href="#" class="btn btn-outline-primary btn-sm">Скоро</a>
|
||||
<a href="/clients" class="btn btn-outline-primary btn-sm">Открыть</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-3 mb-4">
|
||||
<div class="card h-100 text-center p-3">
|
||||
<div class="card-body">
|
||||
<i class="fa-solid fa-chart-line fa-3x text-primary mb-3"></i>
|
||||
<h5 class="card-title">CRM</h5>
|
||||
<p class="card-text">Управление сделками</p>
|
||||
<a href="/crm" class="btn btn-outline-primary btn-sm">Открыть</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -37,23 +37,62 @@
|
||||
<a href="{{ base_url('/clients') }}"
|
||||
class="list-group-item list-group-item-action list-group-item-light py-3 sidebar-link
|
||||
{{ is_active_route('clients') ? 'active' : '' }}">
|
||||
<i class="fa-solid fa-users me-2"></i> Клиенты
|
||||
<i class="fa-solid fa-building text-primary me-2"></i> Клиенты
|
||||
</a>
|
||||
|
||||
{# Модули #}
|
||||
|
||||
<!-- Будущие модули -->
|
||||
<a href="#" class="list-group-item list-group-item-action list-group-item-light py-3 sidebar-link disabled">
|
||||
<i class="fa-solid fa-chart-line me-2"></i> CRM
|
||||
{# CRM модуль #}
|
||||
{% if is_module_available('crm') %}
|
||||
<a href="{{ base_url('/crm') }}"
|
||||
class="list-group-item list-group-item-action list-group-item-light py-3 sidebar-link
|
||||
{{ is_active_route('crm') ? 'active' : '' }}">
|
||||
<i class="fa-solid fa-chart-line text-success me-2"></i> CRM
|
||||
</a>
|
||||
<a href="#" class="list-group-item list-group-item-action list-group-item-light py-3 sidebar-link disabled">
|
||||
{% else %}
|
||||
<a href="#" class="list-group-item list-group-item-action list-group-item-light py-3 sidebar-link disabled" title="Модуль CRM не активен">
|
||||
<i class="fa-solid fa-lock text-muted me-2"></i> CRM
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{# Booking модуль #}
|
||||
{% if is_module_available('booking') %}
|
||||
<a href="{{ base_url('/booking') }}"
|
||||
class="list-group-item list-group-item-action list-group-item-light py-3 sidebar-link
|
||||
{{ is_active_route('booking') ? 'active' : '' }}">
|
||||
<i class="fa-solid fa-calendar me-2"></i> Booking
|
||||
</a>
|
||||
<a href="#" class="list-group-item list-group-item-action list-group-item-light py-3 sidebar-link disabled">
|
||||
<i class="fa-solid fa-file-contract me-2"></i> Proof
|
||||
{% else %}
|
||||
<a href="#" class="list-group-item list-group-item-action list-group-item-light py-3 sidebar-link disabled" title="Модуль Бронирования не активен">
|
||||
<i class="fa-solid fa-lock text-muted me-2"></i> Booking
|
||||
</a>
|
||||
<a href="#" class="list-group-item list-group-item-action list-group-item-light py-3 sidebar-link disabled">
|
||||
{% endif %}
|
||||
|
||||
{# Tasks модуль #}
|
||||
{% if is_module_available('tasks') %}
|
||||
<a href="{{ base_url('/tasks') }}"
|
||||
class="list-group-item list-group-item-action list-group-item-light py-3 sidebar-link
|
||||
{{ is_active_route('tasks') ? 'active' : '' }}">
|
||||
<i class="fa-solid fa-check-square me-2"></i> Tasks
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="#" class="list-group-item list-group-item-action list-group-item-light py-3 sidebar-link disabled" title="Модуль Задачи не активен">
|
||||
<i class="fa-solid fa-lock text-muted me-2"></i> Tasks
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{# Proof модуль #}
|
||||
{% if is_module_available('proof') %}
|
||||
<a href="{{ base_url('/proof') }}"
|
||||
class="list-group-item list-group-item-action list-group-item-light py-3 sidebar-link
|
||||
{{ is_active_route('proof') ? 'active' : '' }}">
|
||||
<i class="fa-solid fa-file-contract me-2"></i> Proof
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="#" class="list-group-item list-group-item-action list-group-item-light py-3 sidebar-link disabled" title="Модуль Proof не активен">
|
||||
<i class="fa-solid fa-lock text-muted me-2"></i> Proof
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- /SIDEBAR -->
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
<div class="icon">📅</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3>Всего тарифов</h3>
|
||||
<div class="value">{{ stats.total_plans|number_format(0, '', ' ') }}</div>
|
||||
<div class="icon">📋</div>
|
||||
<h3>Всего модулей</h3>
|
||||
<div class="value">{{ stats.total_modules|number_format(0, '', ' ') }}</div>
|
||||
<div class="icon">📦</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -71,7 +71,8 @@
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="{{ base_url('/superadmin') }}" class="{{ is_active_route('superadmin') and not is_active_route('superadmin/') ? 'active' : '' }}">📊 Дашборд</a></li>
|
||||
<li><a href="{{ base_url('/superadmin/plans') }}" class="{{ is_active_route('superadmin/plans') ? 'active' : '' }}">📋 Тарифы</a></li>
|
||||
<li><a href="{{ base_url('/superadmin/modules') }}" class="{{ is_active_route('superadmin/modules') ? 'active' : '' }}">📦 Модули</a></li>
|
||||
<li><a href="{{ base_url('/superadmin/subscriptions') }}" class="{{ is_active_route('superadmin/subscriptions') ? 'active' : '' }}">💳 Подписки</a></li>
|
||||
<li><a href="{{ base_url('/superadmin/organizations') }}" class="{{ is_active_route('superadmin/organizations') ? 'active' : '' }}">🏢 Организации</a></li>
|
||||
<li><a href="{{ base_url('/superadmin/users') }}" class="{{ is_active_route('superadmin/users') ? 'active' : '' }}">👥 Пользователи</a></li>
|
||||
<li><a href="{{ base_url('/superadmin/statistics') }}" class="{{ is_active_route('superadmin/statistics') ? 'active' : '' }}">📈 Статистика</a></li>
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
{% extends 'superadmin/layout.twig' %}
|
||||
|
||||
{% block title %}Модули - Суперадмин{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Модули системы</h1>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs" id="modulesTabs" role="tablist">
|
||||
{% for code, module in modules %}
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link {% if loop.first %}active{% endif %}"
|
||||
id="{{ code }}-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#{{ code }}-tab-pane"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="{{ code }}-tab-pane"
|
||||
aria-selected="{{ loop.first ? 'true' : 'false' }}">
|
||||
{{ module.name }}
|
||||
</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="tab-content" id="modulesTabsContent">
|
||||
{% for code, module in modules %}
|
||||
<div class="tab-pane fade {% if loop.first %}show active{% endif %}"
|
||||
id="{{ code }}-tab-pane"
|
||||
role="tabpanel"
|
||||
aria-labelledby="{{ code }}-tab"
|
||||
tabindex="0">
|
||||
|
||||
<form action="{{ base_url('/superadmin/modules/update') }}" method="post" class="row g-3">
|
||||
{{ csrf_field()|raw }}
|
||||
<input type="hidden" name="module_code" value="{{ code }}">
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Название модуля</label>
|
||||
<input type="text" name="name" class="form-control" value="{{ module.name }}" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Описание</label>
|
||||
<input type="text" name="description" class="form-control" value="{{ module.description }}">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Цена (руб/месяц)</label>
|
||||
<input type="number" name="price_monthly" class="form-control" value="{{ module.price_monthly }}" min="0">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Цена (руб/год)</label>
|
||||
<input type="number" name="price_yearly" class="form-control" value="{{ module.price_yearly }}" min="0">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Дней триала</label>
|
||||
<input type="number" name="trial_days" class="form-control" value="{{ module.trial_days }}" min="0">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
<strong>Код модуля:</strong> {{ code }}<br>
|
||||
<strong>Возможности:</strong>
|
||||
<ul class="mb-0 mt-2">
|
||||
{% for feature in module.features %}
|
||||
<li>{{ feature }}</li>
|
||||
{% else %}
|
||||
<li>Нет описания возможностей</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fa-solid fa-save"></i> Сохранить изменения
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,220 +1,245 @@
|
||||
{% extends 'superadmin/layout.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="sa-header">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Организация: {{ organization.name }}</h1>
|
||||
<a href="{{ base_url('/superadmin/organizations') }}" class="btn btn-primary">← Назад</a>
|
||||
<a href="{{ base_url('/superadmin/organizations') }}" class="btn btn-outline-secondary">
|
||||
<i class="fa-solid fa-arrow-left"></i> Назад к списку
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% for alert in get_alerts() %}
|
||||
<div class="alert alert-{{ alert.type }}">{{ alert.message }}</div>
|
||||
<div class="alert alert-{{ alert.type }}">{{ alert.message }}</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="sa-card">
|
||||
<div class="sa-card-header">
|
||||
<h2>Информация об организации</h2>
|
||||
</div>
|
||||
<div class="sa-card-body">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th style="width: 150px;">ID</th>
|
||||
<td>{{ organization.id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<td>{{ organization.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Тип</th>
|
||||
<td>
|
||||
{% if organization.type == 'business' %}
|
||||
<span class="badge badge-info">Бизнес</span>
|
||||
{% else %}
|
||||
<span class="badge badge-warning">Личное пространство</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Тариф</th>
|
||||
<td>
|
||||
{% if currentSubscription %}
|
||||
{% set plan = plans|filter(p => p.id == currentSubscription.plan_id)|first %}
|
||||
{% if plan %}
|
||||
<span class="badge badge-primary">{{ plan.name }}</span>
|
||||
{% else %}
|
||||
<span class="badge badge-secondary">ID: {{ currentSubscription.plan_id }}</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="badge badge-secondary">Не назначен</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Статус подписки</th>
|
||||
<td>
|
||||
{% if currentSubscription %}
|
||||
{% if currentSubscription.status == 'active' %}
|
||||
<span class="badge badge-success">Активна</span>
|
||||
{% elseif currentSubscription.status == 'expired' %}
|
||||
<span class="badge badge-danger">Истёкшая</span>
|
||||
{% elseif currentSubscription.status == 'trial' %}
|
||||
<span class="badge badge-info">Пробный период</span>
|
||||
{% else %}
|
||||
<span class="badge badge-warning">{{ currentSubscription.status }}</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="badge badge-secondary">Нет подписки</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Срок действия</th>
|
||||
<td>
|
||||
{% if currentSubscription and currentSubscription.expires_at %}
|
||||
до {{ currentSubscription.expires_at|date('d.m.Y H:i') }}
|
||||
{% else %}
|
||||
Не ограничен
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Статус организации</th>
|
||||
<td>
|
||||
{% if organization.status == 'active' %}
|
||||
<span class="badge badge-success">Активна</span>
|
||||
{% elseif organization.status == 'blocked' %}
|
||||
<span class="badge badge-danger">Заблокирована</span>
|
||||
{% else %}
|
||||
<span class="badge badge-warning">{{ organization.status|default('Не определён') }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Создана</th>
|
||||
<td>{{ organization.created_at|date('d.m.Y H:i') }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div style="margin-top: 20px; display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
{% if organization.status == 'active' %}
|
||||
<a href="{{ base_url('/superadmin/organizations/block/' ~ organization.id) }}" class="btn btn-warning" onclick="return confirm('Заблокировать организацию?')">🚫 Заблокировать</a>
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">Подписки на модули</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if subscriptions is empty %}
|
||||
<p class="text-muted text-center py-4">У организации нет активных подписок</p>
|
||||
{% else %}
|
||||
<a href="{{ base_url('/superadmin/organizations/unblock/' ~ organization.id) }}" class="btn btn-success" onclick="return confirm('Разблокировать организацию?')">✅ Разблокировать</a>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Модуль</th>
|
||||
<th>Статус</th>
|
||||
<th>Истекает</th>
|
||||
<th>Создана</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for sub in subscriptions %}
|
||||
{% set module = allModules[sub.module_code] %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ module.name|default(sub.module_code) }}</strong>
|
||||
<div class="text-muted small">{{ module.description|default('') }}</div>
|
||||
</td>
|
||||
<td>
|
||||
{% if sub.status == 'active' %}
|
||||
<span class="badge bg-success">Активна</span>
|
||||
{% elseif sub.status == 'trial' %}
|
||||
<span class="badge bg-info">Триал</span>
|
||||
{% elseif sub.status == 'expired' %}
|
||||
<span class="badge bg-danger">Истекла</span>
|
||||
{% else %}
|
||||
<span class="badge bg-warning">{{ sub.status }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if sub.expires_at %}
|
||||
{{ sub.expires_at|date('d.m.Y H:i') }}
|
||||
{% else %}
|
||||
<span class="text-muted">Бессрочно</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ sub.created_at|date('d.m.Y H:i') }}</td>
|
||||
<td>
|
||||
<a href="{{ base_url('/superadmin/organizations/' ~ organization.id ~ '/removeSubscription/' ~ sub.id) }}"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
onclick="return confirm('Удалить подписку на модуль {{ module.name|default(sub.module_code) }}?')"
|
||||
title="Удалить подписку">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">Участники организации</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if users is empty %}
|
||||
<p class="text-muted text-center py-4">Участников пока нет</p>
|
||||
{% else %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Пользователь</th>
|
||||
<th>Email</th>
|
||||
<th>Роль</th>
|
||||
<th>Статус</th>
|
||||
<th>Дата добавления</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.name|default('—') }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>
|
||||
<span class="badge {{ user.role == 'owner' ? 'bg-danger' : (user.role == 'admin' ? 'bg-warning' : 'bg-info') }}">
|
||||
{{ user.role }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if user.status == 'active' %}
|
||||
<span class="badge bg-success">Активен</span>
|
||||
{% elseif user.status == 'blocked' %}
|
||||
<span class="badge bg-danger">Заблокирован</span>
|
||||
{% else %}
|
||||
<span class="badge bg-warning">{{ user.status }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ user.created_at|date('d.m.Y') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
<a href="{{ base_url('/superadmin/organizations/delete/' ~ organization.id) }}" class="btn btn-danger" onclick="return confirm('Удалить организацию? Это действие нельзя отменить!')">🗑️ Удалить</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sa-card">
|
||||
<div class="sa-card-header">
|
||||
<h2>Управление тарифом</h2>
|
||||
</div>
|
||||
<div class="sa-card-body">
|
||||
<form action="{{ base_url('/superadmin/organizations/set-plan/' ~ organization.id) }}" method="post">
|
||||
{{ csrf_field()|raw }}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="plan_id">Выберите тариф</label>
|
||||
<select name="plan_id" id="plan_id" class="form-control" required>
|
||||
<option value="">-- Выберите тариф --</option>
|
||||
{% for plan in plans %}
|
||||
<option value="{{ plan.id }}" {{ currentSubscription and currentSubscription.plan_id == plan.id ? 'selected' : '' }}>
|
||||
{{ plan.name }} - {{ plan.price }} {{ plan.currency }}/{{ plan.billing_period }}
|
||||
<div class="col-md-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">Добавить подписку</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ base_url('/superadmin/organizations/' ~ organization.id ~ '/add-subscription') }}" method="post">
|
||||
{{ csrf_field()|raw }}
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Модуль</label>
|
||||
<select name="module_code" class="form-select" required>
|
||||
<option value="">Выберите модуль...</option>
|
||||
{% for code, module in allModules %}
|
||||
{% set hasSub = false %}
|
||||
{% for sub in subscriptions %}
|
||||
{% if sub.module_code == code %}
|
||||
{% set hasSub = true %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<option value="{{ code }}" {{ hasSub ? 'disabled style="background-color: #f8f9fa;"' : '' }}>
|
||||
{{ module.name }} - {{ module.price_monthly }} руб/мес {{ hasSub ? '(уже есть)' : '' }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="duration_days">Срок действия (дней)</label>
|
||||
<input type="number" name="duration_days" id="duration_days" class="form-control"
|
||||
value="30" min="0" max="365000" placeholder="30">
|
||||
<small class="text-muted">Оставьте пустым или 0 для неограниченного срока</small>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Назначить тариф</button>
|
||||
</form>
|
||||
|
||||
<hr style="margin: 20px 0; border: none; border-top: 1px solid #eee;">
|
||||
|
||||
<h4>Статистика</h4>
|
||||
<div style="text-align: center; padding: 20px;">
|
||||
<div style="font-size: 48px; font-weight: bold; color: #3498db;">{{ users|length }}</div>
|
||||
<div style="color: #7f8c8d;">Участников</div>
|
||||
</div>
|
||||
<hr style="margin: 20px 0; border: none; border-top: 1px solid #eee;">
|
||||
<div style="display: flex; justify-content: space-around; text-align: center;">
|
||||
<div>
|
||||
<div style="font-size: 24px; font-weight: bold; color: #27ae60;">
|
||||
{% set owner_count = users|filter(u => u.role == 'owner')|length %}
|
||||
{{ owner_count }}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div style="color: #7f8c8d; font-size: 12px;">Владельцы</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 24px; font-weight: bold; color: #f39c12;">
|
||||
{% set admin_count = users|filter(u => u.role == 'admin')|length %}
|
||||
{{ admin_count }}
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Срок действия (дней)</label>
|
||||
<input type="number" name="duration_days" class="form-control" value="30" min="0">
|
||||
<div class="form-text">0 - бессрочно</div>
|
||||
</div>
|
||||
<div style="color: #7f8c8d; font-size: 12px;">Админы</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 24px; font-weight: bold; color: #9b59b6;">
|
||||
{% set manager_count = users|filter(u => u.role == 'manager')|length %}
|
||||
{{ manager_count }}
|
||||
|
||||
<div class="mb-3">
|
||||
<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 style="color: #7f8c8d; font-size: 12px;">Менеджеры</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="fa-solid fa-plus"></i> Добавить подписку
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sa-card" style="margin-top: 20px;">
|
||||
<div class="sa-card-header">
|
||||
<h2>Участники организации</h2>
|
||||
</div>
|
||||
<div class="sa-card-body">
|
||||
{% if users is empty %}
|
||||
<p style="color: #7f8c8d; text-align: center; padding: 40px;">Участников пока нет</p>
|
||||
{% else %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">Информация</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<th>Пользователь</th>
|
||||
<th>Email</th>
|
||||
<th>Роль</th>
|
||||
<th>Статус</th>
|
||||
<th>Дата добавления</th>
|
||||
<td>ID</td>
|
||||
<td class="text-end">{{ organization.id }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.name|default('—') }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>
|
||||
<span class="badge {{ user.role == 'owner' ? 'badge-danger' : (user.role == 'admin' ? 'badge-warning' : 'badge-info') }}">
|
||||
{{ user.role }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if user.status == 'active' %}
|
||||
<span class="badge badge-success">Активен</span>
|
||||
{% elseif user.status == 'blocked' %}
|
||||
<span class="badge badge-danger">Заблокирован</span>
|
||||
<td>Тип</td>
|
||||
<td class="text-end">
|
||||
{% if organization.type == 'business' %}
|
||||
<span class="badge bg-info">Бизнес</span>
|
||||
{% else %}
|
||||
<span class="badge badge-warning">{{ user.status }}</span>
|
||||
<span class="badge bg-warning">Личное</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ user.created_at|date('d.m.Y') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>Статус</td>
|
||||
<td class="text-end">
|
||||
{% if organization.status == 'active' %}
|
||||
<span class="badge bg-success">Активна</span>
|
||||
{% elseif organization.status == 'blocked' %}
|
||||
<span class="badge bg-danger">Заблокирована</span>
|
||||
{% else %}
|
||||
<span class="badge bg-warning">{{ organization.status }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Создана</td>
|
||||
<td class="text-end">{{ organization.created_at|date('d.m.Y H:i') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Участников</td>
|
||||
<td class="text-end">{{ users|length }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-column gap-2">
|
||||
{% if organization.status == 'active' %}
|
||||
<a href="{{ base_url('/superadmin/organizations/block/' ~ organization.id) }}"
|
||||
class="btn btn-warning"
|
||||
onclick="return confirm('Заблокировать организацию?')">
|
||||
<i class="fa-solid fa-ban"></i> Заблокировать
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{{ base_url('/superadmin/organizations/unblock/' ~ organization.id) }}"
|
||||
class="btn btn-success"
|
||||
onclick="return confirm('Разблокировать организацию?')">
|
||||
<i class="fa-solid fa-check"></i> Разблокировать
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ base_url('/superadmin/organizations/delete/' ~ organization.id) }}"
|
||||
class="btn btn-danger"
|
||||
onclick="return confirm('Удалить организацию? Это действие нельзя отменить!')">
|
||||
<i class="fa-solid fa-trash"></i> Удалить организацию
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
{% extends 'superadmin/layout.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="sa-header">
|
||||
<h1>Создание тарифа</h1>
|
||||
<a href="{{ base_url('/superadmin/plans') }}" class="btn btn-primary">← Назад</a>
|
||||
</div>
|
||||
|
||||
<div class="sa-card">
|
||||
<div class="sa-card-body">
|
||||
<form action="{{ base_url('/superadmin/plans/store') }}" method="post">
|
||||
{{ csrf_field()|raw }}
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label for="name">Название тарифа *</label>
|
||||
<input type="text" name="name" id="name" class="form-control" value="{{ old('name') }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="price">Цена *</label>
|
||||
<input type="number" name="price" id="price" class="form-control" value="{{ old('price', 0) }}" step="0.01" min="0" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description">Описание</label>
|
||||
<textarea name="description" id="description" class="form-control" rows="3">{{ old('description') }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label for="currency">Валюта</label>
|
||||
<select name="currency" id="currency" class="form-control">
|
||||
<option value="RUB" {{ old('currency', 'RUB') == 'RUB' ? 'selected' : '' }}>Рубль (RUB)</option>
|
||||
<option value="USD" {{ old('currency') == 'USD' ? 'selected' : '' }}>Доллар (USD)</option>
|
||||
<option value="EUR" {{ old('currency') == 'EUR' ? 'selected' : '' }}>Евро (EUR)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="billing_period">Период оплаты</label>
|
||||
<select name="billing_period" id="billing_period" class="form-control">
|
||||
<option value="monthly" {{ old('billing_period', 'monthly') == 'monthly' ? 'selected' : '' }}>Ежемесячно</option>
|
||||
<option value="yearly" {{ old('billing_period') == 'yearly' ? 'selected' : '' }}>Ежегодно</option>
|
||||
<option value="quarterly" {{ old('billing_period') == 'quarterly' ? 'selected' : '' }}>Ежеквартально</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label for="max_users">Максимум пользователей *</label>
|
||||
<input type="number" name="max_users" id="max_users" class="form-control" value="{{ old('max_users', 5) }}" min="1" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="max_clients">Максимум клиентов *</label>
|
||||
<input type="number" name="max_clients" id="max_clients" class="form-control" value="{{ old('max_clients', 100) }}" min="1" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="max_storage">Максимум хранилища (ГБ) *</label>
|
||||
<input type="number" name="max_storage" id="max_storage" class="form-control" value="{{ old('max_storage', 10) }}" min="1" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="features">Возможности (каждая с новой строки)</label>
|
||||
<textarea name="features_list" id="features" class="form-control" rows="5" placeholder="Неограниченные проекты Приоритетная поддержка Экспорт в PDF">{{ old('features_list') }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" name="is_active" value="1" {{ old('is_active', 1) ? 'checked' : '' }}>
|
||||
Активен (доступен для выбора)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" name="is_default" value="1" {{ old('is_default') ? 'checked' : '' }}>
|
||||
Тариф по умолчанию
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-success btn-block">Создать тариф</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,96 +0,0 @@
|
||||
{% extends 'superadmin/layout.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="sa-header">
|
||||
<h1>Редактирование тарифа: {{ plan.name }}</h1>
|
||||
<a href="{{ base_url('/superadmin/plans') }}" class="btn btn-primary">← Назад</a>
|
||||
</div>
|
||||
|
||||
<div class="sa-card">
|
||||
<div class="sa-card-body">
|
||||
<form action="{{ base_url('/superadmin/plans/update/' ~ plan.id) }}" method="post">
|
||||
{{ csrf_field()|raw }}
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label for="name">Название тарифа *</label>
|
||||
<input type="text" name="name" id="name" class="form-control" value="{{ plan.name }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="price">Цена *</label>
|
||||
<input type="number" name="price" id="price" class="form-control" value="{{ plan.price }}" step="0.01" min="0" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description">Описание</label>
|
||||
<textarea name="description" id="description" class="form-control" rows="3">{{ plan.description|default('') }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label for="currency">Валюта</label>
|
||||
<select name="currency" id="currency" class="form-control">
|
||||
<option value="RUB" {{ plan.currency == 'RUB' ? 'selected' : '' }}>Рубль (RUB)</option>
|
||||
<option value="USD" {{ plan.currency == 'USD' ? 'selected' : '' }}>Доллар (USD)</option>
|
||||
<option value="EUR" {{ plan.currency == 'EUR' ? 'selected' : '' }}>Евро (EUR)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="billing_period">Период оплаты</label>
|
||||
<select name="billing_period" id="billing_period" class="form-control">
|
||||
<option value="monthly" {{ plan.billing_period == 'monthly' ? 'selected' : '' }}>Ежемесячно</option>
|
||||
<option value="yearly" {{ plan.billing_period == 'yearly' ? 'selected' : '' }}>Ежегодно</option>
|
||||
<option value="quarterly" {{ plan.billing_period == 'quarterly' ? 'selected' : '' }}>Ежеквартально</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label for="max_users">Максимум пользователей *</label>
|
||||
<input type="number" name="max_users" id="max_users" class="form-control" value="{{ plan.max_users }}" min="1" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="max_clients">Максимум клиентов *</label>
|
||||
<input type="number" name="max_clients" id="max_clients" class="form-control" value="{{ plan.max_clients }}" min="1" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="max_storage">Максимум хранилища (ГБ) *</label>
|
||||
<input type="number" name="max_storage" id="max_storage" class="form-control" value="{{ plan.max_storage }}" min="1" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="features">Возможности (каждая с новой строки)</label>
|
||||
<textarea name="features_list" id="features" class="form-control" rows="5" placeholder="Неограниченные проекты Приоритетная поддержка Экспорт в PDF">{% if plan.features is iterable %}{% for feature in plan.features %}
|
||||
{{ feature }}{% endfor %}{% endif %}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" name="is_active" value="1" {{ plan.is_active ? 'checked' : '' }}>
|
||||
Активен (доступен для выбора)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" name="is_default" value="1" {{ plan.is_default ? 'checked' : '' }}>
|
||||
Тариф по умолчанию
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-success btn-block">Сохранить изменения</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,77 +0,0 @@
|
||||
{% extends 'superadmin/layout.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="sa-header">
|
||||
<h1>Тарифы</h1>
|
||||
<a href="{{ base_url('/superadmin/plans/create') }}" class="btn btn-success">+ Добавить тариф</a>
|
||||
</div>
|
||||
|
||||
{% for alert in get_alerts() %}
|
||||
<div class="alert alert-{{ alert.type }}">{{ alert.message }}</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="sa-card">
|
||||
<div class="sa-card-body">
|
||||
{% if plans is empty %}
|
||||
<p style="color: #7f8c8d; text-align: center; padding: 40px;">Тарифов пока нет. Создайте первый тариф.</p>
|
||||
{% else %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Описание</th>
|
||||
<th>Цена</th>
|
||||
<th>Период</th>
|
||||
<th>Лимиты</th>
|
||||
<th>Статус</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for plan in plans %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ plan.name }}</strong>
|
||||
{% if plan.is_default %}
|
||||
<span class="badge badge-success">По умолчанию</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ plan.description|default('—') }}</td>
|
||||
<td>
|
||||
<strong>{{ plan.price|number_format(0, '', ' ') }}</strong> {{ plan.currency }}
|
||||
</td>
|
||||
<td>
|
||||
{% if plan.billing_period == 'monthly' %}
|
||||
Месяц
|
||||
{% elseif plan.billing_period == 'yearly' %}
|
||||
Год
|
||||
{% else %}
|
||||
{{ plan.billing_period }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<small style="color: #7f8c8d;">
|
||||
👥 до {{ plan.max_users }} чел.<br>
|
||||
👤 до {{ plan.max_clients }} клиентов<br>
|
||||
💾 до {{ plan.max_storage }} ГБ
|
||||
</small>
|
||||
</td>
|
||||
<td>
|
||||
{% if plan.is_active %}
|
||||
<span class="badge badge-success">Активен</span>
|
||||
{% else %}
|
||||
<span class="badge badge-danger">Неактивен</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ base_url('/superadmin/plans/edit/' ~ plan.id) }}" class="btn btn-primary btn-sm">✏️</a>
|
||||
<a href="{{ base_url('/superadmin/plans/delete/' ~ plan.id) }}" class="btn btn-danger btn-sm" onclick="return confirm('Вы уверены?')">🗑️</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -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 %}
|
||||
Reference in New Issue
Block a user