Improves employee selection with checkbox interaction Adds visual refinements to employee and availability sections Updates styling for employee levels and selection states Removes unnecessary consent form toggle
1684 lines
56 KiB
HTML
1684 lines
56 KiB
HTML
<!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');
|
||
|
||
: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: -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);
|
||
}
|
||
|
||
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">‹</button>
|
||
<swp-week-label>23. - 29. december 2025</swp-week-label>
|
||
<button id="nextWeek">›</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>Man</swp-day-name>
|
||
<swp-day-date>23/12</swp-day-date>
|
||
</swp-schedule-cell>
|
||
<swp-schedule-cell class="header">
|
||
<swp-day-name>Tir</swp-day-name>
|
||
<swp-day-date>24/12</swp-day-date>
|
||
</swp-schedule-cell>
|
||
<swp-schedule-cell class="header">
|
||
<swp-day-name>Ons</swp-day-name>
|
||
<swp-day-date>25/12</swp-day-date>
|
||
</swp-schedule-cell>
|
||
<swp-schedule-cell class="header">
|
||
<swp-day-name>Tor</swp-day-name>
|
||
<swp-day-date>26/12</swp-day-date>
|
||
</swp-schedule-cell>
|
||
<swp-schedule-cell class="header">
|
||
<swp-day-name>Fre</swp-day-name>
|
||
<swp-day-date>27/12</swp-day-date>
|
||
</swp-schedule-cell>
|
||
<swp-schedule-cell class="header">
|
||
<swp-day-name>Lør</swp-day-name>
|
||
<swp-day-date>28/12</swp-day-date>
|
||
</swp-schedule-cell>
|
||
<swp-schedule-cell class="header">
|
||
<swp-day-name>Søn</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" 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" 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" 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" 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" 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');
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|