Replaces default system font with locally hosted Poppins font Adds @font-face definitions for multiple font weights Updates global font-family across various HTML pages Improves typography consistency and performance by using local font files
3611 lines
108 KiB
HTML
3611 lines
108 KiB
HTML
<!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>
|
||
/* ==========================================
|
||
FONT FACE (Poppins)
|
||
========================================== */
|
||
@font-face {
|
||
font-family: 'Poppins';
|
||
src: url('fonts/Poppins-Regular.woff') format('woff');
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
font-display: swap;
|
||
}
|
||
@font-face {
|
||
font-family: 'Poppins';
|
||
src: url('fonts/Poppins-Medium.woff') format('woff');
|
||
font-weight: 500;
|
||
font-style: normal;
|
||
font-display: swap;
|
||
}
|
||
@font-face {
|
||
font-family: 'Poppins';
|
||
src: url('fonts/Poppins-SemiBold.woff') format('woff');
|
||
font-weight: 600;
|
||
font-style: normal;
|
||
font-display: swap;
|
||
}
|
||
@font-face {
|
||
font-family: 'Poppins';
|
||
src: url('fonts/Poppins-Bold.woff') format('woff');
|
||
font-weight: 700;
|
||
font-style: normal;
|
||
font-display: swap;
|
||
}
|
||
|
||
/* ==========================================
|
||
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: 'Poppins', -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;
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
/* Service values (read mode) */
|
||
swp-service-values {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-left: auto;
|
||
}
|
||
|
||
swp-service-price,
|
||
swp-service-duration {
|
||
font-size: 13px;
|
||
font-family: var(--font-mono);
|
||
color: var(--color-text-secondary);
|
||
text-align: right;
|
||
}
|
||
|
||
swp-service-price {
|
||
width: 100px;
|
||
}
|
||
|
||
swp-service-duration {
|
||
width: 80px;
|
||
}
|
||
|
||
swp-service-price.modified,
|
||
swp-service-duration.modified {
|
||
color: var(--color-teal);
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* Hide values in edit mode */
|
||
.edit-mode swp-service-values {
|
||
display: none;
|
||
}
|
||
|
||
/* Service controls container - hidden by default */
|
||
swp-service-controls {
|
||
display: none;
|
||
align-items: center;
|
||
margin-left: auto;
|
||
}
|
||
|
||
/* Show controls in edit mode */
|
||
.edit-mode swp-service-controls {
|
||
display: flex;
|
||
}
|
||
|
||
/* Final values with adjustable controls */
|
||
swp-service-final-values {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
swp-adjustable-value {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
background: var(--color-surface);
|
||
border: 1px solid var(--color-border);
|
||
border-radius: 4px;
|
||
padding: 2px;
|
||
}
|
||
|
||
swp-adjustable-value.modified {
|
||
border-color: var(--color-teal);
|
||
background: color-mix(in srgb, var(--color-teal) 5%, white);
|
||
}
|
||
|
||
swp-adjustable-value .adjust-down,
|
||
swp-adjustable-value .adjust-up {
|
||
width: 24px;
|
||
height: 24px;
|
||
border: none;
|
||
background: var(--color-background);
|
||
color: var(--color-text-secondary);
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
border-radius: 3px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 100ms ease;
|
||
}
|
||
|
||
swp-adjustable-value .adjust-down:hover,
|
||
swp-adjustable-value .adjust-up:hover {
|
||
background: var(--color-teal);
|
||
color: white;
|
||
}
|
||
|
||
swp-adjustable-value .adjust-down:active,
|
||
swp-adjustable-value .adjust-up:active {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
swp-adjustable-value .adjust-display {
|
||
min-width: 55px;
|
||
text-align: center;
|
||
font-size: 13px;
|
||
font-family: var(--font-mono);
|
||
color: var(--color-text);
|
||
padding: 0 4px;
|
||
}
|
||
|
||
swp-adjustable-value.modified .adjust-display {
|
||
color: var(--color-teal);
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* Duration display */
|
||
.duration-display {
|
||
min-width: 50px;
|
||
text-align: right;
|
||
font-size: 13px;
|
||
font-family: var(--font-mono);
|
||
color: var(--color-text-secondary);
|
||
}
|
||
|
||
.duration-display.modified {
|
||
color: var(--color-teal);
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* Original values (read mode) */
|
||
swp-service-originals {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
margin-right: 16px;
|
||
}
|
||
|
||
swp-service-originals .original-price-val,
|
||
swp-service-originals .original-duration-val {
|
||
font-family: var(--font-mono);
|
||
font-size: 15px;
|
||
color: var(--color-text-secondary);
|
||
}
|
||
|
||
/* Strikethrough only when value is changed */
|
||
swp-service-originals .original-price-val.struck,
|
||
swp-service-originals .original-duration-val.struck {
|
||
text-decoration: line-through;
|
||
text-decoration-color: var(--color-teal);
|
||
text-decoration-thickness: 1px;
|
||
color: var(--color-text-muted);
|
||
}
|
||
|
||
/* Original values (edit mode) - hidden by default, shown when has-override */
|
||
swp-service-originals-edit {
|
||
display: none;
|
||
align-items: center;
|
||
gap: 16px;
|
||
margin-right: 20px;
|
||
}
|
||
|
||
swp-service-row.has-override swp-service-originals-edit {
|
||
display: flex;
|
||
}
|
||
|
||
swp-service-originals-edit .original-price-val,
|
||
swp-service-originals-edit .original-duration-val {
|
||
font-family: var(--font-mono);
|
||
font-size: 15px;
|
||
color: var(--color-text-secondary);
|
||
}
|
||
|
||
swp-service-originals-edit .original-price-val.struck,
|
||
swp-service-originals-edit .original-duration-val.struck {
|
||
text-decoration: line-through;
|
||
text-decoration-color: var(--color-teal);
|
||
text-decoration-thickness: 1px;
|
||
color: var(--color-text-muted);
|
||
}
|
||
|
||
/* Highlight originals when value changes */
|
||
swp-service-originals.highlight .original-price-val,
|
||
swp-service-originals.highlight .original-duration-val,
|
||
swp-service-originals-edit.highlight .original-price-val,
|
||
swp-service-originals-edit.highlight .original-duration-val {
|
||
animation: flash-text 300ms ease;
|
||
}
|
||
|
||
@keyframes pulse-border {
|
||
0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-teal) 40%, transparent); }
|
||
100% { box-shadow: 0 0 0 4px transparent; }
|
||
}
|
||
|
||
@keyframes flash-text {
|
||
0% { opacity: 0.5; }
|
||
100% { opacity: 1; }
|
||
}
|
||
|
||
/* ==========================================
|
||
LAYOUT GRID
|
||
========================================== */
|
||
.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-subtitle {
|
||
display: block;
|
||
font-size: 11px;
|
||
color: var(--color-text-muted);
|
||
margin-top: 2px;
|
||
}
|
||
|
||
swp-stat-card.highlight swp-stat-value {
|
||
color: var(--color-teal);
|
||
}
|
||
|
||
/* ==========================================
|
||
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;
|
||
}
|
||
|
||
/* ==========================================
|
||
INVOICE TABLE (afsluttede bookinger)
|
||
========================================== */
|
||
swp-invoice-table {
|
||
display: block;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
swp-invoice-table table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 13px;
|
||
}
|
||
|
||
swp-invoice-table th,
|
||
swp-invoice-table td {
|
||
padding: 12px 16px;
|
||
text-align: left;
|
||
border-bottom: 1px solid var(--color-border);
|
||
}
|
||
|
||
swp-invoice-table th {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
color: var(--color-text-secondary);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.3px;
|
||
background: var(--color-background-alt);
|
||
}
|
||
|
||
swp-invoice-table td {
|
||
color: var(--color-text);
|
||
}
|
||
|
||
swp-invoice-table .date,
|
||
swp-invoice-table .time {
|
||
font-family: var(--font-mono);
|
||
font-size: 12px;
|
||
color: var(--color-text-secondary);
|
||
}
|
||
|
||
swp-invoice-table .customer {
|
||
font-weight: 500;
|
||
}
|
||
|
||
swp-invoice-table .services {
|
||
max-width: 250px;
|
||
}
|
||
|
||
swp-invoice-table .duration {
|
||
font-family: var(--font-mono);
|
||
font-size: 12px;
|
||
color: var(--color-text-secondary);
|
||
}
|
||
|
||
swp-invoice-table .amount {
|
||
font-family: var(--font-mono);
|
||
font-weight: 600;
|
||
text-align: right;
|
||
}
|
||
|
||
swp-invoice-table .amount-col {
|
||
text-align: right;
|
||
}
|
||
|
||
/* Status badges */
|
||
swp-status-badge {
|
||
display: inline-block;
|
||
padding: 4px 10px;
|
||
border-radius: 12px;
|
||
font-size: 11px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
swp-status-badge.paid {
|
||
background: color-mix(in srgb, var(--color-teal) 15%, white);
|
||
color: var(--color-teal);
|
||
}
|
||
|
||
swp-status-badge.pending {
|
||
background: color-mix(in srgb, #f59e0b 15%, white);
|
||
color: #d97706;
|
||
}
|
||
|
||
/* ==========================================
|
||
CHART SECTION (matches poc-detail-drawer.html)
|
||
========================================== */
|
||
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-subtitle {
|
||
display: block;
|
||
font-size: 12px;
|
||
color: var(--color-text-muted);
|
||
margin-top: 2px;
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
/* 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;
|
||
}
|
||
</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">
|
||
<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>
|
||
</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>30.825 kr</swp-stat-value>
|
||
<swp-stat-label>Værdi af bookede services</swp-stat-label>
|
||
<swp-stat-subtitle>Baseret på 49 bookinger</swp-stat-subtitle>
|
||
</swp-stat-card>
|
||
<swp-stat-card>
|
||
<swp-stat-value>28.450 kr</swp-stat-value>
|
||
<swp-stat-label>Omsætning denne måned</swp-stat-label>
|
||
</swp-stat-card>
|
||
<swp-stat-card>
|
||
<swp-stat-value>68%</swp-stat-value>
|
||
<swp-stat-label>Gengangere</swp-stat-label>
|
||
</swp-stat-card>
|
||
</div>
|
||
|
||
<!-- Omsætning & Belægningsgrad chart -->
|
||
<swp-card style="margin-bottom: 24px;">
|
||
<swp-chart-section>
|
||
<swp-chart-header>
|
||
<swp-chart-title>Omsætning & Belægningsgrad</swp-chart-title>
|
||
<swp-chart-subtitle>3 måneder bagud · 3 måneder frem</swp-chart-subtitle>
|
||
</swp-chart-header>
|
||
<swp-chart-container id="revenueUtilizationChart" style="height: 300px;"></swp-chart-container>
|
||
</swp-chart-section>
|
||
</swp-card>
|
||
|
||
<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>
|
||
|
||
<!-- Afsluttede bookinger (fakturaliste) -->
|
||
<swp-card style="margin-top: 24px;">
|
||
<swp-section-label>Afsluttede bookinger</swp-section-label>
|
||
<swp-invoice-table>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Dato</th>
|
||
<th>Tid</th>
|
||
<th>Kunde</th>
|
||
<th>Services</th>
|
||
<th>Varighed</th>
|
||
<th class="amount-col">Beløb</th>
|
||
<th>Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td class="date">23. dec 2024</td>
|
||
<td class="time">10:00</td>
|
||
<td class="customer">Maria Hansen</td>
|
||
<td class="services">Dameklip, Bundfarve</td>
|
||
<td class="duration">2t 30m</td>
|
||
<td class="amount">1.510 kr</td>
|
||
<td><swp-status-badge class="paid">Betalt</swp-status-badge></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="date">23. dec 2024</td>
|
||
<td class="time">13:30</td>
|
||
<td class="customer">Louise Nielsen</td>
|
||
<td class="services">Balayage langt hår, Olaplex</td>
|
||
<td class="duration">3t</td>
|
||
<td class="amount">2.700 kr</td>
|
||
<td><swp-status-badge class="paid">Betalt</swp-status-badge></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="date">22. dec 2024</td>
|
||
<td class="time">09:00</td>
|
||
<td class="customer">Sofie Andersen</td>
|
||
<td class="services">Dameklip</td>
|
||
<td class="duration">1t</td>
|
||
<td class="amount">725 kr</td>
|
||
<td><swp-status-badge class="paid">Betalt</swp-status-badge></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="date">22. dec 2024</td>
|
||
<td class="time">11:00</td>
|
||
<td class="customer">Karen Pedersen</td>
|
||
<td class="services">Striber mellemlangt hår, Klip</td>
|
||
<td class="duration">2t 30m</td>
|
||
<td class="amount">2.390 kr</td>
|
||
<td><swp-status-badge class="pending">Afventer</swp-status-badge></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="date">21. dec 2024</td>
|
||
<td class="time">14:00</td>
|
||
<td class="customer">Emma Larsen</td>
|
||
<td class="services">Olaplex Stand alone</td>
|
||
<td class="duration">1t</td>
|
||
<td class="amount">550 kr</td>
|
||
<td><swp-status-badge class="paid">Betalt</swp-status-badge></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="date">21. dec 2024</td>
|
||
<td class="time">10:00</td>
|
||
<td class="customer">Mette Kristensen</td>
|
||
<td class="services">Herreklip</td>
|
||
<td class="duration">1t</td>
|
||
<td class="amount">645 kr</td>
|
||
<td><swp-status-badge class="paid">Betalt</swp-status-badge></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="date">20. dec 2024</td>
|
||
<td class="time">09:30</td>
|
||
<td class="customer">Anne Thomsen</td>
|
||
<td class="services">Glossing mellemlangt hår</td>
|
||
<td class="duration">1t</td>
|
||
<td class="amount">745 kr</td>
|
||
<td><swp-status-badge class="paid">Betalt</swp-status-badge></td>
|
||
</tr>
|
||
<tr>
|
||
<td class="date">20. dec 2024</td>
|
||
<td class="time">12:00</td>
|
||
<td class="customer">Lise Mortensen</td>
|
||
<td class="services">Dameklip, Farvning vipper & bryn</td>
|
||
<td class="duration">1t 30m</td>
|
||
<td class="amount">1.070 kr</td>
|
||
<td><swp-status-badge class="paid">Betalt</swp-status-badge></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</swp-invoice-table>
|
||
</swp-card>
|
||
</swp-tab-content>
|
||
|
||
<script type="module">
|
||
// 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';
|
||
});
|
||
});
|
||
|
||
// ==========================================
|
||
// 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`;
|
||
}
|
||
|
||
// Format duration with offset (e.g., "1t -5m" or "30m +10m")
|
||
function formatDurationWithOffset(baseMinutes, offset) {
|
||
const baseStr = formatDuration(baseMinutes);
|
||
if (offset === 0) return baseStr;
|
||
const sign = offset > 0 ? '+' : '';
|
||
return `${baseStr} <span class="duration-offset">${sign}${offset}m</span>`;
|
||
}
|
||
|
||
// Create service row HTML
|
||
function createServiceRow(service, isSelected) {
|
||
const state = serviceState.get(service.id);
|
||
const effectivePrice = state.priceOverride !== null ? state.priceOverride : service.price;
|
||
const effectiveDuration = service.duration + state.durationOverride;
|
||
const hasPriceOverride = state.priceOverride !== null && state.priceOverride !== service.price;
|
||
const hasDurationOverride = state.durationOverride !== 0;
|
||
|
||
const row = document.createElement('swp-service-row');
|
||
row.dataset.id = service.id;
|
||
row.dataset.category = service.category;
|
||
row.draggable = true;
|
||
|
||
if (hasPriceOverride || hasDurationOverride) {
|
||
row.classList.add('has-override');
|
||
}
|
||
|
||
if (isSelected) {
|
||
// Selected: show adjusted values + original values in bordered box when modified
|
||
const hasAnyOverride = hasPriceOverride || hasDurationOverride;
|
||
|
||
// Original values box (shown when any override exists)
|
||
const originalsHtml = hasAnyOverride
|
||
? `<swp-service-originals>
|
||
<span class="original-price-val ${hasPriceOverride ? 'struck' : ''}" data-original-price="${service.price}">${service.price} kr</span>
|
||
<span class="original-duration-val ${hasDurationOverride ? 'struck' : ''}" data-original-duration="${service.duration}">${formatDuration(service.duration)}</span>
|
||
</swp-service-originals>`
|
||
: '';
|
||
|
||
row.innerHTML = `
|
||
<swp-drag-handle>⋮⋮</swp-drag-handle>
|
||
<swp-service-color style="background: ${service.color};"></swp-service-color>
|
||
<swp-service-info>
|
||
<swp-service-name>${service.name}</swp-service-name>
|
||
</swp-service-info>
|
||
<swp-service-values>
|
||
${originalsHtml}
|
||
<swp-service-price class="${hasPriceOverride ? 'modified' : ''}">${effectivePrice} kr</swp-service-price>
|
||
<swp-service-duration class="${hasDurationOverride ? 'modified' : ''}">${formatDuration(effectiveDuration)}</swp-service-duration>
|
||
</swp-service-values>
|
||
<swp-service-controls>
|
||
<swp-service-originals-edit>
|
||
<span class="original-price-val ${hasPriceOverride ? 'struck' : ''}">${service.price} kr</span>
|
||
<span class="original-duration-val ${hasDurationOverride ? 'struck' : ''}">${formatDuration(service.duration)}</span>
|
||
</swp-service-originals-edit>
|
||
<swp-service-final-values>
|
||
<swp-adjustable-value class="price-adjust ${hasPriceOverride ? 'modified' : ''}" data-type="price" data-original="${service.price}" data-value="${effectivePrice}" data-step="5">
|
||
<button class="adjust-down" type="button">−</button>
|
||
<span class="adjust-display">${effectivePrice} kr</span>
|
||
<button class="adjust-up" type="button">+</button>
|
||
</swp-adjustable-value>
|
||
<swp-adjustable-value class="duration-adjust ${hasDurationOverride ? 'modified' : ''}" data-type="duration" data-base="${service.duration}" data-offset="${state.durationOverride}" data-step="5">
|
||
<button class="adjust-down" type="button">−</button>
|
||
<span class="adjust-display">${formatDuration(effectiveDuration)}</span>
|
||
<button class="adjust-up" type="button">+</button>
|
||
</swp-adjustable-value>
|
||
</swp-service-final-values>
|
||
</swp-service-controls>
|
||
`;
|
||
} else {
|
||
// Available: name only (no price/duration)
|
||
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);
|
||
});
|
||
}
|
||
|
||
// 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);
|
||
}
|
||
});
|
||
|
||
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');
|
||
});
|
||
draggedId = null;
|
||
draggedIds = [];
|
||
selectedContainer.classList.remove('drag-over');
|
||
availableContainer.classList.remove('drag-over');
|
||
});
|
||
|
||
selectedContainer.addEventListener('dragover', (e) => {
|
||
e.preventDefault();
|
||
selectedContainer.classList.add('drag-over');
|
||
});
|
||
|
||
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);
|
||
}
|
||
});
|
||
|
||
// Adjustable value click handler (up/down buttons)
|
||
selectedContainer.addEventListener('click', (e) => {
|
||
const btn = e.target.closest('.adjust-up, .adjust-down');
|
||
if (!btn) return;
|
||
|
||
const adjustable = btn.closest('swp-adjustable-value');
|
||
const row = btn.closest('swp-service-row');
|
||
if (!adjustable || !row) return;
|
||
|
||
const serviceId = parseInt(row.dataset.id);
|
||
const state = serviceState.get(serviceId);
|
||
const service = allServices.find(s => s.id === serviceId);
|
||
const step = parseInt(adjustable.dataset.step);
|
||
const isUp = btn.classList.contains('adjust-up');
|
||
const delta = isUp ? step : -step;
|
||
|
||
if (adjustable.dataset.type === 'price') {
|
||
// Adjust price
|
||
const original = parseInt(adjustable.dataset.original);
|
||
let current = parseInt(adjustable.dataset.value);
|
||
current = Math.max(0, current + delta);
|
||
adjustable.dataset.value = current;
|
||
|
||
const isModified = current !== original;
|
||
state.priceOverride = isModified ? current : null;
|
||
|
||
// Update displays
|
||
adjustable.querySelector('.adjust-display').textContent = `${current} kr`;
|
||
adjustable.classList.toggle('modified', isModified);
|
||
|
||
const priceDisplay = row.querySelector('swp-service-price');
|
||
if (priceDisplay) {
|
||
priceDisplay.textContent = `${current} kr`;
|
||
priceDisplay.classList.toggle('modified', isModified);
|
||
}
|
||
|
||
// Toggle strikethrough on all original price elements (read + edit mode)
|
||
row.querySelectorAll('.original-price-val').forEach(el => {
|
||
el.classList.toggle('struck', isModified);
|
||
});
|
||
} else if (adjustable.dataset.type === 'duration') {
|
||
// Adjust duration offset
|
||
const base = parseInt(adjustable.dataset.base);
|
||
let offset = parseInt(adjustable.dataset.offset);
|
||
offset = Math.max(-base, offset + delta); // Don't go below 0 total
|
||
adjustable.dataset.offset = offset;
|
||
|
||
const effectiveDuration = base + offset;
|
||
const isModified = offset !== 0;
|
||
state.durationOverride = offset;
|
||
|
||
// Update displays
|
||
adjustable.querySelector('.adjust-display').textContent = formatDuration(effectiveDuration);
|
||
adjustable.classList.toggle('modified', isModified);
|
||
|
||
const durationDisplay = row.querySelector('swp-service-duration');
|
||
if (durationDisplay) {
|
||
durationDisplay.textContent = formatDuration(effectiveDuration);
|
||
durationDisplay.classList.toggle('modified', isModified);
|
||
}
|
||
|
||
// Toggle strikethrough on all original duration elements (read + edit mode)
|
||
row.querySelectorAll('.original-duration-val').forEach(el => {
|
||
el.classList.toggle('struck', isModified);
|
||
});
|
||
}
|
||
|
||
// Check if any override exists
|
||
const hasOverride = state.priceOverride !== null || state.durationOverride !== 0;
|
||
row.classList.toggle('has-override', hasOverride);
|
||
|
||
// Handle originals box visibility and highlight
|
||
const valuesContainer = row.querySelector('swp-service-values');
|
||
let originalsBox = valuesContainer.querySelector('swp-service-originals');
|
||
const originalsEditBox = row.querySelector('swp-service-originals-edit');
|
||
|
||
if (hasOverride) {
|
||
// Show originals box if not present (insert at beginning)
|
||
const hasPriceOverride = state.priceOverride !== null;
|
||
const hasDurationOverride = state.durationOverride !== 0;
|
||
|
||
if (!originalsBox) {
|
||
originalsBox = document.createElement('swp-service-originals');
|
||
originalsBox.innerHTML = `
|
||
<span class="original-price-val ${hasPriceOverride ? 'struck' : ''}">${service.price} kr</span>
|
||
<span class="original-duration-val ${hasDurationOverride ? 'struck' : ''}">${formatDuration(service.duration)}</span>
|
||
`;
|
||
valuesContainer.insertBefore(originalsBox, valuesContainer.firstChild);
|
||
} else {
|
||
// Update struck classes on existing originals box
|
||
const priceVal = originalsBox.querySelector('.original-price-val');
|
||
const durationVal = originalsBox.querySelector('.original-duration-val');
|
||
if (priceVal) priceVal.classList.toggle('struck', hasPriceOverride);
|
||
if (durationVal) durationVal.classList.toggle('struck', hasDurationOverride);
|
||
}
|
||
// Highlight animation
|
||
originalsBox.classList.remove('highlight');
|
||
originalsEditBox?.classList.remove('highlight');
|
||
void originalsBox.offsetWidth; // Trigger reflow
|
||
originalsBox.classList.add('highlight');
|
||
originalsEditBox?.classList.add('highlight');
|
||
} else {
|
||
// Remove originals box if no overrides
|
||
if (originalsBox) originalsBox.remove();
|
||
}
|
||
});
|
||
|
||
// 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');
|
||
const selectedServicesCard = document.querySelector('.selected-services-card');
|
||
|
||
function toggleServicesPanel() {
|
||
const isCollapsed = servicesPanel.classList.contains('collapsed');
|
||
servicesPanel.classList.toggle('collapsed', !isCollapsed);
|
||
servicesPanel.classList.toggle('expanded', isCollapsed);
|
||
selectedServicesCard.classList.toggle('edit-mode', isCollapsed);
|
||
editServicesBtn.classList.toggle('active', isCollapsed);
|
||
editServicesBtn.innerHTML = isCollapsed
|
||
? `<svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg> Luk`
|
||
: `<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);
|
||
|
||
// 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
|
||
});
|
||
|
||
// Revenue & Utilization chart - dual axis (3 mdr bagud + 3 mdr frem)
|
||
fetch('data/employee-revenue-utilization.json')
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
createChart(document.getElementById('revenueUtilizationChart'), {
|
||
xAxis: { categories: data.categories },
|
||
yAxis: [
|
||
{ min: 0, max: 50000, format: v => `${(v/1000).toFixed(0)}k` }, // Left: Revenue
|
||
{ min: 0, max: 100, format: v => `${v}%` }, // Right: Utilization
|
||
],
|
||
series: [
|
||
// Actual revenue (solid bars)
|
||
{
|
||
name: 'Omsætning',
|
||
color: '#3b82f6',
|
||
type: 'bar',
|
||
yAxisIndex: 0,
|
||
unit: 'kr',
|
||
data: data.actual.revenue,
|
||
bar: { radius: 2 },
|
||
},
|
||
// Forecast revenue (transparent bars)
|
||
{
|
||
name: 'Omsætning (forecast)',
|
||
color: '#3b82f6',
|
||
type: 'bar',
|
||
yAxisIndex: 0,
|
||
unit: 'kr',
|
||
data: data.forecast.revenue,
|
||
bar: { radius: 2, opacity: 0.35 },
|
||
},
|
||
// Actual utilization (solid line)
|
||
{
|
||
name: 'Belægning',
|
||
color: '#00897b',
|
||
type: 'line',
|
||
yAxisIndex: 1,
|
||
unit: '%',
|
||
data: data.actual.utilization,
|
||
line: { width: 2.5 },
|
||
point: { radius: 0 },
|
||
showArea: false,
|
||
},
|
||
// Forecast utilization (dashed line)
|
||
{
|
||
name: 'Belægning (forecast)',
|
||
color: '#00897b',
|
||
type: 'line',
|
||
yAxisIndex: 1,
|
||
unit: '%',
|
||
data: data.forecast.utilization,
|
||
line: { width: 2.5, dashArray: '4 4' },
|
||
point: { radius: 0 },
|
||
showArea: false,
|
||
},
|
||
],
|
||
annotations: [
|
||
{ type: 'region', x: data.forecastStartCategory, x2: data.categories[data.categories.length - 1], backgroundColor: 'rgba(0,0,0,0.03)' },
|
||
{ type: 'verticalLine', x: data.nowCategory, dashArray: '4 4', label: 'Nu', labelPosition: 'top' },
|
||
],
|
||
legend: { position: 'top', align: 'end' },
|
||
padding: { right: 60 },
|
||
height: 280,
|
||
});
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|