Enhances UI with drag-and-drop card functionality

Adds drag and drop interaction for rearranging cards on the customer and service detail pages

Improves user experience by allowing flexible card positioning
Implements dynamic drop indicators and column management
Supports intuitive card movement between columns
This commit is contained in:
Janus C. H. Knudsen 2025-12-23 09:32:49 +01:00
parent 7965e8e753
commit 9f46ff8824
7 changed files with 2565 additions and 35 deletions

View 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="m3.866,18.965c-.186.32-.521.5-.867.5-.17,0-.342-.043-.5-.134-1.542-.892-2.5-2.551-2.5-4.331V7C0,4.243,2.243,2,5,2v-1c0-.552.448-1,1-1s1,.448,1,1v1h7v-1c0-.552.447-1,1-1s1,.448,1,1v1c2.757,0,5,2.243,5,5v8c0,.552-.447,1-1,1s-1-.448-1-1v-6h-1c-.553,0-1-.448-1-1s.447-1,1-1h1c0-1.654-1.346-3-3-3H5c-1.654,0-3,1.346-3,3h8c.552,0,1,.448,1,1s-.448,1-1,1H2v6c0,1.068.575,2.064,1.5,2.599.478.276.642.888.365,1.366Zm16.892-.385l-3.749-1.401v-5.045c0-1.516-1.076-2.834-2.503-3.066-.881-.143-1.768.102-2.439.673-.672.571-1.058,1.405-1.058,2.286v7.563l-1.015-.808c-.007-.006-.016-.006-.023-.012-1.211-1.053-3.049-.975-4.153.207-1.13,1.208-1.066,3.11.13,4.23l.558.538c.186.18.435.28.694.28.9,0,1.342-1.095.694-1.72l-.568-.548c-.403-.378-.424-1.013-.046-1.416.375-.402,1.008-.421,1.41-.048.01.009,2.697,2.151,2.697,2.151.301.24.713.285,1.057.119.346-.167.566-.517.566-.901v-9.638c0-.294.129-.572.353-.763.228-.193.518-.273.822-.223.463.076.825.556.825,1.093v5.739c0,.417.259.791.65.937l4.399,1.644c1.104.412,1.866,1.438,1.943,2.612.035.529.475.935.997.935.022,0,.044,0,.066-.002.551-.037.969-.513.933-1.063-.129-1.958-1.4-3.668-3.24-4.354Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View 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="m24,7.5v11c0,3.033-2.467,5.5-5.5,5.5H5.5c-2.511,0-4.701-1.697-5.327-4.126-.207-.802.276-1.62,1.079-1.827.804-.208,1.62.277,1.827,1.079.284,1.104,1.28,1.874,2.421,1.874h13c1.378,0,2.5-1.122,2.5-2.5v-9.5H3v.5c0,.829-.671,1.5-1.5,1.5s-1.5-.671-1.5-1.5v-2C0,4.467,2.467,2,5.5,2h.5v-.5c0-.829.671-1.5,1.5-1.5s1.5.671,1.5,1.5v.5h6v-.5c0-.829.671-1.5,1.5-1.5s1.5.671,1.5,1.5v.5h.5c3.033,0,5.5,2.467,5.5,5.5Zm-15.346,10.748l3.063-3.063c.378-.378.378-.991,0-1.369l-3.063-3.063c-.61-.61-1.653-.178-1.653.685v1.563H1.5c-.829,0-1.5.671-1.5,1.5s.671,1.5,1.5,1.5h5.501v1.563c0,.863,1.043,1.295,1.653.685Z"/>
</svg>

After

Width:  |  Height:  |  Size: 745 B

