Feature: таблица товаров без скролла, товар обрезается, цена/стоимость с разделителем разрядов
Made-with: Cursor
This commit is contained in:
10
HISTORY.md
10
HISTORY.md
@@ -2,6 +2,16 @@
|
||||
|
||||
# История изменений ERP WaterSurf
|
||||
|
||||
## 2025-02-25 22:35 UTC – Табличная часть заказа: без скролла, цена/стоимость с разделителем
|
||||
|
||||
**Проблема**: В таблице товаров появлялась горизонтальная полоса прокрутки; поле «Товар» слишком широкое; в полях «Цена» и «Стоимость» не было разделителя разрядов.
|
||||
|
||||
**Решение**: Включён table-layout: fixed и заданы доли колонок в % (Товар 28%, Цена 14%, Валюта 12%, Количество 10%, Стоимость 14%, Удалить 5%), чтобы таблица помещалась без скролла. Поле «Товар» ограничено по ширине, select с overflow: hidden. Поля «Цена» и «Стоимость»: выравнивание по правому краю, узкое поле под 8-значную сумму. В JS добавлены formatNum/parseNum: разделитель тысяч (неразрывный пробел) и запятая как десятичный разделитель; при blur цена форматируется, при focus и при submit — пробелы снимаются перед отправкой. Стоимость в ячейке выводится с разделителем. Аналогичные правки в форме заказа поставщику.
|
||||
|
||||
**Изменения**: order_form.html, supplier_order_form.html (colgroup, классы ячеек, JS форматирование), theme-compact.css (ширины колонок).
|
||||
|
||||
---
|
||||
|
||||
## 2025-02-25 22:25 UTC – Временное отключение авторизации для отладки
|
||||
|
||||
**Проблема**: Для быстрой разработки и отладки нужно работать без входа.
|
||||
|
||||
@@ -84,14 +84,54 @@
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Таблица товаров: колонка Товар шире, Количество уже */
|
||||
/* Таблица товаров: без горизонтального скролла, компактные колонки */
|
||||
.ws-table-wrap {
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.ws-table-items {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Товар: ограниченная ширина, длинное название обрезается */
|
||||
.ws-table-items .ws-col-product {
|
||||
width: 35%;
|
||||
min-width: 14rem;
|
||||
width: 28%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ws-table-items .ws-col-product select {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Цена: поле под 8 знаков, значение справа */
|
||||
.ws-table-items .ws-col-price {
|
||||
width: 14%;
|
||||
}
|
||||
|
||||
.ws-table-items .ws-col-price input {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* Валюта */
|
||||
.ws-table-items .ws-col-currency {
|
||||
width: 12%;
|
||||
}
|
||||
|
||||
.ws-table-items .ws-col-currency select {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.ws-table-items .ws-col-qty {
|
||||
width: 5rem;
|
||||
width: 10%;
|
||||
min-width: 4rem;
|
||||
}
|
||||
|
||||
.ws-table-items td.ws-col-qty input,
|
||||
@@ -101,6 +141,14 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ws-table-items .ws-col-del {
|
||||
width: 4rem;
|
||||
/* Стоимость: справа, разделитель в JS */
|
||||
.ws-table-items .ws-col-cost {
|
||||
width: 14%;
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.ws-table-items .ws-col-del {
|
||||
width: 5%;
|
||||
min-width: 3rem;
|
||||
}
|
||||
|
||||
@@ -48,10 +48,10 @@
|
||||
<table class="ws-table ws-table-items" id="order-items">
|
||||
<colgroup>
|
||||
<col class="ws-col-product">
|
||||
<col>
|
||||
<col>
|
||||
<col class="ws-col-price">
|
||||
<col class="ws-col-currency">
|
||||
<col class="ws-col-qty">
|
||||
<col>
|
||||
<col class="ws-col-cost">
|
||||
<col class="ws-col-del">
|
||||
</colgroup>
|
||||
<thead>
|
||||
@@ -67,11 +67,11 @@
|
||||
<tbody id="order-items-body">
|
||||
{% for f in formset %}
|
||||
<tr class="item-row">
|
||||
<td>{{ f.id }}{{ f.product }}</td>
|
||||
<td>{{ f.price }}</td>
|
||||
<td>{{ f.currency }}</td>
|
||||
<td class="ws-col-product">{{ f.id }}{{ f.product }}</td>
|
||||
<td class="ws-col-price">{{ f.price }}</td>
|
||||
<td class="ws-col-currency">{{ f.currency }}</td>
|
||||
<td class="ws-col-qty">{{ f.quantity }}</td>
|
||||
<td class="row-amount ws-num">—</td>
|
||||
<td class="row-amount ws-col-cost ws-num">—</td>
|
||||
<td>{% if f.DELETE %}{{ f.DELETE }}{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -101,17 +101,52 @@
|
||||
return name.replace(/-TOTAL_FORMS$/, '');
|
||||
}
|
||||
|
||||
function formatNum(x) {
|
||||
var n = parseFloat(x);
|
||||
if (isNaN(n)) return '—';
|
||||
var parts = n.toFixed(2).split('.');
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, '\u202f');
|
||||
return parts.join(',');
|
||||
}
|
||||
function parseNum(s) {
|
||||
return parseFloat(String(s).replace(/\s/g, '').replace(',', '.')) || 0;
|
||||
}
|
||||
|
||||
function updateRowAmounts() {
|
||||
document.querySelectorAll('#order-items .item-row').forEach(function(row) {
|
||||
var price = parseFloat(row.querySelector('input[name$="-price"]')?.value) || 0;
|
||||
var qty = parseFloat(row.querySelector('input[name$="-quantity"]')?.value) || 0;
|
||||
var priceInput = row.querySelector('input[name$="-price"]');
|
||||
var qty = parseNum(row.querySelector('input[name$="-quantity"]')?.value);
|
||||
var price = parseNum(priceInput?.value);
|
||||
var el = row.querySelector('.row-amount');
|
||||
if (el) el.textContent = (price * qty).toFixed(2);
|
||||
if (el) el.textContent = formatNum(price * qty);
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('input', updateRowAmounts);
|
||||
|
||||
form.querySelectorAll('#order-items input[name$="-price"]').forEach(function(input) {
|
||||
input.addEventListener('blur', function() {
|
||||
var v = parseNum(this.value);
|
||||
if (!isNaN(v) && this.value.trim() !== '') this.value = formatNum(v).replace(',', '.');
|
||||
});
|
||||
input.addEventListener('focus', function() {
|
||||
this.value = String(this.value).replace(/\s/g, '').replace(',', '.');
|
||||
});
|
||||
});
|
||||
form.addEventListener('submit', function() {
|
||||
form.querySelectorAll('input[name$="-price"]').forEach(function(input) {
|
||||
input.value = String(input.value).replace(/\s/g, '').replace(',', '.');
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('#order-items input[name$="-price"]').forEach(function(input) {
|
||||
if (input.value && input.value.trim() !== '') {
|
||||
var v = parseNum(input.value);
|
||||
input.value = formatNum(v).replace(',', '.');
|
||||
}
|
||||
});
|
||||
updateRowAmounts();
|
||||
|
||||
document.getElementById('add-order-row').addEventListener('click', function() {
|
||||
var rows = tbody.querySelectorAll('.item-row');
|
||||
var lastRow = rows[rows.length - 1];
|
||||
@@ -135,6 +170,13 @@
|
||||
});
|
||||
var amountCell = clone.querySelector('.row-amount');
|
||||
if (amountCell) amountCell.textContent = '—';
|
||||
clone.querySelector('input[name$="-price"]')?.addEventListener('blur', function() {
|
||||
var v = parseNum(this.value);
|
||||
if (!isNaN(v) && this.value.trim() !== '') this.value = formatNum(v).replace(',', '.');
|
||||
});
|
||||
clone.querySelector('input[name$="-price"]')?.addEventListener('focus', function() {
|
||||
this.value = String(this.value).replace(/\s/g, '').replace(',', '.');
|
||||
});
|
||||
tbody.appendChild(clone);
|
||||
totalInput.value = nextIndex + 1;
|
||||
});
|
||||
|
||||
@@ -55,10 +55,10 @@
|
||||
<table class="ws-table ws-table-items" id="supplier-order-items">
|
||||
<colgroup>
|
||||
<col class="ws-col-product">
|
||||
<col>
|
||||
<col>
|
||||
<col class="ws-col-price">
|
||||
<col class="ws-col-currency">
|
||||
<col class="ws-col-qty">
|
||||
<col>
|
||||
<col class="ws-col-cost">
|
||||
<col class="ws-col-del">
|
||||
</colgroup>
|
||||
<thead>
|
||||
@@ -74,11 +74,11 @@
|
||||
<tbody id="supplier-order-items-body">
|
||||
{% for f in formset %}
|
||||
<tr class="item-row">
|
||||
<td>{{ f.id }}{{ f.product }}</td>
|
||||
<td>{{ f.price }}</td>
|
||||
<td>{{ f.currency }}</td>
|
||||
<td class="ws-col-product">{{ f.id }}{{ f.product }}</td>
|
||||
<td class="ws-col-price">{{ f.price }}</td>
|
||||
<td class="ws-col-currency">{{ f.currency }}</td>
|
||||
<td class="ws-col-qty">{{ f.quantity }}</td>
|
||||
<td class="row-amount ws-num">—</td>
|
||||
<td class="row-amount ws-col-cost ws-num">—</td>
|
||||
<td>{% if f.DELETE %}{{ f.DELETE }}{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -107,17 +107,52 @@
|
||||
return name.replace(/-TOTAL_FORMS$/, '');
|
||||
}
|
||||
|
||||
function formatNum(x) {
|
||||
var n = parseFloat(x);
|
||||
if (isNaN(n)) return '—';
|
||||
var parts = n.toFixed(2).split('.');
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, '\u202f');
|
||||
return parts.join(',');
|
||||
}
|
||||
function parseNum(s) {
|
||||
return parseFloat(String(s).replace(/\s/g, '').replace(',', '.')) || 0;
|
||||
}
|
||||
|
||||
function updateRowAmounts() {
|
||||
document.querySelectorAll('#supplier-order-items .item-row').forEach(function(row) {
|
||||
var price = parseFloat(row.querySelector('input[name$="-price"]')?.value) || 0;
|
||||
var qty = parseFloat(row.querySelector('input[name$="-quantity"]')?.value) || 0;
|
||||
var priceInput = row.querySelector('input[name$="-price"]');
|
||||
var qty = parseNum(row.querySelector('input[name$="-quantity"]')?.value);
|
||||
var price = parseNum(priceInput?.value);
|
||||
var el = row.querySelector('.row-amount');
|
||||
if (el) el.textContent = (price * qty).toFixed(2);
|
||||
if (el) el.textContent = formatNum(price * qty);
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('input', updateRowAmounts);
|
||||
|
||||
form.querySelectorAll('#supplier-order-items input[name$="-price"]').forEach(function(input) {
|
||||
input.addEventListener('blur', function() {
|
||||
var v = parseNum(this.value);
|
||||
if (!isNaN(v) && this.value.trim() !== '') this.value = formatNum(v).replace(',', '.');
|
||||
});
|
||||
input.addEventListener('focus', function() {
|
||||
this.value = String(this.value).replace(/\s/g, '').replace(',', '.');
|
||||
});
|
||||
});
|
||||
form.addEventListener('submit', function() {
|
||||
form.querySelectorAll('input[name$="-price"]').forEach(function(input) {
|
||||
input.value = String(input.value).replace(/\s/g, '').replace(',', '.');
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('#supplier-order-items input[name$="-price"]').forEach(function(input) {
|
||||
if (input.value && input.value.trim() !== '') {
|
||||
var v = parseNum(input.value);
|
||||
input.value = formatNum(v).replace(',', '.');
|
||||
}
|
||||
});
|
||||
updateRowAmounts();
|
||||
|
||||
document.getElementById('add-supplier-order-row').addEventListener('click', function() {
|
||||
var rows = tbody.querySelectorAll('.item-row');
|
||||
var lastRow = rows[rows.length - 1];
|
||||
@@ -141,6 +176,13 @@
|
||||
});
|
||||
var amountCell = clone.querySelector('.row-amount');
|
||||
if (amountCell) amountCell.textContent = '—';
|
||||
clone.querySelector('input[name$="-price"]')?.addEventListener('blur', function() {
|
||||
var v = parseNum(this.value);
|
||||
if (!isNaN(v) && this.value.trim() !== '') this.value = formatNum(v).replace(',', '.');
|
||||
});
|
||||
clone.querySelector('input[name$="-price"]')?.addEventListener('focus', function() {
|
||||
this.value = String(this.value).replace(/\s/g, '').replace(',', '.');
|
||||
});
|
||||
tbody.appendChild(clone);
|
||||
totalInput.value = nextIndex + 1;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user