Calendar/wwwroot/poc-checkout.html
Janus C. H. Knudsen 1b0ef74551 Enhances cart and payment interaction UI
Adds interactive cart item editing with expandable sections
Introduces dynamic discount calculation and display
Implements giftcard lookup and balance tracking functionality

Improves user experience with smooth transitions and more flexible item management
2025-12-19 18:21:55 +01:00

1629 lines
50 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="da">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Checkout POC</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--color-border: #e0e0e0;
--color-surface: #fff;
--color-background: #f5f5f5;
--color-background-hover: #f0f0f0;
--color-background-alt: #fafafa;
--color-text: #333;
--color-text-secondary: #666;
--color-teal: #00897b;
--color-red: #e53935;
--transition-fast: 150ms ease;
--font-mono: 'JetBrains Mono', monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--color-background);
font-size: 14px;
color: var(--color-text);
}
.demo-trigger { padding: 20px; }
.demo-btn {
padding: 12px 24px;
background: var(--color-teal);
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
/* Overlay & Panel */
.overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.25);
opacity: 0;
visibility: hidden;
transition: opacity 200ms, visibility 200ms;
z-index: 100;
}
.overlay.open { opacity: 1; visibility: visible; }
.panel {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: 80%;
background: var(--color-background);
transform: translateX(100%);
transition: transform 200ms ease;
display: flex;
flex-direction: column;
z-index: 101;
box-shadow: -4px 0 20px rgba(0,0,0,0.15);
}
.panel.open { transform: translateX(0); }
/* Header */
.header {
display: flex;
align-items: center;
gap: 40px;
padding: 20px 28px;
background: var(--color-surface);
border-bottom: 1px solid var(--color-border);
}
.header-field { display: flex; align-items: center; gap: 10px; }
.header-label { color: var(--color-text-secondary); font-size: 13px; }
.header-value { font-weight: 500; font-size: 15px; }
.header-link { color: var(--color-teal); cursor: pointer; font-size: 13px; }
.header-select {
padding: 6px 28px 6px 10px;
border: 1px solid var(--color-border);
border-radius: 4px;
font-size: 14px;
background: var(--color-surface) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") no-repeat right 8px center;
appearance: none;
}
.header-close {
margin-left: auto;
background: none;
border: none;
font-size: 20px;
color: var(--color-text-secondary);
cursor: pointer;
padding: 4px 8px;
}
/* Main layout */
.main {
flex: 1;
display: grid;
grid-template-columns: 20% 1fr 35%;
overflow: hidden;
}
/* Sidebar */
.sidebar {
border-right: 1px solid var(--color-border);
display: flex;
flex-direction: column;
background: var(--color-surface);
}
.search-box {
padding: 20px;
border-bottom: 1px solid var(--color-border);
}
.search-input {
width: 100%;
padding: 12px 14px 12px 40px;
border: 1px solid var(--color-border);
border-radius: 6px;
font-size: 14px;
background: var(--color-surface) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.35-4.35'/%3E%3C/svg%3E") no-repeat 12px center;
}
.menu-section {
border-bottom: 1px solid var(--color-border);
padding: 12px 0;
}
.menu-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 20px;
font-size: 14px;
cursor: pointer;
}
.menu-item:hover { background: var(--color-background-hover); }
.menu-item.active { color: var(--color-teal); font-weight: 500; }
.categories {
flex: 1;
overflow-y: auto;
padding: 12px 0;
}
.category {
display: flex;
justify-content: space-between;
padding: 12px 20px;
font-size: 14px;
cursor: pointer;
color: var(--color-text-secondary);
}
.category:hover { background: var(--color-background-hover); color: var(--color-text); }
/* Cart area */
.cart-area {
display: flex;
flex-direction: column;
overflow: hidden;
background: var(--color-background);
}
.cart-list {
flex: 1;
overflow-y: auto;
padding: 20px 24px;
}
.cart-section {
margin-bottom: 24px;
}
.cart-section:last-child {
margin-bottom: 0;
}
.cart-section-header {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--color-text-secondary);
padding: 0 4px 10px;
border-bottom: 1px solid var(--color-border);
margin-bottom: 12px;
}
.cart-section-items {
display: flex;
flex-direction: column;
gap: 8px;
}
/* Cart footer with totals */
.cart-footer {
padding: 20px 24px;
background: var(--color-surface);
border-top: 1px solid var(--color-border);
}
.cart-totals {
display: flex;
flex-direction: column;
gap: 8px;
}
.cart-total-row {
display: flex;
justify-content: space-between;
font-size: 14px;
}
.cart-total-row .label {
color: var(--color-text-secondary);
}
.cart-total-row .value {
font-family: var(--font-mono);
font-weight: 500;
}
.cart-total-row.grand {
padding-top: 12px;
border-top: 1px solid var(--color-border);
margin-top: 4px;
}
.cart-total-row.grand .label {
font-size: 16px;
font-weight: 600;
color: var(--color-text);
}
.cart-total-row.grand .value {
font-family: var(--font-mono);
font-size: 24px;
font-weight: 700;
}
/* 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);
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 {
display: flex;
align-items: center;
gap: 4px;
}
.qty-btn {
width: 26px;
height: 26px;
border: 1px solid var(--color-border);
background: var(--color-surface);
border-radius: 4px;
cursor: pointer;
font-size: 14px;
color: var(--color-text-secondary);
display: flex;
align-items: center;
justify-content: center;
}
.qty-btn:hover { background: var(--color-background-hover); }
.qty-val {
width: 24px;
text-align: center;
font-size: 14px;
font-weight: 500;
}
.item-info {
flex: 1;
}
.item-name {
font-size: 14px;
font-weight: 500;
}
.item-meta {
font-size: 12px;
color: var(--color-text-secondary);
}
.item-price {
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 {
background: none;
border: none;
font-size: 16px;
color: var(--color-text-secondary);
cursor: pointer;
opacity: 0.3;
padding: 4px;
}
.item-remove:hover { opacity: 1; color: var(--color-red); }
/* Payment Panel */
.payment-panel {
background: var(--color-surface);
border-left: 1px solid var(--color-border);
display: flex;
flex-direction: column;
overflow: hidden;
}
.payment-total {
padding: 24px 28px;
text-align: center;
border-bottom: 1px solid var(--color-border);
}
.payment-total-amount {
font-family: var(--font-mono);
font-size: 48px;
font-weight: 700;
color: var(--color-text);
letter-spacing: -2px;
}
.payment-total-label {
font-size: 12px;
color: var(--color-text-secondary);
margin-top: 4px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.payment-methods {
display: flex;
gap: 8px;
padding: 16px 24px;
border-bottom: 1px solid var(--color-border);
flex-wrap: wrap;
}
.method-btn {
flex: 1 1 auto;
min-width: 70px;
padding: 12px 8px;
border: 2px solid var(--color-border);
border-radius: 6px;
background: var(--color-surface);
cursor: pointer;
text-align: center;
transition: all var(--transition-fast);
}
.method-btn:hover {
border-color: #ccc;
}
.method-btn.active {
border-color: var(--color-teal);
background: rgba(0, 137, 123, 0.05);
}
.method-btn-icon {
width: 24px;
height: 24px;
display: block;
margin: 0 auto 4px;
filter: invert(22%) sepia(14%) saturate(1042%) hue-rotate(164deg) brightness(102%) contrast(85%);
}
.method-btn-label {
font-size: 11px;
color: var(--color-text-secondary);
}
.method-btn.active .method-btn-label {
color: var(--color-teal);
font-weight: 500;
}
.payment-input-section {
padding: 16px 24px;
border-bottom: 1px solid var(--color-border);
}
.payment-input-label {
font-size: 11px;
color: var(--color-text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
}
.payment-input-row {
display: flex;
gap: 10px;
}
.payment-input {
flex: 1;
padding: 12px 16px;
border: 1px solid var(--color-border);
border-radius: 6px;
font-family: var(--font-mono);
font-size: 22px;
font-weight: 600;
text-align: right;
}
.payment-input:focus {
outline: none;
border-color: var(--color-teal);
}
.btn-pay-rest {
padding: 10px 12px;
border: 1px solid var(--color-border);
border-radius: 6px;
background: var(--color-surface);
font-size: 11px;
color: var(--color-text-secondary);
cursor: pointer;
white-space: nowrap;
}
.btn-pay-rest:hover {
border-color: var(--color-teal);
color: var(--color-teal);
}
.btn-add-payment {
width: 100%;
padding: 12px;
margin-top: 12px;
border: none;
border-radius: 6px;
background: var(--color-teal);
color: white;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background 200ms ease, opacity 200ms ease, transform 150ms ease;
}
.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 */
.registered-payments {
flex: 1;
overflow-y: auto;
padding: 16px 24px;
}
.registered-payments-label {
font-size: 11px;
color: var(--color-text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 12px;
}
.payment-entry {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 0;
border-bottom: 1px solid var(--color-border);
}
.payment-entry-icon {
width: 20px;
height: 20px;
filter: invert(22%) sepia(14%) saturate(1042%) hue-rotate(164deg) brightness(102%) contrast(85%);
}
.payment-entry-info {
flex: 1;
}
.payment-entry-method {
font-size: 14px;
font-weight: 500;
}
.payment-entry-detail {
font-size: 12px;
color: var(--color-text-secondary);
}
.payment-entry-amount {
font-family: var(--font-mono);
font-size: 16px;
font-weight: 600;
color: var(--color-teal);
}
.payment-entry-remove {
background: none;
border: none;
color: var(--color-text-secondary);
cursor: pointer;
font-size: 16px;
opacity: 0.5;
padding: 4px;
}
.payment-entry-remove:hover {
opacity: 1;
color: var(--color-red);
}
.no-payments {
color: var(--color-text-secondary);
font-size: 13px;
text-align: center;
padding: 24px;
}
/* Payment footer */
.payment-footer {
padding: 16px 24px;
border-top: 1px solid var(--color-border);
background: var(--color-background-alt);
}
.remaining-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.remaining-label {
font-size: 14px;
color: var(--color-text-secondary);
}
.remaining-value {
font-family: var(--font-mono);
font-size: 28px;
font-weight: 700;
color: var(--color-red);
}
.remaining-value.zero {
color: var(--color-teal);
}
.btn-complete {
width: 100%;
padding: 16px;
border: none;
border-radius: 6px;
background: var(--color-teal);
color: white;
font-size: 16px;
font-weight: 500;
cursor: pointer;
}
.btn-complete:hover {
background: #00796b;
}
.btn-complete:disabled {
background: #ccc;
cursor: not-allowed;
}
.payment-actions {
display: flex;
gap: 12px;
margin-top: 16px;
}
.btn-secondary {
flex: 1;
padding: 10px;
border: 1px solid var(--color-border);
border-radius: 6px;
background: var(--color-surface);
font-size: 13px;
color: var(--color-text-secondary);
cursor: pointer;
}
.btn-secondary:hover {
border-color: #ccc;
}
</style>
</head>
<body>
<div class="demo-trigger">
<button class="demo-btn" onclick="openPanel()">Gå til betaling</button>
</div>
<div class="overlay" id="overlay" onclick="closePanel()"></div>
<div class="panel" id="panel">
<div class="header">
<div class="header-field">
<span class="header-label">Kunde</span>
<span class="header-value">Sofie Nielsen</span>
<span class="header-link">Fjern</span>
</div>
<div class="header-field">
<span class="header-label">Dato</span>
<span class="header-value">16. dec 2025</span>
</div>
<div class="header-field">
<span class="header-label">Betjent af</span>
<select class="header-select">
<option>Emma Larsen</option>
<option>Anett Davidsson</option>
</select>
</div>
<button class="header-close" onclick="closePanel()"></button>
</div>
<div class="main">
<div class="sidebar">
<div class="search-box">
<input type="text" class="search-input" placeholder="Søg varer...">
</div>
<div class="menu-section">
<div class="menu-item"><span>Brugerdefineret linje</span></div>
<div class="menu-item"><span>Tidligere salg</span></div>
<div class="menu-item"><span>Refunder</span></div>
<div class="menu-item active"><span>Services</span></div>
</div>
<div class="categories">
<div class="category"><span>Acidic Bonding</span><span></span></div>
<div class="category"><span>Amino Mint</span><span></span></div>
<div class="category"><span>Color Gloss</span><span></span></div>
<div class="category"><span>Conditioner</span><span></span></div>
<div class="category"><span>Styling</span><span></span></div>
<div class="category"><span>Olaplex</span><span></span></div>
</div>
</div>
<div class="cart-area">
<div class="cart-list">
<!-- Services -->
<div class="cart-section">
<div class="cart-section-header">Services</div>
<div class="cart-section-items">
<div class="cart-item" onclick="toggleCartItem(this)" data-base-price="725" data-duration="45 min">
<div class="cart-item-main">
<div class="item-qty" onclick="event.stopPropagation()">
<button class="qty-btn"></button>
<span class="qty-val">1</span>
<button class="qty-btn">+</button>
</div>
<div class="item-info">
<div class="item-name">Dameklip</div>
<div class="item-meta"><span class="item-employee">Emma Larsen</span> · 45 min</div>
</div>
<div class="item-price">
<div class="item-original-price">725 kr</div>
<div class="item-total">725 kr</div>
<div class="item-discount">-0 kr rabat</div>
</div>
<button class="item-remove" onclick="event.stopPropagation()"></button>
</div>
<div class="cart-item-edit" onclick="event.stopPropagation()">
<div class="edit-row">
<div class="edit-field">
<div class="edit-field-label">Pris</div>
<input type="text" class="edit-field-input mono edit-price" value="725" oninput="updateCartItemDisplay(this)">
</div>
<div class="edit-field">
<div class="edit-field-label">Udført af</div>
<select class="edit-field-select edit-employee" onchange="updateCartItemDisplay(this)">
<option selected>Emma Larsen</option>
<option>Anett Davidsson</option>
<option>Maria Jensen</option>
</select>
</div>
</div>
<div class="edit-row">
<div class="edit-field">
<div class="edit-field-label">Rabat</div>
<div class="discount-row">
<input type="text" class="edit-field-input mono edit-discount" value="0" style="width: 80px;" oninput="updateCartItemDisplay(this)">
<div class="discount-type">
<button class="discount-type-btn active" data-type="kr">kr</button>
<button class="discount-type-btn" data-type="pct">%</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="cart-item" onclick="toggleCartItem(this)" data-base-price="900" data-duration="90 min">
<div class="cart-item-main">
<div class="item-qty" onclick="event.stopPropagation()">
<button class="qty-btn"></button>
<span class="qty-val">1</span>
<button class="qty-btn">+</button>
</div>
<div class="item-info">
<div class="item-name">Gloss extra langt/tykt hår</div>
<div class="item-meta"><span class="item-employee">Emma Larsen</span> · 90 min</div>
</div>
<div class="item-price">
<div class="item-original-price">900 kr</div>
<div class="item-total">900 kr</div>
<div class="item-discount">-0 kr rabat</div>
</div>
<button class="item-remove" onclick="event.stopPropagation()"></button>
</div>
<div class="cart-item-edit" onclick="event.stopPropagation()">
<div class="edit-row">
<div class="edit-field">
<div class="edit-field-label">Pris</div>
<input type="text" class="edit-field-input mono edit-price" value="900" oninput="updateCartItemDisplay(this)">
</div>
<div class="edit-field">
<div class="edit-field-label">Udført af</div>
<select class="edit-field-select edit-employee" onchange="updateCartItemDisplay(this)">
<option selected>Emma Larsen</option>
<option>Anett Davidsson</option>
<option>Maria Jensen</option>
</select>
</div>
</div>
<div class="edit-row">
<div class="edit-field">
<div class="edit-field-label">Rabat</div>
<div class="discount-row">
<input type="text" class="edit-field-input mono edit-discount" value="0" style="width: 80px;" oninput="updateCartItemDisplay(this)">
<div class="discount-type">
<button class="discount-type-btn active" data-type="kr">kr</button>
<button class="discount-type-btn" data-type="pct">%</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Produkter -->
<div class="cart-section">
<div class="cart-section-header">Produkter</div>
<div class="cart-section-items">
<div class="cart-item" onclick="toggleCartItem(this)" data-base-price="300">
<div class="cart-item-main">
<div class="item-qty" onclick="event.stopPropagation()">
<button class="qty-btn"></button>
<span class="qty-val">1</span>
<button class="qty-btn">+</button>
</div>
<div class="item-info">
<div class="item-name">Olaplex No. 3</div>
<div class="item-meta">100ml</div>
</div>
<div class="item-price">
<div class="item-original-price">300 kr</div>
<div class="item-total">300 kr</div>
<div class="item-discount">-0 kr rabat</div>
</div>
<button class="item-remove" onclick="event.stopPropagation()"></button>
</div>
<div class="cart-item-edit" onclick="event.stopPropagation()">
<div class="edit-row">
<div class="edit-field">
<div class="edit-field-label">Pris</div>
<input type="text" class="edit-field-input mono edit-price" value="300" oninput="updateCartItemDisplay(this)">
</div>
<div class="edit-field">
<div class="edit-field-label">Rabat</div>
<div class="discount-row">
<input type="text" class="edit-field-input mono edit-discount" value="0" style="width: 80px;" oninput="updateCartItemDisplay(this)">
<div class="discount-type">
<button class="discount-type-btn active" data-type="kr">kr</button>
<button class="discount-type-btn" data-type="pct">%</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="cart-item" onclick="toggleCartItem(this)" data-base-price="175">
<div class="cart-item-main">
<div class="item-qty" onclick="event.stopPropagation()">
<button class="qty-btn"></button>
<span class="qty-val">2</span>
<button class="qty-btn">+</button>
</div>
<div class="item-info">
<div class="item-name">India supercharged mask</div>
<div class="item-meta">250ml</div>
</div>
<div class="item-price">
<div class="item-original-price">350 kr</div>
<div class="item-total">350 kr</div>
<div class="item-discount">-0 kr rabat</div>
</div>
<button class="item-remove" onclick="event.stopPropagation()"></button>
</div>
<div class="cart-item-edit" onclick="event.stopPropagation()">
<div class="edit-row">
<div class="edit-field">
<div class="edit-field-label">Pris</div>
<input type="text" class="edit-field-input mono edit-price" value="175" oninput="updateCartItemDisplay(this)">
</div>
<div class="edit-field">
<div class="edit-field-label">Rabat</div>
<div class="discount-row">
<input type="text" class="edit-field-input mono edit-discount" value="0" style="width: 80px;" oninput="updateCartItemDisplay(this)">
<div class="discount-type">
<button class="discount-type-btn active" data-type="kr">kr</button>
<button class="discount-type-btn" data-type="pct">%</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="cart-footer">
<div class="cart-totals">
<div class="cart-total-row">
<span class="label">Subtotal</span>
<span class="value">1.820 kr</span>
</div>
<div class="cart-total-row">
<span class="label">Moms (25%)</span>
<span class="value">455 kr</span>
</div>
<div class="cart-total-row grand">
<span class="label">Total</span>
<span class="value">2.275 kr</span>
</div>
</div>
</div>
</div>
<!-- Payment Panel -->
<div class="payment-panel">
<div class="payment-total">
<div class="payment-total-amount" id="totalAmount">2.275 kr</div>
<div class="payment-total-label">Total at betale</div>
</div>
<div class="payment-methods">
<button class="method-btn active" onclick="selectMethod('kort')">
<img class="method-btn-icon" src="icons/credit-card.svg" alt="">
<span class="method-btn-label">Kort</span>
</button>
<button class="method-btn" onclick="selectMethod('kontant')">
<img class="method-btn-icon" src="icons/coins.svg" alt="">
<span class="method-btn-label">Kontant</span>
</button>
<button class="method-btn" onclick="selectMethod('mobilepay')">
<img class="method-btn-icon" src="icons/mobilepay.svg" alt="">
<span class="method-btn-label">MobilePay</span>
</button>
<button class="method-btn" onclick="selectMethod('bank')">
<img class="method-btn-icon" src="icons/bank.svg" alt="">
<span class="method-btn-label">Bank</span>
</button>
<button class="method-btn" onclick="selectMethod('gavekort')">
<img class="method-btn-icon" src="icons/gift-card.svg" alt="">
<span class="method-btn-label">Gavekort</span>
</button>
<button class="method-btn" onclick="selectMethod('tilgode')">
<img class="method-btn-icon" src="icons/loan.svg" alt="">
<span class="method-btn-label">Tilgode</span>
</button>
</div>
<div class="payment-input-section">
<!-- Gavekort lookup -->
<div class="giftcard-lookup" id="giftcardLookup">
<div class="giftcard-lookup-label">Indtast gavekortnummer</div>
<div class="giftcard-lookup-row">
<input type="text" class="giftcard-input" id="giftcardInput" placeholder="XXXX-XXXX-XXXX" onkeydown="if(event.key==='Enter')lookupGiftcard()">
<button class="btn-lookup" onclick="lookupGiftcard()">Slå op</button>
</div>
<div class="giftcard-result" id="giftcardResult"></div>
</div>
<div class="payment-input-label" id="inputLabel">Beløb at betale med Kort</div>
<div class="payment-input-row">
<input type="text" class="payment-input" id="paymentInput" value="2.275" inputmode="decimal">
<button class="btn-pay-rest" onclick="payRest()">Betal rest<br><span id="restAmount">(2.275 kr)</span></button>
</div>
<button class="btn-add-payment" id="btnAddPayment" onclick="addPayment()">+ Tilføj betaling</button>
</div>
<div class="registered-payments">
<div class="registered-payments-label">Registrerede betalinger</div>
<div id="paymentsList">
<div class="no-payments">Ingen betalinger registreret</div>
</div>
</div>
<div class="payment-footer">
<div class="remaining-row">
<span class="remaining-label">Restbeløb</span>
<span class="remaining-value" id="remainingAmount">2.275 kr</span>
</div>
<button class="btn-complete" id="btnComplete" disabled>Afslut salg</button>
<div class="payment-actions">
<button class="btn-secondary">Print kvittering</button>
<button class="btn-secondary">Send på mail</button>
</div>
</div>
</div>
</div>
</div>
<script>
const total = 2275;
const payments = [];
let currentMethod = 'kort';
let currentGiftcard = null; // { number, balance }
const methodLabels = {
'kort': 'Kort',
'kontant': 'Kontant',
'mobilepay': 'MobilePay',
'bank': 'Bank',
'gavekort': 'Gavekort',
'tilgode': 'Tilgode'
};
const methodIcons = {
'kort': 'icons/credit-card.svg',
'kontant': 'icons/coins.svg',
'mobilepay': 'icons/mobilepay.svg',
'bank': 'icons/bank.svg',
'gavekort': 'icons/gift-card.svg',
'tilgode': 'icons/loan.svg'
};
function openPanel() {
document.getElementById('overlay').classList.add('open');
document.getElementById('panel').classList.add('open');
}
function closePanel() {
document.getElementById('overlay').classList.remove('open');
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;
// Update active button
document.querySelectorAll('.method-btn').forEach(btn => btn.classList.remove('active'));
event.currentTarget.classList.add('active');
// 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: <span class="giftcard-balance">${formatNumber(simulatedBalance)} kr</span>`;
// 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();
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() {
let amount = parseAmount(document.getElementById('paymentInput').value);
if (amount <= 0) return;
// 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: <span class="giftcard-balance">${formatNumber(currentGiftcard.balance)} kr</span>`;
}
}
payments.push(payment);
updatePaymentsList();
updateTotals();
// Reset input to new remaining
payRest();
}
function removePayment(index) {
payments.splice(index, 1);
updatePaymentsList();
updateTotals();
payRest();
}
function updatePaymentsList() {
const container = document.getElementById('paymentsList');
if (payments.length === 0) {
container.innerHTML = '<div class="no-payments">Ingen betalinger registreret</div>';
return;
}
container.innerHTML = payments.map((p, i) => {
const detail = p.giftcardNumber ? `Kort: ${p.giftcardNumber}` : '';
return `
<div class="payment-entry">
<img class="payment-entry-icon" src="${methodIcons[p.method]}" alt="">
<div class="payment-entry-info">
<div class="payment-entry-method">${methodLabels[p.method]}</div>
${detail ? `<div class="payment-entry-detail">${detail}</div>` : ''}
</div>
<span class="payment-entry-amount">+${formatNumber(p.amount)} kr</span>
<button class="payment-entry-remove" onclick="removePayment(${i})">✕</button>
</div>
`;
}).join('');
}
function updateTotals() {
const remaining = getRemaining();
// Update remaining display
const remainingEl = document.getElementById('remainingAmount');
remainingEl.textContent = formatNumber(remaining) + ' kr';
// Update rest button
document.getElementById('restAmount').textContent = `(${formatNumber(remaining)} kr)`;
// Toggle styling and button state
const btnComplete = document.getElementById('btnComplete');
if (remaining <= 0) {
remainingEl.classList.add('zero');
btnComplete.disabled = false;
} else {
remainingEl.classList.remove('zero');
btnComplete.disabled = true;
}
}
function getRemaining() {
const paid = payments.reduce((sum, p) => sum + p.amount, 0);
return Math.max(0, total - paid);
}
function formatNumber(num) {
return num.toLocaleString('da-DK');
}
function parseAmount(str) {
if (!str) return 0;
const cleaned = str.replace(/\./g, '').replace(',', '.').replace(/[^0-9.]/g, '');
return parseFloat(cleaned) || 0;
}
document.addEventListener('keydown', e => {
if (e.key === 'Escape') closePanel();
});
</script>
</body>
</html>