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
This commit is contained in:
parent
e09048742c
commit
1b0ef74551
4 changed files with 759 additions and 69 deletions
4
wwwroot/icons/journal-alt.svg
Normal file
4
wwwroot/icons/journal-alt.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
|
||||
<path d="m15,2.766v-1.766c0-.553-.448-1-1-1s-1,.447-1,1v1h-2v-1c0-.553-.448-1-1-1s-1,.447-1,1v1h-2v-1c0-.553-.448-1-1-1s-1,.447-1,1v1h-2v-1c0-.553-.448-1-1-1s-1,.447-1,1v1.766c-.613.55-1,1.347-1,2.234v14c0,2.757,2.243,5,5,5h6c2.757,0,5-2.243,5-5V5c0-.886-.387-1.684-1-2.234Zm-1,16.234c0,1.654-1.346,3-3,3h-6c-1.654,0-3-1.346-3-3V5c0-.552.449-1,1-1h10c.551,0,1,.448,1,1v14Zm-2-11c0,.553-.448,1-1,1h-6c-.552,0-1-.447-1-1s.448-1,1-1h6c.552,0,1,.447,1,1Zm0,4c0,.553-.448,1-1,1h-6c-.552,0-1-.447-1-1s.448-1,1-1h6c.552,0,1,.447,1,1Zm-3,4c0,.553-.448,1-1,1h-3c-.552,0-1-.447-1-1s.448-1,1-1h3c.552,0,1,.447,1,1ZM21,0c-1.654,0-3,1.346-3,3v16.758c0,1.054.427,2.084,1.172,2.828l1.121,1.121c.195.195.451.293.707.293s.512-.098.707-.293l1.121-1.121c.745-.744,1.172-1.774,1.172-2.828V3c0-1.654-1.346-3-3-3Zm1,19.758c0,.526-.213,1.042-.586,1.414l-.414.414-.414-.414c-.373-.372-.586-.888-.586-1.414V3c0-.552.449-1,1-1s1,.448,1,1v16.758Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1 KiB |
2
wwwroot/icons/square-plus.svg
Normal file
2
wwwroot/icons/square-plus.svg
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24" width="512" height="512"><path d="M17,12c0,.553-.448,1-1,1h-3v3c0,.553-.448,1-1,1s-1-.447-1-1v-3h-3c-.552,0-1-.447-1-1s.448-1,1-1h3v-3c0-.553,.448-1,1-1s1,.447,1,1v3h3c.552,0,1,.447,1,1Zm7-7v14c0,2.757-2.243,5-5,5H5c-2.757,0-5-2.243-5-5V5C0,2.243,2.243,0,5,0h14c2.757,0,5,2.243,5,5Zm-2,0c0-1.654-1.346-3-3-3H5c-1.654,0-3,1.346-3,3v14c0,1.654,1.346,3,3,3h14c1.654,0,3-1.346,3-3V5Z"/></svg>
|
||||
|
After Width: | Height: | Size: 521 B |
|
|
@ -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 @@
|
|||
<div class="cart-section">
|
||||
<div class="cart-section-header">Services</div>
|
||||
<div class="cart-section-items">
|
||||
<div class="cart-item">
|
||||
<div class="item-qty">
|
||||
<button class="qty-btn">−</button>
|
||||
<span class="qty-val">1</span>
|
||||
<button class="qty-btn">+</button>
|
||||
<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="item-info">
|
||||
<div class="item-name">Dameklip</div>
|
||||
<div class="item-meta">Emma Larsen · 45 min</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 class="item-price">
|
||||
<div class="item-total">725 kr</div>
|
||||
</div>
|
||||
<button class="item-remove">✕</button>
|
||||
</div>
|
||||
<div class="cart-item">
|
||||
<div class="item-qty">
|
||||
<button class="qty-btn">−</button>
|
||||
<span class="qty-val">1</span>
|
||||
<button class="qty-btn">+</button>
|
||||
<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="item-info">
|
||||
<div class="item-name">Gloss extra langt/tykt hår</div>
|
||||
<div class="item-meta">Emma Larsen · 90 min</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 class="item-price">
|
||||
<div class="item-total">900 kr</div>
|
||||
</div>
|
||||
<button class="item-remove">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -712,35 +1072,79 @@
|
|||
<div class="cart-section">
|
||||
<div class="cart-section-header">Produkter</div>
|
||||
<div class="cart-section-items">
|
||||
<div class="cart-item">
|
||||
<div class="item-qty">
|
||||
<button class="qty-btn">−</button>
|
||||
<span class="qty-val">1</span>
|
||||
<button class="qty-btn">+</button>
|
||||
<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="item-info">
|
||||
<div class="item-name">Olaplex No. 3</div>
|
||||
<div class="item-meta">100ml</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 class="item-price">
|
||||
<div class="item-total">300 kr</div>
|
||||
</div>
|
||||
<button class="item-remove">✕</button>
|
||||
</div>
|
||||
<div class="cart-item">
|
||||
<div class="item-qty">
|
||||
<button class="qty-btn">−</button>
|
||||
<span class="qty-val">2</span>
|
||||
<button class="qty-btn">+</button>
|
||||
<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="item-info">
|
||||
<div class="item-name">India supercharged mask</div>
|
||||
<div class="item-meta">250ml</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 class="item-price">
|
||||
<div class="item-total">350 kr</div>
|
||||
</div>
|
||||
<button class="item-remove">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -799,12 +1203,22 @@
|
|||
</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" onclick="addPayment()">+ Tilføj betaling</button>
|
||||
<button class="btn-add-payment" id="btnAddPayment" onclick="addPayment()">+ Tilføj betaling</button>
|
||||
</div>
|
||||
|
||||
<div class="registered-payments">
|
||||
|
|
@ -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: <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();
|
||||
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: <span class="giftcard-balance">${formatNumber(currentGiftcard.balance)} kr</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
payments.push(payment);
|
||||
|
||||
updatePaymentsList();
|
||||
updateTotals();
|
||||
|
|
@ -912,16 +1569,20 @@
|
|||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = payments.map((p, i) => `
|
||||
<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>
|
||||
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>
|
||||
<span class="payment-entry-amount">+${formatNumber(p.amount)} kr</span>
|
||||
<button class="payment-entry-remove" onclick="removePayment(${i})">✕</button>
|
||||
</div>
|
||||
`).join('');
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function updateTotals() {
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
<swp-drawer-section>
|
||||
<swp-section-label>Journal</swp-section-label>
|
||||
<swp-journal-link id="journalLink">
|
||||
<swp-journal-icon>📋</swp-journal-icon>
|
||||
<swp-journal-icon><img id="journalIcon" src="icons/journal-alt.svg" alt=""></swp-journal-icon>
|
||||
<swp-journal-text>Åbn journal</swp-journal-text>
|
||||
<swp-journal-arrow>→</swp-journal-arrow>
|
||||
</swp-journal-link>
|
||||
|
|
@ -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';
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue