Добавлена модель атрибутов для вариативных товаров (ConfigurableKitProductAttribute)

- Создана модель ConfigurableKitProductAttribute с полями name, option, position, visible
- Добавлены формы и formsets для управления атрибутами родительского товара
- Обновлены CRUD представления для работы с атрибутами (создание/редактирование)
- Добавлен блок атрибутов в шаблоны создания/редактирования
- Обновлена страница детального просмотра с отображением атрибутов товара
- Добавлен JavaScript для динамического добавления форм атрибутов
- Реализована валидация дубликатов атрибутов в formset
- Атрибуты сохраняются в transaction.atomic() вместе с вариантами

Теперь можно определять схему атрибутов для экспорта на WooCommerce без использования JSON или ID, только name и option.
This commit is contained in:
2025-11-18 09:24:49 +03:00
parent bdea6b5398
commit c4260f6b1c
15 changed files with 2017 additions and 2 deletions

View File

@@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Удаление вариативного товара{% endblock %}
{% block content %}
<div class="container-fluid px-4 py-3">
<nav aria-label="breadcrumb" class="mb-3">
<ol class="breadcrumb breadcrumb-sm mb-0">
<li class="breadcrumb-item"><a href="{% url 'products:configurablekit-list' %}">Вариативные товары</a></li>
<li class="breadcrumb-item"><a href="{% url 'products:configurablekit-detail' object.pk %}">{{ object.name }}</a></li>
<li class="breadcrumb-item active">Удаление</li>
</ol>
</nav>
<div class="row justify-content-center">
<div class="col-lg-6">
<div class="card border-0 shadow-sm">
<div class="card-header bg-danger text-white">
<h5 class="mb-0"><i class="bi bi-exclamation-triangle me-2"></i>Подтверждение удаления</h5>
</div>
<div class="card-body">
<p class="mb-3">
Вы уверены, что хотите удалить вариативный товар <strong>{{ object.name }}</strong>?
</p>
{% if object.options.count > 0 %}
<div class="alert alert-warning">
<i class="bi bi-exclamation-circle me-2"></i>
<strong>Внимание:</strong> У этого вариативного товара есть {{ object.options.count }} вариант(ов).
При удалении связи с комплектами будут удалены.
</div>
{% endif %}
<form method="post" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-danger">
<i class="bi bi-trash me-1"></i>Да, удалить
</button>
<a href="{% url 'products:configurablekit-detail' object.pk %}" class="btn btn-secondary">
<i class="bi bi-x-circle me-1"></i>Отмена
</a>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,198 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}{{ configurable_kit.name }} - Детали{% endblock %}
{% block content %}
{% csrf_token %}
<div class="container-fluid px-4 py-3">
<nav aria-label="breadcrumb" class="mb-3">
<ol class="breadcrumb breadcrumb-sm mb-0">
<li class="breadcrumb-item"><a href="{% url 'products:configurablekit-list' %}">Вариативные товары</a></li>
<li class="breadcrumb-item active">{{ configurable_kit.name }}</li>
</ol>
</nav>
<!-- Заголовок -->
<div class="mb-3 d-flex justify-content-between align-items-center">
<h4 class="mb-0">{{ configurable_kit.name }}</h4>
<div>
<a href="{% url 'products:configurablekit-update' configurable_kit.pk %}" class="btn btn-warning btn-sm">
<i class="bi bi-pencil me-1"></i>Редактировать
</a>
<a href="{% url 'products:configurablekit-delete' configurable_kit.pk %}" class="btn btn-danger btn-sm">
<i class="bi bi-trash me-1"></i>Удалить
</a>
</div>
</div>
<div class="row">
<!-- Основная информация -->
<div class="col-lg-8">
<div class="card border-0 shadow-sm mb-3">
<div class="card-header bg-white">
<h5 class="mb-0">Основная информация</h5>
</div>
<div class="card-body">
<table class="table table-sm">
<tbody>
<tr>
<th style="width: 200px;">Название:</th>
<td>{{ configurable_kit.name }}</td>
</tr>
<tr>
<th>Артикул:</th>
<td>{{ configurable_kit.sku|default:"—" }}</td>
</tr>
<tr>
<th>Статус:</th>
<td>
{% if configurable_kit.status == 'active' %}
<span class="badge bg-success">Активный</span>
{% elif configurable_kit.status == 'archived' %}
<span class="badge bg-warning">Архивный</span>
{% else %}
<span class="badge bg-secondary">Снятый</span>
{% endif %}
</td>
</tr>
<tr>
<th>Краткое описание:</th>
<td>{{ configurable_kit.short_description|default:"—" }}</td>
</tr>
<tr>
<th>Описание:</th>
<td>{{ configurable_kit.description|default:"—" }}</td>
</tr>
<tr>
<th>Дата создания:</th>
<td>{{ configurable_kit.created_at|date:"d.m.Y H:i" }}</td>
</tr>
<tr>
<th>Дата обновления:</th>
<td>{{ configurable_kit.updated_at|date:"d.m.Y H:i" }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Варианты (комплекты) -->
<div class="card border-0 shadow-sm mb-3">
<div class="card-header bg-white">
<h5 class="mb-0">Варианты (комплекты)</h5>
</div>
<div class="card-body">
{% if configurable_kit.options.all %}
<div class="table-responsive">
<table class="table table-hover table-sm mb-0">
<thead class="table-light">
<tr>
<th>Комплект</th>
<th>Артикул</th>
<th>Цена</th>
<th>Атрибуты</th>
<th style="width: 120px;">По умолчанию</th>
</tr>
</thead>
<tbody>
{% for option in configurable_kit.options.all %}
<tr>
<td>
<a href="{% url 'products:productkit-detail' option.kit.pk %}" class="text-decoration-none">
{{ option.kit.name }}
</a>
</td>
<td><small class="text-muted">{{ option.kit.sku|default:"—" }}</small></td>
<td><strong>{{ option.kit.actual_price }}</strong> руб.</td>
<td><small class="text-muted">{{ option.attributes|default:"—" }}</small></td>
<td class="text-center">
{% if option.is_default %}
<span class="badge bg-primary">Да</span>
{% else %}
<span class="badge bg-secondary">Нет</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted text-center py-4">
Нет вариантов. Перейдите в режим редактирования для добавления.
</p>
{% endif %}
</div>
</div>
<!-- Атрибуты родительского товара -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-white">
<h5 class="mb-0">Атрибуты товара</h5>
</div>
<div class="card-body">
{% if configurable_kit.parent_attributes.all %}
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead class="table-light">
<tr>
<th>Название атрибута</th>
<th>Значение опции</th>
<th>Порядок</th>
<th>Видимый</th>
</tr>
</thead>
<tbody>
{% for attr in configurable_kit.parent_attributes.all %}
<tr>
<td><strong>{{ attr.name }}</strong></td>
<td>{{ attr.option }}</td>
<td><span class="badge bg-secondary">{{ attr.position }}</span></td>
<td>
{% if attr.visible %}
<span class="badge bg-success">Да</span>
{% else %}
<span class="badge bg-secondary">Нет</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted text-center py-4">
Нет атрибутов. Перейдите в режим редактирования для добавления.
</p>
{% endif %}
</div>
</div>
</div>
<!-- Боковая панель -->
<div class="col-lg-4">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0">Справка</h6>
</div>
<div class="card-body">
<p class="small text-muted">
Вариативный товар предназначен для экспорта на WooCommerce и подобные площадки как Variable Product.
</p>
<p class="small text-muted">
Каждый вариант — это отдельный ProductKit с собственной ценой, артикулом и атрибутами.
</p>
<hr>
<p class="small text-muted mb-1">
<strong>Количество вариантов:</strong> {{ configurable_kit.options.count }}
</p>
<p class="small text-muted mb-0">
<strong>Атрибутов товара:</strong> {{ configurable_kit.parent_attributes.count }}
</p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,494 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}{% if object %}Редактирование{% else %}Создание{% endif %} вариативного товара{% endblock %}
{% block extra_css %}
<style>
/* Скрываем чекбоксы DELETE в formset */
input[name*="DELETE"] {
display: none !important;
}
/* Стили для switch */
.form-check-input.is-default-switch {
cursor: pointer;
width: 3rem;
height: 1.5rem;
}
.default-switch-label {
font-size: 0.875rem;
margin-left: 0.5rem;
line-height: 1.5rem;
}
/* Выравнивание switch по центру */
.col-md-2 .form-check.form-switch {
display: flex;
align-items: center;
min-height: 38px;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid px-4 py-3">
<nav aria-label="breadcrumb" class="mb-3">
<ol class="breadcrumb breadcrumb-sm mb-0">
<li class="breadcrumb-item"><a href="{% url 'products:configurablekit-list' %}">Вариативные товары</a></li>
<li class="breadcrumb-item active">{% if object %}Редактирование{% else %}Создание{% endif %}</li>
</ol>
</nav>
<form method="post">
{% csrf_token %}
<div class="row">
<!-- Основная информация -->
<div class="col-lg-8">
<div class="card border-0 shadow-sm mb-3">
<div class="card-header bg-white">
<h5 class="mb-0">Основная информация</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="{{ form.name.id_for_label }}" class="form-label">Название <span class="text-danger">*</span></label>
{{ form.name }}
{% if form.name.errors %}
<div class="text-danger small">{{ form.name.errors.0 }}</div>
{% endif %}
</div>
<div class="mb-3">
<label for="{{ form.sku.id_for_label }}" class="form-label">Артикул</label>
{{ form.sku }}
{% if form.sku.errors %}
<div class="text-danger small">{{ form.sku.errors.0 }}</div>
{% endif %}
</div>
<div class="mb-3">
<label for="{{ form.short_description.id_for_label }}" class="form-label">Краткое описание</label>
{{ form.short_description }}
{% if form.short_description.errors %}
<div class="text-danger small">{{ form.short_description.errors.0 }}</div>
{% endif %}
</div>
<div class="mb-3">
<label for="{{ form.description.id_for_label }}" class="form-label">Полное описание</label>
{{ form.description }}
{% if form.description.errors %}
<div class="text-danger small">{{ form.description.errors.0 }}</div>
{% endif %}
</div>
<div class="mb-0">
<label for="{{ form.status.id_for_label }}" class="form-label">Статус</label>
{{ form.status }}
{% if form.status.errors %}
<div class="text-danger small">{{ form.status.errors.0 }}</div>
{% endif %}
</div>
</div>
</div>
<!-- Варианты (комплекты) -->
<div class="card border-0 shadow-sm mb-3">
<div class="card-header bg-white">
<h5 class="mb-0">Варианты (комплекты)</h5>
</div>
<div class="card-body">
{{ option_formset.management_form }}
{% if option_formset.non_form_errors %}
<div class="alert alert-danger">
{{ option_formset.non_form_errors }}
</div>
{% endif %}
<div id="optionFormsetContainer">
{% for form in option_formset %}
<div class="option-form border rounded p-3 mb-3" style="background: #f8f9fa;">
{{ form.id }}
{% if form.instance.pk %}
<input type="hidden" name="options-{{ forloop.counter0 }}-id" value="{{ form.instance.pk }}">
{% endif %}
<div class="row g-2">
<div class="col-md-4">
<label class="form-label small">{{ form.kit.label }}</label>
{{ form.kit }}
{% if form.kit.errors %}
<div class="text-danger small">{{ form.kit.errors.0 }}</div>
{% endif %}
</div>
<div class="col-md-3">
<label class="form-label small">{{ form.attributes.label }}</label>
{{ form.attributes }}
{% if form.attributes.errors %}
<div class="text-danger small">{{ form.attributes.errors.0 }}</div>
{% endif %}
</div>
<div class="col-md-2">
<label class="form-label small d-block">{{ form.is_default.label }}</label>
<div class="form-check form-switch">
{{ form.is_default }}
<label class="form-check-label" for="{{ form.is_default.id_for_label }}">
<span class="default-switch-label">{% if form.instance.is_default %}Да{% else %}Нет{% endif %}</span>
</label>
</div>
{% if form.is_default.errors %}
<div class="text-danger small">{{ form.is_default.errors.0 }}</div>
{% endif %}
</div>
<div class="col-md-3">
{% if option_formset.can_delete %}
<label class="form-label small d-block">&nbsp;</label>
{{ form.DELETE }}
<label for="{{ form.DELETE.id_for_label }}" class="btn btn-sm btn-outline-danger d-block">
<i class="bi bi-trash"></i> Удалить
</label>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
<button type="button" class="btn btn-sm btn-outline-primary" id="addOptionBtn">
<i class="bi bi-plus-circle me-1"></i>Добавить вариант
</button>
</div>
</div>
<!-- Атрибуты родительского товара -->
<div class="card border-0 shadow-sm mb-3">
<div class="card-header bg-white">
<h5 class="mb-0">Атрибуты товара (для WooCommerce)</h5>
</div>
<div class="card-body">
<p class="small text-muted mb-3">
Определите схему атрибутов для вариативного товара. Например: Цвет=Красный, Размер=M, Длина=60см.
</p>
{{ attribute_formset.management_form }}
{% if attribute_formset.non_form_errors %}
<div class="alert alert-danger">
{{ attribute_formset.non_form_errors }}
</div>
{% endif %}
<div id="attributeFormsetContainer">
{% for form in attribute_formset %}
<div class="attribute-form border rounded p-3 mb-3" style="background: #f8f9fa;">
{{ form.id }}
{% if form.instance.pk %}
<input type="hidden" name="attributes-{{ forloop.counter0 }}-id" value="{{ form.instance.pk }}">
{% endif %}
<div class="row g-2">
<div class="col-md-3">
<label class="form-label small">{{ form.name.label }}</label>
{{ form.name }}
{% if form.name.errors %}
<div class="text-danger small">{{ form.name.errors.0 }}</div>
{% endif %}
</div>
<div class="col-md-3">
<label class="form-label small">{{ form.option.label }}</label>
{{ form.option }}
{% if form.option.errors %}
<div class="text-danger small">{{ form.option.errors.0 }}</div>
{% endif %}
</div>
<div class="col-md-2">
<label class="form-label small">{{ form.position.label }}</label>
{{ form.position }}
{% if form.position.errors %}
<div class="text-danger small">{{ form.position.errors.0 }}</div>
{% endif %}
</div>
<div class="col-md-2">
<label class="form-label small d-block">{{ form.visible.label }}</label>
<div class="form-check">
{{ form.visible }}
<label class="form-check-label" for="{{ form.visible.id_for_label }}">
Показывать
</label>
</div>
{% if form.visible.errors %}
<div class="text-danger small">{{ form.visible.errors.0 }}</div>
{% endif %}
</div>
<div class="col-md-2">
{% if attribute_formset.can_delete %}
<label class="form-label small d-block">&nbsp;</label>
{{ form.DELETE }}
<label for="{{ form.DELETE.id_for_label }}" class="btn btn-sm btn-outline-danger d-block">
<i class="bi bi-trash"></i> Удалить
</label>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
<button type="button" class="btn btn-sm btn-outline-primary" id="addAttributeBtn">
<i class="bi bi-plus-circle me-1"></i>Добавить атрибут
</button>
</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle me-1"></i>Сохранить
</button>
<a href="{% if object %}{% url 'products:configurablekit-detail' object.pk %}{% else %}{% url 'products:configurablekit-list' %}{% endif %}"
class="btn btn-secondary">
<i class="bi bi-x-circle me-1"></i>Отмена
</a>
</div>
</div>
<!-- Боковая панель -->
<div class="col-lg-4">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0">Справка</h6>
</div>
<div class="card-body">
<p class="small text-muted">
Вариативный товар объединяет несколько комплектов как варианты для экспорта на WooCommerce и подобные площадки.
</p>
<p class="small text-muted">
Каждый вариант — это отдельный ProductKit с собственной ценой, артикулом и атрибутами.
</p>
</div>
</div>
</div>
</div>
</form>
</div>
<script>
// Добавление новых форм вариантов
document.getElementById('addOptionBtn').addEventListener('click', function() {
const container = document.getElementById('optionFormsetContainer');
const totalForms = document.querySelector('[name="options-TOTAL_FORMS"]');
const formIdx = parseInt(totalForms.value);
// Создаём новую форму HTML
const newFormHtml = `
<div class="option-form border rounded p-3 mb-3" style="background: #f8f9fa;">
<div class="row g-2">
<div class="col-md-4">
<label class="form-label small">Комплект</label>
<select name="options-${formIdx}-kit" id="id_options-${formIdx}-kit" class="form-select">
<option value="">---------</option>
{% for kit in option_formset.empty_form.fields.kit.queryset %}
<option value="{{ kit.id }}">{{ kit.name }}{% if kit.sku %} ({{ kit.sku }}){% endif %}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<label class="form-label small">Атрибуты варианта</label>
<input type="text" name="options-${formIdx}-attributes"
id="id_options-${formIdx}-attributes"
class="form-control"
placeholder="Например: Количество:15;Длина:60см">
</div>
<div class="col-md-2">
<label class="form-label small d-block">По умолчанию</label>
<div class="form-check form-switch">
<input type="checkbox" name="options-${formIdx}-is_default"
id="id_options-${formIdx}-is_default"
class="form-check-input is-default-switch" role="switch">
<label class="form-check-label" for="id_options-${formIdx}-is_default">
<span class="default-switch-label">Нет</span>
</label>
</div>
</div>
<div class="col-md-3">
<label class="form-label small d-block">&nbsp;</label>
<input type="checkbox" name="options-${formIdx}-DELETE"
id="id_options-${formIdx}-DELETE"
style="display:none;">
<label for="id_options-${formIdx}-DELETE" class="btn btn-sm btn-outline-danger d-block">
<i class="bi bi-trash"></i> Удалить
</label>
</div>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', newFormHtml);
totalForms.value = formIdx + 1;
// Переинициализируем логику switch после добавления новой формы
initDefaultSwitches();
});
// Скрытие удаленных форм
document.addEventListener('change', function(e) {
if (e.target.type === 'checkbox' && e.target.name && e.target.name.includes('DELETE')) {
const form = e.target.closest('.option-form');
if (e.target.checked) {
form.style.opacity = '0.5';
form.style.textDecoration = 'line-through';
} else {
form.style.opacity = '1';
form.style.textDecoration = 'none';
}
}
});
// Логика для switch "По умолчанию"
function initDefaultSwitches() {
const container = document.getElementById('optionFormsetContainer');
// Функция для обновления текста label
function updateSwitchLabel(switchInput) {
const label = switchInput.closest('.form-check').querySelector('.default-switch-label');
if (label) {
label.textContent = switchInput.checked ? 'Да' : 'Нет';
}
}
// Функция для проверки и установки единственного варианта по умолчанию
function ensureSingleDefault() {
const visibleSwitches = Array.from(container.querySelectorAll('.is-default-switch')).filter(sw => {
const form = sw.closest('.option-form');
const deleteCheckbox = form.querySelector('input[name*="DELETE"]');
return !deleteCheckbox || !deleteCheckbox.checked;
});
// Если только один вариант - включаем его автоматически
if (visibleSwitches.length === 1) {
visibleSwitches[0].checked = true;
visibleSwitches[0].disabled = true;
updateSwitchLabel(visibleSwitches[0]);
} else {
// Если вариантов несколько - убираем disabled
visibleSwitches.forEach(sw => {
sw.disabled = false;
});
}
// Проверяем, есть ли хотя бы один включенный
const hasChecked = visibleSwitches.some(sw => sw.checked);
if (!hasChecked && visibleSwitches.length > 0) {
// Если ни один не включен, включаем первый
visibleSwitches[0].checked = true;
updateSwitchLabel(visibleSwitches[0]);
}
}
// Обработчик изменения switch
container.addEventListener('change', function(e) {
if (e.target.classList.contains('is-default-switch')) {
if (e.target.checked) {
// Выключаем все остальные
const allSwitches = container.querySelectorAll('.is-default-switch');
allSwitches.forEach(sw => {
if (sw !== e.target) {
sw.checked = false;
updateSwitchLabel(sw);
}
});
}
updateSwitchLabel(e.target);
ensureSingleDefault();
}
// При изменении DELETE тоже проверяем
if (e.target.name && e.target.name.includes('DELETE')) {
ensureSingleDefault();
}
});
// Инициализация при загрузке
ensureSingleDefault();
container.querySelectorAll('.is-default-switch').forEach(updateSwitchLabel);
}
// Запускаем инициализацию
initDefaultSwitches();
// === Добавление новых форм атрибутов ===
document.getElementById('addAttributeBtn').addEventListener('click', function() {
const container = document.getElementById('attributeFormsetContainer');
const totalForms = document.querySelector('[name="attributes-TOTAL_FORMS"]');
const formIdx = parseInt(totalForms.value);
// Создаём новую форму HTML
const newFormHtml = `
<div class="attribute-form border rounded p-3 mb-3" style="background: #f8f9fa;">
<div class="row g-2">
<div class="col-md-3">
<label class="form-label small">Название атрибута</label>
<input type="text" name="attributes-${formIdx}-name"
id="id_attributes-${formIdx}-name"
class="form-control"
placeholder="Например: Цвет, Размер, Длина">
</div>
<div class="col-md-3">
<label class="form-label small">Значение опции</label>
<input type="text" name="attributes-${formIdx}-option"
id="id_attributes-${formIdx}-option"
class="form-control"
placeholder="Например: Красный, M, 60см">
</div>
<div class="col-md-2">
<label class="form-label small">Порядок</label>
<input type="number" name="attributes-${formIdx}-position"
id="id_attributes-${formIdx}-position"
class="form-control"
min="0" value="0">
</div>
<div class="col-md-2">
<label class="form-label small d-block">Видимый</label>
<div class="form-check">
<input type="checkbox" name="attributes-${formIdx}-visible"
id="id_attributes-${formIdx}-visible"
class="form-check-input" checked>
<label class="form-check-label" for="id_attributes-${formIdx}-visible">
Показывать
</label>
</div>
</div>
<div class="col-md-2">
<label class="form-label small d-block">&nbsp;</label>
<input type="checkbox" name="attributes-${formIdx}-DELETE"
id="id_attributes-${formIdx}-DELETE"
style="display:none;">
<label for="id_attributes-${formIdx}-DELETE" class="btn btn-sm btn-outline-danger d-block">
<i class="bi bi-trash"></i> Удалить
</label>
</div>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', newFormHtml);
totalForms.value = formIdx + 1;
});
// Скрытие удаленных атрибутов
document.addEventListener('change', function(e) {
if (e.target.type === 'checkbox' && e.target.name && e.target.name.includes('attributes') && e.target.name.includes('DELETE')) {
const form = e.target.closest('.attribute-form');
if (form) {
if (e.target.checked) {
form.style.opacity = '0.5';
form.style.textDecoration = 'line-through';
} else {
form.style.opacity = '1';
form.style.textDecoration = 'none';
}
}
}
});
</script>
{% endblock %}

View File

@@ -0,0 +1,146 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Вариативные товары{% endblock %}
{% block content %}
<div class="container-fluid px-4 py-3">
<nav aria-label="breadcrumb" class="mb-3">
<ol class="breadcrumb breadcrumb-sm mb-0">
<li class="breadcrumb-item active">Вариативные товары</li>
</ol>
</nav>
<!-- Заголовок и кнопки действий -->
<div class="mb-3 d-flex justify-content-between align-items-center">
<h4 class="mb-0">Вариативные товары</h4>
<div>
{% for button in action_buttons %}
<a href="{{ button.url }}" class="btn {{ button.class }} btn-sm">
<i class="bi bi-{{ button.icon }} me-1"></i>{{ button.text }}
</a>
{% endfor %}
</div>
</div>
<!-- Поиск и фильтры -->
<div class="card border-0 shadow-sm mb-3">
<div class="card-body p-3">
<form method="get" class="row g-2">
<div class="col-md-6">
<input type="text" name="search" class="form-control form-control-sm"
placeholder="Поиск по названию, артикулу, описанию..."
value="{{ filters.current.search }}">
</div>
<div class="col-md-3">
<select name="status" class="form-select form-select-sm">
<option value="">Все статусы</option>
<option value="active" {% if filters.current.status == 'active' %}selected{% endif %}>Активные</option>
<option value="archived" {% if filters.current.status == 'archived' %}selected{% endif %}>Архивные</option>
<option value="discontinued" {% if filters.current.status == 'discontinued' %}selected{% endif %}>Снятые</option>
</select>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-outline-primary btn-sm w-100">
<i class="bi bi-search"></i> Поиск
</button>
</div>
</form>
</div>
</div>
<!-- Таблица -->
<div class="card border-0 shadow-sm">
<div class="table-responsive">
<table class="table table-hover table-sm mb-0">
<thead class="table-light">
<tr>
<th>Название</th>
<th>Артикул</th>
<th style="width: 120px;">Статус</th>
<th style="width: 100px;">Вариантов</th>
<th style="width: 150px;">Дата создания</th>
<th style="width: 180px;">Действия</th>
</tr>
</thead>
<tbody>
{% for item in configurable_kits %}
<tr>
<td class="fw-semibold">
<a href="{% url 'products:configurablekit-detail' item.pk %}" class="text-decoration-none">
{{ item.name }}
</a>
</td>
<td>
<small class="text-muted">{{ item.sku|default:"-" }}</small>
</td>
<td>
{% if item.status == 'active' %}
<span class="badge bg-success">Активный</span>
{% elif item.status == 'archived' %}
<span class="badge bg-warning">Архивный</span>
{% else %}
<span class="badge bg-secondary">Снятый</span>
{% endif %}
</td>
<td class="text-center">
<span class="badge bg-info">{{ item.options.count }}</span>
</td>
<td><small class="text-muted">{{ item.created_at|date:"d.m.Y H:i" }}</small></td>
<td>
<a href="{% url 'products:configurablekit-detail' item.pk %}"
class="btn btn-sm btn-outline-primary" title="Просмотр">
<i class="bi bi-eye"></i>
</a>
<a href="{% url 'products:configurablekit-update' item.pk %}"
class="btn btn-sm btn-outline-warning" title="Редактировать">
<i class="bi bi-pencil"></i>
</a>
<a href="{% url 'products:configurablekit-delete' item.pk %}"
class="btn btn-sm btn-outline-danger" title="Удалить">
<i class="bi bi-trash"></i>
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center text-muted py-4">
Нет вариативных товаров. <a href="{% url 'products:configurablekit-create' %}">Создать первый</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Пагинация -->
{% if is_paginated %}
<nav class="mt-4" aria-label="Pagination">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if filters.current.search %}&search={{ filters.current.search }}{% endif %}{% if filters.current.status %}&status={{ filters.current.status }}{% endif %}">Первая</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if filters.current.search %}&search={{ filters.current.search }}{% endif %}{% if filters.current.status %}&status={{ filters.current.status }}{% endif %}">Предыдущая</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">{{ page_obj.number }} из {{ page_obj.paginator.num_pages }}</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if filters.current.search %}&search={{ filters.current.search }}{% endif %}{% if filters.current.status %}&status={{ filters.current.status }}{% endif %}">Следующая</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if filters.current.search %}&search={{ filters.current.search }}{% endif %}{% if filters.current.status %}&status={{ filters.current.status }}{% endif %}">Последняя</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
{% endblock %}