listy, i inne funkcje

This commit is contained in:
Mateusz Gruszczyński
2025-09-22 14:01:57 +02:00
parent 0b221696d4
commit 1a423a8b92
8 changed files with 877 additions and 64 deletions

View File

@@ -0,0 +1,153 @@
(function () {
const tbody = document.querySelector('#produkty-body');
const celInput = document.querySelector('#cel');
const box = document.querySelector('#celSyncBox');
const msg = document.querySelector('#celSyncMsg');
const btn = document.querySelector('#btnApplyCelFromSum');
if (!tbody || !celInput || !box || !msg || !btn) return;
const EPS = 0.01; // tolerancja porównania
function parsePrice(raw) {
if (!raw) return NaN;
const s = String(raw).trim().replace(/\s+/g, '').replace(',', '.');
const n = Number(s);
return Number.isFinite(n) && n >= 0 ? n : NaN;
}
function getRows() {
return Array.from(tbody.querySelectorAll('tr'));
}
function computeSum() {
const rows = getRows();
let hasNamed = false;
let sumAll = 0; // suma ze wszystkich wierszy z nazwą i poprawną ceną
let sumToBuy = 0; // suma tylko z wierszy NIE oznaczonych jako "Kupione"
for (const tr of rows) {
const nameInput = tr.querySelector('input[name="item_nazwa[]"]');
const priceInput = tr.querySelector('input[name="item_cena[]"]');
const kupioneSwitch = tr.querySelector('.kupione-switch');
const name = nameInput ? nameInput.value.trim() : '';
if (!name) continue; // ignoruj puste wiersze bez nazwy
hasNamed = true;
const priceVal = priceInput ? parsePrice(priceInput.value) : NaN;
if (Number.isNaN(priceVal)) continue;
// zawsze dolicz do sumy wszystkich
sumAll += priceVal;
// do sumy do-kupienia tylko jeśli nie jest oznaczone jako kupione
if (!(kupioneSwitch && kupioneSwitch.checked)) {
sumToBuy += priceVal;
}
}
return { hasNamed, sumAll, sumToBuy };
}
function readCel() {
const v = parsePrice(celInput.value);
return Number.isNaN(v) ? null : v;
}
function formatPln(n) {
// Nie narzucamy locale prosto 2 miejsca
return n.toFixed(2);
}
function updateUI() {
const { hasNamed, sumAll, sumToBuy } = computeSum();
// Brak produktów (brak nazw) lub obie sumy = 0 → nic nie pokazuj
if (!hasNamed || (sumAll <= 0 && sumToBuy <= 0)) {
box.classList.add('d-none');
btn.classList.add('d-none');
box.classList.remove('alert-success', 'alert-info');
msg.textContent = '';
return;
}
const cel = readCel();
const target = sumToBuy; // porównujemy do kwoty POZOSTAŁE DO KUPIENIA
// Jeśli cel nie ustawiony lub NaN → zaproponuj ustawienie celu = sumToBuy
if (cel === null) {
box.classList.remove('d-none');
box.classList.remove('alert-success');
box.classList.add('alert-info');
// pokazujemy obie sumy w komunikacie
msg.innerHTML = `
<div>Wszystkie: <strong>${formatPln(sumAll)} PLN</strong> ·
Do kupienia: <strong>${formatPln(sumToBuy)} PLN</strong></div>
<div class="mt-1">Możesz ustawić <strong>cel</strong> na kwotę do kupienia.</div>
`;
btn.textContent = `Ustaw cel = ${formatPln(target)} PLN`;
btn.classList.remove('d-none');
return;
}
// Mamy cel — porównanie do sumy do-kupienia
if (Math.abs(cel - target) <= EPS) {
box.classList.remove('d-none');
box.classList.remove('alert-info');
box.classList.add('alert-success');
msg.innerHTML = `
Suma <em>do kupienia</em> (<strong>${formatPln(target)} PLN</strong>) jest równa celowi.
<div class="small text-muted mt-1">Wszystkie: ${formatPln(sumAll)} PLN · Do kupienia: ${formatPln(sumToBuy)} PLN</div>
`;
btn.classList.add('d-none');
} else {
box.classList.remove('d-none');
box.classList.remove('alert-success');
box.classList.add('alert-info');
msg.innerHTML = `
<div>Wszystkie: <strong>${formatPln(sumAll)} PLN</strong> ·
Do kupienia: <strong>${formatPln(sumToBuy)} PLN</strong></div>
<div class="mt-1">Cel: <strong>${formatPln(cel)} PLN</strong></div>
`;
btn.textContent = `Zaktualizuj cel do ${formatPln(target)} PLN`;
btn.classList.remove('d-none');
}
}
btn.addEventListener('click', (e) => {
e.preventDefault();
const { sumToBuy } = computeSum();
if (sumToBuy > 0) {
celInput.value = formatPln(sumToBuy);
celInput.dispatchEvent(new Event('input', { bubbles: true }));
celInput.dispatchEvent(new Event('change', { bubbles: true }));
updateUI();
}
});
// Reaguj na zmiany cen/nazw
tbody.addEventListener('input', (e) => {
const name = e.target.getAttribute('name');
if (name === 'item_nazwa[]' || name === 'item_cena[]') {
updateUI();
}
});
// Reaguj na zmiany celu
celInput.addEventListener('input', updateUI);
celInput.addEventListener('change', updateUI);
// Obserwuj dodawanie/usuwanie wierszy przez inne skrypty
const mo = new MutationObserver(() => updateUI());
mo.observe(tbody, { childList: true, subtree: true });
// Init po załadowaniu
document.addEventListener('DOMContentLoaded', updateUI);
// i jedno wywołanie na starcie (gdy DOMContentLoaded już był)
updateUI();
})();