View 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="M19,24h-5.917C6.082,24,.47,19.208,.03,12.854-.211,9.378,1.057,5.977,3.509,3.521,5.96,1.066,9.364-.205,12.836,.028c6.26,.426,11.164,5.833,11.164,12.312v6.66c0,2.757-2.243,5-5,5ZM12.016,2.001c-2.657,0-5.209,1.049-7.092,2.934-2.043,2.046-3.1,4.882-2.899,7.781,.373,5.38,5.024,9.284,11.059,9.284h5.917c1.654,0,3-1.346,3-3v-6.66c0-5.431-4.084-9.962-9.299-10.315-.229-.016-.458-.023-.685-.023Zm2.984,7.624c0-.345-.28-.624-.625-.625-.399,0-.78,.173-1.042,.474l-1.333,1.526-1.352-1.548c-.251-.287-.578-.452-1.023-.452-.345,0-.625,.28-.625,.625v4.75c0,.345,.28,.625,.625,.625s.625-.28,.625-.625l.002-3.269,1.282,1.389c.251,.272,.681,.272,.932,0l1.281-1.388v3.267h.003c0,.345,.28,.625,.625,.625s.625-.28,.625-.625l-.003-.016,.003-4.734Zm-8.53,1.843c-.658-.243-1.257-.506-1.272-.506-.143-.097-.138-.243-.13-.302,.012-.079,.069-.273,.362-.361,.119-.035,.236-.049,.349-.047,.509,.008,.926,.324,.946,.34,.11,.095,.249,.158,.405,.158,.345,0,.625-.28,.625-.625,0-.197-.097-.365-.239-.48-.037-.031-.773-.648-1.766-.645-.216,0-.445,.03-.68,.101-.671,.202-1.146,.731-1.239,1.38-.087,.61,.178,1.197,.777,1.579,0,0,.678,.303,1.43,.58,.159,.059,.672,.276,.61,.621-.046,.256-.361,.521-.81,.521-.468,0-.919-.187-1.206-.503-.114-.125-.275-.206-.458-.206-.345,0-.625,.28-.625,.625,0,.161,.065,.304,.164,.414,.527,.566,1.309,.92,2.124,.92,1.021,0,1.88-.653,2.04-1.552,.121-.678-.186-1.562-1.408-2.014Zm12.558,0c-.658-.243-1.257-.506-1.272-.506-.143-.097-.138-.243-.13-.302,.012-.079,.069-.273,.362-.361,.119-.035,.236-.049,.349-.047,.509,.008,.926,.324,.946,.34,.11,.095,.249,.158,.405,.158,.345,0,.625-.28,.625-.625,0-.197-.097-.365-.239-.48-.037-.031-.773-.648-1.766-.645-.216,0-.445,.03-.68,.101-.671,.202-1.146,.731-1.239,1.38-.087,.61,.178,1.197,.777,1.579,0,0,.678,.303,1.43,.58,.159,.059,.672,.276,.61,.621-.046,.256-.361,.521-.81,.521-.468,0-.919-.187-1.206-.503-.114-.125-.275-.206-.458-.206-.345,0-.625,.28-.625,.625,0,.161,.065,.304,.164,.414,.527,.566,1.309,.92,2.124,.92,1.021,0,1.88-.653,2.04-1.552,.121-.678-.186-1.562-1.408-2.014Z"/></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1 @@
<svg id="Layer_1" height="512" viewBox="0 0 24 24" width="512" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="m18 9.064a3.049 3.049 0 0 0 -.9-2.164 3.139 3.139 0 0 0 -4.334 0l-11.866 11.869a3.064 3.064 0 0 0 4.33 4.331l11.87-11.869a3.047 3.047 0 0 0 .9-2.167zm-14.184 12.624a1.087 1.087 0 0 1 -1.5 0 1.062 1.062 0 0 1 0-1.5l7.769-7.77 1.505 1.505zm11.872-11.872-2.688 2.689-1.5-1.505 2.689-2.688a1.063 1.063 0 1 1 1.5 1.5zm-10.825-6.961 1.55-.442.442-1.55a1.191 1.191 0 0 1 2.29 0l.442 1.55 1.55.442a1.191 1.191 0 0 1 0 2.29l-1.55.442-.442 1.55a1.191 1.191 0 0 1 -2.29 0l-.442-1.55-1.55-.442a1.191 1.191 0 0 1 0-2.29zm18.274 14.29-1.55.442-.442 1.55a1.191 1.191 0 0 1 -2.29 0l-.442-1.55-1.55-.442a1.191 1.191 0 0 1 0-2.29l1.55-.442.442-1.55a1.191 1.191 0 0 1 2.29 0l.442 1.55 1.55.442a1.191 1.191 0 0 1 0 2.29zm-5.382-14.645 1.356-.387.389-1.358a1.042 1.042 0 0 1 2 0l.387 1.356 1.356.387a1.042 1.042 0 0 1 0 2l-1.356.387-.387 1.359a1.042 1.042 0 0 1 -2 0l-.387-1.355-1.358-.389a1.042 1.042 0 0 1 0-2z"/></svg>

