Calendar/wwwroot/poc-arbejdstidsplan.html

1784 lines
60 KiB
HTML
Raw Permalink Normal View History

<!DOCTYPE html>
<html lang="da">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Arbejdstidsplan</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap');
@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;
}
:root {
--color-background: #ffffff;
--color-background-alt: #f8f9fa;
--color-border: #e5e7eb;
--color-text: #1f2937;
--color-text-secondary: #6b7280;
--color-text-muted: #9ca3af;
--color-teal: #00897b;
--color-teal-light: #ccfbf1;
--font-mono: 'JetBrains Mono', monospace;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: var(--color-background-alt);
color: var(--color-text);
line-height: 1.5;
}
/* ==========================================
PAGE LAYOUT
========================================== */
swp-page {
display: block;
max-width: 1200px;
margin: 0 auto;
padding: 24px;
}
swp-page-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
}
swp-page-title {
font-size: 24px;
font-weight: 600;
color: var(--color-text);
}
swp-page-actions {
display: flex;
gap: 12px;
}
/* ==========================================
WEEK NAVIGATION
========================================== */
swp-week-nav {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 24px;
}
swp-week-date-nav {
display: flex;
align-items: center;
gap: 12px;
}
swp-week-nav button {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: 1px solid var(--color-border);
border-radius: 6px;
background: var(--color-background);
cursor: pointer;
color: var(--color-text);
font-size: 16px;
}
swp-week-nav button:hover {
background: var(--color-background-alt);
}
swp-week-label {
font-size: 15px;
font-weight: 500;
color: var(--color-text-secondary);
min-width: 200px;
text-align: center;
}
/* ==========================================
BUTTONS
========================================== */
swp-button {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
border: none;
transition: all 0.15s ease;
}
swp-button.primary {
background: var(--color-teal);
color: white;
}
swp-button.primary:hover {
background: #0f766e;
}
swp-button.secondary {
background: var(--color-background);
color: var(--color-text);
border: 1px solid var(--color-border);
}
swp-button.secondary:hover {
background: var(--color-background-alt);
}
/* ==========================================
SCHEDULE TABLE (flat grid with cell borders)
========================================== */
swp-schedule-table {
display: grid;
grid-template-columns: 180px repeat(7, minmax(100px, 1fr));
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--color-border);
}
swp-schedule-cell {
display: flex;
flex-direction: column;
justify-content: center;
padding: 12px 16px;
min-height: 60px;
background: var(--color-background);
border-right: 1px solid var(--color-border);
border-bottom: 1px solid var(--color-border);
user-select: none;
}
/* Sidste kolonne: ingen højre border */
swp-schedule-cell:nth-child(8n) {
border-right: none;
}
/* Sidste række: ingen bund border */
swp-schedule-cell:nth-last-child(-n+8) {
border-bottom: none;
}
swp-schedule-cell.header {
background: var(--color-background-alt);
font-weight: 500;
font-size: 13px;
color: var(--color-text-secondary);
min-height: 48px;
text-align: center;
align-items: center;
}
swp-schedule-cell.header.week-number {
font-size: 15px;
font-weight: 600;
color: var(--color-text);
}
/* Lukkedag styling */
body.edit-mode swp-schedule-cell.header:not(.week-number) {
cursor: pointer;
}
body.edit-mode swp-schedule-cell.header:not(.week-number):hover {
background: var(--color-border);
}
swp-schedule-cell.header.closed {
background: color-mix(in srgb, #f59e0b 10%, var(--color-background-alt));
border-top: 2px solid #f59e0b !important;
border-left: 2px solid #f59e0b !important;
border-right: 2px solid #f59e0b !important;
border-bottom: none !important;
}
swp-schedule-cell.header.closed swp-day-name {
color: #d97706;
}
/* Celler i lukket kolonne */
swp-schedule-cell.day.closed-day {
background: color-mix(in srgb, #f59e0b 6%, var(--color-background));
border-left: 2px solid #f59e0b !important;
border-right: 2px solid #f59e0b !important;
}
/* Sidste celle i lukket kolonne får bund-border */
swp-schedule-cell.day.closed-day:nth-last-child(-n+8) {
border-bottom: 2px solid #f59e0b !important;
}
swp-schedule-cell.day.closed-day swp-time-display {
opacity: 0.5;
}
swp-schedule-cell.employee {
align-items: flex-start;
gap: 2px;
}
swp-schedule-cell.day {
align-items: center;
text-align: center;
position: relative;
}
/* ==========================================
EMPLOYEE INFO
========================================== */
swp-employee-name {
font-size: 14px;
font-weight: 500;
color: var(--color-text);
}
swp-employee-hours {
font-family: var(--font-mono);
font-size: 12px;
color: var(--color-text-muted);
}
/* ==========================================
TIME DISPLAY
========================================== */
swp-time-display {
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-display.off {
background: transparent;
color: var(--color-text-muted);
}
swp-time-display.off.off-override {
background: color-mix(in srgb, #7c3aed 12%, white);
color: #6d28d9;
}
swp-time-display.vacation {
background: color-mix(in srgb, #f59e0b 15%, white);
color: #b45309;
}
swp-time-display.sick {
background: color-mix(in srgb, #ef4444 15%, white);
color: #dc2626;
}
/* ==========================================
DAY HEADER
========================================== */
swp-day-name {
font-weight: 500;
color: var(--color-text);
}
swp-day-date {
font-size: 12px;
color: var(--color-text-muted);
font-weight: 400;
}
/* ==========================================
EDIT MODE
========================================== */
body.edit-mode swp-schedule-cell.day {
cursor: pointer;
position: relative;
}
body.edit-mode swp-schedule-cell.day:hover {
background: var(--color-background-alt);
}
body.edit-mode swp-schedule-cell.day.selected {
background: color-mix(in srgb, var(--color-teal) 12%, white);
border-color: var(--color-teal);
border-width: 2px;
border-left: 2px solid var(--color-teal);
border-top: 2px solid var(--color-teal);
/* Kompenser for ekstra border-bredde */
margin-left: -1px;
margin-top: -1px;
padding: 11px 15px;
}
/* Én border mellem tilstødende valgte celler */
/* Venstre celle beholder sin højre border, højre celle fjerner sin venstre */
body.edit-mode swp-schedule-cell.day.selected.adj-left {
border-left: none;
margin-left: 0;
padding-left: 12px;
}
/* Øverste celle beholder sin bund border, nederste celle fjerner sin top */
body.edit-mode swp-schedule-cell.day.selected.adj-top {
border-top: none;
margin-top: 0;
padding-top: 12px;
}
/* ==========================================
DRAWER
========================================== */
swp-drawer {
display: block;
position: fixed;
top: 0;
right: -400px;
width: 400px;
height: 100vh;
background: var(--color-background);
box-shadow: -4px 0 20px rgba(0, 0, 0, 0.1);
z-index: 101;
transition: right 0.3s ease;
overflow-y: auto;
}
swp-drawer.open {
right: 0;
}
swp-drawer-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 24px;
border-bottom: 1px solid var(--color-border);
background: var(--color-background-alt);
}
swp-drawer-title {
font-size: 18px;
font-weight: 600;
color: var(--color-text);
}
swp-drawer-subtitle {
font-size: 13px;
color: var(--color-text-secondary);
margin-top: 2px;
}
swp-drawer-close {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: none;
background: transparent;
cursor: pointer;
border-radius: 6px;
color: var(--color-text-secondary);
font-size: 20px;
}
swp-drawer-close:hover {
background: var(--color-border);
}
swp-drawer-content {
display: block;
padding: 24px;
}
swp-drawer-section {
display: block;
margin-bottom: 24px;
}
swp-drawer-section-title {
display: block;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--color-text-secondary);
margin-bottom: 12px;
}
/* ==========================================
DRAWER FORM ELEMENTS
========================================== */
swp-form-row {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 16px;
}
swp-form-label {
font-size: 11px;
font-weight: 400;
color: var(--color-text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
}
swp-form-label .optional,
swp-form-label .auto {
font-weight: 400;
text-transform: none;
color: var(--color-text-muted);
}
swp-form-value {
font-size: 15px;
font-weight: 500;
color: var(--color-text);
}
swp-form-divider {
display: block;
height: 1px;
background: var(--color-border);
margin: 20px 0;
}
swp-form-group {
display: block;
padding: 16px;
background: var(--color-background-alt);
border-radius: 8px;
margin-top: 16px;
}
swp-form-group swp-form-row:last-child {
margin-bottom: 0;
}
swp-form-hint {
display: block;
font-size: 12px;
color: var(--color-text-muted);
margin: -8px 0 16px 0;
line-height: 1.4;
}
swp-form-select {
display: block;
}
swp-form-select select {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--color-border);
border-radius: 6px;
font-size: 14px;
color: var(--color-text);
background: var(--color-background);
cursor: pointer;
}
swp-form-select select:focus {
outline: none;
border-color: var(--color-teal);
}
swp-drawer-content input[type="time"],
swp-drawer-content input[type="text"],
swp-drawer-content input[type="date"] {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--color-border);
border-radius: 6px;
font-size: 14px;
color: var(--color-text);
background: var(--color-background);
}
swp-drawer-content input[type="time"] {
font-family: var(--font-mono);
}
swp-drawer-content input:focus {
outline: none;
border-color: var(--color-teal);
}
swp-drawer-content input::placeholder {
color: var(--color-text-muted);
}
/* ==========================================
TOGGLE OPTIONS (Enkelt/Gentagelse)
========================================== */
swp-toggle-options {
display: flex;
gap: 0;
border: 1px solid var(--color-border);
border-radius: 6px;
overflow: hidden;
}
swp-toggle-option {
flex: 1;
padding: 10px 16px;
text-align: center;
font-size: 14px;
cursor: pointer;
background: var(--color-background);
border-right: 1px solid var(--color-border);
transition: all 0.15s ease;
}
swp-toggle-option:last-child {
border-right: none;
}
swp-toggle-option:hover {
background: var(--color-background-alt);
}
swp-toggle-option.selected {
background: var(--color-teal);
color: white;
}
/* ==========================================
STATUS OPTIONS (badge style like poc-detail-drawer)
========================================== */
swp-status-options {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
swp-status-option {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.15s ease;
font-size: 13px;
font-weight: 500;
background: var(--color-background-alt);
color: var(--color-text-secondary);
}
swp-status-option::before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
flex-shrink: 0;
}
swp-status-option[data-status="work"] {
--status-color: var(--color-teal);
}
swp-status-option[data-status="off"] {
--status-color: #7c3aed;
}
swp-status-option[data-status="vacation"] {
--status-color: #f59e0b;
}
swp-status-option[data-status="sick"] {
--status-color: #e53935;
}
swp-status-option::before {
background: var(--status-color);
}
swp-status-option:hover {
background: var(--color-border);
}
swp-status-option.selected {
background: color-mix(in srgb, var(--status-color) 15%, white);
color: var(--status-color);
}
/* ==========================================
DRAWER FOOTER
========================================== */
swp-drawer-footer {
display: flex;
gap: 12px;
padding: 20px 24px;
border-top: 1px solid var(--color-border);
background: var(--color-background-alt);
}
swp-drawer-footer swp-button {
flex: 1;
}
/* ==========================================
OVERRIDE DISPLAY
========================================== */
swp-time-override {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
}
swp-time-original {
font-family: var(--font-mono);
font-size: 10px;
color: var(--color-text-muted);
text-decoration: line-through;
}
swp-override-badge {
position: absolute;
top: 4px;
right: 4px;
width: 8px;
height: 8px;
border-radius: 50%;
background: #f59e0b;
}
/* ==========================================
TIME RANGE SLIDER
========================================== */
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-fill:active {
cursor: grabbing;
}
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-slider input[type="range"]::-moz-range-thumb {
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 {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
min-width: 100px;
text-align: center;
background: var(--color-background-alt);
padding: 6px 12px;
border-radius: 4px;
}
swp-time-range-times {
font-size: 13px;
font-family: var(--font-mono);
font-weight: 500;
color: var(--color-text);
white-space: nowrap;
}
swp-time-range-duration {
font-size: 11px;
font-family: var(--font-mono);
color: var(--color-text-secondary);
white-space: nowrap;
}
/* ==========================================
DRAWER EMPLOYEE DISPLAY
========================================== */
swp-employee-display {
display: flex;
align-items: center;
gap: 10px;
}
swp-employee-display swp-employee-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: linear-gradient(135deg, var(--color-teal) 0%, #00695c 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 12px;
flex-shrink: 0;
}
swp-employee-display.empty swp-employee-avatar {
background: var(--color-border);
color: var(--color-text-muted);
}
swp-employee-display.multi swp-employee-avatar {
background: var(--color-text-muted);
}
/* Note icon i celle */
swp-note-icon {
position: absolute;
bottom: 7px;
right: 4px;
width: 14px;
height: 14px;
opacity: 0.5;
}
swp-note-icon img {
width: 100%;
height: 100%;
filter: brightness(0) saturate(100%) invert(20%) sepia(30%) saturate(700%) hue-rotate(190deg) brightness(90%) contrast(95%);
}
/* Override indikator - viser original tid overstreget */
swp-override-original {
position: absolute;
bottom: 1px;
left: 8px;
font-family: var(--font-mono);
font-size: 10px;
color: var(--color-text-muted);
text-decoration: line-through;
opacity: 0.7;
}
</style>
</head>
<body>
<swp-page>
<swp-page-header>
<swp-page-title>Arbejdstidsplan</swp-page-title>
<swp-page-actions>
<swp-button class="primary" id="editModeBtn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
Rediger
</swp-button>
</swp-page-actions>
</swp-page-header>
<swp-week-nav>
<swp-week-date-nav>
<button id="prevWeek">&#8249;</button>
<swp-week-label>23. - 29. december 2025</swp-week-label>
<button id="nextWeek">&#8250;</button>
</swp-week-date-nav>
</swp-week-nav>
<swp-schedule-table>
<!-- Header row -->
<swp-schedule-cell class="header week-number">Uge 52</swp-schedule-cell>
<swp-schedule-cell class="header">
<swp-day-name>Mandag</swp-day-name>
<swp-day-date>23/12</swp-day-date>
</swp-schedule-cell>
<swp-schedule-cell class="header">
<swp-day-name>Tirsdag</swp-day-name>
<swp-day-date>24/12</swp-day-date>
</swp-schedule-cell>
<swp-schedule-cell class="header closed">
<swp-day-name>Onsdag</swp-day-name>
<swp-day-date>25/12</swp-day-date>
</swp-schedule-cell>
<swp-schedule-cell class="header">
<swp-day-name>Torsdag</swp-day-name>
<swp-day-date>26/12</swp-day-date>
</swp-schedule-cell>
<swp-schedule-cell class="header">
<swp-day-name>Fredag</swp-day-name>
<swp-day-date>27/12</swp-day-date>
</swp-schedule-cell>
<swp-schedule-cell class="header">
<swp-day-name>Lørdag</swp-day-name>
<swp-day-date>28/12</swp-day-date>
</swp-schedule-cell>
<swp-schedule-cell class="header">
<swp-day-name>Søndag</swp-day-name>
<swp-day-date>29/12</swp-day-date>
</swp-schedule-cell>
<!-- Anna Sørensen -->
<swp-schedule-cell class="employee">
<swp-employee-name>Anna Sørensen</swp-employee-name>
<swp-employee-hours>32 timer</swp-employee-hours>
</swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Anna Sørensen" data-day="Man" data-date="23/12"><swp-time-display>09:00 - 17:00</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Anna Sørensen" data-day="Tir" data-date="24/12"><swp-time-display>09:00 - 13:00</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day closed-day" data-employee="Anna Sørensen" data-day="Ons" data-date="25/12"><swp-time-display class="off"></swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Anna Sørensen" data-day="Tor" data-date="26/12"><swp-time-display class="off"></swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Anna Sørensen" data-day="Fre" data-date="27/12"><swp-time-display>09:00 - 17:00</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Anna Sørensen" data-day="Lør" data-date="28/12"><swp-time-display>10:00 - 14:00</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Anna Sørensen" data-day="Søn" data-date="29/12"><swp-time-display class="off"></swp-time-display></swp-schedule-cell>
<!-- Mette Jensen -->
<swp-schedule-cell class="employee">
<swp-employee-name>Mette Jensen</swp-employee-name>
<swp-employee-hours>40 timer</swp-employee-hours>
</swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Mette Jensen" data-day="Man" data-date="23/12"><swp-time-display>10:00 - 18:00</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Mette Jensen" data-day="Tir" data-date="24/12"><swp-time-display>10:00 - 18:00</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day closed-day" data-employee="Mette Jensen" data-day="Ons" data-date="25/12"><swp-time-display class="vacation">Ferie</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Mette Jensen" data-day="Tor" data-date="26/12"><swp-time-display class="vacation">Ferie</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Mette Jensen" data-day="Fre" data-date="27/12"><swp-time-display class="vacation">Ferie</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Mette Jensen" data-day="Lør" data-date="28/12"><swp-time-display class="off"></swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Mette Jensen" data-day="Søn" data-date="29/12"><swp-time-display class="off"></swp-time-display></swp-schedule-cell>
<!-- Louise Nielsen -->
<swp-schedule-cell class="employee">
<swp-employee-name>Louise Nielsen</swp-employee-name>
<swp-employee-hours>37 timer</swp-employee-hours>
</swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Louise Nielsen" data-day="Man" data-date="23/12"><swp-time-display>09:00 - 17:00</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Louise Nielsen" data-day="Tir" data-date="24/12"><swp-time-display>09:00 - 17:00</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day closed-day" data-employee="Louise Nielsen" data-day="Ons" data-date="25/12"><swp-time-display class="off"></swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Louise Nielsen" data-day="Tor" data-date="26/12"><swp-time-display class="off"></swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Louise Nielsen" data-day="Fre" data-date="27/12"><swp-time-display>09:00 - 17:00</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Louise Nielsen" data-day="Lør" data-date="28/12"><swp-time-display>09:00 - 14:00</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Louise Nielsen" data-day="Søn" data-date="29/12"><swp-time-display class="off"></swp-time-display></swp-schedule-cell>
<!-- Katrine Pedersen -->
<swp-schedule-cell class="employee">
<swp-employee-name>Katrine Pedersen</swp-employee-name>
<swp-employee-hours>24 timer</swp-employee-hours>
</swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Katrine Pedersen" data-day="Man" data-date="23/12"><swp-time-display>12:00 - 20:00</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Katrine Pedersen" data-day="Tir" data-date="24/12"><swp-time-display class="off"></swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day closed-day" data-employee="Katrine Pedersen" data-day="Ons" data-date="25/12"><swp-time-display class="off"></swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Katrine Pedersen" data-day="Tor" data-date="26/12"><swp-time-display class="off"></swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Katrine Pedersen" data-day="Fre" data-date="27/12"><swp-time-display>12:00 - 20:00</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Katrine Pedersen" data-day="Lør" data-date="28/12"><swp-time-display>10:00 - 18:00</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Katrine Pedersen" data-day="Søn" data-date="29/12"><swp-time-display class="off"></swp-time-display></swp-schedule-cell>
<!-- Sofie Andersen -->
<swp-schedule-cell class="employee">
<swp-employee-name>Sofie Andersen</swp-employee-name>
<swp-employee-hours>20 timer</swp-employee-hours>
</swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Sofie Andersen" data-day="Man" data-date="23/12"><swp-time-display class="sick">Syg</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Sofie Andersen" data-day="Tir" data-date="24/12"><swp-time-display>09:00 - 15:00</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day closed-day" data-employee="Sofie Andersen" data-day="Ons" data-date="25/12"><swp-time-display class="off"></swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Sofie Andersen" data-day="Tor" data-date="26/12"><swp-time-display class="off"></swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Sofie Andersen" data-day="Fre" data-date="27/12"><swp-time-display>09:00 - 15:00</swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Sofie Andersen" data-day="Lør" data-date="28/12"><swp-time-display class="off"></swp-time-display></swp-schedule-cell>
<swp-schedule-cell class="day" data-employee="Sofie Andersen" data-day="Søn" data-date="29/12"><swp-time-display class="off"></swp-time-display></swp-schedule-cell>
</swp-schedule-table>
</swp-page>
<!-- DRAWER -->
<swp-drawer id="scheduleDrawer">
<swp-drawer-header>
<swp-drawer-title id="drawerTitle">Redigér vagt</swp-drawer-title>
<swp-drawer-close id="drawerClose">×</swp-drawer-close>
</swp-drawer-header>
<swp-drawer-content>
<swp-form-row>
<swp-form-label>Medarbejder</swp-form-label>
<swp-employee-display id="fieldEmployeeDisplay">
<swp-employee-avatar id="fieldAvatar"></swp-employee-avatar>
<swp-form-value id="fieldEmployee">Vælg celle...</swp-form-value>
</swp-employee-display>
</swp-form-row>
<swp-form-row>
<swp-form-label>Dato</swp-form-label>
<swp-form-value id="fieldDate"></swp-form-value>
</swp-form-row>
<swp-form-divider></swp-form-divider>
<swp-form-row>
<swp-form-label>Status</swp-form-label>
<swp-status-options id="statusOptions">
<swp-status-option data-status="work" class="selected">Arbejde</swp-status-option>
<swp-status-option data-status="off">Fri</swp-status-option>
<swp-status-option data-status="vacation">Ferie</swp-status-option>
<swp-status-option data-status="sick">Syg</swp-status-option>
</swp-status-options>
</swp-form-row>
<swp-form-row id="timeRow">
<swp-form-label>Tidsrum</swp-form-label>
<swp-time-range id="drawerTimeRange">
<swp-time-range-slider>
<swp-time-range-track></swp-time-range-track>
<swp-time-range-fill></swp-time-range-fill>
<input type="range" class="range-start" min="0" max="60" value="12" step="1">
<input type="range" class="range-end" min="0" max="60" value="44" step="1">
</swp-time-range-slider>
<swp-time-range-label>
<swp-time-range-times>09:00 17:00</swp-time-range-times>
<swp-time-range-duration>8 timer</swp-time-range-duration>
</swp-time-range-label>
</swp-time-range>
</swp-form-row>
<swp-form-row>
<swp-form-label>Note <span class="optional">(valgfrit)</span></swp-form-label>
<input type="text" id="fieldNote" placeholder="F.eks. Aftenvagt">
</swp-form-row>
<swp-form-divider></swp-form-divider>
<swp-form-row>
<swp-form-label>Type</swp-form-label>
<swp-toggle-options id="typeOptions">
<swp-toggle-option data-value="single">Enkelt</swp-toggle-option>
<swp-toggle-option data-value="template" class="selected">Gentagelse</swp-toggle-option>
</swp-toggle-options>
</swp-form-row>
<swp-form-group id="repeatGroup">
<swp-form-row>
<swp-form-label>Gentag</swp-form-label>
<swp-form-select>
<select id="repeatInterval">
<option value="1">Hver uge</option>
<option value="2">Hver 2. uge</option>
<option value="3">Hver 3. uge</option>
<option value="4">Hver 4. uge</option>
</select>
</swp-form-select>
</swp-form-row>
<swp-form-hint>
Gentagelser bruger valgt dato som startuge.
</swp-form-hint>
<swp-form-row>
<swp-form-label>Slutdato <span class="optional">(valgfrit)</span></swp-form-label>
<input type="date" id="repeatEndDate">
</swp-form-row>
<swp-form-row>
<swp-form-label>Ugedag <span class="auto">(auto)</span></swp-form-label>
<swp-form-value id="fieldWeekday"></swp-form-value>
</swp-form-row>
</swp-form-group>
</swp-drawer-content>
<swp-drawer-footer>
<swp-button class="secondary" id="drawerCancel">Annuller</swp-button>
<swp-button class="primary" id="drawerSave">Gem</swp-button>
</swp-drawer-footer>
</swp-drawer>
<script>
// ==========================================
// EDIT MODE
// ==========================================
const editModeBtn = document.getElementById('editModeBtn');
let isEditMode = false;
editModeBtn.addEventListener('click', () => {
isEditMode = !isEditMode;
document.body.classList.toggle('edit-mode', isEditMode);
if (isEditMode) {
editModeBtn.innerHTML = `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
<polyline points="17 21 17 13 7 13 7 21"/>
<polyline points="7 3 7 8 15 8"/>
</svg>
Færdig`;
openDrawer();
showEmptyState();
} else {
editModeBtn.innerHTML = `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
Rediger`;
closeDrawer();
clearSelection();
}
});
// ==========================================
// DRAWER
// ==========================================
const drawer = document.getElementById('scheduleDrawer');
const drawerClose = document.getElementById('drawerClose');
const drawerCancel = document.getElementById('drawerCancel');
const drawerSave = document.getElementById('drawerSave');
const drawerContent = document.querySelector('swp-drawer-content');
const drawerFooter = document.querySelector('swp-drawer-footer');
// Form fields
const fieldEmployee = document.getElementById('fieldEmployee');
const fieldEmployeeDisplay = document.getElementById('fieldEmployeeDisplay');
const fieldAvatar = document.getElementById('fieldAvatar');
const fieldDate = document.getElementById('fieldDate');
const fieldWeekday = document.getElementById('fieldWeekday');
const timeRow = document.getElementById('timeRow');
const repeatGroup = document.getElementById('repeatGroup');
const drawerTimeRange = document.getElementById('drawerTimeRange');
function getInitials(name) {
return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
}
const weekdays = ['Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'];
// ==========================================
// TIME RANGE SLIDER
// ==========================================
const TIME_RANGE_MAX = 60; // 15 hours (06:00-21:00) * 4 intervals
function valueToTime(value) {
const totalMinutes = (value * 15) + (6 * 60); // Add 6 hour offset
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
}
function timeToValue(timeStr) {
const [hours, minutes] = timeStr.split(':').map(Number);
const totalMinutes = hours * 60 + minutes;
return Math.round((totalMinutes - 6 * 60) / 15);
}
function updateTimeRange(slider) {
const startInput = slider.querySelector('.range-start');
const endInput = slider.querySelector('.range-end');
const fill = slider.querySelector('swp-time-range-fill');
const labelContainer = slider.closest('swp-time-range').querySelector('swp-time-range-label');
const timesEl = labelContainer.querySelector('swp-time-range-times');
const durationEl = labelContainer.querySelector('swp-time-range-duration');
let startVal = parseInt(startInput.value);
let endVal = parseInt(endInput.value);
// Ensure start doesn't exceed end
if (startVal > endVal) {
if (startInput === document.activeElement) {
startInput.value = endVal;
startVal = endVal;
} else {
endInput.value = startVal;
endVal = startVal;
}
}
// Update fill bar position
const startPercent = (startVal / TIME_RANGE_MAX) * 100;
const endPercent = (endVal / TIME_RANGE_MAX) * 100;
fill.style.left = startPercent + '%';
fill.style.width = (endPercent - startPercent) + '%';
// Calculate duration in hours
const durationIntervals = endVal - startVal;
const durationMinutes = durationIntervals * 15;
const durationHours = durationMinutes / 60;
const durationText = durationHours % 1 === 0
? `${durationHours} timer`
: `${durationHours.toFixed(1).replace('.', ',')} timer`;
// Update time range and duration separately
timesEl.textContent = `${valueToTime(startVal)} ${valueToTime(endVal)}`;
durationEl.textContent = durationText;
}
function initTimeRangeSlider(sliderContainer) {
const slider = sliderContainer.querySelector('swp-time-range-slider');
const startInput = slider.querySelector('.range-start');
const endInput = slider.querySelector('.range-end');
const fill = slider.querySelector('swp-time-range-fill');
const track = slider.querySelector('swp-time-range-track');
// Initialize
updateTimeRange(slider);
startInput.addEventListener('input', () => {
updateTimeRange(slider);
updateSelectedCellsTime();
});
endInput.addEventListener('input', () => {
updateTimeRange(slider);
updateSelectedCellsTime();
});
// Drag fill bar to move entire range
let isDragging = false;
let dragStartX = 0;
let dragStartValues = { start: 0, end: 0 };
fill.addEventListener('mousedown', (e) => {
isDragging = true;
dragStartX = e.clientX;
dragStartValues.start = parseInt(startInput.value);
dragStartValues.end = parseInt(endInput.value);
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const sliderWidth = track.offsetWidth;
const deltaX = e.clientX - dragStartX;
const deltaValue = Math.round((deltaX / sliderWidth) * TIME_RANGE_MAX);
const duration = dragStartValues.end - dragStartValues.start;
let newStart = dragStartValues.start + deltaValue;
let newEnd = dragStartValues.end + deltaValue;
// Clamp to bounds
if (newStart < 0) {
newStart = 0;
newEnd = duration;
}
if (newEnd > TIME_RANGE_MAX) {
newEnd = TIME_RANGE_MAX;
newStart = TIME_RANGE_MAX - duration;
}
startInput.value = newStart;
endInput.value = newEnd;
updateTimeRange(slider);
updateSelectedCellsTime();
});
document.addEventListener('mouseup', () => {
isDragging = false;
});
}
// Initialize the drawer time range slider
initTimeRangeSlider(drawerTimeRange);
// Opdater valgte celler i realtid når tiden ændres
function updateSelectedCellsTime() {
const selectedStatus = document.querySelector('#statusOptions swp-status-option.selected');
const status = selectedStatus ? selectedStatus.dataset.status : 'work';
// Kun opdater hvis status er "work"
if (status !== 'work') return;
const slider = drawerTimeRange.querySelector('swp-time-range-slider');
const startVal = parseInt(slider.querySelector('.range-start').value);
const endVal = parseInt(slider.querySelector('.range-end').value);
const startTime = valueToTime(startVal);
const endTime = valueToTime(endVal);
const formattedTime = `${startTime} - ${endTime}`;
selectedCells.forEach(cell => {
const timeDisplay = cell.querySelector('swp-time-display');
if (timeDisplay && !timeDisplay.classList.contains('off') &&
!timeDisplay.classList.contains('vacation') &&
!timeDisplay.classList.contains('sick')) {
timeDisplay.textContent = formattedTime;
}
});
}
let selectedCells = [];
const GRID_COLUMNS = 8; // 1 employee + 7 days
const allCells = Array.from(document.querySelectorAll('swp-schedule-table > swp-schedule-cell'));
function updateAdjacentClasses() {
// Fjern adjacency klasser først
selectedCells.forEach(c => c.classList.remove('adj-left', 'adj-right', 'adj-top', 'adj-bottom'));
// Find indices af alle valgte celler
const selectedIndices = selectedCells.map(cell => allCells.indexOf(cell));
selectedCells.forEach(cell => {
const idx = allCells.indexOf(cell);
const col = idx % GRID_COLUMNS;
// Venstre nabo
if (col > 1 && selectedIndices.includes(idx - 1)) {
cell.classList.add('adj-left');
}
// Højre nabo
if (col < GRID_COLUMNS - 1 && selectedIndices.includes(idx + 1)) {
cell.classList.add('adj-right');
}
// Nabo ovenover
if (selectedIndices.includes(idx - GRID_COLUMNS)) {
cell.classList.add('adj-top');
}
// Nabo nedenunder
if (selectedIndices.includes(idx + GRID_COLUMNS)) {
cell.classList.add('adj-bottom');
}
});
}
function showEmptyState() {
fieldEmployee.textContent = 'Vælg celle...';
fieldAvatar.textContent = '?';
fieldEmployeeDisplay.classList.add('empty');
fieldEmployeeDisplay.classList.remove('multi');
fieldDate.textContent = '—';
fieldWeekday.textContent = '—';
drawerContent.style.opacity = '0.5';
drawerContent.style.pointerEvents = 'none';
drawerFooter.style.display = 'none';
}
function showEditState() {
drawerContent.style.opacity = '1';
drawerContent.style.pointerEvents = 'auto';
drawerFooter.style.display = 'flex';
}
function updateDrawerFields() {
if (selectedCells.length === 0) {
showEmptyState();
return;
}
showEditState();
fieldEmployeeDisplay.classList.remove('empty', 'multi');
if (selectedCells.length === 1) {
const cell = selectedCells[0];
const employeeName = cell.dataset.employee;
fieldEmployee.textContent = employeeName;
fieldAvatar.textContent = getInitials(employeeName);
fieldDate.textContent = cell.dataset.date + '/2025';
// Calculate weekday from day name
const dayMap = { 'Man': 1, 'Tir': 2, 'Ons': 3, 'Tor': 4, 'Fre': 5, 'Lør': 6, 'Søn': 0 };
fieldWeekday.textContent = weekdays[dayMap[cell.dataset.day]];
// Pre-fill form with current cell values
prefillFormFromCell(cell);
} else {
const employees = [...new Set(selectedCells.map(c => c.dataset.employee))];
const days = [...new Set(selectedCells.map(c => c.dataset.day))];
if (employees.length === 1) {
fieldEmployee.textContent = employees[0];
fieldAvatar.textContent = getInitials(employees[0]);
fieldDate.textContent = `${selectedCells.length} dage valgt`;
} else if (days.length === 1) {
fieldEmployee.textContent = `${selectedCells.length} medarbejdere`;
fieldAvatar.textContent = employees.length;
fieldEmployeeDisplay.classList.add('multi');
fieldDate.textContent = selectedCells[0].dataset.date + '/2025';
} else {
fieldEmployee.textContent = `${selectedCells.length} valgt`;
fieldAvatar.textContent = selectedCells.length;
fieldEmployeeDisplay.classList.add('multi');
fieldDate.textContent = `${employees.length} medarbejdere, ${days.length} dage`;
}
fieldWeekday.textContent = days.length === 1 ? weekdays[{ 'Man': 1, 'Tir': 2, 'Ons': 3, 'Tor': 4, 'Fre': 5, 'Lør': 6, 'Søn': 0 }[days[0]]] : 'Flere dage';
// Reset form to default for multi-select
resetFormToDefault();
}
}
function prefillFormFromCell(cell) {
const timeDisplay = cell.querySelector('swp-time-display');
if (!timeDisplay) return;
// Determine current status
let status = 'work';
if (timeDisplay.classList.contains('off')) status = 'off';
else if (timeDisplay.classList.contains('vacation')) status = 'vacation';
else if (timeDisplay.classList.contains('sick')) status = 'sick';
// Update status options
document.querySelectorAll('#statusOptions swp-status-option').forEach(opt => {
opt.classList.toggle('selected', opt.dataset.status === status);
});
// Show/hide time slider based on status
const showTime = status === 'work';
timeRow.style.display = showTime ? 'flex' : 'none';
// Parse and fill time if work status
if (status === 'work') {
const timeText = timeDisplay.textContent.trim();
const timeMatch = timeText.match(/(\d{2}:\d{2})\s*-\s*(\d{2}:\d{2})/);
if (timeMatch) {
const slider = drawerTimeRange.querySelector('swp-time-range-slider');
slider.querySelector('.range-start').value = timeToValue(timeMatch[1]);
slider.querySelector('.range-end').value = timeToValue(timeMatch[2]);
updateTimeRange(slider);
}
}
// Reset type to template (gentagelse)
document.querySelectorAll('#typeOptions swp-toggle-option').forEach(opt => {
opt.classList.toggle('selected', opt.dataset.value === 'template');
});
repeatGroup.style.display = 'block';
// Udfyld note hvis den findes
document.getElementById('fieldNote').value = cell.dataset.note || '';
}
function resetFormToDefault() {
// Reset to work status
document.querySelectorAll('#statusOptions swp-status-option').forEach(opt => {
opt.classList.toggle('selected', opt.dataset.status === 'work');
});
timeRow.style.display = 'flex';
// Reset time slider to 09:00-17:00
const slider = drawerTimeRange.querySelector('swp-time-range-slider');
slider.querySelector('.range-start').value = 12; // 09:00
slider.querySelector('.range-end').value = 44; // 17:00
updateTimeRange(slider);
// Reset type to template (gentagelse)
document.querySelectorAll('#typeOptions swp-toggle-option').forEach(opt => {
opt.classList.toggle('selected', opt.dataset.value === 'template');
});
repeatGroup.style.display = 'block';
// Nulstil note
document.getElementById('fieldNote').value = '';
}
function openDrawer() {
drawer.classList.add('open');
}
function closeDrawer() {
drawer.classList.remove('open');
}
function clearSelection() {
selectedCells.forEach(c => {
c.classList.remove('selected', 'adj-left', 'adj-right', 'adj-top', 'adj-bottom');
});
selectedCells = [];
}
drawerClose.addEventListener('click', () => {
// Exit edit mode
isEditMode = false;
document.body.classList.remove('edit-mode');
editModeBtn.innerHTML = `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
Rediger`;
closeDrawer();
clearSelection();
});
drawerCancel.addEventListener('click', () => {
clearSelection();
showEmptyState();
});
drawerSave.addEventListener('click', () => {
if (selectedCells.length === 0) return;
const selectedStatus = document.querySelector('#statusOptions swp-status-option.selected');
const status = selectedStatus ? selectedStatus.dataset.status : 'work';
// Get time from slider
const slider = drawerTimeRange.querySelector('swp-time-range-slider');
const startVal = parseInt(slider.querySelector('.range-start').value);
const endVal = parseInt(slider.querySelector('.range-end').value);
const startTime = valueToTime(startVal);
const endTime = valueToTime(endVal);
const noteValue = document.getElementById('fieldNote').value.trim();
selectedCells.forEach(cell => {
const timeDisplay = cell.querySelector('swp-time-display');
if (!timeDisplay) return;
// Store original if not already stored (kun hvis cellen har en reel tid)
const hasRealTime = !timeDisplay.classList.contains('off') &&
!timeDisplay.classList.contains('vacation') &&
!timeDisplay.classList.contains('sick');
if (!cell.dataset.originalTime && hasRealTime) {
cell.dataset.originalTime = timeDisplay.textContent;
}
// Remove all status classes
timeDisplay.classList.remove('off', 'off-override', 'vacation', 'sick');
switch (status) {
case 'work':
const formattedTime = `${startTime} - ${endTime}`;
timeDisplay.textContent = formattedTime;
break;
case 'off':
timeDisplay.classList.add('off');
if (cell.dataset.originalTime) {
timeDisplay.classList.add('off-override');
timeDisplay.textContent = 'Fri';
} else {
timeDisplay.textContent = '—';
}
break;
case 'vacation':
timeDisplay.classList.add('vacation');
timeDisplay.textContent = 'Ferie';
break;
case 'sick':
timeDisplay.classList.add('sick');
timeDisplay.textContent = 'Syg';
break;
}
// Håndter note-ikon
let noteIcon = cell.querySelector('swp-note-icon');
if (noteValue) {
if (!noteIcon) {
noteIcon = document.createElement('swp-note-icon');
noteIcon.innerHTML = '<img src="icons/note-sticky.svg" alt="Note">';
cell.appendChild(noteIcon);
}
cell.dataset.note = noteValue;
} else {
if (noteIcon) {
noteIcon.remove();
}
delete cell.dataset.note;
}
// Håndter override-indikator (vis original tid når ændret)
let overrideEl = cell.querySelector('swp-override-original');
if (cell.dataset.originalTime) {
// Der er en original værdi - vis override
if (!overrideEl) {
overrideEl = document.createElement('swp-override-original');
cell.appendChild(overrideEl);
}
overrideEl.textContent = cell.dataset.originalTime;
}
// Mark as modified
cell.dataset.modified = 'true';
});
clearSelection();
showEmptyState();
});
// ==========================================
// CELL CLICK (in edit mode)
// ==========================================
const dayCells = Array.from(document.querySelectorAll('swp-schedule-cell.day'));
let anchorCell = null; // Første valgte celle til shift-selection
document.querySelectorAll('swp-schedule-cell.day').forEach(cell => {
// Dobbeltklik aktiverer edit-mode
cell.addEventListener('dblclick', (e) => {
if (!isEditMode) {
isEditMode = true;
document.body.classList.add('edit-mode');
editModeBtn.innerHTML = `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
<polyline points="17 21 17 13 7 13 7 21"/>
<polyline points="7 3 7 8 15 8"/>
</svg>
Færdig`;
openDrawer();
// Vælg den dobbeltklikkede celle
clearSelection();
cell.classList.add('selected');
selectedCells = [cell];
anchorCell = cell;
updateAdjacentClasses();
updateDrawerFields();
}
});
cell.addEventListener('click', (e) => {
if (!isEditMode) return;
if (e.shiftKey && anchorCell) {
// Shift-click: vælg alle celler fra anchor til denne
const anchorIdx = dayCells.indexOf(anchorCell);
const targetIdx = dayCells.indexOf(cell);
const startIdx = Math.min(anchorIdx, targetIdx);
const endIdx = Math.max(anchorIdx, targetIdx);
// Beregn rækkevidde baseret på grid position
const anchorRow = Math.floor(anchorIdx / 7);
const anchorCol = anchorIdx % 7;
const targetRow = Math.floor(targetIdx / 7);
const targetCol = targetIdx % 7;
const minRow = Math.min(anchorRow, targetRow);
const maxRow = Math.max(anchorRow, targetRow);
const minCol = Math.min(anchorCol, targetCol);
const maxCol = Math.max(anchorCol, targetCol);
clearSelection();
// Vælg alle celler i rektanglet
dayCells.forEach((c, idx) => {
const row = Math.floor(idx / 7);
const col = idx % 7;
if (row >= minRow && row <= maxRow && col >= minCol && col <= maxCol) {
c.classList.add('selected');
selectedCells.push(c);
}
});
} else if (e.ctrlKey || e.metaKey) {
// Multi-select with Ctrl/Cmd
if (cell.classList.contains('selected')) {
cell.classList.remove('selected');
selectedCells = selectedCells.filter(c => c !== cell);
} else {
cell.classList.add('selected');
selectedCells.push(cell);
anchorCell = cell;
}
} else {
// Single click - clear previous and select this one
clearSelection();
cell.classList.add('selected');
selectedCells = [cell];
anchorCell = cell;
}
updateAdjacentClasses();
updateDrawerFields();
});
});
// ==========================================
// STATUS OPTIONS
// ==========================================
function updateSelectedCellsStatus() {
const selectedStatus = document.querySelector('#statusOptions swp-status-option.selected');
const status = selectedStatus ? selectedStatus.dataset.status : 'work';
const slider = drawerTimeRange.querySelector('swp-time-range-slider');
const startVal = parseInt(slider.querySelector('.range-start').value);
const endVal = parseInt(slider.querySelector('.range-end').value);
const startTime = valueToTime(startVal);
const endTime = valueToTime(endVal);
selectedCells.forEach(cell => {
const timeDisplay = cell.querySelector('swp-time-display');
if (!timeDisplay) return;
// Gem original tid hvis ikke allerede gemt
const hasRealTime = !timeDisplay.classList.contains('off') &&
!timeDisplay.classList.contains('vacation') &&
!timeDisplay.classList.contains('sick');
const currentText = timeDisplay.textContent;
const isRealTimeText = currentText.includes(':'); // Tjek om det er en tid
if (!cell.dataset.originalTime && (hasRealTime || isRealTimeText)) {
cell.dataset.originalTime = currentText;
}
// Fjern alle status klasser
timeDisplay.classList.remove('off', 'off-override', 'vacation', 'sick');
switch (status) {
case 'work':
timeDisplay.textContent = `${startTime} - ${endTime}`;
break;
case 'off':
timeDisplay.classList.add('off');
if (cell.dataset.originalTime) {
timeDisplay.classList.add('off-override');
timeDisplay.textContent = 'Fri';
} else {
timeDisplay.textContent = '—';
}
break;
case 'vacation':
timeDisplay.classList.add('vacation');
timeDisplay.textContent = 'Ferie';
break;
case 'sick':
timeDisplay.classList.add('sick');
timeDisplay.textContent = 'Syg';
break;
}
});
}
document.querySelectorAll('#statusOptions swp-status-option').forEach(option => {
option.addEventListener('click', () => {
document.querySelectorAll('#statusOptions swp-status-option').forEach(o => o.classList.remove('selected'));
option.classList.add('selected');
const status = option.dataset.status;
const showTime = status === 'work';
timeRow.style.display = showTime ? 'flex' : 'none';
// Opdater celler i realtid
updateSelectedCellsStatus();
});
});
// ==========================================
// TYPE TOGGLE (Enkelt/Gentagelse)
// ==========================================
document.querySelectorAll('#typeOptions swp-toggle-option').forEach(option => {
option.addEventListener('click', () => {
document.querySelectorAll('#typeOptions swp-toggle-option').forEach(o => o.classList.remove('selected'));
option.classList.add('selected');
const isTemplate = option.dataset.value === 'template';
repeatGroup.style.display = isTemplate ? 'block' : 'none';
});
});
// ==========================================
// WEEK NAVIGATION
// ==========================================
document.getElementById('prevWeek').addEventListener('click', () => {
console.log('Previous week');
});
document.getElementById('nextWeek').addEventListener('click', () => {
console.log('Next week');
});
// ==========================================
// LUKKEDAGE (klik på dag-header)
// ==========================================
const scheduleTable = document.querySelector('swp-schedule-table');
const dayHeaders = document.querySelectorAll('swp-schedule-cell.header:not(.week-number)');
dayHeaders.forEach((header, index) => {
header.addEventListener('click', () => {
if (!isEditMode) return;
// Toggle lukket status på header
header.classList.toggle('closed');
// Find alle celler i denne kolonne (kolonne index + 1 pga. employee kolonne)
const columnIndex = index + 1; // 0-baseret, +1 for employee kolonne
// Opdater alle dag-celler i kolonnen
const allDayCells = document.querySelectorAll('swp-schedule-cell.day');
allDayCells.forEach(cell => {
const cellIndex = allCells.indexOf(cell);
const cellColumn = cellIndex % GRID_COLUMNS;
if (cellColumn === columnIndex) {
cell.classList.toggle('closed-day', header.classList.contains('closed'));
}
});
// Opdater has-closed class på tabellen
const hasAnyClosed = document.querySelector('swp-schedule-cell.header.closed');
scheduleTable.classList.toggle('has-closed', !!hasAnyClosed);
});
});
</script>
</body>
</html>