Adds employee work schedule component

Introduces a new work schedule feature for managing employee shifts and schedules

Implements interactive schedule view with:
- Week-based schedule grid
- Shift status tracking (work, vacation, sick, off)
- Editable time ranges
- Repeat shift functionality

Enhances employee management with dynamic scheduling capabilities
This commit is contained in:
Janus C. H. Knudsen 2026-01-14 22:47:40 +01:00
parent d5a803ba80
commit 3214cbdc16
11 changed files with 1669 additions and 0 deletions

View file

@ -879,6 +879,531 @@ swp-data-row.focus-highlight {
}
}
/* ===========================================
WORK SCHEDULE TABLE
=========================================== */
swp-schedule-table {
display: grid;
grid-template-columns: 180px repeat(7, minmax(100px, 1fr));
border-radius: var(--radius-md);
overflow: hidden;
border: 1px solid var(--color-border);
background: var(--color-surface);
}
swp-schedule-cell {
display: flex;
flex-direction: column;
justify-content: center;
padding: 12px 16px;
min-height: 60px;
background: var(--color-surface);
border-right: 1px solid var(--color-border);
border-bottom: 1px solid var(--color-border);
user-select: none;
}
/* Last column: no right border */
swp-schedule-cell:nth-child(8n) {
border-right: none;
}
/* Last row: no bottom border */
swp-schedule-cell:nth-last-child(-n+8) {
border-bottom: none;
}
swp-schedule-cell.header {
background: var(--color-background-alt);
font-weight: var(--font-weight-medium);
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: var(--font-weight-semibold);
color: var(--color-text);
}
swp-schedule-cell.header.closed {
background: color-mix(in srgb, #f59e0b 10%, var(--color-background-alt));
border-top: 2px solid #f59e0b;
border-left: 2px solid #f59e0b;
border-right: 2px solid #f59e0b;
border-bottom: none;
swp-day-name {
color: #d97706;
}
}
swp-schedule-cell.employee {
align-items: flex-start;
gap: 2px;
}
swp-schedule-cell.day {
align-items: center;
text-align: center;
position: relative;
}
swp-schedule-cell.day.closed-day {
background: color-mix(in srgb, #f59e0b 6%, var(--color-surface));
border-left: 2px solid #f59e0b;
border-right: 2px solid #f59e0b;
swp-time-display {
opacity: 0.5;
}
}
/* Last cell in closed column gets bottom border */
swp-schedule-cell.day.closed-day:nth-last-child(-n+8) {
border-bottom: 2px solid #f59e0b;
}
/* Schedule employee info */
swp-schedule-cell swp-employee-name {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--color-text);
}
swp-schedule-cell swp-employee-hours {
font-family: var(--font-mono);
font-size: 12px;
color: var(--color-text-muted);
}
/* Day header */
swp-day-name {
font-weight: var(--font-weight-medium);
color: var(--color-text);
}
swp-day-date {
font-size: 12px;
color: var(--color-text-muted);
font-weight: var(--font-weight-normal);
}
/* Time display variants */
swp-time-display {
font-family: var(--font-mono);
font-size: 12px;
font-weight: var(--font-weight-medium);
padding: 4px 8px;
border-radius: 4px;
background: var(--bg-teal-light);
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;
}
/* Edit mode */
body.schedule-edit-mode swp-schedule-cell.day {
cursor: pointer;
}
body.schedule-edit-mode swp-schedule-cell.day:hover {
background: var(--color-background-alt);
}
body.schedule-edit-mode swp-schedule-cell.day.selected {
background: color-mix(in srgb, var(--color-teal) 12%, white);
border: 2px solid var(--color-teal);
margin: -1px;
padding: 11px 15px;
}
body.schedule-edit-mode swp-schedule-cell.header:not(.week-number) {
cursor: pointer;
}
body.schedule-edit-mode swp-schedule-cell.header:not(.week-number):hover {
background: var(--color-border);
}
/* Status options in 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 var(--transition-fast);
font-size: 13px;
font-weight: var(--font-weight-medium);
background: var(--color-background-alt);
color: var(--color-text-secondary);
&::before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
flex-shrink: 0;
}
&[data-status="work"] {
--status-color: var(--color-teal);
}
&[data-status="off"] {
--status-color: #7c3aed;
}
&[data-status="vacation"] {
--status-color: #f59e0b;
}
&[data-status="sick"] {
--status-color: #e53935;
}
&::before {
background: var(--status-color);
}
&:hover {
background: var(--color-border);
}
&.selected {
background: color-mix(in srgb, var(--status-color) 15%, white);
color: var(--status-color);
}
}
/* 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;
&: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;
&::-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);
}
&::-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: var(--font-weight-medium);
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;
}
/* Toggle options (Enkelt/Gentagelse) */
swp-toggle-options {
display: flex;
gap: 0;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
overflow: hidden;
}
swp-toggle-option {
flex: 1;
padding: 10px 16px;
text-align: center;
font-size: var(--font-size-sm);
cursor: pointer;
background: var(--color-surface);
border-right: 1px solid var(--color-border);
transition: all var(--transition-fast);
&:last-child {
border-right: none;
}
&:hover {
background: var(--color-background-alt);
}
&.selected {
background: var(--color-teal);
color: white;
}
}
/* Schedule drawer employee display */
swp-employee-display {
display: flex;
align-items: center;
gap: 10px;
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: var(--font-weight-semibold);
font-size: 12px;
flex-shrink: 0;
}
&.empty swp-employee-avatar {
background: var(--color-border);
color: var(--color-text-muted);
}
&.multi swp-employee-avatar {
background: var(--color-text-muted);
}
}
/* ===========================================
SCHEDULE DRAWER (matches POC exactly)
=========================================== */
/* Drawer header with background */
#schedule-drawer swp-drawer-header {
background: var(--color-background-alt);
padding: 20px 24px;
}
#schedule-drawer swp-drawer-title {
font-size: 18px;
}
/* Drawer body/content */
#schedule-drawer swp-drawer-body {
padding: 24px;
}
/* Form row layout */
#schedule-drawer swp-form-row {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 16px;
}
/* Form labels - uppercase style from POC */
#schedule-drawer swp-form-label {
font-size: 11px;
font-weight: 400;
color: var(--color-text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
.optional,
.auto {
font-weight: 400;
text-transform: none;
color: var(--color-text-muted);
}
}
/* Form value (read-only display) */
#schedule-drawer swp-form-value {
font-size: 15px;
font-weight: 500;
color: var(--color-text);
}
/* Form divider */
#schedule-drawer swp-form-divider {
display: block;
height: 1px;
background: var(--color-border);
margin: 20px 0;
}
/* Form hint text */
#schedule-drawer swp-form-hint {
display: block;
font-size: 12px;
color: var(--color-text-muted);
margin: -8px 0 16px 0;
line-height: 1.4;
}
/* Form group - gray card background from POC */
#schedule-drawer swp-form-group {
display: block;
padding: 16px;
background: var(--color-background-alt);
border-radius: 8px;
margin-top: 16px;
swp-form-row:last-child {
margin-bottom: 0;
}
}
/* Form select wrapper */
#schedule-drawer swp-form-select {
display: block;
select {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--color-border);
border-radius: 6px;
font-size: 14px;
font-family: var(--font-family);
color: var(--color-text);
background: var(--color-surface);
cursor: pointer;
&:focus {
outline: none;
border-color: var(--color-teal);
}
}
}
/* Text inputs in drawer */
#schedule-drawer input[type="text"],
#schedule-drawer input[type="date"] {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--color-border);
border-radius: 6px;
font-size: 14px;
font-family: var(--font-family);
color: var(--color-text);
background: var(--color-surface);
&::placeholder {
color: var(--color-text-muted);
}
&:focus {
outline: none;
border-color: var(--color-teal);
}
}
/* Drawer footer with background */
#schedule-drawer swp-drawer-footer {
display: flex;
gap: 12px;
padding: 20px 24px;
border-top: 1px solid var(--color-border);
background: var(--color-background-alt);
swp-btn {
flex: 1;
}
}
/* ===========================================
RESPONSIVE
=========================================== */