UI cleanup
This commit is contained in:
+600
-424
File diff suppressed because it is too large
Load Diff
+254
-19
@@ -1,6 +1,11 @@
|
||||
let editingParticipantId = null;
|
||||
let isAdmin = false;
|
||||
|
||||
function fmtDate(iso) {
|
||||
const [y, m, d] = iso.split('-');
|
||||
return `${d}-${m}-${y.slice(2)}`;
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
checkAdminStatus();
|
||||
@@ -412,8 +417,8 @@ async function refreshLogs() {
|
||||
filteredLogs.forEach(log => {
|
||||
const row = document.createElement('tr');
|
||||
const timestamp = log.timestamp;
|
||||
const date = timestamp.split('T')[0];
|
||||
const time = timestamp.split('T')[1].substring(0, 8);
|
||||
const date = fmtDate(timestamp.split('T')[0]);
|
||||
const time = timestamp.split('T')[1].substring(0, 5);
|
||||
|
||||
// Determine status class
|
||||
let statusClass = 'pass';
|
||||
@@ -728,7 +733,14 @@ function switchTab(tabName, event) {
|
||||
|
||||
// Load tab-specific data
|
||||
if (tabName === 'documents') loadDocuments();
|
||||
if (tabName === 'store') loadStore();
|
||||
if (tabName === 'store') {
|
||||
// Always return to shop view when switching to store tab
|
||||
storeViewIsHistory = false;
|
||||
document.getElementById('storeShopView').style.display = '';
|
||||
document.getElementById('storeHistoryView').style.display = 'none';
|
||||
document.getElementById('storeViewToggleBtn').textContent = 'Zgodovina plačil';
|
||||
loadStore();
|
||||
}
|
||||
}
|
||||
|
||||
// Close modal when clicking outside
|
||||
@@ -746,6 +758,10 @@ window.onclick = function(event) {
|
||||
if (event.target === addUnregisteredModal) closeAddUnregisteredModal();
|
||||
if (event.target === addItemModal) closeAddItemModal();
|
||||
if (event.target === editBalanceModal) closeEditBalanceModal();
|
||||
const checkoutModal = document.getElementById('checkoutModal');
|
||||
const monthlyModal = document.getElementById('monthlyModal');
|
||||
if (event.target === checkoutModal) closeCheckoutModal();
|
||||
if (event.target === monthlyModal) closeMonthlyOverview();
|
||||
}
|
||||
|
||||
// ===== LAST SCAN RESULT =====
|
||||
@@ -909,9 +925,10 @@ function renderInventory() {
|
||||
storeItems.forEach(item => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'inventory-card' + (item.stock === 0 ? ' out-of-stock' : '');
|
||||
const imgHtml = item.image
|
||||
const imgHtml = `<div class="inventory-card-img-wrap">${item.image
|
||||
? `<img src="${item.image}" alt="${item.name}" class="inventory-card-img">`
|
||||
: `<div class="inventory-card-img-placeholder">${ITEM_FALLBACK_SVG}</div>`;
|
||||
: `<div class="inventory-card-img-placeholder">${ITEM_FALLBACK_SVG}</div>`
|
||||
}</div>`;
|
||||
card.innerHTML = `
|
||||
${imgHtml}
|
||||
<div class="inventory-card-body">
|
||||
@@ -1008,16 +1025,40 @@ function updateCartTotals() {
|
||||
}
|
||||
}
|
||||
|
||||
async function processPayment() {
|
||||
function processPayment() {
|
||||
if (cart.length === 0) {
|
||||
alert('Košarica je prazna');
|
||||
return;
|
||||
}
|
||||
|
||||
const total = cart.reduce((sum, i) => sum + i.price * i.qty, 0);
|
||||
const balanceAfter = currentBalance + total;
|
||||
|
||||
if (!confirm(`Skupaj: €${total.toFixed(2)}\nStanje po plačilu: €${balanceAfter.toFixed(2)}\n\nPotrdite plačilo?`)) return;
|
||||
document.getElementById('checkoutFirstName').value = '';
|
||||
document.getElementById('checkoutLastName').value = '';
|
||||
document.getElementById('checkoutTotal').textContent = '€' + total.toFixed(2);
|
||||
document.getElementById('checkoutBalanceAfter').textContent = '€' + balanceAfter.toFixed(2);
|
||||
document.getElementById('checkoutModal').classList.add('show');
|
||||
document.getElementById('checkoutFirstName').focus();
|
||||
}
|
||||
|
||||
function closeCheckoutModal() {
|
||||
document.getElementById('checkoutModal').classList.remove('show');
|
||||
}
|
||||
|
||||
async function confirmCheckout() {
|
||||
const firstName = document.getElementById('checkoutFirstName').value.trim();
|
||||
const lastName = document.getElementById('checkoutLastName').value.trim();
|
||||
|
||||
if (!firstName || !lastName) {
|
||||
alert('Prosimo, vnesite ime in priimek kupca');
|
||||
return;
|
||||
}
|
||||
|
||||
const buyer = firstName + ' ' + lastName;
|
||||
const total = cart.reduce((sum, i) => sum + i.price * i.qty, 0);
|
||||
const balanceAfter = currentBalance + total;
|
||||
|
||||
closeCheckoutModal();
|
||||
|
||||
try {
|
||||
const cartItems = cart.map(cartItem => {
|
||||
@@ -1036,6 +1077,7 @@ async function processPayment() {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
buyer,
|
||||
items: cart.map(i => ({ name: i.name, qty: i.qty, price: i.price })),
|
||||
cart_items: cartItems,
|
||||
total,
|
||||
@@ -1047,9 +1089,18 @@ async function processPayment() {
|
||||
const data = await response.json();
|
||||
if (!data.success) { alert('Napaka: ' + data.message); return; }
|
||||
|
||||
const payment = {
|
||||
buyer,
|
||||
items: cart.map(i => ({ name: i.name, qty: i.qty, price: i.price })),
|
||||
total,
|
||||
balance_before: currentBalance,
|
||||
balance_after: balanceAfter,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
cart = [];
|
||||
await loadStore();
|
||||
alert('Plačilo uspešno!');
|
||||
printReceipt(payment);
|
||||
} catch (error) {
|
||||
alert('Napaka: ' + error.message);
|
||||
}
|
||||
@@ -1212,15 +1263,18 @@ async function saveBalance() {
|
||||
}
|
||||
|
||||
// ===== PAYMENT HISTORY =====
|
||||
let paymentHistoryVisible = false;
|
||||
let storeViewIsHistory = false;
|
||||
|
||||
function togglePaymentHistory() {
|
||||
paymentHistoryVisible = !paymentHistoryVisible;
|
||||
const panel = document.getElementById('paymentHistoryPanel');
|
||||
panel.style.display = paymentHistoryVisible ? 'block' : 'none';
|
||||
if (paymentHistoryVisible) loadPaymentHistory();
|
||||
function toggleStoreView() {
|
||||
storeViewIsHistory = !storeViewIsHistory;
|
||||
document.getElementById('storeShopView').style.display = storeViewIsHistory ? 'none' : '';
|
||||
document.getElementById('storeHistoryView').style.display = storeViewIsHistory ? '' : 'none';
|
||||
document.getElementById('storeViewToggleBtn').textContent = storeViewIsHistory ? '← Nazaj v trgovino' : 'Zgodovina plačil';
|
||||
if (storeViewIsHistory) loadPaymentHistory();
|
||||
}
|
||||
|
||||
function togglePaymentHistory() { toggleStoreView(); }
|
||||
|
||||
async function loadPaymentHistory() {
|
||||
try {
|
||||
const response = await fetch('/api/store/payments');
|
||||
@@ -1232,25 +1286,27 @@ async function loadPaymentHistory() {
|
||||
if (clearBtn) clearBtn.style.display = isAdmin ? 'inline-block' : 'none';
|
||||
|
||||
if (payments.length === 0) {
|
||||
tbody.innerHTML = `<tr><td colspan="7" style="text-align:center;padding:20px;color:var(--text-secondary);">Ni zabeleženih plačil</td></tr>`;
|
||||
tbody.innerHTML = `<tr><td colspan="9" style="text-align:center;padding:20px;color:var(--text-secondary);">Ni zabeleženih plačil</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = '';
|
||||
[...payments].reverse().forEach(p => {
|
||||
const ts = p.timestamp;
|
||||
const date = ts.split('T')[0];
|
||||
const time = ts.split('T')[1].substring(0, 8);
|
||||
const date = fmtDate(ts.split('T')[0]);
|
||||
const time = ts.split('T')[1].substring(0, 5);
|
||||
const itemsSummary = p.items.map(i => `${i.name} ×${i.qty}`).join(', ');
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${p.id}</td>
|
||||
<td style="font-weight:600;">${p.buyer || '-'}</td>
|
||||
<td>${date}</td>
|
||||
<td>${time}</td>
|
||||
<td style="max-width:220px;word-break:break-word;">${itemsSummary}</td>
|
||||
<td style="max-width:200px;word-break:break-word;">${itemsSummary}</td>
|
||||
<td style="font-weight:700;color:var(--accent-blue);">€${p.total.toFixed(2)}</td>
|
||||
<td>€${p.balance_before.toFixed(2)}</td>
|
||||
<td>€${p.balance_after.toFixed(2)}</td>
|
||||
<td><button class="action-btn edit-btn" onclick="printReceipt(${JSON.stringify(p).replace(/"/g, '"')})">🖨 Natisni</button></td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
@@ -1259,6 +1315,185 @@ async function loadPaymentHistory() {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== RECEIPT PRINT =====
|
||||
const LOGO_URL = '/static/assets/logo.png';
|
||||
|
||||
function printReceipt(payment) {
|
||||
const ts = payment.timestamp || new Date().toISOString();
|
||||
const date = fmtDate(ts.split('T')[0]);
|
||||
const time = ts.split('T')[1] ? ts.split('T')[1].substring(0, 5) : '';
|
||||
const rows = payment.items.map(i =>
|
||||
`<tr><td class="rp-td-name">${i.name}</td><td class="rp-td-qty">${i.qty}×</td><td class="rp-td-price">€${i.price.toFixed(2)}</td><td class="rp-td-sub">€${(i.price * i.qty).toFixed(2)}</td></tr>`
|
||||
).join('');
|
||||
|
||||
const html = `
|
||||
<div class="rp-page">
|
||||
<div class="rp-logo-block">
|
||||
<img src="${LOGO_URL}" alt="Logo" class="rp-logo" onerror="this.style.display='none'">
|
||||
</div>
|
||||
<div class="rp-divider"></div>
|
||||
<div class="rp-title">POTRDILO O PLAČILU</div>
|
||||
<div class="rp-meta">
|
||||
<span>${date} ${time}</span>
|
||||
<span>Prodajalec: <strong>${payment.buyer || '-'}</strong></span>
|
||||
</div>
|
||||
<div class="rp-divider-dashed"></div>
|
||||
<table class="rp-table">
|
||||
<thead><tr><th>Artikel</th><th>Kol.</th><th>Cena</th><th style="text-align:right;">Znesek</th></tr></thead>
|
||||
<tbody>${rows}</tbody>
|
||||
</table>
|
||||
<div class="rp-divider"></div>
|
||||
<div class="rp-total">
|
||||
<span>SKUPAJ</span>
|
||||
<span>€${payment.total.toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="rp-divider-dashed"></div>
|
||||
<div class="rp-footer">Hvala za nakup!</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const area = document.getElementById('receiptPrintArea');
|
||||
area.innerHTML = html;
|
||||
area.style.display = 'block';
|
||||
document.body.classList.add('printing-receipt');
|
||||
window.print();
|
||||
document.body.classList.remove('printing-receipt');
|
||||
area.style.display = 'none';
|
||||
}
|
||||
|
||||
// ===== MONTHLY OVERVIEW =====
|
||||
let allPaymentsCache = [];
|
||||
|
||||
function openMonthlyOverview() {
|
||||
const now = new Date();
|
||||
const ym = now.toISOString().substring(0, 7);
|
||||
document.getElementById('monthlyPickerInput').value = ym;
|
||||
document.getElementById('monthlyModal').classList.add('show');
|
||||
renderMonthlyOverview();
|
||||
}
|
||||
|
||||
function closeMonthlyOverview() {
|
||||
document.getElementById('monthlyModal').classList.remove('show');
|
||||
}
|
||||
|
||||
async function renderMonthlyOverview() {
|
||||
const picker = document.getElementById('monthlyPickerInput').value;
|
||||
if (!picker) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/store/payments');
|
||||
allPaymentsCache = await response.json();
|
||||
} catch (e) { return; }
|
||||
|
||||
const filtered = allPaymentsCache.filter(p => p.timestamp.startsWith(picker));
|
||||
const content = document.getElementById('monthlyOverviewContent');
|
||||
|
||||
if (filtered.length === 0) {
|
||||
content.innerHTML = '<p style="color:var(--text-secondary);padding:16px 0;">Ni plačil v izbranem mesecu.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
const grandTotal = filtered.reduce((s, p) => s + p.total, 0);
|
||||
|
||||
// Per-item breakdown
|
||||
const itemTotals = {};
|
||||
filtered.forEach(p => p.items.forEach(i => {
|
||||
if (!itemTotals[i.name]) itemTotals[i.name] = { qty: 0, revenue: 0 };
|
||||
itemTotals[i.name].qty += i.qty;
|
||||
itemTotals[i.name].revenue += i.price * i.qty;
|
||||
}));
|
||||
|
||||
const itemRows = Object.entries(itemTotals).map(([name, v]) =>
|
||||
`<tr><td>${name}</td><td style="text-align:center;">${v.qty}</td><td style="text-align:right;font-weight:600;">€${v.revenue.toFixed(2)}</td></tr>`
|
||||
).join('');
|
||||
|
||||
const txRows = [...filtered].reverse().map(p => {
|
||||
const date = fmtDate(p.timestamp.split('T')[0]);
|
||||
const time = p.timestamp.split('T')[1].substring(0, 5);
|
||||
return `<tr><td>${date}</td><td>${time}</td><td style="font-weight:600;">${p.buyer || '-'}</td><td>${p.items.map(i => `${i.name} ×${i.qty}`).join(', ')}</td><td style="text-align:right;font-weight:700;color:var(--accent-blue);">€${p.total.toFixed(2)}</td></tr>`;
|
||||
}).join('');
|
||||
|
||||
content.innerHTML = `
|
||||
<div class="monthly-summary-total">Skupaj prihodek: <strong>€${grandTotal.toFixed(2)}</strong> (${filtered.length} transakcij)</div>
|
||||
<h4 class="monthly-section-title">Po artiklih</h4>
|
||||
<div class="table-container" style="margin-bottom:16px;">
|
||||
<table><thead><tr><th>Artikel</th><th style="text-align:center;">Količina</th><th style="text-align:right;">Prihodek</th></tr></thead>
|
||||
<tbody>${itemRows}</tbody></table>
|
||||
</div>
|
||||
<h4 class="monthly-section-title">Vse transakcije</h4>
|
||||
<div class="table-container">
|
||||
<table><thead><tr><th>Datum</th><th>Čas</th><th>Prodajalec</th><th>Artikli</th><th style="text-align:right;">Skupaj</th></tr></thead>
|
||||
<tbody>${txRows}</tbody></table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function printMonthlyOverview() {
|
||||
const picker = document.getElementById('monthlyPickerInput').value;
|
||||
if (!picker || !allPaymentsCache.length) return;
|
||||
|
||||
const filtered = allPaymentsCache.filter(p => p.timestamp.startsWith(picker));
|
||||
if (filtered.length === 0) { alert('Ni podatkov za izbrani mesec.'); return; }
|
||||
|
||||
const grandTotal = filtered.reduce((s, p) => s + p.total, 0);
|
||||
|
||||
const itemTotals = {};
|
||||
filtered.forEach(p => p.items.forEach(i => {
|
||||
if (!itemTotals[i.name]) itemTotals[i.name] = { qty: 0, revenue: 0 };
|
||||
itemTotals[i.name].qty += i.qty;
|
||||
itemTotals[i.name].revenue += i.price * i.qty;
|
||||
}));
|
||||
|
||||
const itemRows = Object.entries(itemTotals).map(([name, v]) =>
|
||||
`<tr><td>${name}</td><td style="text-align:center;">${v.qty}</td><td style="text-align:right;font-weight:600;">€${v.revenue.toFixed(2)}</td></tr>`
|
||||
).join('');
|
||||
|
||||
const txRows = [...filtered].reverse().map(p => {
|
||||
const date = fmtDate(p.timestamp.split('T')[0]);
|
||||
const time = p.timestamp.split('T')[1].substring(0, 5);
|
||||
const items = p.items.map(i => `${i.name} ×${i.qty} (€${(i.price*i.qty).toFixed(2)})`).join(', ');
|
||||
return `<tr><td>${date}</td><td>${time}</td><td style="font-weight:600;">${p.buyer || '-'}</td><td>${items}</td><td style="text-align:right;font-weight:700;">€${p.total.toFixed(2)}</td></tr>`;
|
||||
}).join('');
|
||||
|
||||
const [year, month] = picker.split('-');
|
||||
const monthName = new Date(year, month - 1, 1).toLocaleDateString('sl-SI', { month: 'long', year: 'numeric' });
|
||||
const printDate = fmtDate(new Date().toISOString().split('T')[0]);
|
||||
|
||||
const html = `
|
||||
<div class="rp-page rp-monthly">
|
||||
<div class="rp-logo-block">
|
||||
<img src="${LOGO_URL}" alt="Logo" class="rp-logo" onerror="this.style.display='none'">
|
||||
</div>
|
||||
<div class="rp-divider"></div>
|
||||
<div class="rp-title">MESEČNI PREGLED</div>
|
||||
<div class="rp-meta"><span>${monthName}</span><span>Natisnjeno: ${printDate}</span></div>
|
||||
<div class="rp-divider-dashed"></div>
|
||||
<div class="rp-monthly-kpi">
|
||||
<div class="rp-kpi-box"><div class="rp-kpi-label">Skupaj prihodek</div><div class="rp-kpi-value">€${grandTotal.toFixed(2)}</div></div>
|
||||
<div class="rp-kpi-box"><div class="rp-kpi-label">Št. transakcij</div><div class="rp-kpi-value">${filtered.length}</div></div>
|
||||
</div>
|
||||
<div class="rp-section-title">Po artiklih</div>
|
||||
<table class="rp-table">
|
||||
<thead><tr><th>Artikel</th><th style="text-align:center;">Količina</th><th style="text-align:right;">Prihodek</th></tr></thead>
|
||||
<tbody>${itemRows}</tbody>
|
||||
</table>
|
||||
<div class="rp-section-title" style="margin-top:16px;">Vse transakcije</div>
|
||||
<table class="rp-table">
|
||||
<thead><tr><th>Datum</th><th>Čas</th><th>Prodajalec</th><th>Artikli</th><th style="text-align:right;">Skupaj</th></tr></thead>
|
||||
<tbody>${txRows}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const area = document.getElementById('receiptPrintArea');
|
||||
area.innerHTML = html;
|
||||
area.style.display = 'block';
|
||||
document.body.classList.add('printing-receipt');
|
||||
window.print();
|
||||
document.body.classList.remove('printing-receipt');
|
||||
area.style.display = 'none';
|
||||
}
|
||||
|
||||
async function clearPaymentHistory() {
|
||||
if (!confirm('Izbriši celotno zgodovino plačil?')) return;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user