Shop update added services and other shhhhhhh
This commit is contained in:
+155
-65
@@ -917,54 +917,102 @@ async function loadBalance() {
|
||||
}
|
||||
}
|
||||
|
||||
const ITEM_FALLBACK_SVG = `<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--text-secondary)"><path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 01-8 0"/></svg>`;
|
||||
|
||||
function renderInventory() {
|
||||
const grid = document.getElementById('inventoryGrid');
|
||||
if (!grid) return;
|
||||
|
||||
if (storeItems.length === 0) {
|
||||
grid.innerHTML = '<p style="color: var(--text-secondary); grid-column: 1/-1;">Ni artiklov v zalogi.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
grid.innerHTML = '';
|
||||
storeItems.forEach(item => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'inventory-card' + (item.stock === 0 ? ' out-of-stock' : '');
|
||||
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>`;
|
||||
card.innerHTML = `
|
||||
${imgHtml}
|
||||
<div class="inventory-card-body">
|
||||
<div class="inventory-card-name">${item.name}</div>
|
||||
<div class="inventory-card-meta">
|
||||
<div class="inventory-card-price">€ ${item.price.toFixed(2)}</div>
|
||||
<div class="inventory-card-stock ${item.stock === 0 ? 'stock-empty' : ''}">${item.stock === 0 ? 'Ni zaloge' : item.stock + ' kos'}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inventory-card-actions">
|
||||
<button class="btn btn-primary inventory-add-btn" onclick="addToCart(${item.id})" ${item.stock === 0 ? 'disabled' : ''}>+ Dodaj</button>
|
||||
${isAdmin ? `<div class="inventory-card-admin-actions">
|
||||
<button class="action-btn edit-btn" onclick="openEditItemModal(${item.id})">Uredi</button>
|
||||
<button class="action-btn delete-btn" onclick="deleteStoreItem(${item.id})">Izbriši</button>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
grid.appendChild(card);
|
||||
});
|
||||
const FALLBACK_SVG_PRODUCT = `<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--text-secondary)"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 001.97 1.61h9.72a2 2 0 001.97-1.61L23 6H6"/></svg>`;
|
||||
const FALLBACK_SVG_SERVICE = `<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--text-secondary)"><path d="M12 22V12"/><path d="M5 12H2a10 10 0 0020 0h-3"/><path d="M12 12a4 4 0 000-8 4 4 0 000 8z"/><path d="M5 12V7"/><path d="M19 12V7"/></svg>`;
|
||||
function getTypeFallbackSvg(type) {
|
||||
if (type === 'service') return FALLBACK_SVG_SERVICE;
|
||||
return FALLBACK_SVG_PRODUCT;
|
||||
}
|
||||
|
||||
function addToCart(itemId) {
|
||||
const item = storeItems.find(i => i.id === itemId);
|
||||
if (!item || item.stock === 0) return;
|
||||
function buildItemCard(item) {
|
||||
const isService = item.type === 'service';
|
||||
const isDual = item.price_member != null && item.price_non_member != null;
|
||||
const outOfStock = !isService && item.stock === 0;
|
||||
const card = document.createElement('div');
|
||||
card.className = 'inventory-card' + (outOfStock ? ' out-of-stock' : '');
|
||||
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">${getTypeFallbackSvg(item.type)}</div>`
|
||||
}</div>`;
|
||||
const stockHtml = isService
|
||||
? `<div class="inventory-card-stock service-label">Storitev</div>`
|
||||
: `<div class="inventory-card-stock ${outOfStock ? 'stock-empty' : ''}">${outOfStock ? 'Ni zaloge' : item.stock + ' kos'}</div>`;
|
||||
const priceHtml = isDual
|
||||
? `<div class="inventory-card-price">€ ${item.price_member.toFixed(2)} / € ${item.price_non_member.toFixed(2)}</div>`
|
||||
: `<div class="inventory-card-price">€ ${item.price.toFixed(2)}</div>`;
|
||||
const addBtns = isDual
|
||||
? `<div class="inventory-dual-btns">
|
||||
<button class="btn btn-primary inventory-add-btn" onclick="addToCart(${item.id}, 'member')" ${outOfStock ? 'disabled' : ''}>+ Član</button>
|
||||
<button class="btn inventory-add-btn inventory-add-btn-secondary" onclick="addToCart(${item.id}, 'non_member')" ${outOfStock ? 'disabled' : ''}>+ Nečlan</button>
|
||||
</div>`
|
||||
: `<button class="btn btn-primary inventory-add-btn" onclick="addToCart(${item.id})" ${outOfStock ? 'disabled' : ''}>+ Dodaj</button>`;
|
||||
card.innerHTML = `
|
||||
${imgHtml}
|
||||
<div class="inventory-card-body">
|
||||
<div class="inventory-card-name">${item.name}</div>
|
||||
<div class="inventory-card-meta">
|
||||
${priceHtml}
|
||||
${stockHtml}
|
||||
</div>
|
||||
</div>
|
||||
<div class="inventory-card-actions">
|
||||
${addBtns}
|
||||
${isAdmin ? `<div class="inventory-card-admin-actions">
|
||||
<button class="action-btn edit-btn" onclick="openEditItemModal(${item.id})">Uredi</button>
|
||||
<button class="action-btn delete-btn" onclick="deleteStoreItem(${item.id})">Izbriši</button>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
return card;
|
||||
}
|
||||
|
||||
const existing = cart.find(c => c.id === itemId);
|
||||
function renderInventory() {
|
||||
const gridProducts = document.getElementById('inventoryGridProducts');
|
||||
const gridServices = document.getElementById('inventoryGridServices');
|
||||
const sectionProducts = document.getElementById('inventorySectionProducts');
|
||||
const sectionServices = document.getElementById('inventorySectionServices');
|
||||
if (!gridProducts || !gridServices) return;
|
||||
|
||||
const products = storeItems.filter(i => !i.type || i.type === 'product');
|
||||
const services = storeItems.filter(i => i.type === 'service');
|
||||
|
||||
gridProducts.innerHTML = '';
|
||||
if (products.length === 0) {
|
||||
gridProducts.innerHTML = '<p style="color: var(--text-secondary); grid-column: 1/-1;">Ni artiklov.</p>';
|
||||
} else {
|
||||
products.forEach(item => gridProducts.appendChild(buildItemCard(item)));
|
||||
}
|
||||
|
||||
gridServices.innerHTML = '';
|
||||
if (services.length === 0) {
|
||||
gridServices.innerHTML = '<p style="color: var(--text-secondary); grid-column: 1/-1;">Ni storitev.</p>';
|
||||
} else {
|
||||
services.forEach(item => gridServices.appendChild(buildItemCard(item)));
|
||||
}
|
||||
|
||||
sectionProducts.style.display = '';
|
||||
sectionServices.style.display = '';
|
||||
}
|
||||
|
||||
function addToCart(itemId, tier) {
|
||||
const item = storeItems.find(i => i.id === itemId);
|
||||
if (!item) return;
|
||||
|
||||
const isService = item.type === 'service';
|
||||
if (!isService && item.stock === 0) return;
|
||||
|
||||
const isDual = item.price_member != null && item.price_non_member != null;
|
||||
const usedTier = isDual ? (tier || 'member') : null;
|
||||
const price = isDual
|
||||
? (usedTier === 'member' ? item.price_member : item.price_non_member)
|
||||
: item.price;
|
||||
|
||||
// For dual-price items, each tier is a separate cart line
|
||||
const cartKey = isDual ? `${itemId}_${usedTier}` : `${itemId}`;
|
||||
const existing = cart.find(c => c.cartKey === cartKey);
|
||||
const currentQty = existing ? existing.qty : 0;
|
||||
|
||||
if (currentQty >= item.stock) {
|
||||
if (!isService && currentQty >= item.stock) {
|
||||
alert('Ni dovolj zaloge');
|
||||
return;
|
||||
}
|
||||
@@ -972,13 +1020,14 @@ function addToCart(itemId) {
|
||||
if (existing) {
|
||||
existing.qty++;
|
||||
} else {
|
||||
cart.push({ id: item.id, name: item.name, price: item.price, qty: 1 });
|
||||
const label = isDual ? `${item.name} (${usedTier === 'member' ? 'član' : 'nečlan'})` : item.name;
|
||||
cart.push({ cartKey, id: item.id, name: label, price, qty: 1, type: item.type || 'product', tier: usedTier });
|
||||
}
|
||||
renderCart();
|
||||
}
|
||||
|
||||
function removeFromCart(itemId) {
|
||||
const idx = cart.findIndex(c => c.id === itemId);
|
||||
function removeFromCart(cartKey) {
|
||||
const idx = cart.findIndex(c => c.cartKey === cartKey);
|
||||
if (idx === -1) return;
|
||||
if (cart[idx].qty > 1) {
|
||||
cart[idx].qty--;
|
||||
@@ -1007,12 +1056,13 @@ function renderCart() {
|
||||
cart.forEach(item => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'cart-item';
|
||||
const tierArg = item.tier ? `,'${item.tier}'` : '';
|
||||
row.innerHTML = `
|
||||
<div class="cart-item-name">${item.name}</div>
|
||||
<div class="cart-item-controls">
|
||||
<button class="cart-qty-btn" onclick="removeFromCart(${item.id})">−</button>
|
||||
<button class="cart-qty-btn" onclick="removeFromCart('${item.cartKey}')">−</button>
|
||||
<span class="cart-qty">${item.qty}</span>
|
||||
<button class="cart-qty-btn" onclick="addToCart(${item.id})">+</button>
|
||||
<button class="cart-qty-btn" onclick="addToCart(${item.id}${tierArg})">+</button>
|
||||
</div>
|
||||
<div class="cart-item-subtotal">€ ${(item.price * item.qty).toFixed(2)}</div>
|
||||
`;
|
||||
@@ -1024,16 +1074,9 @@ function renderCart() {
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
function processPayment() {
|
||||
@@ -1046,7 +1089,6 @@ function processPayment() {
|
||||
|
||||
document.getElementById('checkoutBuyer').value = '';
|
||||
document.getElementById('checkoutTotal').textContent = '€ ' + total.toFixed(2);
|
||||
document.getElementById('checkoutBalanceAfter').textContent = '€ ' + balanceAfter.toFixed(2);
|
||||
document.getElementById('checkoutModal').classList.add('show');
|
||||
document.getElementById('checkoutBuyer').focus();
|
||||
}
|
||||
@@ -1068,14 +1110,20 @@ async function confirmCheckout() {
|
||||
closeCheckoutModal();
|
||||
|
||||
try {
|
||||
// Total qty sold per store item id (may have member+non_member lines)
|
||||
const qtySoldById = {};
|
||||
cart.forEach(ci => { qtySoldById[ci.id] = (qtySoldById[ci.id] || 0) + ci.qty; });
|
||||
|
||||
const cartItems = cart.map(cartItem => {
|
||||
const storeItem = storeItems.find(i => i.id === cartItem.id);
|
||||
const isService = ((storeItem && storeItem.type) || cartItem.type) === 'service';
|
||||
return {
|
||||
id: cartItem.id,
|
||||
name: cartItem.name,
|
||||
price: cartItem.price,
|
||||
qty: cartItem.qty,
|
||||
new_stock: storeItem ? storeItem.stock - cartItem.qty : 0,
|
||||
type: isService ? 'service' : 'product',
|
||||
new_stock: isService ? null : (storeItem ? storeItem.stock - qtySoldById[cartItem.id] : 0),
|
||||
image: storeItem ? storeItem.image : null
|
||||
};
|
||||
});
|
||||
@@ -1115,6 +1163,21 @@ async function confirmCheckout() {
|
||||
|
||||
let currentItemImage = null;
|
||||
|
||||
let currentItemType = 'product';
|
||||
|
||||
function setItemType(type) {
|
||||
currentItemType = type;
|
||||
document.getElementById('typeProductBtn').classList.toggle('active', type === 'product');
|
||||
document.getElementById('typeServiceBtn').classList.toggle('active', type === 'service');
|
||||
document.getElementById('stockFormGroup').style.display = type === 'service' ? 'none' : '';
|
||||
}
|
||||
|
||||
function toggleDualPrice() {
|
||||
const dual = document.getElementById('itemDualPrice').checked;
|
||||
document.getElementById('singlePriceGroup').style.display = dual ? 'none' : '';
|
||||
document.getElementById('dualPriceGroup').style.display = dual ? '' : 'none';
|
||||
}
|
||||
|
||||
function openAddItemModal() {
|
||||
editingItemId = null;
|
||||
currentItemImage = null;
|
||||
@@ -1122,9 +1185,14 @@ function openAddItemModal() {
|
||||
document.getElementById('addItemSubmitBtn').textContent = 'Dodaj';
|
||||
document.getElementById('itemName').value = '';
|
||||
document.getElementById('itemPrice').value = '';
|
||||
document.getElementById('itemPriceMember').value = '';
|
||||
document.getElementById('itemPriceNonMember').value = '';
|
||||
document.getElementById('itemDualPrice').checked = false;
|
||||
document.getElementById('itemStock').value = '';
|
||||
document.getElementById('itemImageInput').value = '';
|
||||
setImagePreview(null);
|
||||
setItemType('product');
|
||||
toggleDualPrice();
|
||||
document.getElementById('addItemModal').classList.add('show');
|
||||
document.getElementById('itemName').focus();
|
||||
}
|
||||
@@ -1134,13 +1202,19 @@ function openEditItemModal(itemId) {
|
||||
if (!item) return;
|
||||
editingItemId = itemId;
|
||||
currentItemImage = item.image || null;
|
||||
const hasDual = !!(item.price_member != null && item.price_non_member != 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('itemPrice').value = hasDual ? '' : item.price;
|
||||
document.getElementById('itemPriceMember').value = hasDual ? item.price_member : '';
|
||||
document.getElementById('itemPriceNonMember').value = hasDual ? item.price_non_member : '';
|
||||
document.getElementById('itemDualPrice').checked = hasDual;
|
||||
document.getElementById('itemStock').value = item.stock || 0;
|
||||
document.getElementById('itemImageInput').value = '';
|
||||
setImagePreview(item.image || null);
|
||||
setItemType(item.type === 'service' ? 'service' : 'product');
|
||||
toggleDualPrice();
|
||||
document.getElementById('addItemModal').classList.add('show');
|
||||
}
|
||||
|
||||
@@ -1186,12 +1260,28 @@ function removeItemImage() {
|
||||
|
||||
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);
|
||||
const isService = currentItemType === 'service';
|
||||
const isDual = document.getElementById('itemDualPrice').checked;
|
||||
const stock = isService ? 0 : parseInt(document.getElementById('itemStock').value, 10);
|
||||
|
||||
let price = null, price_member = null, price_non_member = null;
|
||||
if (isDual) {
|
||||
price_member = parseFloat(document.getElementById('itemPriceMember').value);
|
||||
price_non_member = parseFloat(document.getElementById('itemPriceNonMember').value);
|
||||
price = price_member;
|
||||
if (isNaN(price_member) || price_member < 0) { alert('Vnesite veljavno ceno za člana'); return; }
|
||||
if (isNaN(price_non_member) || price_non_member < 0) { alert('Vnesite veljavno ceno za nečlana'); return; }
|
||||
} else {
|
||||
price = parseFloat(document.getElementById('itemPrice').value);
|
||||
if (isNaN(price) || price < 0) { alert('Vnesite veljavno ceno'); return; }
|
||||
}
|
||||
|
||||
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; }
|
||||
if (!isService && (isNaN(stock) || stock < 0)) { alert('Vnesite veljavno zalogo'); return; }
|
||||
|
||||
const payload = { name, price, stock, type: currentItemType, image: currentItemImage,
|
||||
price_member: isDual ? price_member : null,
|
||||
price_non_member: isDual ? price_non_member : null };
|
||||
|
||||
try {
|
||||
let response;
|
||||
@@ -1199,13 +1289,13 @@ async function submitItem() {
|
||||
response = await fetch(`/api/store/items/${editingItemId}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, price, stock, image: currentItemImage })
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
} else {
|
||||
response = await fetch('/api/store/items', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, price, stock, image: currentItemImage })
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user