Feature: удаление строки табличной части по кнопке-крестику (сразу, без чекбокса)

Made-with: Cursor
This commit is contained in:
2026-02-26 12:56:24 +00:00
parent 817fe38e87
commit 7b596a4dc2
4 changed files with 88 additions and 22 deletions

View File

@@ -1,6 +1,14 @@
# История изменений ERP WaterSurf
# История изменений ERP WaterSurf
## 2025-02-25 23:15 UTC Удаление строки табличной части по кнопке-крестику
**Проблема**: Удаление строки происходило при сохранении документа (чекбокс «Удалить»), что было неочевидно и неудобно.
**Решение**: В формах заказа клиента и заказа поставщику чекбокс и подпись «Удалить» заменены на кнопку в виде красного крестика (×). По нажатию строка сразу удаляется из DOM, индексы полей formset пересчитываются (reindexRows), обновляется TOTAL_FORMS и пересчёт сумм по строкам. При добавлении новой строки после вставки клона также вызывается reindexRows().
**Изменения**: order_form.html (кнопка .ws-btn-remove-row, reindexRows, делегированный click на tbody), supplier_order_form.html (аналогично), theme-compact.css (стили .ws-btn-remove-row — красный крестик, hover; удалены стили .ws-delete-row-label).
---
## 2025-02-25 22:50 UTC Добавление строки: одна строка за клик, понятное удаление

View File

@@ -132,11 +132,25 @@
width: 5%;
}
.ws-table-items label.ws-delete-row-label {
.ws-table-items .ws-btn-remove-row {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
padding: 0;
border: none;
border-radius: 4px;
background: transparent;
color: #c62828;
font-size: 18px;
line-height: 1;
cursor: pointer;
font-size: 13px;
color: var(--ws-text-muted);
white-space: nowrap;
}
.ws-table-items .ws-btn-remove-row:hover {
background: rgba(198, 40, 40, 0.12);
color: #b71c1c;
}
.ws-table-items .ws-col-product select {

View File

@@ -72,7 +72,7 @@
<td class="ws-col-currency">{{ f.currency }}</td>
<td class="ws-col-qty">{{ f.quantity }}</td>
<td class="row-amount ws-col-cost ws-num"></td>
<td class="ws-col-del"><label class="ws-delete-row-label">{% if f.DELETE %}{{ f.DELETE }}{% endif %} Удалить</label></td>
<td class="ws-col-del"><button type="button" class="ws-btn-remove-row" title="Удалить строку" aria-label="Удалить строку">×</button></td>
</tr>
{% endfor %}
</tbody>
@@ -133,6 +133,34 @@
updateRowAmounts();
function reindexRows() {
var prefix = getPrefix();
var rows = tbody.querySelectorAll('.item-row');
rows.forEach(function(row, i) {
row.querySelectorAll('input, select').forEach(function(el) {
var n = el.getAttribute('name');
if (n && n.indexOf(prefix) === 0) {
el.setAttribute('name', n.replace(new RegExp('^' + prefix + '\\d+'), prefix + i));
}
var id = el.getAttribute('id');
if (id) {
el.setAttribute('id', id.replace(new RegExp(prefix + '\\d+'), prefix + i));
}
});
});
totalInput.value = String(rows.length);
updateRowAmounts();
}
tbody.addEventListener('click', function(e) {
var btn = e.target.closest('.ws-btn-remove-row');
if (!btn) return;
var row = btn.closest('tr.item-row');
if (!row) return;
row.remove();
reindexRows();
});
document.getElementById('add-order-row').addEventListener('click', function() {
var rows = tbody.querySelectorAll('.item-row');
var lastRow = rows[rows.length - 1];
@@ -156,13 +184,8 @@
});
var amountCell = clone.querySelector('.row-amount');
if (amountCell) amountCell.textContent = '—';
var delCheckbox = clone.querySelector('input[name$="-DELETE"]');
if (delCheckbox) {
var delLabel = clone.querySelector('label.ws-delete-row-label');
if (delLabel) delLabel.setAttribute('for', delCheckbox.id);
}
tbody.appendChild(clone);
totalInput.value = nextIndex + 1;
reindexRows();
});
})();
</script>

View File

@@ -79,7 +79,7 @@
<td class="ws-col-currency">{{ f.currency }}</td>
<td class="ws-col-qty">{{ f.quantity }}</td>
<td class="row-amount ws-col-cost ws-num"></td>
<td class="ws-col-del"><label class="ws-delete-row-label">{% if f.DELETE %}{{ f.DELETE }}{% endif %} Удалить</label></td>
<td class="ws-col-del"><button type="button" class="ws-btn-remove-row" title="Удалить строку" aria-label="Удалить строку">×</button></td>
</tr>
{% endfor %}
</tbody>
@@ -138,6 +138,34 @@
updateRowAmounts();
function reindexRows() {
var prefix = getPrefix();
var rows = tbody.querySelectorAll('.item-row');
rows.forEach(function(row, i) {
row.querySelectorAll('input, select').forEach(function(el) {
var n = el.getAttribute('name');
if (n && n.indexOf(prefix) === 0) {
el.setAttribute('name', n.replace(new RegExp('^' + prefix + '\\d+'), prefix + i));
}
var id = el.getAttribute('id');
if (id) {
el.setAttribute('id', id.replace(new RegExp(prefix + '\\d+'), prefix + i));
}
});
});
totalInput.value = String(rows.length);
updateRowAmounts();
}
tbody.addEventListener('click', function(e) {
var btn = e.target.closest('.ws-btn-remove-row');
if (!btn) return;
var row = btn.closest('tr.item-row');
if (!row) return;
row.remove();
reindexRows();
});
document.getElementById('add-supplier-order-row').addEventListener('click', function() {
var rows = tbody.querySelectorAll('.item-row');
var lastRow = rows[rows.length - 1];
@@ -151,9 +179,7 @@
el.setAttribute('name', name.replace(new RegExp('^' + prefix + '\\d+-'), prefix + nextIndex + '-'));
el.setAttribute('id', (el.getAttribute('id') || '').replace(new RegExp(prefix + '\\d+-'), prefix + nextIndex + '-'));
}
if (el.name && el.name.indexOf('-DELETE') !== -1) {
el.checked = false;
} else if (el.name && el.name.indexOf('-id') !== -1) {
if (el.name && el.name.indexOf('-id') !== -1) {
el.value = '';
} else if (el.type !== 'hidden') {
el.value = '';
@@ -161,13 +187,8 @@
});
var amountCell = clone.querySelector('.row-amount');
if (amountCell) amountCell.textContent = '—';
var delCheckbox = clone.querySelector('input[name$="-DELETE"]');
if (delCheckbox) {
var delLabel = clone.querySelector('label.ws-delete-row-label');
if (delLabel) delLabel.setAttribute('for', delCheckbox.id);
}
tbody.appendChild(clone);
totalInput.value = nextIndex + 1;
reindexRows();
});
})();
</script>