Files
octopus/myproject/customers/templates/customers/customer_detail.html
Andrey Smakotin ff1c29baae refactor(inventory): redesign inventory home layout with compact cards
- Replace large operation cards with smaller, uniform inventory-card components
- Update icon sizes and text styles for better visual hierarchy
- Group operations into a cleaner 4x3 grid layout using Bootstrap columns
- Simplify card hover effects with subtler shadows and background gradients
- Remove unused purple utility classes and old card-body styles
- Add focus outline support for accessibility on inventory cards

fix(customers): show total debt only when applicable and add external links

- Display total debt row only if debt is greater than zero in customer detail
- Remove redundant conditional inside debt display cell
- Add target="_blank" and rel attributes to order detail links to open in new tab
2025-12-31 23:11:11 +03:00

761 lines
45 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 "base.html" %}
{% block title %}{{ customer.full_name }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="mb-0"><i class="bi bi-person-badge text-primary"></i> <span id="customer-title">{{ customer.full_name }}</span></h2>
<div>
<a href="{% url 'customers:customer-delete' customer.pk %}" class="btn btn-outline-danger">
<i class="bi bi-trash"></i> Удалить
</a>
<a href="{% url 'customers:customer-list' %}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Назад к списку
</a>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Customer Info -->
<div class="col-md-6">
<div class="card mb-4 shadow-sm">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="bi bi-person-circle text-primary"></i> Информация о клиенте</h5>
</div>
<div class="card-body">
<table class="table table-borderless mb-0" id="customer-info-table" data-customer-id="{{ customer.pk }}">
<colgroup>
<col style="width: 40%;">
<col style="width: 50%;">
<col style="width: 10%;">
</colgroup>
<tbody>
<!-- Основная информация -->
<tr data-field="name">
<td class="text-muted"><i class="bi bi-person-fill text-primary"></i> Имя:</td>
<td>
<span class="field-value fw-bold">{{ customer.name|default:"—" }}</span>
<input type="text" class="field-input form-control form-control-sm d-none" value="{{ customer.name|default:'' }}">
<div class="field-actions d-inline-block ms-2 d-none">
<button class="btn btn-sm btn-success save-btn py-0 px-1" title="Сохранить"><i class="bi bi-check-lg"></i></button>
<button class="btn btn-sm btn-outline-secondary cancel-btn py-0 px-1" title="Отменить"><i class="bi bi-x-lg"></i></button>
<span class="save-status ms-1"></span>
</div>
</td>
<td class="text-end">
<div class="d-flex gap-1 justify-content-end">
<button class="btn btn-sm btn-outline-secondary copy-btn" title="Копировать" data-copy-value="{{ customer.name|default:'' }}">
<i class="bi bi-copy"></i>
</button>
<button class="btn btn-sm btn-outline-primary edit-btn" title="Редактировать">
<i class="bi bi-pencil"></i>
</button>
</div>
</td>
</tr>
<tr data-field="phone">
<td class="text-muted"><i class="bi bi-telephone-fill text-success"></i> Телефон:</td>
<td>
<span class="field-value">{{ customer.phone|default:"—" }}</span>
<input type="tel" class="field-input form-control form-control-sm d-none" value="{{ customer.phone|default:'' }}" placeholder="+375...">
<div class="field-actions d-inline-block ms-2 d-none">
<button class="btn btn-sm btn-success save-btn py-0 px-1" title="Сохранить"><i class="bi bi-check-lg"></i></button>
<button class="btn btn-sm btn-outline-secondary cancel-btn py-0 px-1" title="Отменить"><i class="bi bi-x-lg"></i></button>
<span class="save-status ms-1"></span>
</div>
</td>
<td class="text-end">
<div class="d-flex gap-1 justify-content-end">
<button class="btn btn-sm btn-outline-secondary copy-btn" title="Копировать" data-copy-value="{{ customer.phone|default:'' }}">
<i class="bi bi-copy"></i>
</button>
<button class="btn btn-sm btn-outline-primary edit-btn" title="Редактировать">
<i class="bi bi-pencil"></i>
</button>
</div>
</td>
</tr>
<tr data-field="email">
<td class="text-muted"><i class="bi bi-envelope-fill text-info"></i> Email:</td>
<td>
<span class="field-value">{{ customer.email|default:"—" }}</span>
<input type="email" class="field-input form-control form-control-sm d-none" value="{{ customer.email|default:'' }}">
<div class="field-actions d-inline-block ms-2 d-none">
<button class="btn btn-sm btn-success save-btn py-0 px-1" title="Сохранить"><i class="bi bi-check-lg"></i></button>
<button class="btn btn-sm btn-outline-secondary cancel-btn py-0 px-1" title="Отменить"><i class="bi bi-x-lg"></i></button>
<span class="save-status ms-1"></span>
</div>
</td>
<td class="text-end">
<div class="d-flex gap-1 justify-content-end">
<button class="btn btn-sm btn-outline-secondary copy-btn" title="Копировать" data-copy-value="{{ customer.email|default:'' }}">
<i class="bi bi-copy"></i>
</button>
<button class="btn btn-sm btn-outline-primary edit-btn" title="Редактировать">
<i class="bi bi-pencil"></i>
</button>
</div>
</td>
</tr>
<tr data-field="notes">
<td class="text-muted"><i class="bi bi-card-text text-warning"></i> Заметки:</td>
<td>
<span class="field-value" style="white-space: pre-wrap;">{{ customer.notes|default:"—" }}</span>
<textarea class="field-input form-control form-control-sm d-none" rows="2">{{ customer.notes|default:'' }}</textarea>
<div class="field-actions d-inline-block ms-2 d-none">
<button class="btn btn-sm btn-success save-btn py-0 px-1" title="Сохранить"><i class="bi bi-check-lg"></i></button>
<button class="btn btn-sm btn-outline-secondary cancel-btn py-0 px-1" title="Отменить"><i class="bi bi-x-lg"></i></button>
<span class="save-status ms-1"></span>
</div>
</td>
<td class="text-end">
<button class="btn btn-sm btn-outline-primary edit-btn" title="Редактировать">
<i class="bi bi-pencil"></i>
</button>
</td>
</tr>
<!-- Разделитель -->
<tr>
<td colspan="3"><hr class="my-2"></td>
</tr>
<!-- Финансовая информация -->
<tr>
<td class="text-muted"><i class="bi bi-cash-stack text-success"></i> Все успешные заказы:</td>
<td colspan="2">
<span class="badge bg-success">{{ total_orders_sum|floatformat:2 }} руб.</span>
</td>
</tr>
<tr>
<td class="text-muted"><i class="bi bi-calendar-check text-info"></i> За последний год:</td>
<td colspan="2">
<span class="badge bg-info">{{ last_year_orders_sum|floatformat:2 }} руб.</span>
</td>
</tr>
{% if total_debt > 0 %}
<tr>
<td class="text-muted"><i class="bi bi-exclamation-triangle-fill text-danger"></i> Общий долг:</td>
<td colspan="2">
<span class="badge bg-danger">{{ total_debt|floatformat:2 }} руб.</span>
<small class="text-muted ms-2">(Заказов: {{ active_orders_count }})</small>
</td>
</tr>
{% endif %}
<!-- Разделитель -->
<tr>
<td colspan="3"><hr class="my-2"></td>
</tr>
<!-- Системная информация -->
<tr>
<td class="text-muted small"><i class="bi bi-clock-history"></i> Дата создания:</td>
<td colspan="2" class="small">{{ customer.created_at|date:"d.m.Y H:i" }}</td>
</tr>
<tr>
<td class="text-muted small"><i class="bi bi-arrow-clockwise"></i> Последнее изменение:</td>
<td colspan="2" class="small"><span id="updated-at">{{ customer.updated_at|date:"d.m.Y H:i" }}</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Правая колонка: Каналы связи + История заказов + История кошелька -->
<div class="col-md-6">
<!-- Каналы связи -->
<div class="card mb-4 shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-chat-dots text-success"></i> Каналы связи</h5>
<button class="btn btn-sm btn-success" data-bs-toggle="modal" data-bs-target="#addChannelModal">
<i class="bi bi-plus-circle"></i> Добавить
</button>
</div>
<div class="card-body">
{% if contact_channels %}
<ul class="list-group list-group-flush">
{% for channel in contact_channels %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
{% if channel.channel_type == 'telegram' %}
<span class="badge bg-info me-2"><i class="bi bi-telegram"></i> Telegram</span>
{% elif channel.channel_type == 'instagram' %}
<span class="badge bg-danger me-2"><i class="bi bi-instagram"></i> Instagram</span>
{% elif channel.channel_type == 'whatsapp' %}
<span class="badge bg-success me-2"><i class="bi bi-whatsapp"></i> WhatsApp</span>
{% elif channel.channel_type == 'viber' %}
<span class="badge bg-purple me-2" style="background-color: #7360f2 !important;"><i class="bi bi-chat-fill"></i> Viber</span>
{% elif channel.channel_type == 'vk' %}
<span class="badge bg-primary me-2">VK</span>
{% elif channel.channel_type == 'facebook' %}
<span class="badge bg-primary me-2"><i class="bi bi-facebook"></i> Facebook</span>
{% elif channel.channel_type == 'phone' %}
<span class="badge bg-secondary me-2"><i class="bi bi-telephone"></i> Телефон</span>
{% elif channel.channel_type == 'email' %}
<span class="badge bg-secondary me-2"><i class="bi bi-envelope"></i> Email</span>
{% else %}
<span class="badge bg-dark me-2">{{ channel.get_channel_type_display }}</span>
{% endif %}
<strong>{{ channel.value }}</strong>
{% if channel.is_primary %}<span class="badge bg-warning text-dark ms-1">основной</span>{% endif %}
{% if channel.notes %}<small class="text-muted d-block mt-1">{{ channel.notes }}</small>{% endif %}
</div>
<div class="d-flex gap-1">
<button class="btn btn-sm btn-outline-secondary copy-btn" title="Копировать" data-copy-value="{{ channel.value }}">
<i class="bi bi-copy"></i>
</button>
<form method="post" action="{% url 'customers:delete-contact-channel' channel.pk %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-outline-danger" onclick="return confirm('Удалить канал?')">
<i class="bi bi-trash"></i>
</button>
</form>
</div>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted mb-0">Нет дополнительных каналов связи. Добавьте Instagram, Telegram и другие контакты.</p>
{% endif %}
</div>
</div>
<!-- История заказов -->
<div class="card mb-4 shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<button class="btn btn-link text-start text-decoration-none p-0 d-flex align-items-center flex-grow-1"
type="button"
data-bs-toggle="collapse"
data-bs-target="#ordersHistoryCollapse"
aria-expanded="false"
aria-controls="ordersHistoryCollapse"
style="border: none; background: none;">
<h5 class="mb-0 me-2"><i class="bi bi-cart-check text-primary"></i> История заказов</h5>
<span class="badge bg-primary">{{ orders_page.paginator.count }}</span>
</button>
<a href="{% url 'orders:order-create' %}?customer={{ customer.pk }}"
class="btn btn-sm btn-success ms-2">
<i class="bi bi-plus-circle"></i> Новый
</a>
</div>
<div class="collapse" id="ordersHistoryCollapse">
<div class="card-body p-0">
{% if orders_page %}
<div class="table-responsive">
<table class="table table-sm table-hover mb-0">
<thead class="table-light">
<tr>
<th></th>
<th>Дата</th>
<th>Статус</th>
<th>Сумма</th>
<th>Остаток</th>
<th></th>
</tr>
</thead>
<tbody>
{% for order in orders_page %}
<tr {% if order.status and order.status.is_negative_end and order.amount_paid > 0 %}class="table-warning"{% endif %}>
<td><strong>#{{ order.order_number }}</strong></td>
<td><small>{{ order.created_at|date:"d.m.y" }}</small></td>
<td>
{% if order.status %}
{% if order.status.code == 'draft' %}
<span class="badge bg-secondary">Черновик</span>
{% elif order.status.code == 'pending' %}
<span class="badge bg-warning">Ожидает</span>
{% elif order.status.code == 'in_production' %}
<span class="badge bg-info">В пр-ве</span>
{% elif order.status.code == 'ready' %}
<span class="badge bg-primary">Готов</span>
{% elif order.status.code == 'delivered' %}
<span class="badge bg-success">Доставлен</span>
{% elif order.status.code == 'cancelled' %}
<span class="badge bg-danger">Отменён</span>
{% else %}
<span class="badge bg-secondary">{{ order.status.name }}</span>
{% endif %}
{% else %}
<span class="badge bg-secondary">-</span>
{% endif %}
</td>
<td><strong>{{ order.total_amount|floatformat:2 }}</strong></td>
<td>
{% if order.status and order.status.is_negative_end %}
{% if order.amount_paid > 0 %}
<span class="badge bg-warning text-dark">
<i class="bi bi-exclamation-triangle"></i> {{ order.amount_paid|floatformat:2 }}
</span>
{% else %}
<span class="text-muted"></span>
{% endif %}
{% elif order.amount_due > 0 %}
<span class="text-danger fw-bold">{{ order.amount_due|floatformat:2 }}</span>
{% else %}
<span class="text-success">0.00</span>
{% endif %}
</td>
<td class="text-end">
<a href="{% url 'orders:order-detail' order.order_number %}" class="btn btn-sm btn-outline-primary" target="_blank" rel="noopener noreferrer" title="Открыть в новой вкладке">
<i class="bi bi-eye"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Пагинация -->
{% if orders_page.has_other_pages %}
<div class="p-3 bg-light border-top">
<nav aria-label="Навигация по заказам">
<ul class="pagination pagination-sm justify-content-center mb-0">
{% if orders_page.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ orders_page.previous_page_number }}#ordersHistoryCollapse">«</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">
{{ orders_page.number }} / {{ orders_page.paginator.num_pages }}
</span>
</li>
{% if orders_page.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ orders_page.next_page_number }}#ordersHistoryCollapse">»</a>
</li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
{% else %}
<p class="text-muted mb-0 p-3">У клиента пока нет заказов.</p>
{% endif %}
</div>
</div>
</div>
<!-- История транзакций кошелька -->
<div class="card mb-4 shadow-sm">
<div class="card-header bg-light">
<button class="btn btn-link w-100 text-start text-decoration-none p-0 d-flex justify-content-between align-items-center"
type="button"
data-bs-toggle="collapse"
data-bs-target="#walletHistoryCollapse"
aria-expanded="false"
aria-controls="walletHistoryCollapse">
<div class="d-flex align-items-center">
<h5 class="mb-0 me-2"><i class="bi bi-clock-history text-info"></i> История кошелька</h5>
<span class="badge bg-primary">{{ wallet_transactions|length }}</span>
</div>
<i class="bi bi-chevron-down"></i>
</button>
</div>
<div class="collapse" id="walletHistoryCollapse">
<div class="card-body p-0">
{% if wallet_transactions %}
<div class="table-responsive">
<table class="table table-sm table-hover mb-0">
<thead class="table-light">
<tr>
<th>Дата</th>
<th>Тип</th>
<th>Сумма</th>
<th>Описание</th>
<th>Заказ</th>
</tr>
</thead>
<tbody>
{% for transaction in wallet_transactions %}
<tr>
<td><small>{{ transaction.created_at|date:"d.m.y H:i" }}</small></td>
<td>
{% if transaction.transaction_type == 'deposit' %}
<span class="badge bg-success">Пополн.</span>
{% elif transaction.transaction_type == 'spend' %}
<span class="badge bg-danger">Списан.</span>
{% else %}
<span class="badge bg-warning">Корр.</span>
{% endif %}
</td>
<td>
{% if transaction.transaction_type == 'deposit' or transaction.transaction_type == 'adjustment' and transaction.amount > 0 %}
<span class="text-success fw-bold">+{{ transaction.amount|floatformat:2 }}</span>
{% else %}
<span class="text-danger fw-bold">-{{ transaction.amount|floatformat:2 }}</span>
{% endif %}
</td>
<td><small>{{ transaction.description|default:"-"|truncatewords:5 }}</small></td>
<td>
{% if transaction.order %}
<a href="{% url 'orders:order-detail' transaction.order.order_number %}" class="btn btn-sm btn-outline-primary py-0">
#{{ transaction.order.order_number }}
</a>
{% else %}
-
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted mb-0 p-3">История транзакций пуста.</p>
{% endif %}
</div>
</div>
</div>
<!-- Операции с кошельком -->
<div class="card mb-4 shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-wallet2 text-warning"></i> Операции с кошельком клиента</h5>
<span>
{% if customer.wallet_balance > 0 %}
<span class="badge bg-success" style="font-size: 1.1em;">{{ customer.wallet_balance|floatformat:2 }} руб.</span>
{% elif customer.wallet_balance == 0 %}
<span class="badge bg-secondary" style="font-size: 1.1em;">{{ customer.wallet_balance|floatformat:2 }} руб.</span>
{% else %}
<span class="badge bg-danger" style="font-size: 1.1em;">{{ customer.wallet_balance|floatformat:2 }} руб.</span>
{% endif %}
</span>
</div>
<div class="card-body">
<div class="row">
<!-- Пополнение -->
<div class="col-md-6">
<h6 class="text-success mb-3"><i class="bi bi-plus-circle"></i> Пополнение</h6>
<form method="post" action="{% url 'customers:wallet-deposit' customer.pk %}">
{% csrf_token %}
<div class="mb-3">
<label for="wallet_deposit_amount" class="form-label">Сумма, руб.</label>
<input type="number"
step="0.01"
min="0.01"
class="form-control"
id="wallet_deposit_amount"
name="amount"
placeholder="0.00"
required>
<div style="height: 1.25rem;"></div>
</div>
<div class="mb-3">
<label for="wallet_deposit_description" class="form-label">Описание</label>
<textarea class="form-control"
id="wallet_deposit_description"
name="description"
rows="2"
placeholder="Подарок, компенсация..."
required></textarea>
</div>
<button type="submit" class="btn btn-success w-100"><i class="bi bi-plus-circle"></i> Пополнить</button>
</form>
</div>
<!-- Возврат / списание -->
<div class="col-md-6">
<h6 class="text-danger mb-3"><i class="bi bi-dash-circle"></i> Списание</h6>
<form method="post" action="{% url 'customers:wallet-withdraw' customer.pk %}">
{% csrf_token %}
<div class="mb-3">
<label for="wallet_withdraw_amount" class="form-label">Сумма, руб.</label>
<input type="number"
step="0.01"
min="0.01"
max="{{ customer.wallet_balance }}"
class="form-control"
id="wallet_withdraw_amount"
name="amount"
placeholder="0.00"
required>
<small class="text-muted d-block" style="height: 1.25rem; line-height: 1.25rem;">Макс: {{ customer.wallet_balance|floatformat:2 }} р.</small>
</div>
<div class="mb-3">
<label for="wallet_withdraw_description" class="form-label">Описание</label>
<textarea class="form-control"
id="wallet_withdraw_description"
name="description"
rows="2"
placeholder="Возврат наличными..."
required></textarea>
</div>
<button type="submit" class="btn btn-danger w-100"><i class="bi bi-dash-circle"></i> Списать</button>
</form>
</div>
</div>
<div class="alert alert-info mb-0 mt-3">
<i class="bi bi-info-circle"></i> Все операции логируются в истории выше.
</div>
</div>
</div>
</div>
<!-- Конец правой колонки -->
<!-- Алерт о необходимости возврата -->
{% if refund_amount > 0 %}
<div class="col-md-12">
<div class="alert alert-warning shadow-sm d-flex justify-content-between align-items-center mb-4" role="alert">
<div>
<h5 class="alert-heading mb-2">
<i class="bi bi-exclamation-triangle-fill"></i> Требуется возврат средств
</h5>
<p class="mb-0">
Клиент имеет отменённые заказы с внесённой оплатой.
Общая сумма к возврату: <strong>{{ refund_amount|floatformat:2 }} руб.</strong>
</p>
</div>
<div>
<span class="badge bg-warning text-dark" style="font-size: 1.3em;">
<i class="bi bi-exclamation-circle"></i> {{ refund_amount|floatformat:2 }} руб.
</span>
</div>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Модальное окно добавления канала связи -->
<div class="modal fade" id="addChannelModal" tabindex="-1" aria-labelledby="addChannelModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form method="post" action="{% url 'customers:add-contact-channel' customer.pk %}">
{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title" id="addChannelModalLabel">Добавить канал связи</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="channel_type" class="form-label">Тип канала</label>
<select name="channel_type" id="channel_type" class="form-select" required>
<option value="telegram">Telegram</option>
<option value="instagram">Instagram</option>
<option value="whatsapp">WhatsApp</option>
<option value="viber">Viber</option>
<option value="vk">ВКонтакте</option>
<option value="facebook">Facebook</option>
<option value="phone">Телефон</option>
<option value="email">Email</option>
<option value="other">Другое</option>
</select>
</div>
<div class="mb-3">
<label for="channel_value" class="form-label">Значение</label>
<input type="text" name="value" id="channel_value" class="form-control" placeholder="@username, номер, ссылка..." required>
<small class="text-muted">Например: @flower_lover, +375291234567, flower.shop</small>
</div>
<div class="mb-3">
<label for="channel_notes" class="form-label">Примечание <span class="text-muted">(необязательно)</span></label>
<input type="text" name="notes" id="channel_notes" class="form-control" placeholder="Личный аккаунт, рабочий...">
</div>
<div class="form-check">
<input type="checkbox" name="is_primary" class="form-check-input" id="isPrimary" value="true">
<label class="form-check-label" for="isPrimary">Основной канал связи</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="submit" class="btn btn-success"><i class="bi bi-plus"></i> Добавить</button>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Автооткрытие collapse при наличии якоря в URL
const hash = window.location.hash;
if (hash === '#ordersHistoryCollapse') {
const collapseElement = document.getElementById('ordersHistoryCollapse');
if (collapseElement) {
const bsCollapse = new bootstrap.Collapse(collapseElement, {
show: true
});
collapseElement.addEventListener('shown.bs.collapse', function() {
collapseElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, { once: true });
}
}
// ========== INLINE EDITING ==========
const table = document.getElementById('customer-info-table');
if (!table) return;
const customerId = table.dataset.customerId;
const updateUrl = `/customers/${customerId}/api/update/`;
// Находим все редактируемые строки
const editableRows = table.querySelectorAll('tr[data-field]');
editableRows.forEach(row => {
const field = row.dataset.field;
const fieldValue = row.querySelector('.field-value');
const fieldInput = row.querySelector('.field-input');
const fieldActions = row.querySelector('.field-actions');
const editBtn = row.querySelector('.edit-btn');
const saveBtn = row.querySelector('.save-btn');
const cancelBtn = row.querySelector('.cancel-btn');
const saveStatus = row.querySelector('.save-status');
let originalValue = '';
// Клик на карандаш — начать редактирование
editBtn.addEventListener('click', function() {
originalValue = fieldInput.value;
fieldValue.classList.add('d-none');
fieldInput.classList.remove('d-none');
fieldActions.classList.remove('d-none');
editBtn.style.visibility = 'hidden'; // Скрываем карандаш но сохраняем место
fieldInput.focus();
if (fieldInput.tagName === 'INPUT') {
fieldInput.select();
}
});
// Клик на галочку или Enter — сохранить
saveBtn.addEventListener('click', saveField);
fieldInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && fieldInput.tagName !== 'TEXTAREA') {
e.preventDefault();
saveField();
}
if (e.key === 'Escape') {
cancelEdit();
}
});
// Клик на крестик — отменить
cancelBtn.addEventListener('click', cancelEdit);
function cancelEdit() {
fieldInput.value = originalValue;
fieldValue.classList.remove('d-none');
fieldInput.classList.add('d-none');
fieldActions.classList.add('d-none');
editBtn.style.visibility = 'visible';
saveStatus.innerHTML = '';
}
function saveField() {
const newValue = fieldInput.value.trim();
// Показываем индикатор загрузки
saveStatus.innerHTML = '<span class="spinner-border spinner-border-sm text-primary"></span>';
saveBtn.disabled = true;
cancelBtn.disabled = true;
fetch(updateUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
},
body: JSON.stringify({
field: field,
value: newValue
})
})
.then(response => response.json())
.then(data => {
saveBtn.disabled = false;
cancelBtn.disabled = false;
if (data.success) {
// Обновляем отображаемое значение
const displayValue = data.value || '—';
fieldValue.textContent = displayValue;
fieldInput.value = data.value || '';
originalValue = data.value || '';
// Обновляем заголовок страницы если изменилось имя
if (field === 'name') {
const titleSpan = document.getElementById('customer-title');
if (titleSpan) {
titleSpan.textContent = displayValue;
}
}
// Возвращаем в режим просмотра
fieldValue.classList.remove('d-none');
fieldInput.classList.add('d-none');
fieldActions.classList.add('d-none');
editBtn.style.visibility = 'visible';
// Показываем успех
saveStatus.innerHTML = '<i class="bi bi-check-circle-fill text-success"></i>';
setTimeout(() => {
saveStatus.innerHTML = '';
}, 1500);
} else {
// Показываем ошибку
saveStatus.innerHTML = `<span class="text-danger small"><i class="bi bi-exclamation-circle"></i> ${data.error}</span>`;
}
})
.catch(error => {
saveBtn.disabled = false;
cancelBtn.disabled = false;
saveStatus.innerHTML = '<span class="text-danger small"><i class="bi bi-exclamation-circle"></i> Ошибка сети</span>';
console.error('Error:', error);
});
}
});
// Функция получения CSRF токена
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
// ========== COPY TO CLIPBOARD ==========
document.querySelectorAll('.copy-btn').forEach(btn => {
btn.addEventListener('click', function() {
const value = this.dataset.copyValue;
if (!value) return;
navigator.clipboard.writeText(value).then(() => {
// Меняем иконку на галочку и стиль кнопки
const icon = this.querySelector('i');
const originalIconClass = icon.className;
const originalBtnClass = this.className;
// Меняем кнопку на зелёную с белой галочкой
this.className = 'btn btn-sm btn-success copy-btn';
icon.className = 'bi bi-check-lg';
// Возвращаем обратно через 1 сек
setTimeout(() => {
this.className = originalBtnClass;
icon.className = originalIconClass;
}, 1000);
}).catch(err => {
console.error('Ошибка копирования:', err);
});
});
});
});
</script>
{% endblock %}