Moving away from Azure Devops #1
1 changed files with 532 additions and 24 deletions
|
|
@ -961,6 +961,199 @@
|
|||
color: var(--color-teal);
|
||||
}
|
||||
|
||||
/* Service values (read mode) */
|
||||
swp-service-values {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
swp-service-price,
|
||||
swp-service-duration {
|
||||
font-size: 13px;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--color-text-secondary);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
swp-service-price {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
swp-service-duration {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
swp-service-price.modified,
|
||||
swp-service-duration.modified {
|
||||
color: var(--color-teal);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Hide values in edit mode */
|
||||
.edit-mode swp-service-values {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Service controls container - hidden by default */
|
||||
swp-service-controls {
|
||||
display: none;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Show controls in edit mode */
|
||||
.edit-mode swp-service-controls {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Final values with adjustable controls */
|
||||
swp-service-final-values {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
swp-adjustable-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
swp-adjustable-value.modified {
|
||||
border-color: var(--color-teal);
|
||||
background: color-mix(in srgb, var(--color-teal) 5%, white);
|
||||
}
|
||||
|
||||
swp-adjustable-value .adjust-down,
|
||||
swp-adjustable-value .adjust-up {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
background: var(--color-background);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 100ms ease;
|
||||
}
|
||||
|
||||
swp-adjustable-value .adjust-down:hover,
|
||||
swp-adjustable-value .adjust-up:hover {
|
||||
background: var(--color-teal);
|
||||
color: white;
|
||||
}
|
||||
|
||||
swp-adjustable-value .adjust-down:active,
|
||||
swp-adjustable-value .adjust-up:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
swp-adjustable-value .adjust-display {
|
||||
min-width: 55px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--color-text);
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
swp-adjustable-value.modified .adjust-display {
|
||||
color: var(--color-teal);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Duration display */
|
||||
.duration-display {
|
||||
min-width: 50px;
|
||||
text-align: right;
|
||||
font-size: 13px;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.duration-display.modified {
|
||||
color: var(--color-teal);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Original values (read mode) */
|
||||
swp-service-originals {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
swp-service-originals .original-price-val,
|
||||
swp-service-originals .original-duration-val {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 15px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Strikethrough only when value is changed */
|
||||
swp-service-originals .original-price-val.struck,
|
||||
swp-service-originals .original-duration-val.struck {
|
||||
text-decoration: line-through;
|
||||
text-decoration-color: var(--color-teal);
|
||||
text-decoration-thickness: 1px;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* Original values (edit mode) - hidden by default, shown when has-override */
|
||||
swp-service-originals-edit {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
swp-service-row.has-override swp-service-originals-edit {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
swp-service-originals-edit .original-price-val,
|
||||
swp-service-originals-edit .original-duration-val {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 15px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
swp-service-originals-edit .original-price-val.struck,
|
||||
swp-service-originals-edit .original-duration-val.struck {
|
||||
text-decoration: line-through;
|
||||
text-decoration-color: var(--color-teal);
|
||||
text-decoration-thickness: 1px;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* Highlight originals when value changes */
|
||||
swp-service-originals.highlight .original-price-val,
|
||||
swp-service-originals.highlight .original-duration-val,
|
||||
swp-service-originals-edit.highlight .original-price-val,
|
||||
swp-service-originals-edit.highlight .original-duration-val {
|
||||
animation: flash-text 300ms ease;
|
||||
}
|
||||
|
||||
@keyframes pulse-border {
|
||||
0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-teal) 40%, transparent); }
|
||||
100% { box-shadow: 0 0 0 4px transparent; }
|
||||
}
|
||||
|
||||
@keyframes flash-text {
|
||||
0% { opacity: 0.5; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
LAYOUT GRID
|
||||
========================================== */
|
||||
|
|
@ -1033,6 +1226,13 @@
|
|||
margin-top: 4px;
|
||||
}
|
||||
|
||||
swp-stat-subtitle {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: var(--color-text-muted);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
swp-stat-card.highlight swp-stat-value {
|
||||
color: var(--color-teal);
|
||||
}
|
||||
|
|
@ -1589,6 +1789,90 @@
|
|||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
INVOICE TABLE (afsluttede bookinger)
|
||||
========================================== */
|
||||
swp-invoice-table {
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
swp-invoice-table table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
swp-invoice-table th,
|
||||
swp-invoice-table td {
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
swp-invoice-table th {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
background: var(--color-background-alt);
|
||||
}
|
||||
|
||||
swp-invoice-table td {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
swp-invoice-table .date,
|
||||
swp-invoice-table .time {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
swp-invoice-table .customer {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
swp-invoice-table .services {
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
swp-invoice-table .duration {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
swp-invoice-table .amount {
|
||||
font-family: var(--font-mono);
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
swp-invoice-table .amount-col {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Status badges */
|
||||
swp-status-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
swp-status-badge.paid {
|
||||
background: color-mix(in srgb, var(--color-teal) 15%, white);
|
||||
color: var(--color-teal);
|
||||
}
|
||||
|
||||
swp-status-badge.pending {
|
||||
background: color-mix(in srgb, #f59e0b 15%, white);
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
CHART SECTION (matches poc-detail-drawer.html)
|
||||
========================================== */
|
||||
|
|
@ -2360,12 +2644,13 @@
|
|||
<swp-stat-label>Bookinger denne måned</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card>
|
||||
<swp-stat-value>28.450 kr</swp-stat-value>
|
||||
<swp-stat-label>Omsætning denne måned</swp-stat-label>
|
||||
<swp-stat-value>30.825 kr</swp-stat-value>
|
||||
<swp-stat-label>Værdi af bookede services</swp-stat-label>
|
||||
<swp-stat-subtitle>Baseret på 49 bookinger</swp-stat-subtitle>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card>
|
||||
<swp-stat-value>4.9</swp-stat-value>
|
||||
<swp-stat-label>Gns. kundetilfredshed</swp-stat-label>
|
||||
<swp-stat-value>28.450 kr</swp-stat-value>
|
||||
<swp-stat-label>Omsætning denne måned</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card>
|
||||
<swp-stat-value>68%</swp-stat-value>
|
||||
|
|
@ -2441,6 +2726,100 @@
|
|||
</swp-booking-table>
|
||||
</swp-card>
|
||||
</div>
|
||||
|
||||
<!-- Afsluttede bookinger (fakturaliste) -->
|
||||
<swp-card style="margin-top: 24px;">
|
||||
<swp-section-label>Afsluttede bookinger</swp-section-label>
|
||||
<swp-invoice-table>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Dato</th>
|
||||
<th>Tid</th>
|
||||
<th>Kunde</th>
|
||||
<th>Services</th>
|
||||
<th>Varighed</th>
|
||||
<th class="amount-col">Beløb</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="date">23. dec 2024</td>
|
||||
<td class="time">10:00</td>
|
||||
<td class="customer">Maria Hansen</td>
|
||||
<td class="services">Dameklip, Bundfarve</td>
|
||||
<td class="duration">2t 30m</td>
|
||||
<td class="amount">1.510 kr</td>
|
||||
<td><swp-status-badge class="paid">Betalt</swp-status-badge></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="date">23. dec 2024</td>
|
||||
<td class="time">13:30</td>
|
||||
<td class="customer">Louise Nielsen</td>
|
||||
<td class="services">Balayage langt hår, Olaplex</td>
|
||||
<td class="duration">3t</td>
|
||||
<td class="amount">2.700 kr</td>
|
||||
<td><swp-status-badge class="paid">Betalt</swp-status-badge></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="date">22. dec 2024</td>
|
||||
<td class="time">09:00</td>
|
||||
<td class="customer">Sofie Andersen</td>
|
||||
<td class="services">Dameklip</td>
|
||||
<td class="duration">1t</td>
|
||||
<td class="amount">725 kr</td>
|
||||
<td><swp-status-badge class="paid">Betalt</swp-status-badge></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="date">22. dec 2024</td>
|
||||
<td class="time">11:00</td>
|
||||
<td class="customer">Karen Pedersen</td>
|
||||
<td class="services">Striber mellemlangt hår, Klip</td>
|
||||
<td class="duration">2t 30m</td>
|
||||
<td class="amount">2.390 kr</td>
|
||||
<td><swp-status-badge class="pending">Afventer</swp-status-badge></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="date">21. dec 2024</td>
|
||||
<td class="time">14:00</td>
|
||||
<td class="customer">Emma Larsen</td>
|
||||
<td class="services">Olaplex Stand alone</td>
|
||||
<td class="duration">1t</td>
|
||||
<td class="amount">550 kr</td>
|
||||
<td><swp-status-badge class="paid">Betalt</swp-status-badge></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="date">21. dec 2024</td>
|
||||
<td class="time">10:00</td>
|
||||
<td class="customer">Mette Kristensen</td>
|
||||
<td class="services">Herreklip</td>
|
||||
<td class="duration">1t</td>
|
||||
<td class="amount">645 kr</td>
|
||||
<td><swp-status-badge class="paid">Betalt</swp-status-badge></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="date">20. dec 2024</td>
|
||||
<td class="time">09:30</td>
|
||||
<td class="customer">Anne Thomsen</td>
|
||||
<td class="services">Glossing mellemlangt hår</td>
|
||||
<td class="duration">1t</td>
|
||||
<td class="amount">745 kr</td>
|
||||
<td><swp-status-badge class="paid">Betalt</swp-status-badge></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="date">20. dec 2024</td>
|
||||
<td class="time">12:00</td>
|
||||
<td class="customer">Lise Mortensen</td>
|
||||
<td class="services">Dameklip, Farvning vipper & bryn</td>
|
||||
<td class="duration">1t 30m</td>
|
||||
<td class="amount">1.070 kr</td>
|
||||
<td><swp-status-badge class="paid">Betalt</swp-status-badge></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</swp-invoice-table>
|
||||
</swp-card>
|
||||
</swp-tab-content>
|
||||
|
||||
<script type="module">
|
||||
|
|
@ -2569,33 +2948,72 @@
|
|||
return `${minutes}m`;
|
||||
}
|
||||
|
||||
// Format duration with offset (e.g., "1t -5m" or "30m +10m")
|
||||
function formatDurationWithOffset(baseMinutes, offset) {
|
||||
const baseStr = formatDuration(baseMinutes);
|
||||
if (offset === 0) return baseStr;
|
||||
const sign = offset > 0 ? '+' : '';
|
||||
return `${baseStr} <span class="duration-offset">${sign}${offset}m</span>`;
|
||||
}
|
||||
|
||||
// Create service row HTML
|
||||
function createServiceRow(service, isSelected) {
|
||||
const state = serviceState.get(service.id);
|
||||
const effectivePrice = state.priceOverride !== null ? state.priceOverride : service.price;
|
||||
const effectiveDuration = service.duration + state.durationOverride;
|
||||
const hasPriceOverride = state.priceOverride !== null && state.priceOverride !== service.price;
|
||||
const hasDurationOverride = state.durationOverride !== 0;
|
||||
|
||||
const row = document.createElement('swp-service-row');
|
||||
row.dataset.id = service.id;
|
||||
row.dataset.category = service.category;
|
||||
row.draggable = true;
|
||||
|
||||
if (state.priceOverride !== null || state.durationOverride !== 0) {
|
||||
if (hasPriceOverride || hasDurationOverride) {
|
||||
row.classList.add('has-override');
|
||||
}
|
||||
|
||||
if (isSelected) {
|
||||
// Selected: with editable inputs
|
||||
// Selected: show adjusted values + original values in bordered box when modified
|
||||
const hasAnyOverride = hasPriceOverride || hasDurationOverride;
|
||||
|
||||
// Original values box (shown when any override exists)
|
||||
const originalsHtml = hasAnyOverride
|
||||
? `<swp-service-originals>
|
||||
<span class="original-price-val ${hasPriceOverride ? 'struck' : ''}" data-original-price="${service.price}">${service.price} kr</span>
|
||||
<span class="original-duration-val ${hasDurationOverride ? 'struck' : ''}" data-original-duration="${service.duration}">${formatDuration(service.duration)}</span>
|
||||
</swp-service-originals>`
|
||||
: '';
|
||||
|
||||
row.innerHTML = `
|
||||
<swp-drag-handle>⋮⋮</swp-drag-handle>
|
||||
<swp-service-color style="background: ${service.color};"></swp-service-color>
|
||||
<swp-service-info>
|
||||
<swp-service-name>${service.name}</swp-service-name>
|
||||
</swp-service-info>
|
||||
<swp-service-price-col>
|
||||
<input type="number" value="${effectivePrice}" data-original="${service.price}"> kr
|
||||
</swp-service-price-col>
|
||||
<swp-service-duration-col>${formatDuration(effectiveDuration)}</swp-service-duration-col>
|
||||
<swp-service-values>
|
||||
${originalsHtml}
|
||||
<swp-service-price class="${hasPriceOverride ? 'modified' : ''}">${effectivePrice} kr</swp-service-price>
|
||||
<swp-service-duration class="${hasDurationOverride ? 'modified' : ''}">${formatDuration(effectiveDuration)}</swp-service-duration>
|
||||
</swp-service-values>
|
||||
<swp-service-controls>
|
||||
<swp-service-originals-edit>
|
||||
<span class="original-price-val ${hasPriceOverride ? 'struck' : ''}">${service.price} kr</span>
|
||||
<span class="original-duration-val ${hasDurationOverride ? 'struck' : ''}">${formatDuration(service.duration)}</span>
|
||||
</swp-service-originals-edit>
|
||||
<swp-service-final-values>
|
||||
<swp-adjustable-value class="price-adjust ${hasPriceOverride ? 'modified' : ''}" data-type="price" data-original="${service.price}" data-value="${effectivePrice}" data-step="5">
|
||||
<button class="adjust-down" type="button">−</button>
|
||||
<span class="adjust-display">${effectivePrice} kr</span>
|
||||
<button class="adjust-up" type="button">+</button>
|
||||
</swp-adjustable-value>
|
||||
<swp-adjustable-value class="duration-adjust ${hasDurationOverride ? 'modified' : ''}" data-type="duration" data-base="${service.duration}" data-offset="${state.durationOverride}" data-step="5">
|
||||
<button class="adjust-down" type="button">−</button>
|
||||
<span class="adjust-display">${formatDuration(effectiveDuration)}</span>
|
||||
<button class="adjust-up" type="button">+</button>
|
||||
</swp-adjustable-value>
|
||||
</swp-service-final-values>
|
||||
</swp-service-controls>
|
||||
`;
|
||||
} else {
|
||||
// Available: name only (no price/duration)
|
||||
|
|
@ -2840,22 +3258,110 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Price input handler
|
||||
selectedContainer.addEventListener('input', (e) => {
|
||||
if (e.target.type === 'number') {
|
||||
const row = e.target.closest('swp-service-row');
|
||||
// Adjustable value click handler (up/down buttons)
|
||||
selectedContainer.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.adjust-up, .adjust-down');
|
||||
if (!btn) return;
|
||||
|
||||
const adjustable = btn.closest('swp-adjustable-value');
|
||||
const row = btn.closest('swp-service-row');
|
||||
if (!adjustable || !row) return;
|
||||
|
||||
const serviceId = parseInt(row.dataset.id);
|
||||
const state = serviceState.get(serviceId);
|
||||
const originalPrice = parseInt(e.target.dataset.original);
|
||||
const newPrice = parseInt(e.target.value) || originalPrice;
|
||||
const service = allServices.find(s => s.id === serviceId);
|
||||
const step = parseInt(adjustable.dataset.step);
|
||||
const isUp = btn.classList.contains('adjust-up');
|
||||
const delta = isUp ? step : -step;
|
||||
|
||||
if (newPrice !== originalPrice) {
|
||||
state.priceOverride = newPrice;
|
||||
row.classList.add('has-override');
|
||||
} else {
|
||||
state.priceOverride = null;
|
||||
row.classList.remove('has-override');
|
||||
if (adjustable.dataset.type === 'price') {
|
||||
// Adjust price
|
||||
const original = parseInt(adjustable.dataset.original);
|
||||
let current = parseInt(adjustable.dataset.value);
|
||||
current = Math.max(0, current + delta);
|
||||
adjustable.dataset.value = current;
|
||||
|
||||
const isModified = current !== original;
|
||||
state.priceOverride = isModified ? current : null;
|
||||
|
||||
// Update displays
|
||||
adjustable.querySelector('.adjust-display').textContent = `${current} kr`;
|
||||
adjustable.classList.toggle('modified', isModified);
|
||||
|
||||
const priceDisplay = row.querySelector('swp-service-price');
|
||||
if (priceDisplay) {
|
||||
priceDisplay.textContent = `${current} kr`;
|
||||
priceDisplay.classList.toggle('modified', isModified);
|
||||
}
|
||||
|
||||
// Toggle strikethrough on all original price elements (read + edit mode)
|
||||
row.querySelectorAll('.original-price-val').forEach(el => {
|
||||
el.classList.toggle('struck', isModified);
|
||||
});
|
||||
} else if (adjustable.dataset.type === 'duration') {
|
||||
// Adjust duration offset
|
||||
const base = parseInt(adjustable.dataset.base);
|
||||
let offset = parseInt(adjustable.dataset.offset);
|
||||
offset = Math.max(-base, offset + delta); // Don't go below 0 total
|
||||
adjustable.dataset.offset = offset;
|
||||
|
||||
const effectiveDuration = base + offset;
|
||||
const isModified = offset !== 0;
|
||||
state.durationOverride = offset;
|
||||
|
||||
// Update displays
|
||||
adjustable.querySelector('.adjust-display').textContent = formatDuration(effectiveDuration);
|
||||
adjustable.classList.toggle('modified', isModified);
|
||||
|
||||
const durationDisplay = row.querySelector('swp-service-duration');
|
||||
if (durationDisplay) {
|
||||
durationDisplay.textContent = formatDuration(effectiveDuration);
|
||||
durationDisplay.classList.toggle('modified', isModified);
|
||||
}
|
||||
|
||||
// Toggle strikethrough on all original duration elements (read + edit mode)
|
||||
row.querySelectorAll('.original-duration-val').forEach(el => {
|
||||
el.classList.toggle('struck', isModified);
|
||||
});
|
||||
}
|
||||
|
||||
// Check if any override exists
|
||||
const hasOverride = state.priceOverride !== null || state.durationOverride !== 0;
|
||||
row.classList.toggle('has-override', hasOverride);
|
||||
|
||||
// Handle originals box visibility and highlight
|
||||
const valuesContainer = row.querySelector('swp-service-values');
|
||||
let originalsBox = valuesContainer.querySelector('swp-service-originals');
|
||||
const originalsEditBox = row.querySelector('swp-service-originals-edit');
|
||||
|
||||
if (hasOverride) {
|
||||
// Show originals box if not present (insert at beginning)
|
||||
const hasPriceOverride = state.priceOverride !== null;
|
||||
const hasDurationOverride = state.durationOverride !== 0;
|
||||
|
||||
if (!originalsBox) {
|
||||
originalsBox = document.createElement('swp-service-originals');
|
||||
originalsBox.innerHTML = `
|
||||
<span class="original-price-val ${hasPriceOverride ? 'struck' : ''}">${service.price} kr</span>
|
||||
<span class="original-duration-val ${hasDurationOverride ? 'struck' : ''}">${formatDuration(service.duration)}</span>
|
||||
`;
|
||||
valuesContainer.insertBefore(originalsBox, valuesContainer.firstChild);
|
||||
} else {
|
||||
// Update struck classes on existing originals box
|
||||
const priceVal = originalsBox.querySelector('.original-price-val');
|
||||
const durationVal = originalsBox.querySelector('.original-duration-val');
|
||||
if (priceVal) priceVal.classList.toggle('struck', hasPriceOverride);
|
||||
if (durationVal) durationVal.classList.toggle('struck', hasDurationOverride);
|
||||
}
|
||||
// Highlight animation
|
||||
originalsBox.classList.remove('highlight');
|
||||
originalsEditBox?.classList.remove('highlight');
|
||||
void originalsBox.offsetWidth; // Trigger reflow
|
||||
originalsBox.classList.add('highlight');
|
||||
originalsEditBox?.classList.add('highlight');
|
||||
} else {
|
||||
// Remove originals box if no overrides
|
||||
if (originalsBox) originalsBox.remove();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -2875,11 +3381,13 @@
|
|||
const servicesPanel = document.getElementById('servicesPanel');
|
||||
const panelToggle = document.getElementById('panelToggle');
|
||||
const editServicesBtn = document.getElementById('editServicesBtn');
|
||||
const selectedServicesCard = document.querySelector('.selected-services-card');
|
||||
|
||||
function toggleServicesPanel() {
|
||||
const isCollapsed = servicesPanel.classList.contains('collapsed');
|
||||
servicesPanel.classList.toggle('collapsed', !isCollapsed);
|
||||
servicesPanel.classList.toggle('expanded', isCollapsed);
|
||||
selectedServicesCard.classList.toggle('edit-mode', isCollapsed);
|
||||
editServicesBtn.classList.toggle('active', isCollapsed);
|
||||
editServicesBtn.innerHTML = isCollapsed
|
||||
? `<svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg> Luk`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue