Moving away from Azure Devops #1
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