2025-12-26 14:05:56 +01:00
<!DOCTYPE html>
< html lang = "da" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Medarbejder Detaljer - Anna Sørensen< / title >
< link href = "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel = "stylesheet" >
< style >
/* ==========================================
CSS VARIABLES (Design System)
========================================== */
:root {
--color-surface: #fff;
--color-background: #f5f5f5;
--color-background-hover: #f0f0f0;
--color-background-alt: #fafafa;
--color-border: #e0e0e0;
--color-text: #333;
--color-text-secondary: #666;
--color-teal: #00897b;
--color-blue: #1976d2;
--color-red: #e53935;
--color-amber: #f59e0b;
--color-purple: #8b5cf6;
--color-green: #43a047;
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
}
/* ==========================================
RESET & BASE
========================================== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-family);
font-size: 14px;
color: var(--color-text);
background: var(--color-background);
line-height: 1.5;
}
/* ==========================================
TOPBAR
========================================== */
swp-topbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 24px;
background: var(--color-surface);
border-bottom: 1px solid var(--color-border);
position: sticky;
top: 0;
z-index: 100;
}
swp-topbar-left {
display: flex;
align-items: center;
gap: 16px;
}
swp-back-link {
display: flex;
align-items: center;
gap: 6px;
color: var(--color-text-secondary);
text-decoration: none;
font-size: 13px;
cursor: pointer;
transition: color 150ms ease;
}
swp-back-link:hover {
color: var(--color-teal);
}
swp-back-link svg {
width: 16px;
height: 16px;
fill: currentColor;
}
swp-topbar-title {
font-size: 16px;
font-weight: 600;
color: var(--color-text);
}
swp-topbar-actions {
display: flex;
align-items: center;
gap: 12px;
}
swp-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
font-size: 13px;
font-weight: 500;
border-radius: 6px;
border: none;
cursor: pointer;
transition: all 150ms ease;
}
swp-btn.primary {
background: var(--color-teal);
color: white;
}
swp-btn.primary:hover {
background: #00796b;
}
swp-btn.secondary {
background: var(--color-surface);
color: var(--color-text);
border: 1px solid var(--color-border);
}
swp-btn.secondary:hover {
background: var(--color-background);
}
/* ==========================================
EMPLOYEE HEADER
========================================== */
swp-employee-header {
display: flex;
gap: 24px;
padding: 24px;
background: var(--color-surface);
border-bottom: 1px solid var(--color-border);
position: sticky;
top: 49px;
z-index: 90;
}
swp-employee-avatar-large {
width: 80px;
height: 80px;
border-radius: 50%;
background: linear-gradient(135deg, var(--color-teal) 0%, #00695c 100%);
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
font-weight: 600;
color: white;
flex-shrink: 0;
position: relative;
cursor: pointer;
overflow: hidden;
}
swp-employee-avatar-large img {
width: 100%;
height: 100%;
object-fit: cover;
}
swp-employee-avatar-large swp-avatar-initials {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
swp-employee-avatar-large.has-image swp-avatar-initials {
display: none;
}
swp-avatar-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
opacity: 0;
transition: opacity 150ms ease;
border-radius: 50%;
}
swp-employee-avatar-large:hover swp-avatar-overlay {
opacity: 1;
}
swp-avatar-overlay svg {
width: 24px;
height: 24px;
fill: white;
}
swp-avatar-overlay span {
font-size: 9px;
font-weight: 500;
color: white;
text-transform: uppercase;
letter-spacing: 0.3px;
}
swp-avatar-overlay .upload-text { display: block; }
swp-avatar-overlay .change-text { display: none; }
swp-employee-avatar-large.has-image swp-avatar-overlay .upload-text { display: none; }
swp-employee-avatar-large.has-image swp-avatar-overlay .change-text { display: block; }
swp-avatar-remove {
position: absolute;
top: -4px;
right: -4px;
width: 20px;
height: 20px;
background: var(--color-red);
border-radius: 50%;
display: none;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
font-weight: 600;
cursor: pointer;
z-index: 2;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
transition: transform 150ms ease;
}
swp-employee-avatar-large.has-image:hover swp-avatar-remove {
display: flex;
}
swp-avatar-remove:hover {
transform: scale(1.1);
}
swp-avatar-upload-input {
display: none;
}
/* ==========================================
CHECKBOX ROW
========================================== */
swp-checkbox-list {
display: flex;
flex-direction: column;
gap: 4px;
}
swp-checkbox-row {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 10px 12px;
border-radius: 6px;
cursor: pointer;
transition: background 150ms ease;
}
swp-checkbox-row:hover {
background: var(--color-background-alt);
}
swp-checkbox-box {
width: 18px;
height: 18px;
border: 2px solid var(--color-border);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-top: 1px;
transition: all 150ms ease;
}
swp-checkbox-row.checked swp-checkbox-box {
background: var(--color-teal);
border-color: var(--color-teal);
}
swp-checkbox-box svg {
width: 12px;
height: 12px;
fill: white;
opacity: 0;
transition: opacity 150ms ease;
}
swp-checkbox-row.checked swp-checkbox-box svg {
opacity: 1;
}
swp-checkbox-text {
font-size: 14px;
color: var(--color-text);
line-height: 1.4;
}
/* ==========================================
SCHEDULE TABLE (read-only, single employee)
Matches poc-arbejdstidsplan.html styling
========================================== */
swp-schedule-grid {
display: grid;
grid-template-columns: 120px repeat(7, minmax(80px, 1fr));
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--color-border);
background: var(--color-surface);
}
swp-schedule-grid swp-cell {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 12px 16px;
min-height: 60px;
border-right: 1px solid var(--color-border);
border-bottom: 1px solid var(--color-border);
background: var(--color-surface);
}
swp-schedule-grid swp-cell:nth-child(8n) {
border-right: none;
}
swp-schedule-grid swp-cell:nth-last-child(-n+8) {
border-bottom: none;
}
swp-schedule-grid swp-cell.header {
background: var(--color-background-alt);
font-weight: 500;
font-size: 13px;
color: var(--color-text-secondary);
min-height: 48px;
text-align: center;
}
swp-schedule-grid swp-cell.header swp-day-name {
font-weight: 500;
color: var(--color-text);
}
swp-schedule-grid swp-cell.header swp-day-date {
font-size: 12px;
color: var(--color-text-secondary);
font-weight: 400;
}
swp-schedule-grid swp-cell.header.week-col {
font-size: 13px;
font-weight: 600;
color: var(--color-text);
}
swp-schedule-grid swp-cell.week-label {
font-size: 14px;
font-weight: 500;
color: var(--color-text);
background: var(--color-background-alt);
justify-content: center;
align-items: flex-start;
padding-left: 16px;
}
swp-schedule-grid swp-cell.week-label swp-week-hours {
font-size: 12px;
font-weight: 400;
color: var(--color-text-secondary);
font-family: var(--font-mono);
margin-top: 2px;
}
swp-time-badge {
font-family: var(--font-mono);
font-size: 12px;
font-weight: 500;
padding: 4px 8px;
border-radius: 4px;
background: color-mix(in srgb, var(--color-teal) 10%, white);
color: var(--color-text);
white-space: nowrap;
min-width: 90px;
text-align: center;
display: inline-block;
}
swp-time-badge.off {
background: transparent;
color: var(--color-text-secondary);
}
swp-time-badge.vacation {
background: color-mix(in srgb, var(--color-amber) 15%, white);
color: #b45309;
}
swp-time-badge.sick {
background: color-mix(in srgb, var(--color-red) 15%, white);
color: var(--color-red);
}
swp-schedule-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
swp-schedule-header swp-section-label {
margin-bottom: 0;
}
swp-edit-link {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: var(--color-teal);
text-decoration: none;
cursor: pointer;
transition: color 150ms ease;
}
swp-edit-link:hover {
color: #00695c;
}
swp-edit-link svg {
width: 14px;
height: 14px;
fill: currentColor;
}
swp-employee-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
swp-employee-name-row {
display: flex;
align-items: center;
gap: 16px;
}
swp-employee-name {
font-size: 24px;
font-weight: 600;
color: var(--color-text);
}
swp-tags-row {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
swp-tag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.3px;
border-radius: 4px;
background: var(--color-background);
color: var(--color-text-secondary);
}
swp-tag.master {
background: color-mix(in srgb, var(--color-purple) 15%, white);
color: var(--color-purple);
}
swp-tag.senior {
background: color-mix(in srgb, var(--color-blue) 15%, white);
color: var(--color-blue);
}
swp-tag.junior {
background: color-mix(in srgb, var(--color-amber) 15%, white);
color: #b45309;
}
swp-tag.cert {
background: color-mix(in srgb, var(--color-teal) 15%, white);
color: var(--color-teal);
}
swp-employee-status {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
font-size: 12px;
font-weight: 500;
border-radius: 6px;
cursor: pointer;
transition: all 150ms ease;
margin-left: auto;
}
swp-employee-status[data-active="true"] {
background: color-mix(in srgb, var(--color-green) 15%, white);
color: var(--color-green);
border: 1px solid color-mix(in srgb, var(--color-green) 30%, white);
}
swp-employee-status[data-active="false"] {
background: color-mix(in srgb, var(--color-red) 12%, white);
color: var(--color-red);
border: 1px solid color-mix(in srgb, var(--color-red) 30%, white);
}
swp-employee-status .icon {
font-size: 14px;
}
swp-fact-boxes-inline {
display: flex;
gap: 24px;
margin-top: 4px;
}
swp-fact-inline {
display: flex;
align-items: baseline;
gap: 6px;
}
swp-fact-inline-value {
font-size: 18px;
font-weight: 600;
font-family: var(--font-mono);
color: var(--color-text);
}
swp-fact-inline-label {
font-size: 12px;
color: var(--color-text-secondary);
}
.stars {
color: var(--color-amber);
}
/* ==========================================
TAB BAR
========================================== */
swp-tab-bar {
display: flex;
gap: 0;
background: var(--color-surface);
border-bottom: 1px solid var(--color-border);
padding: 0 24px;
position: sticky;
top: 178px;
z-index: 80;
}
swp-tab {
padding: 14px 24px;
font-size: 14px;
font-weight: 500;
color: var(--color-text-secondary);
cursor: pointer;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
transition: all 150ms ease;
}
swp-tab:hover {
color: var(--color-text);
background: var(--color-background-alt);
}
swp-tab.active {
color: var(--color-teal);
border-bottom-color: var(--color-teal);
}
/* ==========================================
TAB CONTENT
========================================== */
swp-tab-content {
display: none;
padding: 24px;
max-width: 1200px;
margin: 0 auto;
}
swp-tab-content.active {
display: block;
}
2025-12-27 12:18:23 +01:00
swp-tab-info {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 12px 16px;
background: color-mix(in srgb, var(--color-teal) 8%, white);
border: 1px solid color-mix(in srgb, var(--color-teal) 25%, white);
border-radius: 8px;
margin-bottom: 20px;
font-size: 13px;
color: var(--color-text-secondary);
line-height: 1.4;
}
swp-tab-info svg {
flex-shrink: 0;
color: var(--color-teal);
margin-top: 1px;
}
swp-tab-info strong {
color: var(--color-teal);
font-weight: 600;
}
/* ==========================================
SERVICES DRAG-DROP
========================================== */
.services-grid {
align-items: start;
}
/* Services layout with collapsible panel */
.services-layout {
display: flex;
gap: 0;
align-items: stretch;
min-height: 500px;
}
swp-services-panel {
display: flex;
transition: width 300ms ease;
overflow: hidden;
}
swp-services-panel.collapsed {
width: 40px;
}
swp-services-panel.expanded {
width: 420px;
}
swp-panel-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
min-width: 40px;
background: linear-gradient(135deg, var(--color-teal) 0%, color-mix(in srgb, var(--color-teal) 80%, #000) 100%);
color: white;
cursor: pointer;
border-radius: 8px 0 0 8px;
transition: all 200ms ease;
flex-shrink: 0;
}
swp-panel-toggle:hover {
background: linear-gradient(135deg, color-mix(in srgb, var(--color-teal) 90%, #fff) 0%, var(--color-teal) 100%);
}
swp-panel-toggle svg {
transition: transform 200ms ease;
}
swp-services-panel.expanded swp-panel-toggle svg {
transform: rotate(180deg);
}
swp-panel-content {
display: none;
flex: 1;
min-width: 0;
}
swp-services-panel.expanded swp-panel-content {
display: block;
}
swp-services-panel swp-card {
border-radius: 0 8px 8px 0;
border-left: none;
height: 100%;
}
.selected-services-card {
flex: 1;
}
/* Edit button in section label */
.selected-services-card swp-section-label {
display: flex;
align-items: center;
justify-content: space-between;
}
.edit-services-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
background: var(--color-teal);
color: white;
border: none;
border-radius: 4px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 150ms ease;
}
.edit-services-btn:hover {
background: color-mix(in srgb, var(--color-teal) 85%, #000);
}
.edit-services-btn.active {
background: var(--color-text-secondary);
}
.services-card {
display: flex;
flex-direction: column;
}
.services-card swp-search-field {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 6px;
margin-bottom: 12px;
}
.services-card swp-search-field img {
opacity: 0.5;
flex-shrink: 0;
}
.services-card swp-search-field input {
flex: 1;
border: none;
background: none;
outline: none;
font-size: 14px;
color: var(--color-text);
}
.services-card swp-search-field input::placeholder {
color: var(--color-text-muted);
}
swp-services-selected,
swp-services-available {
display: flex;
flex-direction: column;
gap: 8px;
min-height: 300px;
max-height: 600px;
overflow-y: auto;
padding: 4px;
border-radius: 8px;
transition: all 150ms ease;
}
swp-services-selected.drag-over,
swp-services-available.drag-over {
background: color-mix(in srgb, var(--color-teal) 8%, white);
outline: 2px dashed var(--color-teal);
outline-offset: -2px;
}
swp-services-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
padding: 40px 20px;
color: var(--color-text-muted);
border: 2px dashed var(--color-border);
border-radius: 8px;
text-align: center;
font-size: 14px;
}
swp-services-empty svg {
opacity: 0.4;
}
/* Service category in columns */
swp-service-category-group {
display: flex;
flex-direction: column;
gap: 4px;
}
swp-service-category-header {
font-size: 11px;
font-weight: 600;
color: var(--color-text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 8px 0 4px 0;
border-bottom: 1px solid var(--color-border);
margin-bottom: 4px;
}
/* Service row - draggable */
swp-service-row {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 6px;
cursor: url('icons/drag.svg') 12 12, grab;
transition: all 150ms ease;
user-select: none;
}
swp-service-row:hover {
border-color: var(--color-teal);
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
swp-service-row:active {
cursor: url('icons/drag.svg') 12 12, grabbing;
}
swp-service-row.dragging {
opacity: 0.5;
transform: scale(0.98);
}
swp-service-row.multi-selected {
background: color-mix(in srgb, var(--color-teal) 12%, white);
border-color: var(--color-teal);
box-shadow: 0 0 0 1px var(--color-teal);
}
swp-drag-handle {
color: var(--color-text-muted);
font-size: 14px;
cursor: url('icons/drag.svg') 12 12, grab;
padding: 0 2px;
}
swp-service-row:hover swp-drag-handle {
color: var(--color-text-secondary);
}
swp-service-color {
width: 4px;
height: 28px;
border-radius: 2px;
flex-shrink: 0;
}
swp-service-info {
flex: 1;
min-width: 0;
}
swp-service-name {
font-size: 12px;
font-weight: 500;
color: var(--color-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
swp-service-meta {
font-size: 12px;
color: var(--color-text-secondary);
margin-top: 2px;
}
/* Price and duration columns */
swp-service-price-col {
width: 75px;
text-align: right;
font-size: 13px;
font-family: var(--font-mono);
color: var(--color-text-secondary);
flex-shrink: 0;
}
swp-service-duration-col {
width: 50px;
text-align: right;
font-size: 13px;
font-family: var(--font-mono);
color: var(--color-text-secondary);
flex-shrink: 0;
}
/* Editable inputs in selected column */
swp-services-selected swp-service-price-col,
swp-services-selected swp-service-duration-col {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
}
swp-services-selected swp-service-price-col input,
swp-services-selected swp-service-duration-col input {
width: 55px;
padding: 4px 6px;
border: 1px solid var(--color-border);
border-radius: 4px;
font-size: 12px;
font-family: var(--font-mono);
text-align: right;
background: var(--color-surface);
}
swp-services-selected swp-service-price-col input:focus,
swp-services-selected swp-service-duration-col input:focus {
border-color: var(--color-teal);
outline: none;
}
swp-services-selected swp-service-price-col input::-webkit-inner-spin-button,
swp-services-selected swp-service-price-col input::-webkit-outer-spin-button {
opacity: 1;
}
/* Override indicator */
swp-service-row.has-override swp-service-price-col,
swp-service-row.has-override swp-service-duration-col {
color: var(--color-teal);
}
2025-12-26 14:05:56 +01:00
/* ==========================================
LAYOUT GRID
========================================== */
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
.grid-4 {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
}
@media (max-width: 900px) {
.grid-2 { grid-template-columns: 1fr; }
.grid-4 { grid-template-columns: repeat(2, 1fr); }
}
/* ==========================================
CARDS
========================================== */
swp-card {
display: block;
background: var(--color-surface);
border-radius: 8px;
padding: 20px;
border: 1px solid var(--color-border);
margin-bottom: 16px;
}
swp-card:last-child {
margin-bottom: 0;
}
swp-section-label {
display: block;
font-size: 11px;
font-weight: 600;
color: var(--color-text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 16px;
}
/* ==========================================
STAT CARDS
========================================== */
swp-stat-card {
background: var(--color-surface);
border-radius: 8px;
padding: 20px;
text-align: center;
border: 1px solid var(--color-border);
}
swp-stat-value {
display: block;
font-size: 28px;
font-weight: 600;
font-family: var(--font-mono);
color: var(--color-text);
}
swp-stat-label {
display: block;
font-size: 12px;
color: var(--color-text-secondary);
margin-top: 4px;
}
swp-stat-card.highlight swp-stat-value {
color: var(--color-teal);
}
/* ==========================================
EDIT SECTION
========================================== */
swp-edit-section {
display: flex;
flex-direction: column;
gap: 12px;
}
swp-edit-row {
display: grid;
grid-template-columns: 140px 1fr;
align-items: center;
gap: 12px;
}
swp-edit-label {
font-size: 13px;
color: var(--color-text-secondary);
}
swp-edit-value {
font-size: 14px;
padding: 8px 12px;
border-radius: 4px;
background: var(--color-background-alt);
border: 1px solid transparent;
transition: all 150ms ease;
cursor: text;
}
swp-edit-value:hover {
background: var(--color-background);
}
swp-edit-value:focus {
outline: none;
background: var(--color-surface);
border-color: var(--color-teal);
}
swp-edit-value.masked {
letter-spacing: 1px;
}
swp-edit-select select {
width: 100%;
font-size: 14px;
font-family: inherit;
padding: 8px 12px;
border-radius: 4px;
background: var(--color-background-alt);
border: 1px solid transparent;
cursor: pointer;
transition: all 150ms ease;
}
swp-edit-select select:hover {
background: var(--color-background);
}
swp-edit-select select:focus {
outline: none;
background: var(--color-surface);
border-color: var(--color-teal);
}
/* ==========================================
TOGGLE SLIDER
========================================== */
swp-toggle-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid var(--color-border);
}
swp-toggle-row:last-child {
border-bottom: none;
}
swp-toggle-label {
font-size: 14px;
color: var(--color-text);
}
swp-toggle-description {
display: block;
font-size: 12px;
color: var(--color-text-secondary);
margin-top: 2px;
}
swp-toggle-slider {
display: inline-flex;
width: fit-content;
background: var(--color-background);
border-radius: 6px;
border: 1px solid var(--color-border);
overflow: hidden;
position: relative;
}
swp-toggle-slider::before {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: calc(50% - 4px);
height: calc(100% - 4px);
background: color-mix(in srgb, var(--color-green) 18%, white);
border-radius: 4px;
transition: transform 200ms ease, background 200ms ease;
}
swp-toggle-slider[data-value="no"]::before {
transform: translateX(100%);
background: color-mix(in srgb, var(--color-red) 18%, white);
}
swp-toggle-option {
position: relative;
z-index: 1;
padding: 5px 16px;
font-size: 12px;
font-weight: 500;
color: var(--color-text-secondary);
cursor: pointer;
transition: color 150ms ease;
user-select: none;
}
swp-toggle-slider[data-value="yes"] swp-toggle-option:first-child {
color: var(--color-green);
font-weight: 600;
}
swp-toggle-slider[data-value="no"] swp-toggle-option:last-child {
color: var(--color-red);
font-weight: 600;
}
/* ==========================================
AVAILABILITY LIST
========================================== */
swp-availability-list {
display: flex;
flex-direction: column;
}
swp-availability-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 16px;
padding: 12px 0;
border-bottom: 1px solid var(--color-border);
}
swp-availability-row:last-child {
border-bottom: none;
}
swp-availability-row[data-enabled="false"] {
opacity: 0.5;
}
swp-availability-day {
width: 80px;
font-size: 14px;
font-weight: 500;
color: var(--color-text);
}
swp-availability-time {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
swp-time-range {
display: flex;
align-items: center;
gap: 12px;
}
swp-time-range-slider {
position: relative;
flex: 1;
height: 20px;
display: flex;
align-items: center;
}
swp-time-range-track {
position: absolute;
width: 100%;
height: 4px;
background: var(--color-border);
border-radius: 2px;
}
swp-time-range-fill {
position: absolute;
height: 4px;
background: var(--color-teal);
border-radius: 2px;
cursor: grab;
}
swp-time-range-slider input[type="range"] {
position: absolute;
width: 100%;
height: 4px;
-webkit-appearance: none;
appearance: none;
background: transparent;
pointer-events: none;
margin: 0;
}
swp-time-range-slider input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;
background: var(--color-teal);
border: 2px solid white;
border-radius: 50%;
cursor: pointer;
pointer-events: auto;
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
swp-time-range-label {
font-size: 12px;
font-family: var(--font-mono);
color: var(--color-text);
min-width: 90px;
text-align: center;
background: var(--color-background-alt);
padding: 3px 8px;
border-radius: 4px;
}
/* ==========================================
VACATION LIST
========================================== */
swp-vacation-list {
display: flex;
flex-direction: column;
gap: 8px;
}
swp-vacation-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: var(--color-background-alt);
border-radius: 6px;
border: 1px solid var(--color-border);
}
swp-vacation-dates {
flex: 1;
font-size: 14px;
font-family: var(--font-mono);
}
swp-vacation-type {
font-size: 12px;
padding: 3px 10px;
border-radius: 12px;
font-weight: 500;
}
swp-vacation-type.ferie {
background: color-mix(in srgb, var(--color-amber) 15%, white);
color: #b45309;
}
swp-vacation-type.fri {
background: color-mix(in srgb, var(--color-purple) 15%, white);
color: var(--color-purple);
}
swp-vacation-type.syg {
background: color-mix(in srgb, var(--color-red) 15%, white);
color: var(--color-red);
}
swp-vacation-delete {
padding: 4px 8px;
color: var(--color-text-secondary);
cursor: pointer;
border-radius: 4px;
transition: all 150ms ease;
}
swp-vacation-delete:hover {
color: var(--color-red);
background: color-mix(in srgb, var(--color-red) 10%, white);
}
/* ==========================================
SERVICE LIST
========================================== */
swp-service-list {
display: flex;
flex-direction: column;
gap: 8px;
}
swp-service-item {
display: flex;
align-items: center;
gap: 16px;
padding: 16px;
background: var(--color-background-alt);
border-radius: 6px;
border: 1px solid var(--color-border);
cursor: pointer;
transition: border-color 0.15s ease;
}
swp-service-item:hover {
border-color: var(--color-teal);
}
swp-service-item.selected {
border-color: var(--color-teal);
background: color-mix(in srgb, var(--color-teal) 5%, var(--color-background-alt));
}
swp-service-checkbox {
width: 20px;
height: 20px;
border: 2px solid var(--color-border);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: all 0.15s ease;
}
swp-service-item.selected swp-service-checkbox {
background: var(--color-teal);
border-color: var(--color-teal);
color: white;
}
swp-service-checkbox svg {
width: 14px;
height: 14px;
fill: currentColor;
opacity: 0;
}
swp-service-item.selected swp-service-checkbox svg {
opacity: 1;
}
swp-service-color {
width: 4px;
height: 40px;
border-radius: 2px;
flex-shrink: 0;
}
swp-service-info {
flex: 1;
}
swp-service-name {
font-weight: 500;
color: var(--color-text);
}
swp-service-meta {
font-size: 12px;
color: var(--color-text-secondary);
margin-top: 2px;
}
swp-service-override {
font-size: 13px;
color: var(--color-text-secondary);
}
swp-service-override-value {
color: var(--color-text);
font-weight: 500;
}
/* ==========================================
CERTIFICATION LIST
========================================== */
swp-cert-list {
display: flex;
flex-direction: column;
gap: 8px;
}
swp-cert-item {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 16px;
background: var(--color-background-alt);
border-radius: 6px;
border: 1px solid var(--color-border);
}
swp-cert-icon {
width: 36px;
height: 36px;
background: color-mix(in srgb, var(--color-teal) 15%, white);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
}
swp-cert-info {
flex: 1;
}
swp-cert-name {
font-weight: 500;
color: var(--color-text);
}
swp-cert-dates {
font-size: 12px;
color: var(--color-text-secondary);
margin-top: 2px;
}
swp-cert-status {
font-size: 11px;
font-weight: 600;
padding: 4px 10px;
border-radius: 12px;
}
swp-cert-status.valid {
background: color-mix(in srgb, var(--color-green) 15%, white);
color: var(--color-green);
}
swp-cert-status.expiring {
background: color-mix(in srgb, var(--color-amber) 15%, white);
color: #b45309;
}
swp-cert-status.expired {
background: color-mix(in srgb, var(--color-red) 15%, white);
color: var(--color-red);
}
/* ==========================================
SALARY TABLE
========================================== */
swp-salary-history {
display: block;
margin-top: 16px;
}
swp-salary-history table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
swp-salary-history th,
swp-salary-history td {
padding: 10px 12px;
text-align: left;
border-bottom: 1px solid var(--color-border);
}
swp-salary-history 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-salary-history td {
color: var(--color-text);
}
swp-salary-history td.mono {
font-family: var(--font-mono);
}
/* ==========================================
BOOKING TABLE
========================================== */
swp-booking-table {
display: block;
margin-top: 16px;
}
swp-booking-table table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
swp-booking-table th,
swp-booking-table td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid var(--color-border);
}
swp-booking-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-booking-table td {
color: var(--color-text);
}
swp-booking-table .customer {
font-weight: 500;
}
swp-booking-table .date {
font-family: var(--font-mono);
font-size: 12px;
color: var(--color-text-secondary);
}
swp-booking-table .amount {
font-family: var(--font-mono);
font-weight: 500;
}
/* ==========================================
CHART SECTION (matches poc-detail-drawer.html)
========================================== */
swp-chart-section {
display: block;
}
swp-chart-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
swp-chart-title {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--color-text-secondary);
}
swp-chart-legend {
display: flex;
gap: 16px;
}
swp-chart-legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--color-text-secondary);
}
swp-chart-legend-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
swp-chart-legend-dot.services {
background: var(--color-teal);
}
swp-chart-legend-dot.products {
background: var(--color-blue);
}
swp-chart-container {
display: block;
background: var(--color-background-alt);
border-radius: 8px;
border: 1px solid var(--color-border);
padding: 16px;
}
/* ==========================================
ADD BUTTON
========================================== */
swp-add-button {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px;
border: 2px dashed var(--color-border);
border-radius: 6px;
color: var(--color-text-secondary);
font-size: 13px;
cursor: pointer;
transition: all 150ms ease;
margin-top: 12px;
}
swp-add-button:hover {
border-color: var(--color-teal);
color: var(--color-teal);
background: color-mix(in srgb, var(--color-teal) 5%, white);
}
2025-12-27 12:18:23 +01:00
/* Duration slider (same style as time-range-slider) */
swp-duration-slider {
position: relative;
width: 80px;
height: 20px;
display: flex;
align-items: center;
}
swp-duration-slider swp-slider-track {
position: absolute;
width: 100%;
height: 4px;
background: var(--color-border);
border-radius: 2px;
}
swp-duration-slider input[type="range"] {
position: absolute;
width: 100%;
height: 4px;
-webkit-appearance: none;
appearance: none;
background: transparent;
margin: 0;
}
swp-duration-slider input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;
background: var(--color-teal);
border: 2px solid white;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
swp-duration-slider input[type="range"]::-moz-range-thumb {
width: 14px;
height: 14px;
background: var(--color-teal);
border: 2px solid white;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
/* Compact service list (main view) */
swp-service-compact-list {
display: flex;
flex-direction: column;
gap: 6px;
}
swp-service-compact-category {
font-size: 11px;
font-weight: 600;
color: var(--color-text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 12px;
padding-bottom: 4px;
}
swp-service-compact-category:first-child {
margin-top: 0;
}
swp-service-compact-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
background: var(--color-background-alt);
border-radius: 6px;
}
swp-service-compact-color {
width: 4px;
height: 24px;
border-radius: 2px;
flex-shrink: 0;
}
swp-service-compact-name {
flex: 1;
font-size: 14px;
color: var(--color-text);
}
swp-service-compact-override {
display: flex;
gap: 8px;
font-size: 12px;
font-family: var(--font-mono);
}
swp-service-compact-override .duration-override {
color: var(--color-teal);
padding: 2px 8px;
background: color-mix(in srgb, var(--color-teal) 10%, white);
border-radius: 4px;
}
swp-service-compact-override .price-override {
color: var(--color-teal);
padding: 2px 8px;
background: color-mix(in srgb, var(--color-teal) 10%, white);
border-radius: 4px;
}
swp-service-compact-override .price-override s {
color: var(--color-text-muted);
margin-right: 4px;
}
swp-service-compact-override.standard {
display: none;
}
swp-service-compact-empty {
display: block;
padding: 24px 16px;
text-align: center;
color: var(--color-text-muted);
font-size: 14px;
}
/* Edit link for services card */
swp-card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
swp-card-header swp-section-label {
margin-bottom: 0;
}
swp-edit-link {
font-size: 13px;
color: var(--color-teal);
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
transition: color 150ms ease;
}
swp-edit-link:hover {
color: #00695c;
}
swp-edit-link svg {
width: 14px;
height: 14px;
fill: currentColor;
}
2025-12-26 14:05:56 +01:00
< / style >
< / head >
< body >
<!-- Topbar -->
< swp-topbar >
< swp-topbar-left >
< swp-back-link >
< svg viewBox = "0 0 24 24" > < path d = "M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" / > < / svg >
Tilbage til medarbejdere
< / swp-back-link >
< swp-topbar-title > Medarbejder detaljer< / swp-topbar-title >
< / swp-topbar-left >
< swp-topbar-actions >
< swp-btn class = "secondary" > Slet medarbejder< / swp-btn >
< swp-btn class = "primary" > Gem ændringer< / swp-btn >
< / swp-topbar-actions >
< / swp-topbar >
<!-- Employee Header -->
< swp-employee-header >
< swp-employee-avatar-large id = "avatarUpload" >
< swp-avatar-initials > AS< / swp-avatar-initials >
< swp-avatar-overlay >
< svg viewBox = "0 0 24 24" > < path d = "M12 15.2c1.77 0 3.2-1.43 3.2-3.2 0-1.77-1.43-3.2-3.2-3.2-1.77 0-3.2 1.43-3.2 3.2 0 1.77 1.43 3.2 3.2 3.2zm8-10.2h-3.17L15 3H9L7.17 5H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-8 13c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z" / > < / svg >
< span class = "upload-text" > Upload< / span >
< span class = "change-text" > Skift< / span >
< / swp-avatar-overlay >
< swp-avatar-remove id = "avatarRemove" title = "Fjern billede" > ✕< / swp-avatar-remove >
< input type = "file" accept = "image/*" id = "avatarInput" style = "display: none;" >
< / swp-employee-avatar-large >
< swp-employee-info >
< swp-employee-name-row >
< swp-employee-name contenteditable = "true" > Anna Sørensen< / swp-employee-name >
< swp-tags-row >
< swp-tag class = "master" > Master Stylist< / swp-tag >
< swp-tag class = "cert" > Farvecertificeret< / swp-tag >
< swp-tag class = "cert" > Balayage< / swp-tag >
< / swp-tags-row >
< swp-employee-status data-active = "true" >
< span class = "icon" > ●< / span >
< span class = "text" > Aktiv< / span >
< / swp-employee-status >
< / swp-employee-name-row >
< swp-fact-boxes-inline >
< swp-fact-inline >
< swp-fact-inline-value > 248< / swp-fact-inline-value >
< swp-fact-inline-label > bookinger i år< / swp-fact-inline-label >
< / swp-fact-inline >
< swp-fact-inline >
< swp-fact-inline-value > 186.450 kr< / swp-fact-inline-value >
< swp-fact-inline-label > omsætning i år< / swp-fact-inline-label >
< / swp-fact-inline >
< swp-fact-inline >
< swp-fact-inline-value class = "stars" > ★★★★★< / swp-fact-inline-value >
< swp-fact-inline-label > 4.9 rating< / swp-fact-inline-label >
< / swp-fact-inline >
< swp-fact-inline >
< swp-fact-inline-value > 2019< / swp-fact-inline-value >
< swp-fact-inline-label > ansat siden< / swp-fact-inline-label >
< / swp-fact-inline >
< / swp-fact-boxes-inline >
< / swp-employee-info >
< / swp-employee-header >
<!-- Tab Bar -->
< swp-tab-bar >
< swp-tab class = "active" data-tab = "general" > Generelt< / swp-tab >
< swp-tab data-tab = "hours" > Arbejdstid< / swp-tab >
< swp-tab data-tab = "services" > Services< / swp-tab >
< swp-tab data-tab = "salary" > Løn< / swp-tab >
< swp-tab data-tab = "stats" > Statistik< / swp-tab >
< / swp-tab-bar >
<!-- ==========================================
GENERELT TAB
========================================== -->
< swp-tab-content class = "active" data-tab = "general" >
< div class = "grid-2" >
< div >
< swp-card >
< swp-section-label > Kontaktoplysninger< / swp-section-label >
< swp-edit-section >
< swp-edit-row >
< swp-edit-label > Fulde navn< / swp-edit-label >
< swp-edit-value contenteditable = "true" > Anna Sørensen< / swp-edit-value >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > E-mail< / swp-edit-label >
< swp-edit-value contenteditable = "true" > anna@salon.dk< / swp-edit-value >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > Telefon< / swp-edit-label >
< swp-edit-value contenteditable = "true" > +45 12 34 56 78< / swp-edit-value >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > Adresse< / swp-edit-label >
< swp-edit-value contenteditable = "true" > Vestergade 15, 3. tv< / swp-edit-value >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > Postnr. & By< / swp-edit-label >
< swp-edit-value contenteditable = "true" > 8000 Aarhus C< / swp-edit-value >
< / swp-edit-row >
< / swp-edit-section >
< / swp-card >
< swp-card >
< swp-section-label > Personlige oplysninger< / swp-section-label >
< swp-edit-section >
< swp-edit-row >
< swp-edit-label > Fødselsdato< / swp-edit-label >
< swp-edit-value contenteditable = "true" > 15. marts 1992< / swp-edit-value >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > CPR-nummer< / swp-edit-label >
< swp-edit-value class = "masked" contenteditable = "true" > 150392-****< / swp-edit-value >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > Nødkontakt< / swp-edit-label >
< swp-edit-value contenteditable = "true" > Peter Sørensen (ægtefælle)< / swp-edit-value >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > Nødkontakt tlf.< / swp-edit-label >
< swp-edit-value contenteditable = "true" > +45 87 65 43 21< / swp-edit-value >
< / swp-edit-row >
< / swp-edit-section >
< / swp-card >
< / div >
< div >
< swp-card >
< swp-section-label > Ansættelse< / swp-section-label >
< swp-edit-section >
< swp-edit-row >
< swp-edit-label > Ansættelsesdato< / swp-edit-label >
< swp-edit-value contenteditable = "true" > 1. august 2019< / swp-edit-value >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > Stilling< / swp-edit-label >
< swp-edit-select >
< select >
< option > Junior Stylist< / option >
< option > Stylist< / option >
< option > Senior Stylist< / option >
< option selected > Master Stylist< / option >
< / select >
< / swp-edit-select >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > Ansættelsestype< / swp-edit-label >
< swp-edit-select >
< select >
< option selected > Fuldtid< / option >
< option > Deltid< / option >
< option > Vikar< / option >
< option > Elev< / option >
< / select >
< / swp-edit-select >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > Timer/uge< / swp-edit-label >
< swp-edit-value contenteditable = "true" > 37< / swp-edit-value >
< / swp-edit-row >
< / swp-edit-section >
< / swp-card >
< swp-card >
< swp-section-label > Indstillinger< / swp-section-label >
< swp-toggle-row >
< div >
< swp-toggle-label > Vis i online booking< / swp-toggle-label >
< swp-toggle-description > Kunder kan vælge denne medarbejder< / swp-toggle-description >
< / div >
< swp-toggle-slider data-value = "yes" >
< swp-toggle-option > Ja< / swp-toggle-option >
< swp-toggle-option > Nej< / swp-toggle-option >
< / swp-toggle-slider >
< / swp-toggle-row >
< swp-toggle-row >
< div >
< swp-toggle-label > Modtag SMS-påmindelser< / swp-toggle-label >
< swp-toggle-description > Få besked om nye bookinger< / swp-toggle-description >
< / div >
< swp-toggle-slider data-value = "yes" >
< swp-toggle-option > Ja< / swp-toggle-option >
< swp-toggle-option > Nej< / swp-toggle-option >
< / swp-toggle-slider >
< / swp-toggle-row >
< swp-toggle-row >
< div >
< swp-toggle-label > Kan redigere egen kalender< / swp-toggle-label >
< swp-toggle-description > Tillad ændringer i egne bookinger< / swp-toggle-description >
< / div >
< swp-toggle-slider data-value = "yes" >
< swp-toggle-option > Ja< / swp-toggle-option >
< swp-toggle-option > Nej< / swp-toggle-option >
< / swp-toggle-slider >
< / swp-toggle-row >
< / swp-card >
< swp-card >
< swp-section-label > Notifikationer< / swp-section-label >
< p style = "font-size: 13px; color: var(--color-text-secondary); margin-bottom: 16px;" >
Vælg hvilke email-notifikationer medarbejderen skal modtage.
< / p >
< swp-checkbox-list >
< swp-checkbox-row class = "checked" >
< swp-checkbox-box >
< svg viewBox = "0 0 24 24" > < path d = "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" / > < / svg >
< / swp-checkbox-box >
< swp-checkbox-text > Modtag email ved online booking< / swp-checkbox-text >
< / swp-checkbox-row >
< swp-checkbox-row class = "checked" >
< swp-checkbox-box >
< svg viewBox = "0 0 24 24" > < path d = "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" / > < / svg >
< / swp-checkbox-box >
< swp-checkbox-text > Modtag email ved manuel booking< / swp-checkbox-text >
< / swp-checkbox-row >
< swp-checkbox-row >
< swp-checkbox-box >
< svg viewBox = "0 0 24 24" > < path d = "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" / > < / svg >
< / swp-checkbox-box >
< swp-checkbox-text > Modtag email ved aflysning< / swp-checkbox-text >
< / swp-checkbox-row >
< swp-checkbox-row >
< swp-checkbox-box >
< svg viewBox = "0 0 24 24" > < path d = "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" / > < / svg >
< / swp-checkbox-box >
< swp-checkbox-text > Modtag email ved opskrivning til venteliste< / swp-checkbox-text >
< / swp-checkbox-row >
< swp-checkbox-row class = "checked" >
< swp-checkbox-box >
< svg viewBox = "0 0 24 24" > < path d = "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" / > < / svg >
< / swp-checkbox-box >
< swp-checkbox-text > Modtag daglig oversigt over morgendagens bookinger< / swp-checkbox-text >
< / swp-checkbox-row >
< / swp-checkbox-list >
< / swp-card >
< / div >
< / div >
< / swp-tab-content >
<!-- ==========================================
ARBEJDSTID TAB
========================================== -->
< swp-tab-content data-tab = "hours" >
< swp-card >
< swp-schedule-header >
< swp-section-label > Arbejdstidsplan< / swp-section-label >
< swp-edit-link href = "poc-arbejdstidsplan.html" >
< svg viewBox = "0 0 24 24" > < path d = "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" / > < / svg >
Rediger i arbejdstidsplan
< / swp-edit-link >
< / swp-schedule-header >
< swp-schedule-grid >
<!-- Header row -->
< swp-cell class = "header week-col" > < / swp-cell >
< swp-cell class = "header" > < swp-day-name > Mandag< / swp-day-name > < / swp-cell >
< swp-cell class = "header" > < swp-day-name > Tirsdag< / swp-day-name > < / swp-cell >
< swp-cell class = "header" > < swp-day-name > Onsdag< / swp-day-name > < / swp-cell >
< swp-cell class = "header" > < swp-day-name > Torsdag< / swp-day-name > < / swp-cell >
< swp-cell class = "header" > < swp-day-name > Fredag< / swp-day-name > < / swp-cell >
< swp-cell class = "header" > < swp-day-name > Lørdag< / swp-day-name > < / swp-cell >
< swp-cell class = "header" > < swp-day-name > Søndag< / swp-day-name > < / swp-cell >
<!-- Uge 51 -->
< swp-cell class = "week-label" > Uge 51< swp-week-hours > 38 timer< / swp-week-hours > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 17:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 17:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 10:00 - 18:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 17:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 15:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "off" > —< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "off" > —< / swp-time-badge > < / swp-cell >
<!-- Uge 52 -->
< swp-cell class = "week-label" > Uge 52< swp-week-hours > 20 timer< / swp-week-hours > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 17:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 13:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "vacation" > Ferie< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "vacation" > Ferie< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "vacation" > Ferie< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "vacation" > Ferie< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "vacation" > Ferie< / swp-time-badge > < / swp-cell >
<!-- Uge 1 -->
< swp-cell class = "week-label" > Uge 1< swp-week-hours > 0 timer< / swp-week-hours > < / swp-cell >
< swp-cell > < swp-time-badge class = "vacation" > Ferie< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "vacation" > Ferie< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "off" > —< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "off" > —< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "off" > —< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "off" > —< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "off" > —< / swp-time-badge > < / swp-cell >
<!-- Uge 2 -->
< swp-cell class = "week-label" > Uge 2< swp-week-hours > 38 timer< / swp-week-hours > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 17:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 17:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 10:00 - 18:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 17:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 15:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "off" > —< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "off" > —< / swp-time-badge > < / swp-cell >
<!-- Uge 3 -->
< swp-cell class = "week-label" > Uge 3< swp-week-hours > 38 timer< / swp-week-hours > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 17:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 17:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 10:00 - 18:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 17:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 15:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "off" > —< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "off" > —< / swp-time-badge > < / swp-cell >
<!-- Uge 4 -->
< swp-cell class = "week-label" > Uge 4< swp-week-hours > 38 timer< / swp-week-hours > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 17:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 17:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 10:00 - 18:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 17:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge > 09:00 - 15:00< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "off" > —< / swp-time-badge > < / swp-cell >
< swp-cell > < swp-time-badge class = "off" > —< / swp-time-badge > < / swp-cell >
< / swp-schedule-grid >
< / swp-card >
< div class = "grid-2" style = "margin-top: 24px;" >
< swp-card >
< swp-section-label > Planlagt fravær< / swp-section-label >
< swp-vacation-list >
< swp-vacation-item >
< swp-vacation-dates > 23. dec – 2. jan 2026< / swp-vacation-dates >
< swp-vacation-type class = "ferie" > Ferie< / swp-vacation-type >
< / swp-vacation-item >
< swp-vacation-item >
< swp-vacation-dates > 14. feb 2025< / swp-vacation-dates >
< swp-vacation-type class = "fri" > Fri< / swp-vacation-type >
< / swp-vacation-item >
< swp-vacation-item >
< swp-vacation-dates > 7. apr – 11. apr 2025< / swp-vacation-dates >
< swp-vacation-type class = "ferie" > Ferie< / swp-vacation-type >
< / swp-vacation-item >
< / swp-vacation-list >
< / swp-card >
< swp-card >
< swp-section-label > Ferie-saldo< / swp-section-label >
< swp-edit-section >
< swp-edit-row >
< swp-edit-label > Optjente feriedage< / swp-edit-label >
< swp-edit-value > 25 dage< / swp-edit-value >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > Brugte feriedage< / swp-edit-label >
< swp-edit-value > 12 dage< / swp-edit-value >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > Resterende< / swp-edit-label >
< swp-edit-value style = "font-weight: 600; color: var(--color-teal);" > 13 dage< / swp-edit-value >
< / swp-edit-row >
< / swp-edit-section >
< / swp-card >
< / div >
< / swp-tab-content >
<!-- ==========================================
SERVICES TAB
========================================== -->
< swp-tab-content data-tab = "services" >
2025-12-27 12:18:23 +01:00
< swp-tab-info >
< svg viewBox = "0 0 24 24" width = "16" height = "16" > < path fill = "currentColor" d = "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z" / > < / svg >
< span > Priserne vises for stillingen "< strong > Master Stylist< / strong > ". < strong > Tip:< / strong > Hold Shift nede for at vælge en række, eller Ctrl for at vælge flere enkeltvis.< / span >
< / swp-tab-info >
< div class = "services-layout" >
<!-- Venstre panel: Tilgængelige services (collapsible) -->
< swp-services-panel id = "servicesPanel" class = "collapsed" >
< swp-panel-toggle id = "panelToggle" title = "Klik for at redigere services" >
< svg viewBox = "0 0 24 24" width = "16" height = "16" > < path fill = "currentColor" d = "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" / > < / svg >
< / swp-panel-toggle >
< swp-panel-content >
< swp-card class = "services-card" >
< swp-section-label > Tilgængelige services< / swp-section-label >
< swp-search-field >
< img src = "icons/search.svg" alt = "Søg" width = "16" height = "16" >
< input type = "text" id = "serviceSearch" placeholder = "Søg i services..." >
< / swp-search-field >
< swp-services-available id = "availableServices" >
<!-- Services vil blive indsat her -->
< / swp-services-available >
< / swp-card >
< / swp-panel-content >
< / swp-services-panel >
<!-- Højre kolonne: Valgte services -->
< swp-card class = "services-card selected-services-card" >
< swp-section-label >
Valgte services
< button type = "button" id = "editServicesBtn" class = "edit-services-btn" >
< svg viewBox = "0 0 24 24" width = "14" height = "14" > < path fill = "currentColor" d = "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" / > < / svg >
Rediger
< / button >
< / swp-section-label >
< swp-services-selected id = "selectedServices" >
< swp-services-empty >
< svg viewBox = "0 0 24 24" width = "32" height = "32" > < path fill = "currentColor" d = "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" / > < / svg >
< span > Træk services hertil< / span >
< / swp-services-empty >
< / swp-services-selected >
2025-12-26 14:05:56 +01:00
< / swp-card >
< / div >
< / swp-tab-content >
<!-- ==========================================
LØN TAB
========================================== -->
< swp-tab-content data-tab = "salary" >
< div class = "grid-2" >
< div >
< swp-card >
< swp-section-label > Grundløn< / swp-section-label >
< swp-edit-section >
< swp-edit-row >
< swp-edit-label > Timeløn< / swp-edit-label >
< swp-edit-value contenteditable = "true" > 185 kr/time< / swp-edit-value >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > Overarbejde< / swp-edit-label >
< swp-edit-value contenteditable = "true" > 150%< / swp-edit-value >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > Weekendtillæg< / swp-edit-label >
< swp-edit-value contenteditable = "true" > 25%< / swp-edit-value >
< / swp-edit-row >
< / swp-edit-section >
< swp-toggle-row style = "margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--color-border);" >
< swp-toggle-label > ATP-bidrag< / swp-toggle-label >
< swp-toggle-slider data-value = "yes" >
< swp-toggle-option > Ja< / swp-toggle-option >
< swp-toggle-option > Nej< / swp-toggle-option >
< / swp-toggle-slider >
< / swp-toggle-row >
< swp-toggle-row >
< swp-toggle-label > Pension (arbejdsgiver)< / swp-toggle-label >
< swp-toggle-slider data-value = "yes" >
< swp-toggle-option > Ja< / swp-toggle-option >
< swp-toggle-option > Nej< / swp-toggle-option >
< / swp-toggle-slider >
< / swp-toggle-row >
< / swp-card >
< swp-card >
< swp-section-label > Provision< / swp-section-label >
< swp-edit-section >
< swp-edit-row >
< swp-edit-label > På services< / swp-edit-label >
< swp-edit-value contenteditable = "true" > 12%< / swp-edit-value >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > På produktsalg< / swp-edit-label >
< swp-edit-value contenteditable = "true" > 8%< / swp-edit-value >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > Bonus ved mål< / swp-edit-label >
< swp-edit-value contenteditable = "true" > 2.500 kr/md< / swp-edit-value >
< / swp-edit-row >
< swp-edit-row >
< swp-edit-label > Månedligt mål< / swp-edit-label >
< swp-edit-value contenteditable = "true" > 45.000 kr< / swp-edit-value >
< / swp-edit-row >
< / swp-edit-section >
< / swp-card >
< / div >
< swp-card >
< swp-section-label > Lønhistorik< / swp-section-label >
< swp-salary-history >
< table >
< thead >
< tr >
< th > Dato< / th >
< th > Ændring< / th >
< th > Ny værdi< / th >
< / tr >
< / thead >
< tbody >
< tr >
< td class = "mono" > 01.01.2025< / td >
< td > Lønforhøjelse< / td >
< td class = "mono" > 185 kr/time< / td >
< / tr >
< tr >
< td class = "mono" > 01.01.2024< / td >
< td > Lønforhøjelse< / td >
< td class = "mono" > 175 kr/time< / td >
< / tr >
< tr >
< td class = "mono" > 01.07.2023< / td >
< td > Provision ændret< / td >
< td class = "mono" > 12%< / td >
< / tr >
< tr >
< td class = "mono" > 01.01.2023< / td >
< td > Lønforhøjelse< / td >
< td class = "mono" > 165 kr/time< / td >
< / tr >
< tr >
< td class = "mono" > 01.08.2019< / td >
< td > Ansættelse< / td >
< td class = "mono" > 145 kr/time< / td >
< / tr >
< / tbody >
< / table >
< / swp-salary-history >
< / swp-card >
< / div >
< / swp-tab-content >
<!-- ==========================================
STATISTIK TAB
========================================== -->
< swp-tab-content data-tab = "stats" >
< div class = "grid-4" style = "margin-bottom: 24px;" >
< swp-stat-card class = "highlight" >
< swp-stat-value > 42< / swp-stat-value >
< 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-card >
< swp-stat-card >
< swp-stat-value > 4.9< / swp-stat-value >
< swp-stat-label > Gns. kundetilfredshed< / swp-stat-label >
< / swp-stat-card >
< swp-stat-card >
< swp-stat-value > 68%< / swp-stat-value >
< swp-stat-label > Gengangere< / swp-stat-label >
< / swp-stat-card >
< / div >
< div class = "grid-2" >
< swp-card >
< swp-chart-section >
< swp-chart-header >
< swp-chart-title > Omsætning (sidste 6 mdr)< / swp-chart-title >
< swp-chart-legend >
< swp-chart-legend-item >
< swp-chart-legend-dot class = "services" > < / swp-chart-legend-dot >
< span > Services< / span >
< / swp-chart-legend-item >
< swp-chart-legend-item >
< swp-chart-legend-dot class = "products" > < / swp-chart-legend-dot >
< span > Produkter< / span >
< / swp-chart-legend-item >
< / swp-chart-legend >
< / swp-chart-header >
< swp-chart-container id = "revenueChart" style = "height: 220px;" > < / swp-chart-container >
< / swp-chart-section >
< / swp-card >
< swp-card >
< swp-section-label > Seneste bookinger< / swp-section-label >
< swp-booking-table >
< table >
< thead >
< tr >
< th > Kunde< / th >
< th > Service< / th >
< th > Dato< / th >
< th > Beløb< / th >
< / tr >
< / thead >
< tbody >
< tr >
< td class = "customer" > Maria Hansen< / td >
< td > Klip & Farve< / td >
< td class = "date" > 23. dec 2024< / td >
< td class = "amount" > 995 kr< / td >
< / tr >
< tr >
< td class = "customer" > Louise Nielsen< / td >
< td > Balayage< / td >
< td class = "date" > 22. dec 2024< / td >
< td class = "amount" > 1.495 kr< / td >
< / tr >
< tr >
< td class = "customer" > Sofie Andersen< / td >
< td > Dameklip< / td >
< td class = "date" > 22. dec 2024< / td >
< td class = "amount" > 425 kr< / td >
< / tr >
< tr >
< td class = "customer" > Karen Pedersen< / td >
< td > Klip & Farve< / td >
< td class = "date" > 21. dec 2024< / td >
< td class = "amount" > 1.095 kr< / td >
< / tr >
< tr >
< td class = "customer" > Emma Larsen< / td >
< td > Olaplex< / td >
< td class = "date" > 21. dec 2024< / td >
< td class = "amount" > 350 kr< / td >
< / tr >
< / tbody >
< / table >
< / swp-booking-table >
< / swp-card >
< / div >
< / swp-tab-content >
< script type = "module" >
// Tab switching
document.querySelectorAll('swp-tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('swp-tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('swp-tab-content').forEach(c => c.classList.remove('active'));
tab.classList.add('active');
document.querySelector(`swp-tab-content[data-tab="${tab.dataset.tab}"]`).classList.add('active');
});
});
// Toggle sliders
document.querySelectorAll('swp-toggle-slider').forEach(slider => {
slider.addEventListener('click', () => {
slider.dataset.value = slider.dataset.value === 'yes' ? 'no' : 'yes';
});
});
2025-12-27 12:18:23 +01:00
// ==========================================
// Services Drag-Drop Functionality
// ==========================================
// Category colors
const categoryColors = {
'Klip dame, herre og børn': '#1e88e5',
'Farvebehandlinger': '#8e24aa',
'Striber/ Refleksbehandling': '#f4511e',
'Hårvask med styling eller uden styling': '#00897b',
'Henna naturlig hårfarver': '#6d4c41',
'Kurbehandling': '#43a047',
'Bryn og vipper': '#ec407a',
'Balayage': '#ab47bc',
'Skæg': '#5c6bc0',
'Gloss': '#ffb300',
'Håropsætning': '#e91e63',
'Modeller': '#78909c',
'Tristan farve modeller': '#26a69a',
'Uden kategori': '#607d8b',
'Tilvalg services': '#009688'
};
// Pre-selected services (by name)
const preSelectedServices = [
// Klip
'Dameklip',
'Dameklip uden snak',
'Herreklip',
'Herreklip uden snak',
'Skin fade',
'Børneklip 0-4 år',
'Børneklip 4-8 år',
// Farve
'Bundfarve almindelig udgroning maks 3 cm',
'Helfarve kort hår',
'Helfarve mellemlangt hår',
// Striber
'Striber kort hår',
'Striber mellemlangt hår',
// Bryn og vipper
'Farvning vipper & bryn',
'Farvning vipper',
'Farvning og retning af bryn',
// Balayage
'Balayage maks til skulderen',
'Balayage langt hår',
// Gloss
'Glossing kort hår',
'Glossing mellemlangt/ langt hår'
];
// All services data - loaded from JSON
let allServices = [];
const serviceState = new Map();
const selectedContainer = document.getElementById('selectedServices');
const availableContainer = document.getElementById('availableServices');
const serviceSearch = document.getElementById('serviceSearch');
// Load services from JSON
async function loadServices() {
try {
const response = await fetch('data/kk-services.json');
const data = await response.json();
let id = 1;
for (const [category, services] of Object.entries(data)) {
const color = categoryColors[category] || '#607d8b';
services.forEach(service => {
const isPreSelected = preSelectedServices.includes(service.name);
allServices.push({
id: id,
name: service.name,
category: category,
color: color,
duration: service.duration,
price: service.price,
selected: isPreSelected
});
serviceState.set(id, {
selected: isPreSelected,
priceOverride: null,
durationOverride: 0
});
id++;
});
}
console.log(`Loaded ${allServices.length} services from JSON`);
renderServices();
} catch (error) {
console.error('Fejl ved indlæsning af services:', error);
}
}
// Format duration
function formatDuration(minutes) {
if (minutes >= 60) {
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
return mins > 0 ? `${hours}t ${mins}m` : `${hours}t`;
}
return `${minutes}m`;
}
// 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 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) {
row.classList.add('has-override');
}
if (isSelected) {
// Selected: with editable inputs
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 >
`;
} else {
// Available: name only (no price/duration)
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 >
`;
}
return row;
}
// Render all services
function renderServices(searchQuery = '') {
const lowerQuery = searchQuery.toLowerCase();
// Group services by category
const selectedByCategory = {};
const availableByCategory = {};
allServices.forEach(service => {
const state = serviceState.get(service.id);
const matchesSearch = !searchQuery || service.name.toLowerCase().includes(lowerQuery);
if (state.selected) {
if (!selectedByCategory[service.category]) {
selectedByCategory[service.category] = [];
}
selectedByCategory[service.category].push(service);
} else if (matchesSearch) {
if (!availableByCategory[service.category]) {
availableByCategory[service.category] = [];
}
availableByCategory[service.category].push(service);
}
});
// Render selected services
selectedContainer.innerHTML = '';
const selectedCategories = Object.entries(selectedByCategory);
if (selectedCategories.length === 0) {
selectedContainer.innerHTML = `
< swp-services-empty >
< svg viewBox = "0 0 24 24" width = "32" height = "32" > < path fill = "currentColor" d = "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" / > < / svg >
< span > Træk services hertil< / span >
< / swp-services-empty >
`;
} else {
selectedCategories.forEach(([category, services]) => {
const group = document.createElement('swp-service-category-group');
group.innerHTML = `< swp-service-category-header > ${category}< / swp-service-category-header > `;
services.forEach(service => {
group.appendChild(createServiceRow(service, true));
});
selectedContainer.appendChild(group);
});
}
// Render available services
availableContainer.innerHTML = '';
const availableCategories = Object.entries(availableByCategory);
availableCategories.forEach(([category, services]) => {
const group = document.createElement('swp-service-category-group');
group.innerHTML = `< swp-service-category-header > ${category}< / swp-service-category-header > `;
services.forEach(service => {
group.appendChild(createServiceRow(service, false));
});
availableContainer.appendChild(group);
2025-12-26 14:05:56 +01:00
});
2025-12-27 12:18:23 +01:00
}
// Multi-selection state
let selectedServiceIds = new Set();
let lastClickedId = null;
// Get all service IDs in order (for shift-selection)
function getOrderedServiceIds(container) {
return Array.from(container.querySelectorAll('swp-service-row'))
.map(row => parseInt(row.dataset.id));
}
// Handle service row click for selection
function handleServiceClick(e, row) {
const serviceId = parseInt(row.dataset.id);
const container = row.closest('swp-services-selected, swp-services-available');
if (e.shiftKey & & lastClickedId !== null) {
// Shift+click: range selection
const orderedIds = getOrderedServiceIds(container);
const startIndex = orderedIds.indexOf(lastClickedId);
const endIndex = orderedIds.indexOf(serviceId);
if (startIndex !== -1 & & endIndex !== -1) {
const minIndex = Math.min(startIndex, endIndex);
const maxIndex = Math.max(startIndex, endIndex);
for (let i = minIndex; i < = maxIndex; i++) {
selectedServiceIds.add(orderedIds[i]);
}
}
} else if (e.ctrlKey || e.metaKey) {
// Ctrl/Cmd+click: toggle individual selection
if (selectedServiceIds.has(serviceId)) {
selectedServiceIds.delete(serviceId);
} else {
selectedServiceIds.add(serviceId);
}
} else {
// Simple click: single selection
selectedServiceIds.clear();
selectedServiceIds.add(serviceId);
}
lastClickedId = serviceId;
updateSelectionVisual();
}
// Update visual selection state
function updateSelectionVisual() {
document.querySelectorAll('swp-service-row').forEach(row => {
const serviceId = parseInt(row.dataset.id);
if (selectedServiceIds.has(serviceId)) {
row.classList.add('multi-selected');
} else {
row.classList.remove('multi-selected');
}
});
}
// Move service between columns
function moveService(serviceId, toSelected) {
const state = serviceState.get(parseInt(serviceId));
if (state) {
state.selected = toSelected;
renderServices(serviceSearch.value);
}
}
// Move multiple services
function moveMultipleServices(serviceIds, toSelected) {
serviceIds.forEach(id => {
const state = serviceState.get(id);
if (state) {
state.selected = toSelected;
}
});
selectedServiceIds.clear();
renderServices(serviceSearch.value);
}
// Drag and drop handlers
let draggedId = null;
let draggedIds = [];
// Click handler for row selection
document.addEventListener('click', (e) => {
const row = e.target.closest('swp-service-row');
// Don't select on input focus
if (row & & e.target.tagName !== 'INPUT') {
handleServiceClick(e, row);
}
2025-12-26 14:05:56 +01:00
});
2025-12-27 12:18:23 +01:00
document.addEventListener('dragstart', (e) => {
const row = e.target.closest('swp-service-row');
if (row) {
const serviceId = parseInt(row.dataset.id);
// If dragging a selected row, drag all selected
if (selectedServiceIds.has(serviceId) & & selectedServiceIds.size > 1) {
draggedIds = Array.from(selectedServiceIds);
// Mark all selected as dragging
document.querySelectorAll('swp-service-row.multi-selected').forEach(r => {
r.classList.add('dragging');
});
} else {
// Otherwise, just drag this one
draggedIds = [serviceId];
row.classList.add('dragging');
}
draggedId = row.dataset.id;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', draggedIds.join(','));
}
});
document.addEventListener('dragend', (e) => {
document.querySelectorAll('swp-service-row.dragging').forEach(row => {
row.classList.remove('dragging');
2025-12-26 14:05:56 +01:00
});
2025-12-27 12:18:23 +01:00
draggedId = null;
draggedIds = [];
selectedContainer.classList.remove('drag-over');
availableContainer.classList.remove('drag-over');
});
selectedContainer.addEventListener('dragover', (e) => {
e.preventDefault();
selectedContainer.classList.add('drag-over');
2025-12-26 14:05:56 +01:00
});
2025-12-27 12:18:23 +01:00
selectedContainer.addEventListener('dragleave', (e) => {
if (!selectedContainer.contains(e.relatedTarget)) {
selectedContainer.classList.remove('drag-over');
}
});
selectedContainer.addEventListener('drop', (e) => {
e.preventDefault();
selectedContainer.classList.remove('drag-over');
if (draggedIds.length > 0) {
moveMultipleServices(draggedIds, true);
} else if (draggedId) {
moveService(draggedId, true);
}
});
availableContainer.addEventListener('dragover', (e) => {
e.preventDefault();
availableContainer.classList.add('drag-over');
});
availableContainer.addEventListener('dragleave', (e) => {
if (!availableContainer.contains(e.relatedTarget)) {
availableContainer.classList.remove('drag-over');
}
});
availableContainer.addEventListener('drop', (e) => {
e.preventDefault();
availableContainer.classList.remove('drag-over');
if (draggedIds.length > 0) {
moveMultipleServices(draggedIds, false);
} else if (draggedId) {
moveService(draggedId, false);
}
});
// Price input handler
selectedContainer.addEventListener('input', (e) => {
if (e.target.type === 'number') {
const row = e.target.closest('swp-service-row');
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;
if (newPrice !== originalPrice) {
state.priceOverride = newPrice;
row.classList.add('has-override');
} else {
state.priceOverride = null;
row.classList.remove('has-override');
}
}
});
// Search handler
let searchTimeout;
serviceSearch.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
renderServices(e.target.value);
}, 150);
});
// Initial load from JSON
loadServices();
// Services panel toggle
const servicesPanel = document.getElementById('servicesPanel');
const panelToggle = document.getElementById('panelToggle');
const editServicesBtn = document.getElementById('editServicesBtn');
function toggleServicesPanel() {
const isCollapsed = servicesPanel.classList.contains('collapsed');
servicesPanel.classList.toggle('collapsed', !isCollapsed);
servicesPanel.classList.toggle('expanded', 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`
: `< svg viewBox = "0 0 24 24" width = "14" height = "14" > < path fill = "currentColor" d = "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" / > < / svg > Rediger`;
}
panelToggle.addEventListener('click', toggleServicesPanel);
editServicesBtn.addEventListener('click', toggleServicesPanel);
2025-12-26 14:05:56 +01:00
// Checkbox rows
document.querySelectorAll('swp-checkbox-row').forEach(row => {
row.addEventListener('click', () => {
row.classList.toggle('checked');
});
});
// Employee status toggle
const statusEl = document.querySelector('swp-employee-status');
statusEl.addEventListener('click', () => {
const isActive = statusEl.dataset.active === 'true';
statusEl.dataset.active = isActive ? 'false' : 'true';
statusEl.querySelector('.text').textContent = isActive ? 'Inaktiv' : 'Aktiv';
});
// Avatar upload
const avatarUpload = document.getElementById('avatarUpload');
const avatarInput = document.getElementById('avatarInput');
avatarUpload.addEventListener('click', () => {
avatarInput.click();
});
avatarInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file & & file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (event) => {
// Check if image already exists
let img = avatarUpload.querySelector('img');
if (!img) {
img = document.createElement('img');
avatarUpload.insertBefore(img, avatarUpload.firstChild);
}
img.src = event.target.result;
avatarUpload.classList.add('has-image');
};
reader.readAsDataURL(file);
}
});
// Avatar remove
const avatarRemove = document.getElementById('avatarRemove');
avatarRemove.addEventListener('click', (e) => {
e.stopPropagation();
const img = avatarUpload.querySelector('img');
if (img) {
img.remove();
}
avatarUpload.classList.remove('has-image');
avatarInput.value = '';
});
// Revenue chart - line (Services + Produkter)
import { createChart } from 'https://unpkg.com/@sevenweirdpeople/swp-charting@latest/dist/index.js';
createChart(document.getElementById('revenueChart'), {
xAxis: { categories: ['Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'] },
series: [
{
name: 'Services',
color: '#00897b',
data: [
{ x: 'Jul', y: 22000 },
{ x: 'Aug', y: 28500 },
{ x: 'Sep', y: 26200 },
{ x: 'Okt', y: 30500 },
{ x: 'Nov', y: 27100 },
{ x: 'Dec', y: 26000 },
],
showArea: true,
area: { gradient: { startOpacity: 0.3, endOpacity: 0.05 } }
},
{
name: 'Produkter',
color: '#1976d2',
data: [
{ x: 'Jul', y: 2500 },
{ x: 'Aug', y: 2700 },
{ x: 'Sep', y: 2600 },
{ x: 'Okt', y: 2600 },
{ x: 'Nov', y: 2500 },
{ x: 'Dec', y: 2450 },
],
showArea: true,
area: { gradient: { startOpacity: 0.3, endOpacity: 0.05 } }
}
],
yAxis: { format: (v) => (v/1000).toFixed(0) + 'k' },
height: 200,
legend: false
});
< / script >
< / body >
< / html >