diff --git a/wwwroot/icons/booking.svg b/wwwroot/icons/booking.svg
new file mode 100644
index 0000000..7a65614
--- /dev/null
+++ b/wwwroot/icons/booking.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/wwwroot/icons/check-in-calendar.svg b/wwwroot/icons/check-in-calendar.svg
new file mode 100644
index 0000000..a47968f
--- /dev/null
+++ b/wwwroot/icons/check-in-calendar.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/wwwroot/icons/comment-sms.svg b/wwwroot/icons/comment-sms.svg
new file mode 100644
index 0000000..f6e7a17
--- /dev/null
+++ b/wwwroot/icons/comment-sms.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/wwwroot/icons/created.svg b/wwwroot/icons/created.svg
new file mode 100644
index 0000000..663e635
--- /dev/null
+++ b/wwwroot/icons/created.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wwwroot/icons/unlock.svg b/wwwroot/icons/unlock.svg
new file mode 100644
index 0000000..35971b9
--- /dev/null
+++ b/wwwroot/icons/unlock.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/wwwroot/poc-customer-detail.html b/wwwroot/poc-customer-detail.html
index fce68e1..56146e1 100644
--- a/wwwroot/poc-customer-detail.html
+++ b/wwwroot/poc-customer-detail.html
@@ -1633,6 +1633,16 @@
font-size: 11px;
}
+ swp-activity-filter .filter-icon img {
+ width: 12px;
+ height: 12px;
+ filter: invert(45%) sepia(0%) saturate(0%) brightness(90%);
+ }
+
+ swp-activity-filter.active .filter-icon img {
+ filter: brightness(0) invert(1);
+ }
+
/* Activity Timeline */
swp-activity-timeline {
display: block;
@@ -1687,39 +1697,65 @@
flex-shrink: 0;
}
+ swp-activity-icon img {
+ width: 16px;
+ height: 16px;
+ }
+
swp-activity-icon.system {
background: var(--color-background);
- color: var(--color-text-secondary);
+ }
+
+ swp-activity-icon.system img {
+ filter: invert(45%) sepia(0%) saturate(0%) brightness(90%);
}
swp-activity-icon.customer {
background: color-mix(in srgb, var(--color-blue) 15%, white);
- color: var(--color-blue);
+ }
+
+ swp-activity-icon.customer img {
+ filter: invert(32%) sepia(98%) saturate(1234%) hue-rotate(196deg) brightness(93%) contrast(92%);
}
swp-activity-icon.employee {
background: color-mix(in srgb, var(--color-teal) 15%, white);
- color: var(--color-teal);
+ }
+
+ swp-activity-icon.employee img {
+ filter: invert(32%) sepia(98%) saturate(1234%) hue-rotate(152deg) brightness(93%) contrast(92%);
}
swp-activity-icon.booking {
background: color-mix(in srgb, var(--color-purple) 15%, white);
- color: var(--color-purple);
+ }
+
+ swp-activity-icon.booking img {
+ filter: invert(45%) sepia(70%) saturate(2000%) hue-rotate(235deg) brightness(90%) contrast(95%);
}
swp-activity-icon.communication {
background: color-mix(in srgb, var(--color-amber) 15%, white);
- color: #b8860b;
+ }
+
+ swp-activity-icon.communication img {
+ filter: invert(55%) sepia(80%) saturate(500%) hue-rotate(10deg) brightness(95%) contrast(95%);
}
swp-activity-icon.payment {
background: color-mix(in srgb, var(--color-green) 15%, white);
- color: var(--color-green);
+ }
+
+ swp-activity-icon.payment img {
+ filter: invert(45%) sepia(70%) saturate(500%) hue-rotate(90deg) brightness(95%) contrast(90%);
}
swp-activity-icon.warning {
background: color-mix(in srgb, var(--color-red) 15%, white);
- color: var(--color-red);
+ }
+
+ swp-activity-icon.warning img {
+ filter: invert(30%) sepia(90%) saturate(2000%) hue-rotate(340deg) brightness(90%) contrast(95%);
}
swp-activity-content {
@@ -1834,6 +1870,94 @@
swp-activity-load-more:hover {
text-decoration: underline;
}
+
+ /* ==========================================
+ DRAG & DROP CARDS
+ ========================================== */
+
+ /* Draggable Card */
+ swp-card[draggable="true"] {
+ position: relative;
+ transition: transform 200ms ease, box-shadow 200ms ease, opacity 200ms ease;
+ }
+
+ swp-card[draggable="true"]:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ }
+
+ swp-card[draggable="true"].dragging {
+ opacity: 0.5;
+ transform: scale(0.98);
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+ }
+
+ /* Drag Handle */
+ swp-drag-handle {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ width: 24px;
+ height: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ color: var(--color-text-secondary);
+ opacity: 0;
+ transition: opacity 150ms ease, background 150ms ease;
+ cursor: move;
+ }
+
+ swp-drag-handle svg {
+ width: 14px;
+ height: 14px;
+ fill: currentColor;
+ }
+
+ swp-card[draggable="true"]:hover swp-drag-handle {
+ opacity: 0.5;
+ }
+
+ swp-drag-handle:hover {
+ opacity: 1 !important;
+ background: var(--color-background);
+ }
+
+ /* Column Drop Zones */
+ swp-card-column {
+ display: flex;
+ flex-direction: column;
+ min-height: 200px;
+ position: relative;
+ }
+
+ /* Drop zone indicator element - dynamically inserted */
+ swp-drop-indicator {
+ display: block;
+ height: 60px;
+ background: color-mix(in srgb, var(--color-teal) 8%, transparent);
+ border: 2px dashed var(--color-teal);
+ border-radius: 8px;
+ margin: 8px 0;
+ transition: opacity 150ms ease;
+ }
+
+ /* Column empty state drop zone */
+ swp-card-column.drag-over-empty {
+ background: color-mix(in srgb, var(--color-teal) 5%, white);
+ border: 2px dashed var(--color-teal);
+ border-radius: 8px;
+ min-height: 100px;
+ }
+
+ /* Cards have no gap by default */
+ swp-card-column swp-card {
+ margin-bottom: 16px;
+ }
+
+ swp-card-column swp-card:last-child {
+ margin-bottom: 0;
+ }
@@ -1912,8 +2036,9 @@
-
-
+
+
+
Kontaktoplysninger
@@ -1935,7 +2060,8 @@
-
+
+
Profil
@@ -1956,11 +2082,12 @@
-
+
-
-
+
+
+
Marketing
Email marketing
@@ -1978,7 +2105,8 @@
-
+
+
Præferencer
@@ -1996,7 +2124,8 @@
-
+
+
Advarsler
@@ -2006,7 +2135,8 @@
-
+
+
Kundegruppe & Relationer
@@ -2056,7 +2186,7 @@
-
+
@@ -2621,11 +2751,11 @@
Alle
- 📅 Bookinger
- 💬 Kommunikation
- ✏️ Ændringer
- 💳 Betalinger
- 🔐 Login
+ Bookinger
+ Kommunikation
+ Ændringer
+ Betalinger
+ Login
@@ -2636,7 +2766,7 @@
I dag
- 💬
+
SMS påmindelse sendt om aftale i morgen
@@ -2650,7 +2780,7 @@
- 👤
+
Kunde loggede ind via online booking
@@ -2669,7 +2799,7 @@
9. december 2025
- 💳
+
Betaling modtaget — 1.799 kr (Dankort)
@@ -2682,7 +2812,7 @@
- 📅
+
Aftale gennemført — Klip + Farve hos Emma L.
@@ -2695,7 +2825,7 @@
- 📅
+
Check-in — Kunden er ankommet
@@ -2708,7 +2838,7 @@
- ✏️
+
Journal opdateret — Ny farveformel tilføjet
@@ -2726,7 +2856,7 @@
5. december 2025
- 📅
+
Ny booking oprettet — 9. dec kl. 10:00, Klip + Farve
@@ -2740,7 +2870,7 @@
- 👤
+
Kunde loggede ind via online booking
@@ -2759,7 +2889,7 @@
28. november 2025
- ✏️
+
Telefon ændret — +45 12 34 56 78 → +45 23 45 67 89
@@ -2775,7 +2905,7 @@
- ✏️
+
Tag tilføjet — VIP
@@ -2793,7 +2923,7 @@
12. november 2025
- ⚠️
+
Booking aflyst — Af kunden med 2 dages varsel
@@ -2809,7 +2939,7 @@
- 💬
+
Email sendt — Booking bekræftelse
@@ -2828,7 +2958,7 @@
15. marts 2024
- ✨
+
Kunde oprettet
@@ -3048,6 +3178,149 @@
// In a real app, this would filter the activity items
});
});
+
+ // ==========================================
+ // CARD DRAG & DROP
+ // ==========================================
+ let draggedCard = null;
+ let dropIndicator = null;
+
+ // Create drop indicator element
+ function createDropIndicator() {
+ if (!dropIndicator) {
+ dropIndicator = document.createElement('swp-drop-indicator');
+ }
+ return dropIndicator;
+ }
+
+ function removeDropIndicator() {
+ if (dropIndicator && dropIndicator.parentNode) {
+ dropIndicator.remove();
+ }
+ }
+
+ function clearDropIndicators() {
+ removeDropIndicator();
+ document.querySelectorAll('.drag-over-empty').forEach(el => {
+ el.classList.remove('drag-over-empty');
+ });
+ }
+
+ // Find the closest card and position based on mouse Y
+ function findDropPosition(column, mouseY) {
+ const cards = Array.from(column.querySelectorAll('swp-card[draggable="true"]:not(.dragging)'));
+ if (cards.length === 0) return { card: null, position: null };
+
+ for (let i = 0; i < cards.length; i++) {
+ const card = cards[i];
+ const rect = card.getBoundingClientRect();
+ const cardMiddle = rect.top + rect.height / 2;
+
+ // If mouse is above the middle of this card, insert before it
+ if (mouseY < cardMiddle) {
+ return { card, position: 'before' };
+ }
+ }
+
+ // Mouse is below all cards, insert after the last one
+ return { card: cards[cards.length - 1], position: 'after' };
+ }
+
+ // Setup draggable cards
+ document.querySelectorAll('swp-card[draggable="true"]').forEach(card => {
+ card.addEventListener('dragstart', (e) => {
+ draggedCard = card;
+ card.classList.add('dragging');
+ e.dataTransfer.effectAllowed = 'move';
+ e.dataTransfer.setData('text/plain', card.dataset.cardId);
+
+ setTimeout(() => {
+ card.style.opacity = '0.4';
+ }, 0);
+ });
+
+ card.addEventListener('dragend', () => {
+ card.classList.remove('dragging');
+ card.style.opacity = '';
+ draggedCard = null;
+ clearDropIndicators();
+ });
+ });
+
+ // Handle all dragover at column level for seamless detection
+ document.querySelectorAll('swp-card-column').forEach(column => {
+ column.addEventListener('dragover', (e) => {
+ e.preventDefault();
+ if (!draggedCard) return;
+
+ e.dataTransfer.dropEffect = 'move';
+
+ const cards = column.querySelectorAll('swp-card[draggable="true"]:not(.dragging)');
+
+ if (cards.length === 0) {
+ // Empty column
+ column.classList.add('drag-over-empty');
+ removeDropIndicator();
+ return;
+ }
+
+ column.classList.remove('drag-over-empty');
+
+ // Find where to show the indicator
+ const { card, position } = findDropPosition(column, e.clientY);
+
+ if (card) {
+ const indicator = createDropIndicator();
+
+ if (position === 'before') {
+ if (indicator.nextElementSibling !== card) {
+ card.before(indicator);
+ }
+ } else {
+ if (indicator.previousElementSibling !== card) {
+ card.after(indicator);
+ }
+ }
+ }
+ });
+
+ column.addEventListener('dragleave', (e) => {
+ if (!column.contains(e.relatedTarget)) {
+ column.classList.remove('drag-over-empty');
+ removeDropIndicator();
+ }
+ });
+
+ column.addEventListener('drop', (e) => {
+ e.preventDefault();
+ if (!draggedCard) return;
+
+ // Insert dragged card where the indicator is, or at end of empty column
+ if (dropIndicator && dropIndicator.parentNode) {
+ dropIndicator.before(draggedCard);
+ } else if (column.classList.contains('drag-over-empty')) {
+ column.appendChild(draggedCard);
+ }
+
+ clearDropIndicators();
+ });
+ });
+
+ // Also handle drop on the indicator itself
+ document.addEventListener('dragover', (e) => {
+ if (e.target.tagName === 'SWP-DROP-INDICATOR') {
+ e.preventDefault();
+ e.dataTransfer.dropEffect = 'move';
+ }
+ });
+
+ document.addEventListener('drop', (e) => {
+ if (e.target.tagName === 'SWP-DROP-INDICATOR' && draggedCard) {
+ e.preventDefault();
+ dropIndicator.before(draggedCard);
+ clearDropIndicators();
+ }
+ });
diff --git a/wwwroot/poc-service-detail.html b/wwwroot/poc-service-detail.html
new file mode 100644
index 0000000..1dc3bf2
--- /dev/null
+++ b/wwwroot/poc-service-detail.html
@@ -0,0 +1,2244 @@
+
+
+
+
+
+ Service Detaljer - Klip & Farve
+
+
+
+
+
+
+
+
+
+
+ Tilbage til services
+
+ Service detaljer
+
+
+ Duplikér
+ Gem ændringer
+
+
+
+
+
+
+
+ Klip & Farve
+
+ Populær
+ Kombi
+ Farve
+ + Tilføj tag
+
+
+ ●
+ Aktiv
+
+
+
+
+ 60-120
+ min varighed
+
+
+ 795 kr
+ fra pris
+
+
+ 4
+ medarbejdere
+
+
+ 156
+ bookinger i år
+
+
+
+
+
+
+
+ Generelt
+ Priser
+ Varighed
+ Medarbejdere
+ Tilvalg
+ Regler
+
+
+
+
+
+
+
+ Grundlæggende
+
+
+ Servicenavn
+ Klip & Farve
+
+
+ Kategori
+
+
+ Kombi-behandlinger
+
+
+
+ Kombi-behandlinger
+ Klip
+ Farve
+ Behandlinger
+ Styling
+
+
+
+
+ Farve i kalenderen
+
+
+
+ Teal
+
+
+
+ Rød
+ Pink
+ Magenta
+ Lilla
+ Violet
+ Mørk lilla
+ Indigo
+ Blå
+ Lyseblå
+ Cyan
+ Teal
+ Grøn
+ Lysegrøn
+ Lime
+ Gul
+ Amber
+ Orange
+ Mørk orange
+
+
+
+
+ Service aktiv
+
+ Ja
+ Nej
+
+
+
+
+ Interne noter
+ Komplet farvebehandling med klip. Husk konsultation ved første besøg. Anbefal Olaplex til kemisk behandlet hår.
+
+
+
+ Bookingtype
+
+
+ Kan bookes som hovedservice
+ Vises i servicelisten og kan bookes selvstændigt
+
+
+ Ja
+ Nej
+
+
+
+
+ Kan bookes som tilvalg
+ Kan tilføjes som ekstra ydelse til andre services
+
+
+ Ja
+ Nej
+
+
+
+
+
+
+
+ Online booking
+
+
+ Vis i online booking
+ Synlig for kunder i online booking
+
+
+ Ja
+ Nej
+
+
+
+
+ Fremhævet service
+ Vises øverst med fremhævet styling
+
+
+ Ja
+ Nej
+
+
+
+ Beskrivelse
+ Forkæl dig selv med en komplet forvandling! Vores Klip & Farve behandling inkluderer professionel farverådgivning, farvning tilpasset din hudtone, præcisionsklip og styling. Perfekt til dig der ønsker et helt nyt look.
+
+ Billede
+ + Upload billede
+
+
+
+
+
+
+
+
+
+ Prisstruktur
+
+
+ Simpel pris
+ Matrix-pris
+
+
+
+
+
+
+ Pris
+ 995 kr
+
+
+
+
+
+
+
+
+
+ Niveau
+ Kort hår
+ Mellem hår
+ Langt hår
+ Ekstra langt
+
+
+
+
+ Junior
+ 795 kr
+ 895 kr
+ 995 kr
+ 1.095 kr
+
+
+ Senior
+ 895 kr
+ 995 kr
+ 1.095 kr
+ 1.195 kr
+
+
+ Master
+ 995 kr
+ 1.095 kr
+ 1.195 kr
+ 1.295 kr
+
+
+
+
+ + Tilføj niveau eller hårlængde
+
+
+
+
+
+ Økonomi
+
+
+ Momssats
+
+
+ 25% (standard)
+ 0% (momsfri)
+
+
+
+
+ Produktomkostning
+ 85 kr
+
+
+ Provision
+
+
+ Standard (fra lønsats)
+ Fast beløb
+ Procent af pris
+
+
+
+
+
+
+
+ Rabatter & Loyalitet
+
+ Medlemsrabat (10%)
+
+ Ja
+ Nej
+
+
+
+ Kan betales med gavekort
+
+ Ja
+ Nej
+
+
+
+ Optjen loyalitetspoint
+
+ Ja
+ Nej
+
+
+
+
+
+
+
+
+
+
+ Varighedsvarianter
+
+
+
+ Kort hår
+
+ 60
+ min
+
+ ✕
+
+
+
+ Mellem hår
+
+ 90
+ min
+
+ ✕
+
+
+
+ Langt hår
+
+ 120
+ min
+
+ ✕
+
+
+
+ Ekstra langt hår
+
+ 150
+ min
+
+ ✕
+
+
+
+ + Tilføj variant
+
+
+
+ Buffer-tider
+
+
+ Buffer før aftale
+
+
+ Ingen
+ 5 minutter
+ 10 minutter
+ 15 minutter
+ 30 minutter
+
+
+
+
+ Buffer efter aftale
+
+
+ Ingen
+ 5 minutter
+ 10 minutter
+ 15 minutter
+ 30 minutter
+
+
+
+
+ Oprydningstid
+
+
+ Ingen
+ 5 minutter
+ 10 minutter
+ 15 minutter
+
+
+
+
+
+
+
+
+
+
+
+ Medarbejdere der udfører denne service
+
+
+
+ AS
+
+ Anna Sørensen
+ Master Stylist
+
+
+ Pris: Standard
+ Varighed: Standard
+
+
+ Ja
+ Nej
+
+
+
+
+ MJ
+
+ Mette Jensen
+ Senior Stylist
+
+
+ Pris: Standard
+ Varighed: +15 min
+
+
+ Ja
+ Nej
+
+
+
+
+ LN
+
+ Louise Nielsen
+ Senior Stylist
+
+
+ Pris: Standard
+ Varighed: Standard
+
+
+ Ja
+ Nej
+
+
+
+
+ KP
+
+ Katrine Pedersen
+ Stylist
+
+
+ Pris: Standard
+ Varighed: +15 min
+
+
+ Ja
+ Nej
+
+
+
+
+ SA
+
+ Sofie Andersen⚠ Ikke certificeret
+ Junior Stylist
+
+
+ Pris: —
+ Varighed: —
+
+
+ Ja
+ Nej
+
+
+
+
+ Vælg alle / Fravælg alle
+
+
+
+
+
+
+ Tilvalg til denne service
+
+
+
+
+
+
+
+ Olaplex Behandling
+
+ +250 kr
+ +15 min
+ Valgfri
+
+
+
+
+
+
+
+
+
+ Kerastase Hårkur
+
+ +195 kr
+ +10 min
+ Valgfri
+
+
+
+
+
+
+
+
+
+ Styling / Curls
+
+ +150 kr
+ +20 min
+ Valgfri
+
+
+
+
+
+
+
+
+
+ Hovedbundsmassage
+
+ +75 kr
+ +5 min
+ Valgfri
+
+
+
+
+
+
+
+
+
+ Patch Test
+
+ Gratis
+ 48t før
+ Påkrævet (nye)
+
+
+
+
+
+ + Tilføj eksisterende tilvalg
+
+
+
+
+
+
+
+ Booking-regler
+
+
+ Minimum varsel
+
+
+ Ingen
+ 2 timer
+ 4 timer
+ 24 timer
+ 48 timer
+ 1 uge
+
+
+
+
+ Maks. forudbooking
+
+
+ 1 måned
+ 2 måneder
+ 3 måneder
+ 6 måneder
+ 1 år
+
+
+
+
+ Afbestillingsfrist
+
+
+ Ingen
+ 2 timer
+ 4 timer
+ 24 timer
+ 48 timer
+
+
+
+
+ No-show gebyr
+
+
+ Intet
+ 25% af pris
+ 50% af pris
+ Fuld pris
+ Fast beløb
+
+
+
+
+
+
+
+ Tilgængelighed
+
+
+ Mandag
+
+
+ Hele dagen
+ Formiddag (før 12:00)
+ Eftermiddag (efter 12:00)
+ Brugerdefineret
+
+
+
+ Ja
+ Nej
+
+
+
+ Tirsdag
+
+
+ Hele dagen
+ Formiddag (før 12:00)
+ Eftermiddag (efter 12:00)
+ Brugerdefineret
+
+
+
+ Ja
+ Nej
+
+
+
+ Onsdag
+
+
+ Hele dagen
+ Formiddag (før 12:00)
+ Eftermiddag (efter 12:00)
+ Brugerdefineret
+
+
+
+ Ja
+ Nej
+
+
+
+ Torsdag
+
+
+ Hele dagen
+ Formiddag (før 12:00)
+ Eftermiddag (efter 12:00)
+ Brugerdefineret
+
+
+
+ Ja
+ Nej
+
+
+
+ Fredag
+
+
+ Hele dagen
+ Formiddag (før 12:00)
+ Eftermiddag (efter 12:00)
+ Brugerdefineret
+
+
+
+ Ja
+ Nej
+
+
+
+ Lørdag
+
+
+ Hele dagen
+ Formiddag (før 12:00)
+ Eftermiddag (efter 12:00)
+ Brugerdefineret
+
+
+
+ Ja
+ Nej
+
+
+
+ Søndag
+
+
+ Hele dagen
+ Formiddag (før 12:00)
+ Eftermiddag (efter 12:00)
+ Brugerdefineret
+
+
+
+ Ja
+ Nej
+
+
+
+
+
+
+ Krav & Forberedelse
+
+
+ Konsultation påkrævet
+ Kunde skal have konsultation før første booking
+
+
+ Ja
+ Nej
+
+
+
+
+ Patch test påkrævet
+ Allergitest 48 timer før farvebehandling (nye kunder)
+
+
+ Ja
+ Nej
+
+
+
+
+ Aldersbegrænsning
+ Minimum alder for booking af denne service
+
+
+ Ja
+ Nej
+
+
+
+
+ Samtykke-formular
+ Kunde skal underskrive samtykke før behandling
+
+
+ Ja
+ Nej
+
+
+
+
+
+
+ Online booking indstillinger
+
+ Vis i online booking
+
+ Ja
+ Nej
+
+
+
+ Tillad valg af medarbejder
+
+ Ja
+ Nej
+
+
+
+ Vis pris
+
+ Ja
+ Nej
+
+
+
+ Vis varighed
+
+ Ja
+ Nej
+
+
+
+
+
+
+
+
+
+