diff --git a/wwwroot/icons/journal-alt.svg b/wwwroot/icons/journal-alt.svg new file mode 100644 index 0000000..2cc7599 --- /dev/null +++ b/wwwroot/icons/journal-alt.svg @@ -0,0 +1,4 @@ + + + + diff --git a/wwwroot/icons/square-plus.svg b/wwwroot/icons/square-plus.svg new file mode 100644 index 0000000..d9e4ae1 --- /dev/null +++ b/wwwroot/icons/square-plus.svg @@ -0,0 +1,2 @@ + + diff --git a/wwwroot/poc-checkout.html b/wwwroot/poc-checkout.html index b7162e3..55af769 100644 --- a/wwwroot/poc-checkout.html +++ b/wwwroot/poc-checkout.html @@ -253,12 +253,160 @@ /* Cart item card */ .cart-item { + background: var(--color-surface); + border-radius: 6px; + overflow: hidden; + transition: box-shadow 200ms ease; + } + + .cart-item.expanded { + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + } + + .cart-item-main { display: flex; align-items: center; gap: 16px; padding: 12px 16px; + cursor: pointer; + transition: background 150ms ease; + } + + .cart-item-main:hover { + background: var(--color-background-hover); + } + + .cart-item.expanded .cart-item-main { + border-bottom: 1px solid var(--color-border); + } + + .cart-item-main::after { + content: ''; + width: 8px; + height: 8px; + border-right: 2px solid var(--color-text-secondary); + border-bottom: 2px solid var(--color-text-secondary); + transform: rotate(45deg); + transition: transform 200ms ease; + margin-left: 4px; + opacity: 0.5; + } + + .cart-item:hover .cart-item-main::after { + opacity: 1; + } + + .cart-item.expanded .cart-item-main::after { + transform: rotate(-135deg); + opacity: 1; + } + + /* Expandable edit section */ + .cart-item-edit { + max-height: 0; + opacity: 0; + overflow: hidden; + transition: max-height 300ms cubic-bezier(0.4, 0, 0.2, 1), + opacity 250ms ease, + padding 300ms ease; + padding: 0 16px; + background: var(--color-background-alt); + } + + .cart-item.expanded .cart-item-edit { + max-height: 200px; + opacity: 1; + padding: 16px; + } + + .edit-row { + display: flex; + gap: 12px; + margin-bottom: 12px; + } + + .edit-row:last-child { + margin-bottom: 0; + } + + .edit-field { + flex: 1; + } + + .edit-field-label { + font-size: 11px; + color: var(--color-text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 6px; + } + + .edit-field-input { + width: 100%; + padding: 8px 10px; + border: 1px solid var(--color-border); + border-radius: 4px; + font-size: 13px; + transition: border-color 150ms ease; + } + + .edit-field-input:focus { + outline: none; + border-color: var(--color-teal); + } + + .edit-field-input.mono { + font-family: var(--font-mono); + text-align: right; + } + + .edit-field-select { + width: 100%; + padding: 8px 10px; + border: 1px solid var(--color-border); + border-radius: 4px; + font-size: 13px; background: var(--color-surface); - border-radius: 6px; + cursor: pointer; + } + + .edit-field-select:focus { + outline: none; + border-color: var(--color-teal); + } + + .discount-row { + display: flex; + gap: 8px; + align-items: center; + } + + .discount-type { + display: flex; + gap: 4px; + } + + .discount-type-btn { + padding: 6px 10px; + border: 1px solid var(--color-border); + background: var(--color-surface); + font-size: 12px; + cursor: pointer; + transition: all 150ms ease; + } + + .discount-type-btn:first-child { + border-radius: 4px 0 0 4px; + } + + .discount-type-btn:last-child { + border-radius: 0 4px 4px 0; + } + + .discount-type-btn.active { + background: var(--color-teal); + border-color: var(--color-teal); + color: white; } .item-qty { @@ -307,10 +455,46 @@ text-align: right; } + .item-original-price { + font-family: var(--font-mono); + font-size: 12px; + color: var(--color-text-secondary); + text-decoration: line-through; + max-height: 0; + opacity: 0; + overflow: hidden; + transition: max-height 200ms ease, opacity 150ms ease; + } + + .item-original-price.visible { + max-height: 20px; + opacity: 1; + } + .item-total { font-family: var(--font-mono); font-size: 15px; font-weight: 600; + transition: color 200ms ease; + } + + .cart-item:has(.item-discount.visible) .item-total { + color: var(--color-teal); + } + + .item-discount { + font-size: 11px; + color: var(--color-teal); + font-weight: 500; + max-height: 0; + opacity: 0; + overflow: hidden; + transition: max-height 200ms ease, opacity 150ms ease; + } + + .item-discount.visible { + max-height: 20px; + opacity: 1; } .item-remove { @@ -463,15 +647,127 @@ font-size: 14px; font-weight: 500; cursor: pointer; + transition: background 200ms ease, opacity 200ms ease, transform 150ms ease; } - .btn-add-payment:hover { + .btn-add-payment:hover:not(:disabled) { background: #00796b; } + .btn-add-payment:active:not(:disabled) { + transform: scale(0.98); + } + .btn-add-payment:disabled { background: #ccc; cursor: not-allowed; + opacity: 0.7; + } + + /* Giftcard lookup */ + .giftcard-lookup { + max-height: 0; + opacity: 0; + overflow: hidden; + transition: max-height 300ms cubic-bezier(0.4, 0, 0.2, 1), + opacity 250ms ease, + margin-bottom 300ms ease, + padding-bottom 300ms ease; + margin-bottom: 0; + padding-bottom: 0; + border-bottom: 1px solid transparent; + } + + .giftcard-lookup.visible { + max-height: 150px; + opacity: 1; + margin-bottom: 16px; + padding-bottom: 16px; + border-bottom-color: var(--color-border); + } + + .giftcard-lookup-label { + font-size: 11px; + color: var(--color-text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 8px; + } + + .giftcard-lookup-row { + display: flex; + gap: 10px; + } + + .giftcard-input { + flex: 1; + padding: 10px 12px; + border: 1px solid var(--color-border); + border-radius: 6px; + font-family: var(--font-mono); + font-size: 14px; + letter-spacing: 1px; + } + + .giftcard-input:focus { + outline: none; + border-color: var(--color-teal); + } + + .btn-lookup { + padding: 10px 16px; + border: 1px solid var(--color-teal); + border-radius: 6px; + background: var(--color-surface); + color: var(--color-teal); + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: background 150ms ease, transform 150ms ease; + } + + .btn-lookup:hover { + background: rgba(0, 137, 123, 0.08); + } + + .btn-lookup:active { + transform: scale(0.97); + } + + .giftcard-result { + margin-top: 0; + padding: 0 12px; + border-radius: 6px; + font-size: 13px; + max-height: 0; + opacity: 0; + overflow: hidden; + transition: max-height 250ms ease, + opacity 200ms ease, + margin-top 250ms ease, + padding 250ms ease; + } + + .giftcard-result.visible { + max-height: 50px; + opacity: 1; + margin-top: 10px; + padding: 10px 12px; + } + + .giftcard-result.success { + background: rgba(0, 137, 123, 0.1); + color: var(--color-teal); + } + + .giftcard-result.error { + background: rgba(229, 57, 53, 0.1); + color: var(--color-red); + } + + .giftcard-balance { + font-family: var(--font-mono); + font-weight: 600; } /* Registered payments */ @@ -675,35 +971,99 @@
Services
-
-
- - 1 - +
+
+
+ + 1 + +
+
+
Dameklip
+
Emma Larsen · 45 min
+
+
+
725 kr
+
725 kr
+
-0 kr rabat
+
+
-
-
Dameklip
-
Emma Larsen · 45 min
+
+
+
+
Pris
+ +
+
+
Udført af
+ +
+
+
+
+
Rabat
+
+ +
+ + +
+
+
+
-
-
725 kr
-
-
-
-
- - 1 - +
+
+
+ + 1 + +
+
+
Gloss extra langt/tykt hår
+
Emma Larsen · 90 min
+
+
+
900 kr
+
900 kr
+
-0 kr rabat
+
+
-
-
Gloss extra langt/tykt hår
-
Emma Larsen · 90 min
+
+
+
+
Pris
+ +
+
+
Udført af
+ +
+
+
+
+
Rabat
+
+ +
+ + +
+
+
+
-
-
900 kr
-
-
@@ -712,35 +1072,79 @@
Produkter
-
-
- - 1 - +
+
+
+ + 1 + +
+
+
Olaplex No. 3
+
100ml
+
+
+
300 kr
+
300 kr
+
-0 kr rabat
+
+
-
-
Olaplex No. 3
-
100ml
+
+
+
+
Pris
+ +
+
+
Rabat
+
+ +
+ + +
+
+
+
-
-
300 kr
-
-
-
-
- - 2 - +
+
+
+ + 2 + +
+
+
India supercharged mask
+
250ml
+
+
+
350 kr
+
350 kr
+
-0 kr rabat
+
+
-
-
India supercharged mask
-
250ml
+
+
+
+
Pris
+ +
+
+
Rabat
+
+ +
+ + +
+
+
+
-
-
350 kr
-
-
@@ -799,12 +1203,22 @@
+ +
+
Indtast gavekortnummer
+
+ + +
+
+
+
Beløb at betale med Kort
- +
@@ -833,6 +1247,7 @@ const total = 2275; const payments = []; let currentMethod = 'kort'; + let currentGiftcard = null; // { number, balance } const methodLabels = { 'kort': 'Kort', @@ -862,6 +1277,118 @@ document.getElementById('panel').classList.remove('open'); } + function toggleCartItem(item) { + // Close other expanded items + document.querySelectorAll('.cart-item.expanded').forEach(el => { + if (el !== item) el.classList.remove('expanded'); + }); + // Toggle this item + item.classList.toggle('expanded'); + } + + function toggleDiscountType(btn) { + const container = btn.closest('.discount-type'); + const previousType = container.querySelector('.discount-type-btn.active')?.dataset.type; + const newType = btn.dataset.type; + + // Don't do anything if clicking the same button + if (previousType === newType) return; + + container.querySelectorAll('.discount-type-btn').forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + + // Convert discount value + const cartItem = btn.closest('.cart-item'); + const discountInput = cartItem.querySelector('.edit-discount'); + const priceInput = cartItem.querySelector('.edit-price'); + + if (discountInput && priceInput) { + const currentValue = parseFloat(discountInput.value.replace(/\./g, '').replace(',', '.')) || 0; + const basePrice = parseFloat(priceInput.value.replace(/\./g, '').replace(',', '.')) || 0; + + if (basePrice > 0 && currentValue > 0) { + let newValue; + if (previousType === 'pct' && newType === 'kr') { + // Converting from % to kr + newValue = Math.round(basePrice * currentValue / 100); + } else if (previousType === 'kr' && newType === 'pct') { + // Converting from kr to % + newValue = Math.round((currentValue / basePrice) * 100 * 10) / 10; // 1 decimal + } + discountInput.value = newValue; + } + + updateCartItemDisplay(discountInput); + } + } + + function updateCartItemDisplay(inputEl) { + const cartItem = inputEl.closest('.cart-item'); + if (!cartItem) return; + + // Get elements + const priceInput = cartItem.querySelector('.edit-price'); + const employeeSelect = cartItem.querySelector('.edit-employee'); + const discountInput = cartItem.querySelector('.edit-discount'); + const discountTypeBtn = cartItem.querySelector('.discount-type-btn.active'); + + const itemTotal = cartItem.querySelector('.item-total'); + const itemOriginalPrice = cartItem.querySelector('.item-original-price'); + const itemDiscount = cartItem.querySelector('.item-discount'); + const itemEmployee = cartItem.querySelector('.item-employee'); + + // Get values + const basePrice = parseFloat(priceInput?.value.replace(/\./g, '').replace(',', '.')) || 0; + const discountValue = parseFloat(discountInput?.value.replace(/\./g, '').replace(',', '.')) || 0; + const discountType = discountTypeBtn?.dataset.type || 'kr'; + const qty = parseInt(cartItem.querySelector('.qty-val')?.textContent) || 1; + + // Calculate discount + let discountAmount = 0; + if (discountType === 'kr') { + discountAmount = discountValue; + } else { + discountAmount = (basePrice * discountValue / 100); + } + + const finalPrice = Math.max(0, (basePrice - discountAmount) * qty); + const totalBeforeDiscount = basePrice * qty; + + // Update display + itemTotal.textContent = formatNumber(finalPrice) + ' kr'; + + // Show original price and discount if there's a discount + if (discountAmount > 0) { + itemOriginalPrice.textContent = formatNumber(totalBeforeDiscount) + ' kr'; + itemOriginalPrice.classList.add('visible'); + + if (discountType === 'pct') { + itemDiscount.textContent = `-${discountValue}% rabat`; + } else { + itemDiscount.textContent = `-${formatNumber(discountAmount * qty)} kr rabat`; + } + itemDiscount.classList.add('visible'); + } else { + itemOriginalPrice.classList.remove('visible'); + itemDiscount.classList.remove('visible'); + } + + // Update employee if exists + if (employeeSelect && itemEmployee) { + itemEmployee.textContent = employeeSelect.value; + } + } + + // Setup discount type button handlers + document.addEventListener('DOMContentLoaded', () => { + document.querySelectorAll('.discount-type-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + e.stopPropagation(); + toggleDiscountType(btn); + }); + }); + }); + function selectMethod(method) { currentMethod = method; @@ -872,23 +1399,153 @@ // Update label document.getElementById('inputLabel').textContent = `Beløb at betale med ${methodLabels[method]}`; + // Show/hide giftcard lookup + const giftcardLookup = document.getElementById('giftcardLookup'); + const btnAddPayment = document.getElementById('btnAddPayment'); + + if (method === 'gavekort') { + giftcardLookup.classList.add('visible'); + // Disable add payment until giftcard is looked up + if (!currentGiftcard) { + btnAddPayment.disabled = true; + } + } else { + giftcardLookup.classList.remove('visible'); + currentGiftcard = null; + resetGiftcardLookup(); + btnAddPayment.disabled = false; + } + // Set input to remaining amount payRest(); } + function lookupGiftcard() { + const input = document.getElementById('giftcardInput'); + const result = document.getElementById('giftcardResult'); + const cardNumber = input.value.trim(); + + if (!cardNumber) { + result.className = 'giftcard-result visible error'; + result.innerHTML = 'Indtast venligst et gavekortnummer'; + return; + } + + // Simulate lookup - in real app this would be an API call + // For demo: simulate different balances based on card number + const simulatedBalance = simulateGiftcardBalance(cardNumber); + + if (simulatedBalance === null) { + result.className = 'giftcard-result visible error'; + result.innerHTML = 'Gavekort ikke fundet'; + currentGiftcard = null; + document.getElementById('btnAddPayment').disabled = true; + return; + } + + // Success - card found + currentGiftcard = { + number: cardNumber, + balance: simulatedBalance + }; + + result.className = 'giftcard-result visible success'; + result.innerHTML = `Gavekort fundet! Saldo: ${formatNumber(simulatedBalance)} kr`; + + // Enable payment button + document.getElementById('btnAddPayment').disabled = false; + + // Set payment input to min of remaining and giftcard balance + const remaining = getRemaining(); + const maxPayment = Math.min(remaining, simulatedBalance); + const paymentInput = document.getElementById('paymentInput'); + paymentInput.value = formatNumber(maxPayment); + + // Focus payment input and select the value + setTimeout(() => { + paymentInput.focus(); + paymentInput.select(); + }, 50); + } + + function simulateGiftcardBalance(cardNumber) { + // Demo simulation: different cards have different balances + // Cards starting with "1" = 500 kr, "2" = 1000 kr, "3" = 1500 kr + // Any other = 750 kr, empty/invalid = not found + + if (cardNumber.length < 4) return null; + + const firstChar = cardNumber.charAt(0); + switch (firstChar) { + case '1': return 500; + case '2': return 1000; + case '3': return 1500; + case '0': return null; // Not found + default: return 750; + } + } + + function resetGiftcardLookup() { + document.getElementById('giftcardInput').value = ''; + const result = document.getElementById('giftcardResult'); + result.className = 'giftcard-result'; + result.innerHTML = ''; + } + function payRest() { const remaining = getRemaining(); - document.getElementById('paymentInput').value = formatNumber(remaining); + let maxPayment = remaining; + + // For giftcard, limit to available balance + if (currentMethod === 'gavekort' && currentGiftcard) { + maxPayment = Math.min(remaining, currentGiftcard.balance); + } + + document.getElementById('paymentInput').value = formatNumber(maxPayment); } function addPayment() { - const amount = parseAmount(document.getElementById('paymentInput').value); + let amount = parseAmount(document.getElementById('paymentInput').value); if (amount <= 0) return; - payments.push({ + // For giftcard payments, validate and limit to balance + if (currentMethod === 'gavekort') { + if (!currentGiftcard) { + alert('Slå gavekortet op først'); + return; + } + // Limit to available balance + if (amount > currentGiftcard.balance) { + amount = currentGiftcard.balance; + } + } + + const payment = { method: currentMethod, amount: amount - }); + }; + + // Add giftcard details if applicable + if (currentMethod === 'gavekort' && currentGiftcard) { + payment.giftcardNumber = currentGiftcard.number; + payment.giftcardOriginalBalance = currentGiftcard.balance; + + // Reduce the giftcard balance (simulate using it) + currentGiftcard.balance -= amount; + + // If balance is 0, reset the giftcard lookup for next use + if (currentGiftcard.balance <= 0) { + currentGiftcard = null; + resetGiftcardLookup(); + document.getElementById('btnAddPayment').disabled = true; + } else { + // Update displayed balance + const result = document.getElementById('giftcardResult'); + result.innerHTML = `Gavekort fundet! Resterende saldo: ${formatNumber(currentGiftcard.balance)} kr`; + } + } + + payments.push(payment); updatePaymentsList(); updateTotals(); @@ -912,16 +1569,20 @@ return; } - container.innerHTML = payments.map((p, i) => ` -
- - - `).join(''); + `; + }).join(''); } function updateTotals() { diff --git a/wwwroot/poc-detail-drawer.html b/wwwroot/poc-detail-drawer.html index cd1a644..cd4148d 100644 --- a/wwwroot/poc-detail-drawer.html +++ b/wwwroot/poc-detail-drawer.html @@ -442,7 +442,30 @@ } swp-journal-icon { - font-size: 18px; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + } + + swp-journal-icon img { + width: 20px; + height: 20px; + filter: invert(22%) sepia(14%) saturate(1042%) hue-rotate(164deg) brightness(102%) contrast(85%); + transition: filter 150ms ease; + } + + swp-journal-link:hover swp-journal-icon img { + filter: invert(32%) sepia(98%) saturate(1234%) hue-rotate(196deg) brightness(93%) contrast(92%); + } + + swp-journal-link.no-journal swp-journal-icon img { + filter: invert(45%) sepia(8%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(90%); + } + + swp-journal-link.no-journal:hover swp-journal-icon img { + filter: invert(32%) sepia(98%) saturate(1234%) hue-rotate(196deg) brightness(93%) contrast(92%); } swp-journal-text { @@ -827,7 +850,7 @@ Journal - 📋 + Åbn journal @@ -898,15 +921,15 @@ // Update journal link const journalLink = document.getElementById('journalLink'); const journalText = journalLink.querySelector('swp-journal-text'); - const journalIcon = journalLink.querySelector('swp-journal-icon'); + const journalIcon = document.getElementById('journalIcon'); if (hasJournal) { journalLink.classList.remove('no-journal'); - journalIcon.textContent = '📋'; + journalIcon.src = 'icons/journal-alt.svg'; journalText.textContent = 'Åbn journal'; } else { journalLink.classList.add('no-journal'); - journalIcon.textContent = '➕'; + journalIcon.src = 'icons/square-plus.svg'; journalText.textContent = 'Opret journal'; }