View File

@@ -0,0 +1,73 @@
(function () {
const body = document.querySelector('#produkty-body');
const addBtn = document.querySelector('#add-row');
const clearBtn = document.querySelector('#clear-empty');
if (!body) return;
function reindexHidden() {
const rows = [...body.querySelectorAll('tr')];
rows.forEach((tr, idx) => {
const hidden = tr.querySelector('input[type="hidden"][name^="item_kupione_val_"]');
if (hidden) hidden.name = `item_kupione_val_${idx}`;
});
}
function makeRow() {
const tr = document.createElement('tr');
tr.innerHTML = `
<td><input type="text" class="form-control" name="item_nazwa[]" placeholder="np. Karma Brit 10kg" required></td>
<td><input type="url" class="form-control" name="item_link[]" placeholder="https://..."></td>
<td><input type="text" inputmode="decimal" class="form-control text-end" name="item_cena[]" placeholder="0,00"></td>
<td>
<div class="form-check form-switch">
<input class="form-check-input kupione-switch" type="checkbox">
<input type="hidden" name="item_kupione_val_TMP" value="0">
<label class="form-check-label small">Do kupienia</label>
</div>
</td>
<td class="text-end">
<button type="button" class="btn btn-sm btn-outline-light border remove-row" title="Usuń wiersz">✕</button>
</td>`;
return tr;
}
body.addEventListener('change', (e) => {
if (e.target.classList.contains('kupione-switch')) {
const tr = e.target.closest('tr');
const hidden = tr.querySelector('input[type="hidden"][name^="item_kupione_val_"]');
const label = tr.querySelector('.form-check-label');
if (hidden) hidden.value = e.target.checked ? '1' : '0';
if (label) label.textContent = e.target.checked ? 'Kupione' : 'Do kupienia';
}
});
body.addEventListener('click', (e) => {
if (e.target.classList.contains('remove-row')) {
e.preventDefault();
const tr = e.target.closest('tr');
tr.remove();
reindexHidden();
}
});
addBtn?.addEventListener('click', (e) => {
e.preventDefault();
body.appendChild(makeRow());
reindexHidden();
});
clearBtn?.addEventListener('click', (e) => {
e.preventDefault();
[...body.querySelectorAll('tr')].forEach(tr => {
const name = tr.querySelector('input[name="item_nazwa[]"]')?.value.trim();
const link = tr.querySelector('input[name="item_link[]"]')?.value.trim();
const cena = tr.querySelector('input[name="item_cena[]"]')?.value.trim();
if (!name && !link && !cena) tr.remove();
});
reindexHidden();
});
// startowa normalizacja nazw hiddenów (ważne w trybie edycji)
reindexHidden();
})();

26
static/js/transakcje.js Normal file
View File

@@ -0,0 +1,26 @@
document.addEventListener('DOMContentLoaded', function () {
const modalW = new bootstrap.Modal(document.getElementById('modalWplata'));
const modalX = new bootstrap.Modal(document.getElementById('modalWydatek'));
// WPŁATA
document.querySelectorAll('.btn-edit-wplata').forEach(btn => {
btn.addEventListener('click', () => {
const form = document.getElementById('formWplata');
form.action = btn.dataset.action;
document.getElementById('wplataKwota').value = btn.dataset.kwota || '';
document.getElementById('wplataOpis').value = btn.dataset.opis || '';
modalW.show();
});
});
// WYDATEK
document.querySelectorAll('.btn-edit-wydatek').forEach(btn => {
btn.addEventListener('click', () => {
const form = document.getElementById('formWydatek');
form.action = btn.dataset.action;
document.getElementById('wydatekKwota').value = btn.dataset.kwota || '';
document.getElementById('wydatekOpis').value = btn.dataset.opis || '';
modalX.show();
});
});
});