let editingParticipantId = null;
let isAdmin = false;
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
checkAdminStatus();
loadParticipants();
// Show today's entries by default
filterByToday();
updateLastScanResult();
// Auto-refresh logs every 5 seconds
setInterval(refreshLogs, 5000);
setInterval(loadParticipants, 5000);
setInterval(updateLastScanResult, 1000);
setInterval(checkAdminStatus, 10000);
});
// ===== AUTHENTICATION =====
async function checkAdminStatus() {
try {
const response = await fetch('/api/check-admin');
const data = await response.json();
isAdmin = data.is_admin;
updateAdminUI();
} catch (error) {
console.error('Error checking admin status:', error);
}
}
function updateAdminUI() {
const adminBtn = document.getElementById('adminToggleBtn');
const addBtn = document.querySelector('[onclick="openAddParticipantModal()"]');
const addUnregisteredBtn = document.getElementById('addUnregisteredBtn');
const addItemBtn = document.getElementById('addItemBtn');
const editBalanceBtn = document.getElementById('editBalanceBtn');
if (!adminBtn) return;
if (isAdmin) {
adminBtn.textContent = 'Odjava Admin';
adminBtn.onclick = adminLogout;
adminBtn.classList.add('active');
if (addBtn) addBtn.style.display = 'block';
if (addItemBtn) addItemBtn.style.display = 'inline-flex';
if (editBalanceBtn) editBalanceBtn.style.display = 'inline-flex';
} else {
adminBtn.textContent = 'Prijava Admin';
adminBtn.onclick = openLoginModal;
adminBtn.classList.remove('active');
if (addBtn) addBtn.style.display = 'none';
if (addItemBtn) addItemBtn.style.display = 'none';
if (editBalanceBtn) editBalanceBtn.style.display = 'none';
}
if (addUnregisteredBtn) addUnregisteredBtn.style.display = 'block';
updateUIDVisibility();
}
async function adminLogin() {
const password = document.getElementById('loginPassword').value;
if (!password) {
alert('Prosimo, vnesite geslo');
return;
}
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password })
});
const data = await response.json();
if (data.success) {
isAdmin = true;
closeLoginModal();
updateAdminUI();
loadParticipants();
loadStore();
alert('Uspešna prijava!');
} else {
alert('Napaka: ' + data.message);
}
} catch (error) {
alert('Napaka: ' + error.message);
}
}
async function adminLogout() {
try {
const response = await fetch('/api/logout', {
method: 'POST'
});
if (response.ok) {
isAdmin = false;
updateAdminUI();
loadParticipants();
loadStore();
alert('Odjavljen');
}
} catch (error) {
console.error('Logout error:', error);
}
}
function openLoginModal() {
document.getElementById('loginModal').classList.add('show');
document.getElementById('loginPassword').focus();
}
function closeLoginModal() {
document.getElementById('loginModal').classList.remove('show');
document.getElementById('loginPassword').value = '';
}
function handleLoginKeypress(event) {
if (event.key === 'Enter') {
adminLogin();
}
}
// ===== PARTICIPANT MANAGEMENT =====
async function loadParticipants() {
try {
const response = await fetch('/api/participants');
const participants = await response.json();
const tbody = document.getElementById('participantsTable');
tbody.innerHTML = '';
participants.forEach(p => {
const row = document.createElement('tr');
const dateAdded = p.date_added ? p.date_added.split('T')[0] : '-';
const expiration = p.expiration_date ? p.expiration_date.split('T')[0] : '-';
// Check if card is expired
let isExpired = false;
if (expiration !== '-') {
const expirationDate = new Date(expiration);
const today = new Date();
today.setHours(0, 0, 0, 0);
isExpired = expirationDate < today;
}
const keyUID = p.key_uid ? p.key_uid : '-';
const actionButtons = isAdmin ? `
` : '';
row.innerHTML = `
${p.id} |
${p.name} |
${p.card_uid || '-'} |
${keyUID} |
${expiration} |
${dateAdded} |
${actionButtons || '-'} |
`;
// Add expired class if card is expired
if (isExpired) {
row.classList.add('expired');
}
tbody.appendChild(row);
});
// Update participant filter dropdown
updateParticipantFilter(participants);
updateUIDVisibility();
} catch (error) {
console.error('Error loading participants:', error);
}
}
function updateParticipantFilter(participants) {
const filterSelect = document.getElementById('participantFilter');
const currentValue = filterSelect.value;
// Clear existing options except the first one
filterSelect.innerHTML = '';
// Sort participants by name and add to dropdown
participants
.sort((a, b) => a.name.localeCompare(b.name))
.forEach(p => {
const option = document.createElement('option');
option.value = p.name;
option.textContent = p.name;
filterSelect.appendChild(option);
});
// Restore previous selection if it still exists
filterSelect.value = currentValue;
}
async function addParticipant() {
const name = document.getElementById('inputName').value.trim();
const cardUID = document.getElementById('inputCardUID').value.trim();
const keyUID = document.getElementById('inputKeyUID').value.trim();
const expiration = document.getElementById('inputExpiration').value;
if (!name) {
alert('Prosimo, izpolnite polje za ime');
return;
}
if (!cardUID && !keyUID) {
alert('Prosimo, vnesite vsaj UID kartice ali UID ključa');
return;
}
try {
const response = await fetch('/api/participants', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: name,
card_uid: cardUID || null,
key_uid: keyUID || null,
expiration_date: expiration || null
})
});
if (!response.ok) {
const errorData = await response.json();
alert('Napaka: ' + (errorData.message || 'Unknown error'));
return;
}
const data = await response.json();
if (data.success) {
alert('Udeleženec je bil uspešno dodan');
closeAddParticipantModal();
loadParticipants();
} else {
alert('Napaka: ' + data.message);
}
} catch (error) {
alert('Napaka: ' + error.message);
}
}
function openEditModal(id, name, cardUID, keyUID, expiration) {
editingParticipantId = id;
document.getElementById('editName').value = name;
document.getElementById('editCardUID').value = cardUID;
document.getElementById('editKeyUID').value = keyUID;
document.getElementById('editExpiration').value = expiration !== '-' ? expiration : '';
document.getElementById('editModal').classList.add('show');
}
function openAddParticipantModal() {
document.getElementById('addParticipantModal').classList.add('show');
}
function closeAddParticipantModal() {
document.getElementById('addParticipantModal').classList.remove('show');
document.getElementById('inputName').value = '';
document.getElementById('inputCardUID').value = '';
document.getElementById('inputKeyUID').value = '';
document.getElementById('inputExpiration').value = '';
}
function openAddUnregisteredModal() {
document.getElementById('addUnregisteredModal').classList.add('show');
document.getElementById('inputUnregisteredName').focus();
}
function closeAddUnregisteredModal() {
document.getElementById('addUnregisteredModal').classList.remove('show');
document.getElementById('inputUnregisteredName').value = '';
}
async function addUnregisteredUser() {
const name = document.getElementById('inputUnregisteredName').value.trim();
if (!name) {
alert('Prosimo, izpolnite polje za ime');
return;
}
try {
const response = await fetch('/api/logs/unregistered', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: name
})
});
const data = await response.json();
if (data.success) {
alert('Neregistrirani udeleženec je bil uspešno dodan');
closeAddUnregisteredModal();
refreshLogs();
} else {
alert('Napaka: ' + data.message);
}
} catch (error) {
alert('Napaka: ' + error.message);
}
}
function closeModal() {
document.getElementById('editModal').classList.remove('show');
editingParticipantId = null;
}
async function saveChanges() {
if (!editingParticipantId) return;
const name = document.getElementById('editName').value.trim();
const cardUID = document.getElementById('editCardUID').value.trim();
const keyUID = document.getElementById('editKeyUID').value.trim();
const expiration = document.getElementById('editExpiration').value;
if (!name) {
alert('Prosimo, izpolnite polje za ime');
return;
}
if (!cardUID && !keyUID) {
alert('Prosimo, vnesite vsaj UID kartice ali UID ključa');
return;
}
try {
const response = await fetch(`/api/participants/${editingParticipantId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: name,
card_uid: cardUID,
key_uid: keyUID,
expiration_date: expiration || null
})
});
if (!response.ok) {
const errorData = await response.json();
alert('Napaka: ' + (errorData.message || 'Unknown error'));
return;
}
const data = await response.json();
if (data.success) {
alert('Udeleženec je bil uspešno posodobljen');
closeModal();
loadParticipants();
} else {
alert('Napaka: ' + data.message);
}
} catch (error) {
alert('Napaka: ' + error.message);
}
}
async function deleteParticipant(id) {
if (!confirm('Ali ste prepričani, da želite izbrisati tega udeleženca?')) return;
try {
const response = await fetch(`/api/participants/${id}`, {
method: 'DELETE'
});
const data = await response.json();
if (data.success) {
alert('Udeleženec je bil uspešno izbrisan');
loadParticipants();
} else {
alert('Napaka: ' + data.message);
}
} catch (error) {
alert('Napaka: ' + error.message);
}
}
// ===== LOGS MANAGEMENT =====
async function refreshLogs() {
try {
const filter = document.getElementById('filterSelect').value || 'All';
const response = await fetch(`/api/logs?filter=${filter}`);
const logs = await response.json();
// Apply client-side filtering
const filteredLogs = applyLogFilters(logs);
const tbody = document.getElementById('logsTable');
tbody.innerHTML = '';
if (filteredLogs.length === 0) {
tbody.innerHTML = `| Ni najdenih dnevnikov, ki bi ustrezali filtrom |
`;
return;
}
// Count occurrences of each name to detect duplicates
const nameCounts = {};
filteredLogs.forEach(log => {
nameCounts[log.name] = (nameCounts[log.name] || 0) + 1;
});
// Count registered vs unregistered users
let registeredCount = 0;
let unregisteredCount = 0;
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);
// Determine status class
let statusClass = 'pass';
if (log.scan_result === 'FAIL') {
statusClass = 'fail';
} else if (log.scan_result === 'MANUAL') {
statusClass = 'manual';
}
// Check if this is an unregistered duplicate
const isDuplicate = nameCounts[log.name] > 1;
const actionButtons = isAdmin ? `
` : '';
row.innerHTML = `
${log.id} |
${log.name} |
${log.card_uid || '-'} |
${log.key_uid || '-'} |
${log.scan_result} |
${date} |
${time} |
${actionButtons} |
`;
// Add duplicate warning class if this is a duplicate unregistered user
if (isDuplicate && log.scan_result === 'MANUAL') {
row.classList.add('duplicate-warning');
}
// Count registered vs unregistered
if (log.scan_result === 'MANUAL') {
unregisteredCount++;
} else if (log.scan_result === 'PASS') {
registeredCount++;
}
tbody.appendChild(row);
});
// Update stats display
document.getElementById('statRegistered').textContent = registeredCount;
document.getElementById('statUnregistered').textContent = unregisteredCount;
updateUIDVisibility();
} catch (error) {
console.error('Error loading logs:', error);
}
}
function applyLogFilters(logs) {
const participantFilter = document.getElementById('participantFilter').value;
const startDateFilter = document.getElementById('startDateFilter').value;
const endDateFilter = document.getElementById('endDateFilter').value;
return logs.filter(log => {
// Filter by participant
if (participantFilter && log.name !== participantFilter) {
return false;
}
// Filter by date range
if (startDateFilter || endDateFilter) {
const logDate = log.timestamp.split('T')[0];
if (startDateFilter && logDate < startDateFilter) {
return false;
}
if (endDateFilter && logDate > endDateFilter) {
return false;
}
}
return true;
});
}
async function deleteLog(logId) {
if (!confirm('Ali ste prepričani, da želite izbrisati ta dnevnik?')) return;
try {
const response = await fetch(`/api/logs/${logId}`, {
method: 'DELETE'
});
const data = await response.json();
if (data.success) {
alert('Dnevnik je bil uspešno izbrisan');
refreshLogs();
} else {
alert('Napaka: ' + data.message);
}
} catch (error) {
alert('Napaka: ' + error.message);
}
}
function resetFilters() {
document.getElementById('filterSelect').value = 'All';
document.getElementById('participantFilter').value = '';
document.getElementById('startDateFilter').value = '';
document.getElementById('endDateFilter').value = '';
// Remove active class from all quick filter buttons
document.querySelectorAll('.btn-quick-filter').forEach(btn => {
btn.classList.remove('active');
});
refreshLogs();
}
function getDateString(date) {
return date.toISOString().split('T')[0];
}
function filterByToday() {
const today = new Date();
document.getElementById('startDateFilter').value = getDateString(today);
document.getElementById('endDateFilter').value = getDateString(today);
updateQuickFilterButtons('today');
refreshLogs();
}
function filterByLastWeek() {
const today = new Date();
const lastWeek = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
document.getElementById('startDateFilter').value = getDateString(lastWeek);
document.getElementById('endDateFilter').value = getDateString(today);
updateQuickFilterButtons('week');
refreshLogs();
}
function filterByLastMonth() {
const today = new Date();
const lastMonth = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000);
document.getElementById('startDateFilter').value = getDateString(lastMonth);
document.getElementById('endDateFilter').value = getDateString(today);
updateQuickFilterButtons('month');
refreshLogs();
}
function filterByLastYear() {
const today = new Date();
const lastYear = new Date(today.getTime() - 365 * 24 * 60 * 60 * 1000);
document.getElementById('startDateFilter').value = getDateString(lastYear);
document.getElementById('endDateFilter').value = getDateString(today);
updateQuickFilterButtons('year');
refreshLogs();
}
function updateQuickFilterButtons(activeFilter) {
const buttons = document.querySelectorAll('.btn-quick-filter');
buttons.forEach(btn => btn.classList.remove('active'));
if (activeFilter === 'today') buttons[0].classList.add('active');
else if (activeFilter === 'week') buttons[1].classList.add('active');
else if (activeFilter === 'month') buttons[2].classList.add('active');
else if (activeFilter === 'year') buttons[3].classList.add('active');
}
async function exportLogs() {
try {
// Get current filter values
const filter = document.getElementById('filterSelect').value;
const participant = document.getElementById('participantFilter').value;
const startDate = document.getElementById('startDateFilter').value;
const endDate = document.getElementById('endDateFilter').value;
// Build query string with filters
const params = new URLSearchParams({
filter: filter,
participant: participant,
startDate: startDate,
endDate: endDate
});
const response = await fetch('/api/export?' + params.toString());
const result = await response.json();
if (!result.success) {
alert('Napaka pri izvozу: ' + result.message);
return;
}
// Decode base64 file data
const binaryString = atob(result.data);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
// Create blob and trigger download with file dialog
const blob = new Blob([bytes], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
// Try to use pywebview if available (for desktop app)
if (window.pywebview && window.pywebview.api) {
try {
const success = await window.pywebview.api.save_file(result.filename, result.data);
if (success) {
alert('Datoteka je bila uspešno izvožena');
return;
} else {
console.log('PyWebView save was cancelled, falling back to browser download');
}
} catch (error) {
console.log('PyWebView save failed, falling back to browser download', error);
}
}
// Fallback to browser download
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = result.filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
alert('Datoteka je bila uspešno izvožena');
} catch (error) {
alert('Napaka: ' + error.message);
}
}
// ===== PRINT LOGS =====
function printLogs() {
const start = document.getElementById('startDateFilter').value;
const end = document.getElementById('endDateFilter').value;
const dateRange = document.getElementById('print-date-range');
if (start && end) {
dateRange.textContent = `Obdobje: ${start} – ${end}`;
} else if (start) {
dateRange.textContent = `Od: ${start}`;
} else if (end) {
dateRange.textContent = `Do: ${end}`;
} else {
dateRange.textContent = `Natisnjeno: ${new Date().toLocaleDateString('sl-SI')}`;
}
window.print();
}
// ===== DOCUMENTS =====
async function loadDocuments() {
try {
const response = await fetch('/api/documents');
const files = await response.json();
const grid = document.getElementById('documentsGrid');
if (files.length === 0) {
grid.innerHTML = `Ni dokumentov. Dodajte PDF datoteke v mapo static/PDF.
`;
return;
}
grid.innerHTML = '';
files.forEach(filename => {
const card = document.createElement('div');
card.className = 'document-card';
const displayName = filename.replace(/\.pdf$/i, '').replace(/_/g, ' ');
card.innerHTML = `
${displayName}
`;
grid.appendChild(card);
});
} catch (error) {
console.error('Error loading documents:', error);
}
}
function printDocument(filename) {
const url = `/static/PDF/${encodeURIComponent(filename)}`;
let iframe = document.getElementById('print-iframe');
if (!iframe) {
iframe = document.createElement('iframe');
iframe.id = 'print-iframe';
iframe.style.cssText = 'display:none;position:fixed;top:0;left:0;width:0;height:0;';
document.body.appendChild(iframe);
}
iframe.onload = () => iframe.contentWindow.print();
iframe.src = url;
}
// ===== TAB SWITCHING =====
function switchTab(tabName, event) {
// Hide all tabs
document.querySelectorAll('.tab-panel').forEach(tab => {
tab.classList.remove('active');
});
// Remove active class from all buttons
document.querySelectorAll('.tab-nav-btn').forEach(btn => {
btn.classList.remove('active');
});
// Show selected tab
document.getElementById(tabName + '-tab').classList.add('active');
// Add active class to clicked button
event.target.classList.add('active');
// Load tab-specific data
if (tabName === 'documents') loadDocuments();
if (tabName === 'store') loadStore();
}
// Close modal when clicking outside
window.onclick = function(event) {
const editModal = document.getElementById('editModal');
const addModal = document.getElementById('addParticipantModal');
const loginModal = document.getElementById('loginModal');
const addUnregisteredModal = document.getElementById('addUnregisteredModal');
const addItemModal = document.getElementById('addItemModal');
const editBalanceModal = document.getElementById('editBalanceModal');
if (event.target === editModal) closeModal();
if (event.target === addModal) closeAddParticipantModal();
if (event.target === loginModal) closeLoginModal();
if (event.target === addUnregisteredModal) closeAddUnregisteredModal();
if (event.target === addItemModal) closeAddItemModal();
if (event.target === editBalanceModal) closeEditBalanceModal();
}
// ===== LAST SCAN RESULT =====
let lastScannedUID = '-';
let lastScanTimestamp = null;
async function updateLastScanResult() {
try {
const response = await fetch('/api/last-scan');
const result = await response.json();
const resultCard = document.getElementById('resultCard');
const uidDiv = resultCard.querySelector('.result-uid');
const nameDiv = resultCard.querySelector('.result-name');
const statusDiv = resultCard.querySelector('.result-status');
uidDiv.textContent = result.uid;
nameDiv.textContent = result.name;
statusDiv.textContent = result.status;
lastScannedUID = result.uid;
// If a new scan just happened, immediately refresh the logs table
if (result.timestamp && result.timestamp !== lastScanTimestamp) {
lastScanTimestamp = result.timestamp;
refreshLogs();
}
// Update card styling based on result
resultCard.className = 'result-card';
if (result.status === 'PASS') {
resultCard.classList.add('pass');
} else if (result.status === 'FAIL') {
resultCard.classList.add('fail');
}
} catch (error) {
console.error('Error updating last scan result:', error);
}
}
function copyUID(evt) {
if (lastScannedUID === '-') {
alert('Ni skeniranega UID-ja za kopiranje');
return;
}
const copyBtn = evt.currentTarget;
// Try using clipboard API first
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(lastScannedUID).then(() => {
showCopySuccess(copyBtn);
}).catch(err => {
console.error('Clipboard API failed:', err);
fallbackCopy(lastScannedUID, copyBtn);
});
} else {
// Fallback for older browsers
fallbackCopy(lastScannedUID, copyBtn);
}
}
function fallbackCopy(text, copyBtn) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
showCopySuccess(copyBtn);
} catch (err) {
console.error('Fallback copy failed:', err);
alert('Napaka pri kopiranju UID-ja');
}
document.body.removeChild(textarea);
}
function showCopySuccess(copyBtn) {
copyBtn.style.color = 'var(--accent-green)';
setTimeout(() => {
copyBtn.style.color = '';
}, 1500);
}
// ===== UID VISIBILITY MANAGEMENT =====
function updateUIDVisibility() {
const display = isAdmin ? '' : 'none';
// Participants table columns
const cardUIDHeader = document.getElementById('card-uid-header');
const keyUIDHeader = document.getElementById('key-uid-header');
if (cardUIDHeader) cardUIDHeader.style.display = display;
if (keyUIDHeader) keyUIDHeader.style.display = display;
// Logs table columns
const logsCardUIDHeader = document.getElementById('logs-card-uid-header');
const logsKeyUIDHeader = document.getElementById('logs-key-uid-header');
const logsActionsHeader = document.getElementById('logs-actions-header');
if (logsCardUIDHeader) logsCardUIDHeader.style.display = display;
if (logsKeyUIDHeader) logsKeyUIDHeader.style.display = display;
if (logsActionsHeader) logsActionsHeader.style.display = display;
// Hide/show corresponding cells in all table rows
hideTableColumn('participantsTable', 2, !isAdmin); // Card UID column
hideTableColumn('participantsTable', 3, !isAdmin); // Key UID column
hideTableColumn('logsTable', 2, !isAdmin); // Card UID column
hideTableColumn('logsTable', 3, !isAdmin); // Key UID column
hideTableColumn('logsTable', 7, !isAdmin); // Actions column
// Last scan result UID
const scanUIDContainer = document.getElementById('scan-result-uid-container');
if (scanUIDContainer) scanUIDContainer.style.display = display;
}
// ===== STORE =====
let cart = [];
let storeItems = [];
let currentBalance = 0;
let editingItemId = null;
async function loadStore() {
await Promise.all([loadStoreItems(), loadBalance()]);
}
async function loadStoreItems() {
try {
const response = await fetch('/api/store/items');
storeItems = await response.json();
renderInventory();
renderCart();
} catch (error) {
console.error('Error loading store items:', error);
}
}
async function loadBalance() {
try {
const response = await fetch('/api/store/balance');
const data = await response.json();
currentBalance = data.balance;
document.getElementById('storeBalanceDisplay').textContent = '€' + currentBalance.toFixed(2);
updateCartTotals();
} catch (error) {
console.error('Error loading balance:', error);
}
}
const ITEM_FALLBACK_SVG = ``;
function renderInventory() {
const grid = document.getElementById('inventoryGrid');
if (!grid) return;
if (storeItems.length === 0) {
grid.innerHTML = 'Ni artiklov v zalogi.
';
return;
}
grid.innerHTML = '';
storeItems.forEach(item => {
const card = document.createElement('div');
card.className = 'inventory-card' + (item.stock === 0 ? ' out-of-stock' : '');
const imgHtml = item.image
? `
`
: `${ITEM_FALLBACK_SVG}
`;
card.innerHTML = `
${imgHtml}
${item.name}
€${item.price.toFixed(2)}
Zaloga: ${item.stock}
${isAdmin ? `
` : ''}
`;
grid.appendChild(card);
});
}
function addToCart(itemId) {
const item = storeItems.find(i => i.id === itemId);
if (!item || item.stock === 0) return;
const existing = cart.find(c => c.id === itemId);
const currentQty = existing ? existing.qty : 0;
if (currentQty >= item.stock) {
alert('Ni dovolj zaloge');
return;
}
if (existing) {
existing.qty++;
} else {
cart.push({ id: item.id, name: item.name, price: item.price, qty: 1 });
}
renderCart();
}
function removeFromCart(itemId) {
const idx = cart.findIndex(c => c.id === itemId);
if (idx === -1) return;
if (cart[idx].qty > 1) {
cart[idx].qty--;
} else {
cart.splice(idx, 1);
}
renderCart();
}
function clearCart() {
cart = [];
renderCart();
}
function renderCart() {
const container = document.getElementById('cartItems');
if (!container) return;
if (cart.length === 0) {
container.innerHTML = 'Košarica je prazna
';
updateCartTotals();
return;
}
container.innerHTML = '';
cart.forEach(item => {
const row = document.createElement('div');
row.className = 'cart-item';
row.innerHTML = `
${item.name}
${item.qty}
€${(item.price * item.qty).toFixed(2)}
`;
container.appendChild(row);
});
updateCartTotals();
}
function updateCartTotals() {
const total = cart.reduce((sum, i) => sum + i.price * i.qty, 0);
const balanceAfter = currentBalance + total;
const totalEl = document.getElementById('cartTotal');
const balanceAfterEl = document.getElementById('cartBalanceAfter');
if (totalEl) totalEl.textContent = '€' + total.toFixed(2);
if (balanceAfterEl) {
balanceAfterEl.textContent = '€' + balanceAfter.toFixed(2);
balanceAfterEl.className = 'cart-balance-after';
}
}
async 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;
try {
const cartItems = cart.map(cartItem => {
const storeItem = storeItems.find(i => i.id === cartItem.id);
return {
id: cartItem.id,
name: cartItem.name,
price: cartItem.price,
qty: cartItem.qty,
new_stock: storeItem ? storeItem.stock - cartItem.qty : 0,
image: storeItem ? storeItem.image : null
};
});
const response = await fetch('/api/store/payments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
items: cart.map(i => ({ name: i.name, qty: i.qty, price: i.price })),
cart_items: cartItems,
total,
balance_before: currentBalance,
balance_after: balanceAfter
})
});
const data = await response.json();
if (!data.success) { alert('Napaka: ' + data.message); return; }
cart = [];
await loadStore();
alert('Plačilo uspešno!');
} catch (error) {
alert('Napaka: ' + error.message);
}
}
let currentItemImage = null;
function openAddItemModal() {
editingItemId = null;
currentItemImage = null;
document.getElementById('addItemModalTitle').textContent = 'Dodaj artikel';
document.getElementById('addItemSubmitBtn').textContent = 'Dodaj';
document.getElementById('itemName').value = '';
document.getElementById('itemPrice').value = '';
document.getElementById('itemStock').value = '';
document.getElementById('itemImageInput').value = '';
setImagePreview(null);
document.getElementById('addItemModal').classList.add('show');
document.getElementById('itemName').focus();
}
function openEditItemModal(itemId) {
const item = storeItems.find(i => i.id === itemId);
if (!item) return;
editingItemId = itemId;
currentItemImage = item.image || null;
document.getElementById('addItemModalTitle').textContent = 'Uredi artikel';
document.getElementById('addItemSubmitBtn').textContent = 'Shrani';
document.getElementById('itemName').value = item.name;
document.getElementById('itemPrice').value = item.price;
document.getElementById('itemStock').value = item.stock;
document.getElementById('itemImageInput').value = '';
setImagePreview(item.image || null);
document.getElementById('addItemModal').classList.add('show');
}
function closeAddItemModal() {
document.getElementById('addItemModal').classList.remove('show');
editingItemId = null;
currentItemImage = null;
}
function handleImageUpload(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
currentItemImage = e.target.result;
setImagePreview(currentItemImage);
};
reader.readAsDataURL(file);
}
function setImagePreview(src) {
const preview = document.getElementById('imagePreview');
const placeholder = document.getElementById('imageUploadPlaceholder');
const removeBtn = document.getElementById('removeImageBtn');
if (src) {
preview.src = src;
preview.style.display = 'block';
placeholder.style.display = 'none';
removeBtn.style.display = 'inline-block';
} else {
preview.src = '';
preview.style.display = 'none';
placeholder.style.display = 'flex';
removeBtn.style.display = 'none';
}
}
function removeItemImage() {
currentItemImage = null;
document.getElementById('itemImageInput').value = '';
setImagePreview(null);
}
async function submitItem() {
const name = document.getElementById('itemName').value.trim();
const price = parseFloat(document.getElementById('itemPrice').value);
const stock = parseInt(document.getElementById('itemStock').value, 10);
if (!name) { alert('Vnesite ime artikla'); return; }
if (isNaN(price) || price < 0) { alert('Vnesite veljavno ceno'); return; }
if (isNaN(stock) || stock < 0) { alert('Vnesite veljavno zalogo'); return; }
try {
let response;
if (editingItemId) {
response = await fetch(`/api/store/items/${editingItemId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, price, stock, image: currentItemImage })
});
} else {
response = await fetch('/api/store/items', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, price, stock, image: currentItemImage })
});
}
const data = await response.json();
if (data.success) {
closeAddItemModal();
await loadStoreItems();
} else {
alert('Napaka: ' + data.message);
}
} catch (error) {
alert('Napaka: ' + error.message);
}
}
async function deleteStoreItem(itemId) {
if (!confirm('Izbriši artikel?')) return;
try {
const response = await fetch(`/api/store/items/${itemId}`, { method: 'DELETE' });
const data = await response.json();
if (data.success) {
cart = cart.filter(c => c.id !== itemId);
await loadStoreItems();
} else {
alert('Napaka: ' + data.message);
}
} catch (error) {
alert('Napaka: ' + error.message);
}
}
function openEditBalanceModal() {
document.getElementById('balanceInput').value = currentBalance.toFixed(2);
document.getElementById('editBalanceModal').classList.add('show');
document.getElementById('balanceInput').focus();
}
function closeEditBalanceModal() {
document.getElementById('editBalanceModal').classList.remove('show');
}
async function saveBalance() {
const amount = parseFloat(document.getElementById('balanceInput').value);
if (isNaN(amount)) { alert('Vnesite veljavno vrednost'); return; }
try {
const response = await fetch('/api/store/balance', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ balance: amount })
});
const data = await response.json();
if (data.success) {
closeEditBalanceModal();
await loadBalance();
} else {
alert('Napaka: ' + data.message);
}
} catch (error) {
alert('Napaka: ' + error.message);
}
}
// ===== PAYMENT HISTORY =====
let paymentHistoryVisible = false;
function togglePaymentHistory() {
paymentHistoryVisible = !paymentHistoryVisible;
const panel = document.getElementById('paymentHistoryPanel');
panel.style.display = paymentHistoryVisible ? 'block' : 'none';
if (paymentHistoryVisible) loadPaymentHistory();
}
async function loadPaymentHistory() {
try {
const response = await fetch('/api/store/payments');
const payments = await response.json();
const tbody = document.getElementById('paymentHistoryTable');
if (!tbody) return;
const clearBtn = document.getElementById('clearHistoryBtn');
if (clearBtn) clearBtn.style.display = isAdmin ? 'inline-block' : 'none';
if (payments.length === 0) {
tbody.innerHTML = `| Ni zabeleženih plačil |
`;
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 itemsSummary = p.items.map(i => `${i.name} ×${i.qty}`).join(', ');
const row = document.createElement('tr');
row.innerHTML = `
${p.id} |
${date} |
${time} |
${itemsSummary} |
€${p.total.toFixed(2)} |
€${p.balance_before.toFixed(2)} |
€${p.balance_after.toFixed(2)} |
`;
tbody.appendChild(row);
});
} catch (error) {
console.error('Error loading payment history:', error);
}
}
async function clearPaymentHistory() {
if (!confirm('Izbriši celotno zgodovino plačil?')) return;
try {
const response = await fetch('/api/store/payments', { method: 'DELETE' });
const data = await response.json();
if (data.success) loadPaymentHistory();
else alert('Napaka: ' + data.message);
} catch (error) {
alert('Napaka: ' + error.message);
}
}
function hideTableColumn(tableId, columnIndex, hide) {
const table = document.getElementById(tableId);
if (!table) return;
const rows = table.getElementsByTagName('tr');
for (let row of rows) {
const cell = row.cells[columnIndex];
if (cell) {
cell.style.display = hide ? 'none' : '';
}
}
}