PlanTempusApp/PlanTempus.Application/wwwroot/ts/modules/suppliers.ts
Janus C. H. Knudsen dc2bab5702 Adds suppliers feature to application
Introduces comprehensive suppliers management with mock data, localization, and UI components

Implements:
- Suppliers page with data table
- Localization for Danish and English
- Search and filtering functionality
- Responsive table design
- Mock data for initial population
2026-01-24 00:13:05 +01:00

146 lines
4 KiB
TypeScript

/**
* Suppliers Controller
*
* Handles:
* - Fuzzy search with Fuse.js
* - Row click navigation
*/
import Fuse from 'fuse.js';
interface SupplierItem {
name: string;
contact: string;
city: string;
element: HTMLElement;
}
export class SuppliersController {
private fuse: Fuse<SupplierItem> | null = null;
private suppliers: SupplierItem[] = [];
private searchInput: HTMLInputElement | null = null;
private emptyState: HTMLElement | null = null;
private dataTable: HTMLElement | null = null;
constructor() {
// Only initialize if we're on the suppliers page
const suppliersTable = document.querySelector('swp-card.suppliers-list');
if (!suppliersTable) return;
this.init();
}
private init(): void {
this.searchInput = document.getElementById('supplierSearchInput') as HTMLInputElement;
this.emptyState = document.getElementById('supplierEmptyState');
this.dataTable = document.querySelector('swp-card.suppliers-list swp-data-table');
this.buildSupplierIndex();
this.setupSearch();
this.setupRowNavigation();
}
private buildSupplierIndex(): void {
const supplierRows = document.querySelectorAll('swp-card.suppliers-list swp-data-table-row');
supplierRows.forEach((row) => {
const element = row as HTMLElement;
const name = element.dataset.name || '';
const contact = element.dataset.contact || '';
const city = element.dataset.city || '';
this.suppliers.push({
name,
contact,
city,
element
});
});
}
private setupSearch(): void {
if (!this.searchInput) return;
// Initialize Fuse.js with multiple search keys
this.fuse = new Fuse(this.suppliers, {
keys: ['name', 'contact', 'city'],
threshold: 0.3,
minMatchCharLength: 2
});
// Listen for input with debounce
let debounceTimer: number;
this.searchInput.addEventListener('input', (e) => {
clearTimeout(debounceTimer);
debounceTimer = window.setTimeout(() => {
const query = (e.target as HTMLInputElement).value.trim();
this.filterSuppliers(query);
}, 150);
});
}
private filterSuppliers(query: string): void {
if (!query || query.length < 2) {
this.showAll();
return;
}
if (!this.fuse) return;
// Get matching suppliers
const results = this.fuse.search(query);
const matchingSuppliers = new Set(results.map(r => r.item.element));
let visibleCount = 0;
// Show/hide suppliers
this.suppliers.forEach(supplier => {
if (matchingSuppliers.has(supplier.element)) {
supplier.element.style.display = 'grid';
visibleCount++;
} else {
supplier.element.style.display = 'none';
}
});
// Show/hide empty state
this.updateEmptyState(visibleCount);
}
private showAll(): void {
this.suppliers.forEach(supplier => {
supplier.element.style.display = 'grid';
});
this.updateEmptyState(this.suppliers.length);
}
private updateEmptyState(visibleCount: number): void {
if (!this.emptyState || !this.dataTable) return;
if (visibleCount === 0) {
this.emptyState.style.display = 'flex';
// Hide header when no results
const header = this.dataTable.querySelector('swp-data-table-header') as HTMLElement;
if (header) header.style.display = 'none';
} else {
this.emptyState.style.display = 'none';
// Show header when results exist
const header = this.dataTable.querySelector('swp-data-table-header') as HTMLElement;
if (header) header.style.display = 'grid';
}
}
private setupRowNavigation(): void {
// Click on rows navigates to supplier detail page
document.addEventListener('click', (e) => {
const row = (e.target as HTMLElement).closest<HTMLElement>('swp-card.suppliers-list swp-data-table-row[data-href]');
if (!row) return;
const href = row.dataset.href;
if (href) {
window.location.href = href;
}
});
}
}