After

Width:  |  Height:  |  Size: 1 KiB

2
wwwroot/icons/unlock.svg Normal file
View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" id="Isolation_Mode" data-name="Isolation Mode" viewBox="0 0 24 24" width="512" height="512"><path d="M8,8V7.151A4,4,0,0,1,15.494,5.2l2.618-1.465A7,7,0,0,0,5,7.151V8H2V21a3,3,0,0,0,3,3H19a3,3,0,0,0,3-3V8ZM5,21V11H19l0,10Z"/><rect x="10" y="14" width="4" height="3"/></svg>

After

Width:  |  Height:  |  Size: 351 B

View file

@ -1633,6 +1633,16 @@
font-size: 11px; 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 */ /* Activity Timeline */
swp-activity-timeline { swp-activity-timeline {
display: block; display: block;
@ -1687,39 +1697,65 @@
flex-shrink: 0; flex-shrink: 0;
} }
swp-activity-icon img {
width: 16px;
height: 16px;
}
swp-activity-icon.system { swp-activity-icon.system {
background: var(--color-background); 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 { swp-activity-icon.customer {
background: color-mix(in srgb, var(--color-blue) 15%, white); 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 { swp-activity-icon.employee {
background: color-mix(in srgb, var(--color-teal) 15%, white); 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 { swp-activity-icon.booking {
background: color-mix(in srgb, var(--color-purple) 15%, white); 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 { swp-activity-icon.communication {
background: color-mix(in srgb, var(--color-amber) 15%, white); 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 { swp-activity-icon.payment {
background: color-mix(in srgb, var(--color-green) 15%, white); 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 { swp-activity-icon.warning {
background: color-mix(in srgb, var(--color-red) 15%, white); 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 { swp-activity-content {
@ -1834,6 +1870,94 @@
swp-activity-load-more:hover { swp-activity-load-more:hover {
text-decoration: underline; 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;
}
</style> </style>
</head> </head>
<body> <body>
@ -1912,8 +2036,9 @@
<swp-tab-content class="active" data-tab="overview"> <swp-tab-content class="active" data-tab="overview">
<div class="grid-2"> <div class="grid-2">
<!-- Left Column --> <!-- Left Column -->
<div> <swp-card-column data-column="left">
<swp-card> <swp-card draggable="true" data-card-id="kontakt">
<swp-drag-handle><svg viewBox="0 0 24 24"><path d="M13 6v5h5V8l4 4-4 4v-3h-5v5h3l-4 4-4-4h3v-5H6v3l-4-4 4-4v3h5V6H8l4-4 4 4h-3z"/></svg></swp-drag-handle>
<swp-section-label>Kontaktoplysninger</swp-section-label> <swp-section-label>Kontaktoplysninger</swp-section-label>
<swp-edit-section> <swp-edit-section>
<swp-edit-row> <swp-edit-row>
@ -1935,7 +2060,8 @@
</swp-edit-section> </swp-edit-section>
</swp-card> </swp-card>
<swp-card> <swp-card draggable="true" data-card-id="profil">
<swp-drag-handle><svg viewBox="0 0 24 24"><path d="M13 6v5h5V8l4 4-4 4v-3h-5v5h3l-4 4-4-4h3v-5H6v3l-4-4 4-4v3h5V6H8l4-4 4 4h-3z"/></svg></swp-drag-handle>
<swp-section-label>Profil</swp-section-label> <swp-section-label>Profil</swp-section-label>
<swp-profile-boxes> <swp-profile-boxes>
<swp-profile-box> <swp-profile-box>
@ -1956,11 +2082,12 @@
</swp-profile-box> </swp-profile-box>
</swp-profile-boxes> </swp-profile-boxes>
</swp-card> </swp-card>
</div> </swp-card-column>
<!-- Right Column --> <!-- Right Column -->
<div> <swp-card-column data-column="right">
<swp-card> <swp-card draggable="true" data-card-id="marketing">
<swp-drag-handle><svg viewBox="0 0 24 24"><path d="M13 6v5h5V8l4 4-4 4v-3h-5v5h3l-4 4-4-4h3v-5H6v3l-4-4 4-4v3h5V6H8l4-4 4 4h-3z"/></svg></swp-drag-handle>
<swp-section-label>Marketing</swp-section-label> <swp-section-label>Marketing</swp-section-label>
<swp-toggle-row> <swp-toggle-row>
<swp-toggle-label>Email marketing</swp-toggle-label> <swp-toggle-label>Email marketing</swp-toggle-label>
@ -1978,7 +2105,8 @@
</swp-toggle-row> </swp-toggle-row>
</swp-card> </swp-card>
<swp-card> <swp-card draggable="true" data-card-id="praeferencer">
<swp-drag-handle><svg viewBox="0 0 24 24"><path d="M13 6v5h5V8l4 4-4 4v-3h-5v5h3l-4 4-4-4h3v-5H6v3l-4-4 4-4v3h5V6H8l4-4 4 4h-3z"/></svg></swp-drag-handle>
<swp-section-label>Præferencer</swp-section-label> <swp-section-label>Præferencer</swp-section-label>
<swp-profile-boxes> <swp-profile-boxes>
<swp-profile-box> <swp-profile-box>
@ -1996,7 +2124,8 @@
</swp-profile-boxes> </swp-profile-boxes>
</swp-card> </swp-card>
<swp-card> <swp-card draggable="true" data-card-id="advarsler">
<swp-drag-handle><svg viewBox="0 0 24 24"><path d="M13 6v5h5V8l4 4-4 4v-3h-5v5h3l-4 4-4-4h3v-5H6v3l-4-4 4-4v3h5V6H8l4-4 4 4h-3z"/></svg></swp-drag-handle>
<swp-section-label>Advarsler</swp-section-label> <swp-section-label>Advarsler</swp-section-label>
<swp-profile-boxes> <swp-profile-boxes>
<swp-profile-box class="warning full-width"> <swp-profile-box class="warning full-width">
@ -2006,7 +2135,8 @@
</swp-profile-boxes> </swp-profile-boxes>
</swp-card> </swp-card>
<swp-card> <swp-card draggable="true" data-card-id="relationer">
<swp-drag-handle><svg viewBox="0 0 24 24"><path d="M13 6v5h5V8l4 4-4 4v-3h-5v5h3l-4 4-4-4h3v-5H6v3l-4-4 4-4v3h5V6H8l4-4 4 4h-3z"/></svg></swp-drag-handle>
<swp-section-label>Kundegruppe & Relationer</swp-section-label> <swp-section-label>Kundegruppe & Relationer</swp-section-label>
<swp-customer-group-row> <swp-customer-group-row>
@ -2056,7 +2186,7 @@
</swp-add-relation> </swp-add-relation>
</swp-relations-list> </swp-relations-list>
</swp-card> </swp-card>
</div> </swp-card-column>
</div> </div>
</swp-tab-content> </swp-tab-content>
@ -2621,11 +2751,11 @@
<!-- Filters --> <!-- Filters -->
<swp-activity-filters> <swp-activity-filters>
<swp-activity-filter class="active">Alle</swp-activity-filter> <swp-activity-filter class="active">Alle</swp-activity-filter>
<swp-activity-filter><span class="filter-icon">📅</span> Bookinger</swp-activity-filter> <swp-activity-filter><span class="filter-icon"><img src="icons/booking.svg" alt=""></span> Bookinger</swp-activity-filter>
<swp-activity-filter><span class="filter-icon">💬</span> Kommunikation</swp-activity-filter> <swp-activity-filter><span class="filter-icon"><img src="icons/envelope.svg" alt=""></span> Kommunikation</swp-activity-filter>
<swp-activity-filter><span class="filter-icon">✏️</span> Ændringer</swp-activity-filter> <swp-activity-filter><span class="filter-icon"><img src="icons/journal-alt.svg" alt=""></span> Ændringer</swp-activity-filter>
<swp-activity-filter><span class="filter-icon">💳</span> Betalinger</swp-activity-filter> <swp-activity-filter><span class="filter-icon"><img src="icons/credit-card.svg" alt=""></span> Betalinger</swp-activity-filter>
<swp-activity-filter><span class="filter-icon">🔐</span> Login</swp-activity-filter> <swp-activity-filter><span class="filter-icon"><img src="icons/unlock.svg" alt=""></span> Login</swp-activity-filter>
</swp-activity-filters> </swp-activity-filters>
<swp-card> <swp-card>
@ -2636,7 +2766,7 @@
<swp-activity-date-header>I dag</swp-activity-date-header> <swp-activity-date-header>I dag</swp-activity-date-header>
<swp-activity-item> <swp-activity-item>
<swp-activity-icon class="communication">💬</swp-activity-icon> <swp-activity-icon class="communication"><img src="icons/comment-sms.svg" alt=""></swp-activity-icon>
<swp-activity-content> <swp-activity-content>
<swp-activity-title> <swp-activity-title>
<strong>SMS påmindelse</strong> sendt om aftale i morgen <strong>SMS påmindelse</strong> sendt om aftale i morgen
@ -2650,7 +2780,7 @@
</swp-activity-item> </swp-activity-item>
<swp-activity-item> <swp-activity-item>
<swp-activity-icon class="customer">👤</swp-activity-icon> <swp-activity-icon class="customer"><img src="icons/unlock.svg" alt=""></swp-activity-icon>
<swp-activity-content> <swp-activity-content>
<swp-activity-title> <swp-activity-title>
Kunde <strong>loggede ind</strong> via online booking Kunde <strong>loggede ind</strong> via online booking
@ -2669,7 +2799,7 @@
<swp-activity-date-header>9. december 2025</swp-activity-date-header> <swp-activity-date-header>9. december 2025</swp-activity-date-header>
<swp-activity-item> <swp-activity-item>
<swp-activity-icon class="payment">💳</swp-activity-icon> <swp-activity-icon class="payment"><img src="icons/credit-card.svg" alt=""></swp-activity-icon>
<swp-activity-content> <swp-activity-content>
<swp-activity-title> <swp-activity-title>
<strong>Betaling modtaget</strong><span class="highlight">1.799 kr</span> (Dankort) <strong>Betaling modtaget</strong><span class="highlight">1.799 kr</span> (Dankort)
@ -2682,7 +2812,7 @@
</swp-activity-item> </swp-activity-item>
<swp-activity-item> <swp-activity-item>
<swp-activity-icon class="booking">📅</swp-activity-icon> <swp-activity-icon class="booking"><img src="icons/booking.svg" alt=""></swp-activity-icon>
<swp-activity-content> <swp-activity-content>
<swp-activity-title> <swp-activity-title>
<strong>Aftale gennemført</strong> — Klip + Farve hos Emma L. <strong>Aftale gennemført</strong> — Klip + Farve hos Emma L.
@ -2695,7 +2825,7 @@
</swp-activity-item> </swp-activity-item>
<swp-activity-item> <swp-activity-item>
<swp-activity-icon class="booking">📅</swp-activity-icon> <swp-activity-icon class="booking"><img src="icons/check-in-calendar.svg" alt=""></swp-activity-icon>
<swp-activity-content> <swp-activity-content>
<swp-activity-title> <swp-activity-title>
<strong>Check-in</strong> — Kunden er ankommet <strong>Check-in</strong> — Kunden er ankommet
@ -2708,7 +2838,7 @@
</swp-activity-item> </swp-activity-item>
<swp-activity-item> <swp-activity-item>
<swp-activity-icon class="employee">✏️</swp-activity-icon> <swp-activity-icon class="employee"><img src="icons/journal-alt.svg" alt=""></swp-activity-icon>
<swp-activity-content> <swp-activity-content>
<swp-activity-title> <swp-activity-title>
<strong>Journal opdateret</strong> — Ny farveformel tilføjet <strong>Journal opdateret</strong> — Ny farveformel tilføjet
@ -2726,7 +2856,7 @@
<swp-activity-date-header>5. december 2025</swp-activity-date-header> <swp-activity-date-header>5. december 2025</swp-activity-date-header>
<swp-activity-item> <swp-activity-item>
<swp-activity-icon class="booking">📅</swp-activity-icon> <swp-activity-icon class="booking"><img src="icons/created.svg" alt=""></swp-activity-icon>
<swp-activity-content> <swp-activity-content>
<swp-activity-title> <swp-activity-title>
<strong>Ny booking</strong> oprettet — 9. dec kl. 10:00, Klip + Farve <strong>Ny booking</strong> oprettet — 9. dec kl. 10:00, Klip + Farve
@ -2740,7 +2870,7 @@
</swp-activity-item> </swp-activity-item>
<swp-activity-item> <swp-activity-item>
<swp-activity-icon class="customer">👤</swp-activity-icon> <swp-activity-icon class="customer"><img src="icons/unlock.svg" alt=""></swp-activity-icon>
<swp-activity-content> <swp-activity-content>
<swp-activity-title> <swp-activity-title>
Kunde <strong>loggede ind</strong> via online booking Kunde <strong>loggede ind</strong> via online booking
@ -2759,7 +2889,7 @@
<swp-activity-date-header>28. november 2025</swp-activity-date-header> <swp-activity-date-header>28. november 2025</swp-activity-date-header>
<swp-activity-item> <swp-activity-item>
<swp-activity-icon class="employee">✏️</swp-activity-icon> <swp-activity-icon class="employee"><img src="icons/journal-alt.svg" alt=""></swp-activity-icon>
<swp-activity-content> <swp-activity-content>
<swp-activity-title> <swp-activity-title>
<strong>Telefon ændret</strong><span class="old-value">+45 12 34 56 78</span><span class="new-value">+45 23 45 67 89</span> <strong>Telefon ændret</strong><span class="old-value">+45 12 34 56 78</span><span class="new-value">+45 23 45 67 89</span>
@ -2775,7 +2905,7 @@
</swp-activity-item> </swp-activity-item>
<swp-activity-item> <swp-activity-item>
<swp-activity-icon class="employee">✏️</swp-activity-icon> <swp-activity-icon class="employee"><img src="icons/journal-alt.svg" alt=""></swp-activity-icon>
<swp-activity-content> <swp-activity-content>
<swp-activity-title> <swp-activity-title>
<strong>Tag tilføjet</strong><span class="highlight">VIP</span> <strong>Tag tilføjet</strong><span class="highlight">VIP</span>
@ -2793,7 +2923,7 @@
<swp-activity-date-header>12. november 2025</swp-activity-date-header> <swp-activity-date-header>12. november 2025</swp-activity-date-header>
<swp-activity-item> <swp-activity-item>
<swp-activity-icon class="warning">⚠️</swp-activity-icon> <swp-activity-icon class="warning"><img src="icons/warning.svg" alt=""></swp-activity-icon>
<swp-activity-content> <swp-activity-content>
<swp-activity-title> <swp-activity-title>
<strong>Booking aflyst</strong> — Af kunden med 2 dages varsel <strong>Booking aflyst</strong> — Af kunden med 2 dages varsel
@ -2809,7 +2939,7 @@
</swp-activity-item> </swp-activity-item>
<swp-activity-item> <swp-activity-item>
<swp-activity-icon class="communication">💬</swp-activity-icon> <swp-activity-icon class="communication"><img src="icons/envelope.svg" alt=""></swp-activity-icon>
<swp-activity-content> <swp-activity-content>
<swp-activity-title> <swp-activity-title>
<strong>Email sendt</strong> — Booking bekræftelse <strong>Email sendt</strong> — Booking bekræftelse
@ -2828,7 +2958,7 @@
<swp-activity-date-header>15. marts 2024</swp-activity-date-header> <swp-activity-date-header>15. marts 2024</swp-activity-date-header>
<swp-activity-item> <swp-activity-item>
<swp-activity-icon class="system"></swp-activity-icon> <swp-activity-icon class="system"><img src="icons/created.svg" alt=""></swp-activity-icon>
<swp-activity-content> <swp-activity-content>
<swp-activity-title> <swp-activity-title>
<strong>Kunde oprettet</strong> <strong>Kunde oprettet</strong>
@ -3048,6 +3178,149 @@
// In a real app, this would filter the activity items // 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();
}
});
</script> </script>
</body> </body>

File diff suppressed because it is too large Load diff