192 lines
5 KiB
TypeScript
192 lines
5 KiB
TypeScript
|
|
/**
|
||
|
|
* Employees Controller
|
||
|
|
*
|
||
|
|
* Handles content swap between list view and detail view,
|
||
|
|
* plus tab switching within each view.
|
||
|
|
* Uses History API for browser back/forward navigation.
|
||
|
|
*/
|
||
|
|
|
||
|
|
export class EmployeesController {
|
||
|
|
private listView: HTMLElement | null = null;
|
||
|
|
private detailView: HTMLElement | null = null;
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
this.listView = document.getElementById('employees-list-view');
|
||
|
|
this.detailView = document.getElementById('employee-detail-view');
|
||
|
|
|
||
|
|
// Only initialize if we're on the employees page
|
||
|
|
if (!this.listView) return;
|
||
|
|
|
||
|
|
this.setupListTabs();
|
||
|
|
this.setupDetailTabs();
|
||
|
|
this.setupChevronNavigation();
|
||
|
|
this.setupBackNavigation();
|
||
|
|
this.setupHistoryNavigation();
|
||
|
|
this.restoreStateFromUrl();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Setup popstate listener for browser back/forward
|
||
|
|
*/
|
||
|
|
private setupHistoryNavigation(): void {
|
||
|
|
window.addEventListener('popstate', (e: PopStateEvent) => {
|
||
|
|
if (e.state?.employeeKey) {
|
||
|
|
this.showDetailViewInternal(e.state.employeeKey);
|
||
|
|
} else {
|
||
|
|
this.showListViewInternal();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Restore view state from URL on page load
|
||
|
|
*/
|
||
|
|
private restoreStateFromUrl(): void {
|
||
|
|
const hash = window.location.hash;
|
||
|
|
if (hash.startsWith('#employee-')) {
|
||
|
|
const employeeKey = hash.substring(1); // Remove #
|
||
|
|
this.showDetailViewInternal(employeeKey);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Setup tab switching for the list view
|
||
|
|
*/
|
||
|
|
private setupListTabs(): void {
|
||
|
|
if (!this.listView) return;
|
||
|
|
|
||
|
|
const tabs = this.listView.querySelectorAll<HTMLElement>('swp-tab-bar > swp-tab[data-tab]');
|
||
|
|
|
||
|
|
tabs.forEach(tab => {
|
||
|
|
tab.addEventListener('click', () => {
|
||
|
|
const targetTab = tab.dataset.tab;
|
||
|
|
if (targetTab) {
|
||
|
|
this.switchTab(this.listView!, targetTab);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Setup tab switching for the detail view
|
||
|
|
*/
|
||
|
|
private setupDetailTabs(): void {
|
||
|
|
if (!this.detailView) return;
|
||
|
|
|
||
|
|
const tabs = this.detailView.querySelectorAll<HTMLElement>('swp-tab-bar > swp-tab[data-tab]');
|
||
|
|
|
||
|
|
tabs.forEach(tab => {
|
||
|
|
tab.addEventListener('click', () => {
|
||
|
|
const targetTab = tab.dataset.tab;
|
||
|
|
if (targetTab) {
|
||
|
|
this.switchTab(this.detailView!, targetTab);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Switch to a specific tab within a container
|
||
|
|
*/
|
||
|
|
private switchTab(container: HTMLElement, targetTab: string): void {
|
||
|
|
const tabs = container.querySelectorAll<HTMLElement>('swp-tab-bar > swp-tab[data-tab]');
|
||
|
|
const contents = container.querySelectorAll<HTMLElement>('swp-tab-content[data-tab]');
|
||
|
|
|
||
|
|
tabs.forEach(t => {
|
||
|
|
t.classList.toggle('active', t.dataset.tab === targetTab);
|
||
|
|
});
|
||
|
|
|
||
|
|
contents.forEach(content => {
|
||
|
|
content.classList.toggle('active', content.dataset.tab === targetTab);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Setup row click to show detail view
|
||
|
|
* Ignores clicks on action buttons
|
||
|
|
*/
|
||
|
|
private setupChevronNavigation(): void {
|
||
|
|
document.addEventListener('click', (e: Event) => {
|
||
|
|
const target = e.target as HTMLElement;
|
||
|
|
|
||
|
|
// Ignore clicks on action buttons
|
||
|
|
if (target.closest('swp-icon-btn') || target.closest('swp-table-actions')) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const row = target.closest<HTMLElement>('swp-employee-row[data-employee-detail]');
|
||
|
|
|
||
|
|
if (row) {
|
||
|
|
const employeeKey = row.dataset.employeeDetail;
|
||
|
|
if (employeeKey) {
|
||
|
|
this.showDetailView(employeeKey);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Setup back button to return to list view
|
||
|
|
*/
|
||
|
|
private setupBackNavigation(): void {
|
||
|
|
document.addEventListener('click', (e: Event) => {
|
||
|
|
const target = e.target as HTMLElement;
|
||
|
|
const backLink = target.closest<HTMLElement>('[data-employee-back]');
|
||
|
|
|
||
|
|
if (backLink) {
|
||
|
|
this.showListView();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Show the detail view and hide list view (with history push)
|
||
|
|
*/
|
||
|
|
private showDetailView(employeeKey: string): void {
|
||
|
|
// Push state to history
|
||
|
|
history.pushState(
|
||
|
|
{ employeeKey },
|
||
|
|
'',
|
||
|
|
`#${employeeKey}`
|
||
|
|
);
|
||
|
|
this.showDetailViewInternal(employeeKey);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Show detail view without modifying history (for popstate)
|
||
|
|
*/
|
||
|
|
private showDetailViewInternal(employeeKey: string): void {
|
||
|
|
if (this.listView && this.detailView) {
|
||
|
|
this.listView.style.display = 'none';
|
||
|
|
this.detailView.style.display = 'block';
|
||
|
|
this.detailView.dataset.employee = employeeKey;
|
||
|
|
|
||
|
|
// Reset to first tab
|
||
|
|
this.switchTab(this.detailView, 'general');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Show the list view and hide detail view (with history push)
|
||
|
|
*/
|
||
|
|
private showListView(): void {
|
||
|
|
// Push state to history (clear hash)
|
||
|
|
history.pushState(
|
||
|
|
{},
|
||
|
|
'',
|
||
|
|
window.location.pathname
|
||
|
|
);
|
||
|
|
this.showListViewInternal();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Show list view without modifying history (for popstate)
|
||
|
|
*/
|
||
|
|
private showListViewInternal(): void {
|
||
|
|
if (this.listView && this.detailView) {
|
||
|
|
this.detailView.style.display = 'none';
|
||
|
|
this.listView.style.display = 'block';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|