Refactors employee details and UI controls

Enhances employee hours view with dynamic weekly schedule rendering
Updates toggle slider and theme switch components with improved interactions
Adds more flexible notification and settings configurations for employees

Improves user experience by streamlining UI controls and schedule display
This commit is contained in:
Janus C. H. Knudsen 2026-01-15 16:59:56 +01:00
parent 6746e876d7
commit 545d6606a6
18 changed files with 506 additions and 206 deletions

View file

@ -11,6 +11,7 @@ import { SearchController } from './modules/search';
import { LockScreenController } from './modules/lockscreen';
import { CashController } from './modules/cash';
import { EmployeesController } from './modules/employees';
import { ControlsController } from './modules/controls';
/**
* Main application class
@ -23,6 +24,7 @@ export class App {
readonly lockScreen: LockScreenController;
readonly cash: CashController;
readonly employees: EmployeesController;
readonly controls: ControlsController;
constructor() {
// Initialize controllers
@ -33,6 +35,7 @@ export class App {
this.lockScreen = new LockScreenController(this.drawers);
this.cash = new CashController();
this.employees = new EmployeesController();
this.controls = new ControlsController();
}
}

View file

@ -0,0 +1,36 @@
/**
* Controls Module
*
* Handles generic UI controls functionality:
* - Toggle sliders (Ja/Nej switches)
*/
/**
* Controller for generic UI controls
*/
export class ControlsController {
constructor() {
this.initToggleSliders();
}
/**
* Initialize all toggle sliders on the page
* Toggle slider: Ja/Nej button switch with data-value attribute
* Clicking anywhere on the slider toggles the value
*/
private initToggleSliders(): void {
document.querySelectorAll('swp-toggle-slider').forEach(slider => {
slider.addEventListener('click', () => {
const el = slider as HTMLElement;
const newValue = el.dataset.value === 'yes' ? 'no' : 'yes';
el.dataset.value = newValue;
// Dispatch custom event for listeners
slider.dispatchEvent(new CustomEvent('toggle', {
bubbles: true,
detail: { value: newValue }
}));
});
});
}
}

View file

@ -1028,55 +1028,66 @@ class ScheduleController {
* Open the schedule drawer (no overlay - user can still interact with table)
*/
private openDrawer(): void {
// Lås tabelbredde før drawer åbner for at undgå "hop"
const container = document.querySelector('swp-tab-content[data-tab="schedule"] swp-page-container') as HTMLElement;
const table = document.getElementById('scheduleTable');
// Gem nuværende padding FØR klasser tilføjes
const startPadding = container ? getComputedStyle(container).paddingRight : '0px';
// Lås tabelbredde før drawer åbner for at undgå "hop"
if (table) {
const rect = table.getBoundingClientRect();
table.style.width = `${rect.width}px`;
}
// Animate container med Web Animations API
const container = document.querySelector('swp-tab-content[data-tab="schedule"] swp-page-container') as HTMLElement;
if (container) {
const currentStyles = getComputedStyle(container);
const currentMaxWidth = currentStyles.maxWidth;
const currentMargin = currentStyles.margin;
const currentPaddingRight = currentStyles.paddingRight;
// Tilføj klasser med det samme (maxWidth og margin ændres instant)
this.drawer?.classList.add('open');
document.body.classList.add('schedule-drawer-open');
// Animate kun padding fra gemt værdi
if (container) {
container.animate([
{ maxWidth: currentMaxWidth, margin: currentMargin, paddingRight: currentPaddingRight },
{ maxWidth: 'none', margin: '0px', paddingRight: '420px' }
{ paddingRight: startPadding },
{ paddingRight: '420px' }
], {
duration: 300,
duration: 200,
easing: 'ease',
fill: 'forwards'
});
}
this.drawer?.classList.add('open');
document.body.classList.add('schedule-drawer-open');
}
/**
* Close the schedule drawer
*/
private closeDrawer(): void {
// Luk drawer med det samme (visuelt)
this.drawer?.classList.remove('open');
const container = document.querySelector('swp-tab-content[data-tab="schedule"] swp-page-container') as HTMLElement;
const table = document.getElementById('scheduleTable');
if (container) {
// Hent nuværende computed styles for animation
const animation = container.getAnimations()[0];
if (animation) {
animation.cancel();
// Afspil animationen baglæns
animation.reverse();
animation.onfinish = () => {
animation.cancel();
// Fjern klasser og låst bredde når animation er færdig
document.body.classList.remove('schedule-drawer-open');
if (table) {
table.style.width = '';
}
};
return; // Exit early - cleanup happens in onfinish
}
}
// Fjern låst bredde så tabellen kan tilpasse sig igen
const table = document.getElementById('scheduleTable');
// Ingen animation, fjern klasser og låst bredde med det samme
document.body.classList.remove('schedule-drawer-open');
if (table) {
table.style.width = '';
}
this.drawer?.classList.remove('open');
document.body.classList.remove('schedule-drawer-open');
}
}

View file

@ -13,10 +13,12 @@ export class ThemeController {
private root: HTMLElement;
private themeOptions: NodeListOf<HTMLElement>;
private themeCheckbox: HTMLInputElement | null;
constructor() {
this.root = document.documentElement;
this.themeOptions = document.querySelectorAll<HTMLElement>('swp-theme-option');
this.themeCheckbox = document.getElementById('themeCheckbox') as HTMLInputElement | null;
this.applyTheme(this.current);
this.updateUI();
@ -77,15 +79,19 @@ export class ThemeController {
}
private updateUI(): void {
if (!this.themeOptions) return;
const darkActive = this.isDark;
this.themeOptions.forEach(option => {
// Update theme options
this.themeOptions?.forEach(option => {
const theme = option.dataset.theme as Theme;
const isActive = (theme === 'dark' && darkActive) || (theme === 'light' && !darkActive);
option.classList.toggle('active', isActive);
});
// Update checkbox (checked = dark mode)
if (this.themeCheckbox) {
this.themeCheckbox.checked = darkActive;
}
}
private setupListeners(): void {
@ -94,6 +100,11 @@ export class ThemeController {
option.addEventListener('click', (e) => this.handleOptionClick(e));
});
// Theme checkbox toggle
this.themeCheckbox?.addEventListener('change', () => {
this.set(this.themeCheckbox!.checked ? 'dark' : 'light');
});
// System theme changes
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', () => this.handleSystemChange());