PlanTempusApp/PlanTempus.Application/wwwroot/ts/modules/drawers.ts
Janus C. H. Knudsen 12869e35bf wip
2026-01-11 18:18:36 +01:00

285 lines
8.1 KiB
TypeScript

/**
* Drawer Controller
*
* Handles all drawer functionality including profile, notifications, and todo drawers
*/
export type DrawerName = 'profile' | 'notification' | 'todo' | 'newTodo';
export class DrawerController {
private profileDrawer: HTMLElement | null = null;
private notificationDrawer: HTMLElement | null = null;
private todoDrawer: HTMLElement | null = null;
private newTodoDrawer: HTMLElement | null = null;
private overlay: HTMLElement | null = null;
private activeDrawer: DrawerName | null = null;
private activeGenericDrawer: HTMLElement | null = null;
constructor() {
this.profileDrawer = document.getElementById('profileDrawer');
this.notificationDrawer = document.getElementById('notificationDrawer');
this.todoDrawer = document.getElementById('todoDrawer');
this.newTodoDrawer = document.getElementById('newTodoDrawer');
this.overlay = document.getElementById('drawerOverlay');
this.setupListeners();
this.setupGenericDrawers();
}
/**
* Get currently active drawer name
*/
get active(): DrawerName | null {
return this.activeDrawer;
}
/**
* Open a drawer by name
*/
open(name: DrawerName): void {
this.closeAll();
const drawer = this.getDrawer(name);
if (drawer && this.overlay) {
drawer.classList.add('active');
this.overlay.classList.add('active');
document.body.style.overflow = 'hidden';
this.activeDrawer = name;
}
}
/**
* Close a specific drawer
*/
close(name: DrawerName): void {
const drawer = this.getDrawer(name);
drawer?.classList.remove('active');
// Only hide overlay if no drawers are active
if (this.overlay && !document.querySelector('.active[class*="drawer"]')) {
this.overlay.classList.remove('active');
document.body.style.overflow = '';
}
if (this.activeDrawer === name) {
this.activeDrawer = null;
}
}
/**
* Close all drawers
*/
closeAll(): void {
[this.profileDrawer, this.notificationDrawer, this.todoDrawer, this.newTodoDrawer]
.forEach(drawer => drawer?.classList.remove('active'));
// Close any generic drawers
this.closeGenericDrawer();
this.overlay?.classList.remove('active');
document.body.style.overflow = '';
this.activeDrawer = null;
}
/**
* Open a generic drawer by ID
*/
openGenericDrawer(drawerId: string): void {
this.closeAll();
const drawer = document.getElementById(drawerId);
if (drawer && this.overlay) {
drawer.classList.add('open');
this.overlay.classList.add('active');
document.body.style.overflow = 'hidden';
this.activeGenericDrawer = drawer;
}
}
/**
* Close the currently open generic drawer
*/
closeGenericDrawer(): void {
this.activeGenericDrawer?.classList.remove('open');
this.activeGenericDrawer = null;
}
/**
* Open profile drawer
*/
openProfile(): void {
this.open('profile');
}
/**
* Open notification drawer
*/
openNotification(): void {
this.open('notification');
}
/**
* Open todo drawer (slides on top of profile)
*/
openTodo(): void {
this.todoDrawer?.classList.add('active');
}
/**
* Close todo drawer
*/
closeTodo(): void {
this.todoDrawer?.classList.remove('active');
this.closeNewTodo();
}
/**
* Open new todo drawer
*/
openNewTodo(): void {
this.newTodoDrawer?.classList.add('active');
}
/**
* Close new todo drawer
*/
closeNewTodo(): void {
this.newTodoDrawer?.classList.remove('active');
}
/**
* Mark all notifications as read
*/
markAllNotificationsRead(): void {
if (!this.notificationDrawer) return;
const unreadItems = this.notificationDrawer.querySelectorAll<HTMLElement>(
'swp-notification-item[data-unread="true"]'
);
unreadItems.forEach(item => item.removeAttribute('data-unread'));
const badge = document.querySelector<HTMLElement>('swp-notification-badge');
if (badge) {
badge.style.display = 'none';
}
}
private getDrawer(name: DrawerName): HTMLElement | null {
switch (name) {
case 'profile': return this.profileDrawer;
case 'notification': return this.notificationDrawer;
case 'todo': return this.todoDrawer;
case 'newTodo': return this.newTodoDrawer;
}
}
private setupListeners(): void {
// Profile drawer triggers
document.getElementById('profileTrigger')
?.addEventListener('click', () => this.openProfile());
document.getElementById('drawerClose')
?.addEventListener('click', () => this.close('profile'));
// Notification drawer triggers
document.getElementById('notificationsBtn')
?.addEventListener('click', () => this.openNotification());
document.getElementById('notificationDrawerClose')
?.addEventListener('click', () => this.close('notification'));
document.getElementById('markAllRead')
?.addEventListener('click', () => this.markAllNotificationsRead());
// Todo drawer triggers
document.getElementById('openTodoDrawer')
?.addEventListener('click', () => this.openTodo());
document.getElementById('todoDrawerBack')
?.addEventListener('click', () => this.closeTodo());
// New todo drawer triggers
document.getElementById('addTodoBtn')
?.addEventListener('click', () => this.openNewTodo());
document.getElementById('newTodoDrawerBack')
?.addEventListener('click', () => this.closeNewTodo());
document.getElementById('cancelNewTodo')
?.addEventListener('click', () => this.closeNewTodo());
document.getElementById('saveNewTodo')
?.addEventListener('click', () => this.closeNewTodo());
// Overlay click closes all
this.overlay?.addEventListener('click', () => this.closeAll());
// Escape key closes all
document.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Escape') this.closeAll();
});
// Todo interactions
this.todoDrawer?.addEventListener('click', (e) => this.handleTodoClick(e));
// Visibility options
document.addEventListener('click', (e) => this.handleVisibilityClick(e));
}
private handleTodoClick(e: Event): void {
const target = e.target as HTMLElement;
const todoItem = target.closest<HTMLElement>('swp-todo-item');
const checkbox = target.closest<HTMLElement>('swp-todo-checkbox');
if (checkbox && todoItem) {
const isCompleted = todoItem.dataset.completed === 'true';
if (isCompleted) {
todoItem.removeAttribute('data-completed');
} else {
todoItem.dataset.completed = 'true';
}
}
// Toggle section collapse
const sectionHeader = target.closest<HTMLElement>('swp-todo-section-header');
if (sectionHeader) {
const section = sectionHeader.closest<HTMLElement>('swp-todo-section');
section?.classList.toggle('collapsed');
}
}
private handleVisibilityClick(e: Event): void {
const target = e.target as HTMLElement;
const option = target.closest<HTMLElement>('swp-visibility-option');
if (option) {
document.querySelectorAll<HTMLElement>('swp-visibility-option')
.forEach(o => o.classList.remove('active'));
option.classList.add('active');
}
}
/**
* Setup generic drawer triggers and close buttons
* Uses data-drawer-trigger="drawer-id" and data-drawer-close attributes
*/
private setupGenericDrawers(): void {
// Handle drawer triggers
document.addEventListener('click', (e: Event) => {
const target = e.target as HTMLElement;
const trigger = target.closest<HTMLElement>('[data-drawer-trigger]');
if (trigger) {
const drawerId = trigger.dataset.drawerTrigger;
if (drawerId) {
this.openGenericDrawer(drawerId);
}
}
});
// Handle drawer close buttons
document.addEventListener('click', (e: Event) => {
const target = e.target as HTMLElement;
const closeBtn = target.closest<HTMLElement>('[data-drawer-close]');
if (closeBtn) {
this.closeGenericDrawer();
this.overlay?.classList.remove('active');
document.body.style.overflow = '';
}
});
}
}