Files
octopus/myproject/discounts/templates/discounts/promocode_list.html
Andrey Smakotin 2369cfc997 feat(ui): улучшения UX для промокодов и форм заказа
- Добавлена кнопка копирования промокода в клипборд с визуальной обратной связью
- Улучшено отображение ошибок валидации в форме заказа (is-invalid класс)
- Добавлен флаг _draftFieldsFilled для корректной обработки пустого черновика
- Убран value="1" для quantity чтобы избежать конфликтов с draft-data

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 19:07:19 +03:00

256 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "system_settings/base_settings.html" %}
{% block title %}Промокоды{% endblock %}
{% block settings_content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h3>Промокоды</h3>
<div class="d-flex gap-2">
<a href="{% url 'system_settings:discounts:list' %}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> К скидкам
</a>
{% if can_edit %}
<a href="{% url 'system_settings:discounts:promo-create' %}" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Создать промокод
</a>
{% endif %}
</div>
</div>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
<!-- Фильтры -->
<div class="card mb-3">
<div class="card-body">
<form method="get" class="row g-3 align-items-end">
<div class="col-md-4">
<label class="form-label">Поиск по коду</label>
<input type="text" name="search" class="form-control" placeholder="Код промокода"
value="{{ current_filters.search }}">
</div>
<div class="col-md-3">
<label class="form-label">Статус</label>
<select name="is_active" class="form-select">
<option value="">Все</option>
<option value="active" {% if current_filters.is_active == 'active' %}selected{% endif %}>Активные</option>
<option value="inactive" {% if current_filters.is_active == 'inactive' %}selected{% endif %}>Неактивные</option>
</select>
</div>
<div class="col-md-5">
<button type="submit" class="btn btn-primary me-2">
<i class="bi bi-funnel"></i> Применить
</button>
<a href="{% url 'system_settings:discounts:promo-list' %}" class="btn btn-outline-secondary">
<i class="bi bi-x-circle"></i> Сбросить
</a>
</div>
</form>
</div>
</div>
<div class="card">
<div class="card-body">
{% if promocodes %}
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Код</th>
<th>Скидка</th>
<th>Тип</th>
<th>Значение</th>
<th>Использований</th>
<th>Период действия</th>
<th>Статус</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
{% for promo in promocodes %}
<tr>
<td>
<div class="d-flex align-items-center gap-2">
<code class="fs-5 promo-code-text">{{ promo.code }}</code>
<button class="btn btn-sm btn-outline-secondary btn-copy-promo"
data-code="{{ promo.code }}"
title="Скопировать">
<i class="bi bi-copy"></i>
</button>
</div>
</td>
<td>
<a href="{% url 'system_settings:discounts:update' promo.discount.id %}" class="text-decoration-none">
{{ promo.discount.name }}
</a>
</td>
<td>
{% if promo.discount.discount_type == 'percentage' %}
<span class="badge bg-primary">%</span>
{% else %}
<span class="badge bg-info">руб.</span>
{% endif %}
</td>
<td>
{% if promo.discount.discount_type == 'percentage' %}
{{ promo.discount.value }}%
{% else %}
{{ promo.discount.value }} руб.
{% endif %}
</td>
<td>
{{ promo.current_uses }}
{% if promo.max_total_uses %}
/ {{ promo.max_total_uses }}
{% else %}
/ &infin;
{% endif %}
{% if promo.max_uses_per_user %}
<br><small class="text-muted">макс. {{ promo.max_uses_per_user }} на пользователя</small>
{% endif %}
</td>
<td>
<small class="text-muted">
{% if promo.start_date and promo.end_date %}
с {{ promo.start_date|date:"d.m.Y" }}<br>по {{ promo.end_date|date:"d.m.Y" }}
{% elif promo.start_date %}
с {{ promo.start_date|date:"d.m.Y" }}
{% elif promo.end_date %}
до {{ promo.end_date|date:"d.m.Y" }}
{% else %}
Бессрочный
{% endif %}
</small>
</td>
<td>
{% if promo.is_active %}
<span class="badge bg-success">Активен</span>
{% else %}
<span class="badge bg-secondary">Неактивен</span>
{% endif %}
</td>
<td>
{% if can_edit %}
<div class="btn-group btn-group-sm">
<a href="{% url 'system_settings:discounts:promo-update' promo.pk %}"
class="btn btn-outline-primary" title="Редактировать">
<i class="bi bi-pencil"></i>
</a>
<a href="{% url 'system_settings:discounts:promo-toggle' promo.pk %}"
class="btn btn-outline-{% if promo.is_active %}warning{% else %}success{% endif %}"
title="{% if promo.is_active %}Деактивировать{% else %}Активировать{% endif %}">
<i class="bi bi-{% if promo.is_active %}pause{% else %}play{% endif %}"></i>
</a>
<a href="{% url 'system_settings:discounts:promo-delete' promo.pk %}"
class="btn btn-outline-danger" title="Удалить">
<i class="bi bi-trash"></i>
</a>
</div>
{% else %}
<span class="text-muted small"><i class="bi bi-lock"></i></span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Пагинация -->
{% if is_paginated %}
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if current_filters.search %}&search={{ current_filters.search }}{% endif %}{% if current_filters.is_active %}&is_active={{ current_filters.is_active }}{% endif %}">&laquo;</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if current_filters.search %}&search={{ current_filters.search }}{% endif %}{% if current_filters.is_active %}&is_active={{ current_filters.is_active }}{% endif %}">&lsaquo;</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">{{ page_obj.number }}</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if current_filters.search %}&search={{ current_filters.search }}{% endif %}{% if current_filters.is_active %}&is_active={{ current_filters.is_active }}{% endif %}">&rsaquo;</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if current_filters.search %}&search={{ current_filters.search }}{% endif %}{% if current_filters.is_active %}&is_active={{ current_filters.is_active }}{% endif %}">&raquo;</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %}
<div class="text-center py-5">
<p class="text-muted">Нет промокодов</p>
<i class="bi bi-ticket-perforated display-4 text-muted"></i>
{% if can_edit %}
<div class="mt-3">
<a href="{% url 'system_settings:discounts:promo-create' %}" class="btn btn-primary">
Создать первый промокод
</a>
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const copyButtons = document.querySelectorAll('.btn-copy-promo');
copyButtons.forEach(button => {
button.addEventListener('click', async function() {
const code = this.getAttribute('data-code');
const icon = this.querySelector('i');
try {
await navigator.clipboard.writeText(code);
// Визуальная обратная связь - зелёная галочка на белом
icon.className = 'bi bi-check2 text-success';
this.classList.remove('btn-outline-secondary');
this.classList.add('btn-light', 'border-success');
setTimeout(() => {
icon.className = 'bi bi-copy';
this.classList.remove('btn-light', 'border-success');
this.classList.add('btn-outline-secondary');
}, 1500);
} catch (err) {
// Fallback для старых браузеров
const textArea = document.createElement('textarea');
textArea.value = code;
textArea.style.position = 'fixed';
textArea.style.left = '-9999px';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
icon.className = 'bi bi-check2 text-success';
this.classList.remove('btn-outline-secondary');
this.classList.add('btn-light', 'border-success');
setTimeout(() => {
icon.className = 'bi bi-copy';
this.classList.remove('btn-light', 'border-success');
this.classList.add('btn-outline-secondary');
}, 1500);
}
});
});
});
</script>
{% endblock %}