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
This commit is contained in:
Janus C. H. Knudsen 2026-01-24 00:13:05 +01:00
parent 7aaa475a14
commit dc2bab5702
16 changed files with 622 additions and 8 deletions

View file

@ -14,6 +14,7 @@ import { EmployeesController } from './modules/employees';
import { ControlsController } from './modules/controls';
import { ServicesController } from './modules/services';
import { CustomersController } from './modules/customers';
import { SuppliersController } from './modules/suppliers';
import { TrackingController } from './modules/tracking';
import { ReportsController } from './modules/reports';
@ -31,6 +32,7 @@ export class App {
readonly controls: ControlsController;
readonly services: ServicesController;
readonly customers: CustomersController;
readonly suppliers: SuppliersController;
readonly tracking: TrackingController;
readonly reports: ReportsController;
@ -46,6 +48,7 @@ export class App {
this.controls = new ControlsController();
this.services = new ServicesController();
this.customers = new CustomersController();
this.suppliers = new SuppliersController();
this.tracking = new TrackingController();
this.reports = new ReportsController();
}

View file

@ -0,0 +1,146 @@
/**
* 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;
}
});
}
}