From dc2bab57021a0bc0fa2f6f9e4477f23dd16b07f1 Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Sat, 24 Jan 2026 00:13:05 +0100 Subject: [PATCH] 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 --- .../ReconciliationTable/Default.cshtml | 8 +- .../Localization/Translations/da.json | 27 +++- .../Localization/Translations/en.json | 27 +++- .../Features/Menu/Services/MockMenuService.cs | 2 +- .../Components/SupplierRow/Default.cshtml | 16 ++ .../SupplierRow/SupplierRowViewComponent.cs | 11 ++ .../Components/SupplierTable/Default.cshtml | 39 +++++ .../SupplierTableViewComponent.cs | 116 ++++++++++++++ .../Suppliers/Data/suppliersMock.json | 112 ++++++++++++++ .../Features/Suppliers/Pages/Index.cshtml | 39 +++++ .../Features/Suppliers/Pages/Index.cshtml.cs | 10 ++ .../Features/_Shared/Pages/_Layout.cshtml | 1 + .../wwwroot/css/components.css | 3 +- .../wwwroot/css/suppliers.css | 70 +++++++++ PlanTempus.Application/wwwroot/ts/app.ts | 3 + .../wwwroot/ts/modules/suppliers.ts | 146 ++++++++++++++++++ 16 files changed, 622 insertions(+), 8 deletions(-) create mode 100644 PlanTempus.Application/Features/Suppliers/Components/SupplierRow/Default.cshtml create mode 100644 PlanTempus.Application/Features/Suppliers/Components/SupplierRow/SupplierRowViewComponent.cs create mode 100644 PlanTempus.Application/Features/Suppliers/Components/SupplierTable/Default.cshtml create mode 100644 PlanTempus.Application/Features/Suppliers/Components/SupplierTable/SupplierTableViewComponent.cs create mode 100644 PlanTempus.Application/Features/Suppliers/Data/suppliersMock.json create mode 100644 PlanTempus.Application/Features/Suppliers/Pages/Index.cshtml create mode 100644 PlanTempus.Application/Features/Suppliers/Pages/Index.cshtml.cs create mode 100644 PlanTempus.Application/wwwroot/css/suppliers.css create mode 100644 PlanTempus.Application/wwwroot/ts/modules/suppliers.ts diff --git a/PlanTempus.Application/Features/CashRegister/Components/ReconciliationTable/Default.cshtml b/PlanTempus.Application/Features/CashRegister/Components/ReconciliationTable/Default.cshtml index a341694..840525f 100644 --- a/PlanTempus.Application/Features/CashRegister/Components/ReconciliationTable/Default.cshtml +++ b/PlanTempus.Application/Features/CashRegister/Components/ReconciliationTable/Default.cshtml @@ -64,7 +64,7 @@ - Download PDF + Download Z-Rapport @@ -95,7 +95,7 @@ - Download PDF + Download Z-Rapport @@ -126,7 +126,7 @@ - Download PDF + Download Z-Rapport @@ -157,7 +157,7 @@ - Download PDF + Download Z_Rapport diff --git a/PlanTempus.Application/Features/Localization/Translations/da.json b/PlanTempus.Application/Features/Localization/Translations/da.json index efd6404..a1cebce 100644 --- a/PlanTempus.Application/Features/Localization/Translations/da.json +++ b/PlanTempus.Application/Features/Localization/Translations/da.json @@ -125,7 +125,7 @@ "showingCount": "Viser {count} afstemninger", "exportSaft": "Eksporter SAF-T", "downloadCsv": "Download CSV", - "downloadPdf": "Download PDF", + "downloadPdf": "Download Z-Rapport", "viewTransactions": "Se transaktioner" }, "revenue": { @@ -575,6 +575,31 @@ } } }, + "suppliers": { + "title": "Leverandører", + "subtitle": "Administrer leverandører og indkøb", + "searchPlaceholder": "Søg leverandør, kontaktperson...", + "export": "Eksporter", + "create": "Ny leverandør", + "emptySearch": "Ingen leverandører matcher din søgning", + "stats": { + "total": "Leverandører i alt", + "active": "Aktive", + "purchasesThisMonth": "Indkøb denne måned", + "pendingOrders": "Afventende ordrer" + }, + "column": { + "supplier": "Leverandør", + "contact": "Kontakt", + "products": "Produkter", + "lastOrder": "Sidste ordre", + "status": "Status" + }, + "status": { + "active": "Aktiv", + "inactive": "Inaktiv" + } + }, "customers": { "title": "Kunder", "subtitle": "Administrer kunder og kundekort", diff --git a/PlanTempus.Application/Features/Localization/Translations/en.json b/PlanTempus.Application/Features/Localization/Translations/en.json index b378573..9c55c3e 100644 --- a/PlanTempus.Application/Features/Localization/Translations/en.json +++ b/PlanTempus.Application/Features/Localization/Translations/en.json @@ -125,7 +125,7 @@ "showingCount": "Showing {count} reconciliations", "exportSaft": "Export SAF-T", "downloadCsv": "Download CSV", - "downloadPdf": "Download PDF", + "downloadPdf": "Download Z-Report", "viewTransactions": "View transactions" }, "revenue": { @@ -575,6 +575,31 @@ } } }, + "suppliers": { + "title": "Suppliers", + "subtitle": "Manage suppliers and purchases", + "searchPlaceholder": "Search supplier, contact person...", + "export": "Export", + "create": "New supplier", + "emptySearch": "No suppliers match your search", + "stats": { + "total": "Total suppliers", + "active": "Active", + "purchasesThisMonth": "Purchases this month", + "pendingOrders": "Pending orders" + }, + "column": { + "supplier": "Supplier", + "contact": "Contact", + "products": "Products", + "lastOrder": "Last order", + "status": "Status" + }, + "status": { + "active": "Active", + "inactive": "Inactive" + } + }, "customers": { "detail": { "tabs": { diff --git a/PlanTempus.Application/Features/Menu/Services/MockMenuService.cs b/PlanTempus.Application/Features/Menu/Services/MockMenuService.cs index 229a7ff..7a916aa 100644 --- a/PlanTempus.Application/Features/Menu/Services/MockMenuService.cs +++ b/PlanTempus.Application/Features/Menu/Services/MockMenuService.cs @@ -109,7 +109,7 @@ public class MockMenuService : IMenuService Id = "suppliers", Label = "Leverandører", Icon = "ph-truck", - Url = "/poc-leverandoerer.html", + Url = "/leverandoerer", MinimumRole = UserRole.Manager, SortOrder = 2 }, diff --git a/PlanTempus.Application/Features/Suppliers/Components/SupplierRow/Default.cshtml b/PlanTempus.Application/Features/Suppliers/Components/SupplierRow/Default.cshtml new file mode 100644 index 0000000..9da30f7 --- /dev/null +++ b/PlanTempus.Application/Features/Suppliers/Components/SupplierRow/Default.cshtml @@ -0,0 +1,16 @@ +@model PlanTempus.Application.Features.Suppliers.Components.SupplierItemViewModel + + + + + @Model.Name + @Model.City + + + @Model.ContactPerson + @Model.ProductCount + @Model.LastOrderDate + + @Model.StatusText + + diff --git a/PlanTempus.Application/Features/Suppliers/Components/SupplierRow/SupplierRowViewComponent.cs b/PlanTempus.Application/Features/Suppliers/Components/SupplierRow/SupplierRowViewComponent.cs new file mode 100644 index 0000000..7299679 --- /dev/null +++ b/PlanTempus.Application/Features/Suppliers/Components/SupplierRow/SupplierRowViewComponent.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; + +namespace PlanTempus.Application.Features.Suppliers.Components; + +public class SupplierRowViewComponent : ViewComponent +{ + public IViewComponentResult Invoke(SupplierItemViewModel supplier) + { + return View(supplier); + } +} diff --git a/PlanTempus.Application/Features/Suppliers/Components/SupplierTable/Default.cshtml b/PlanTempus.Application/Features/Suppliers/Components/SupplierTable/Default.cshtml new file mode 100644 index 0000000..6207f1b --- /dev/null +++ b/PlanTempus.Application/Features/Suppliers/Components/SupplierTable/Default.cshtml @@ -0,0 +1,39 @@ +@model PlanTempus.Application.Features.Suppliers.Components.SupplierTableViewModel + + + + + + + + + + @Model.ExportButtonText + + + + @Model.CreateButtonText + + + + + + + + @Model.ColumnSupplier + @Model.ColumnContact + @Model.ColumnProducts + @Model.ColumnLastOrder + @Model.ColumnStatus + + @foreach (var supplier in Model.Suppliers) + { + @await Component.InvokeAsync("SupplierRow", supplier) + } + + + + diff --git a/PlanTempus.Application/Features/Suppliers/Components/SupplierTable/SupplierTableViewComponent.cs b/PlanTempus.Application/Features/Suppliers/Components/SupplierTable/SupplierTableViewComponent.cs new file mode 100644 index 0000000..6003e4d --- /dev/null +++ b/PlanTempus.Application/Features/Suppliers/Components/SupplierTable/SupplierTableViewComponent.cs @@ -0,0 +1,116 @@ +using System.Globalization; +using System.Text.Json; +using Microsoft.AspNetCore.Mvc; +using PlanTempus.Application.Features.Localization.Services; + +namespace PlanTempus.Application.Features.Suppliers.Components; + +public class SupplierTableViewComponent : ViewComponent +{ + private readonly ILocalizationService _localization; + private readonly IWebHostEnvironment _env; + + public SupplierTableViewComponent(ILocalizationService localization, IWebHostEnvironment env) + { + _localization = localization; + _env = env; + } + + public IViewComponentResult Invoke() + { + var data = LoadSupplierData(); + var model = new SupplierTableViewModel + { + SearchPlaceholder = _localization.Get("suppliers.searchPlaceholder"), + ExportButtonText = _localization.Get("suppliers.export"), + CreateButtonText = _localization.Get("suppliers.create"), + ColumnSupplier = _localization.Get("suppliers.column.supplier"), + ColumnContact = _localization.Get("suppliers.column.contact"), + ColumnProducts = _localization.Get("suppliers.column.products"), + ColumnLastOrder = _localization.Get("suppliers.column.lastOrder"), + ColumnStatus = _localization.Get("suppliers.column.status"), + EmptySearchText = _localization.Get("suppliers.emptySearch"), + Suppliers = data.Suppliers + .OrderBy(s => s.Name) + .Select(s => new SupplierItemViewModel + { + Id = s.Id, + Name = s.Name, + City = s.City, + ContactPerson = s.ContactPerson, + ProductCount = s.ProductCount, + LastOrderDate = FormatLastOrder(s.LastOrder), + IsActive = s.IsActive, + StatusClass = s.IsActive ? "active" : "inactive", + StatusText = s.IsActive + ? _localization.Get("suppliers.status.active") + : _localization.Get("suppliers.status.inactive") + }) + .ToList() + }; + + return View(model); + } + + private SupplierMockData LoadSupplierData() + { + var jsonPath = Path.Combine(_env.ContentRootPath, "Features", "Suppliers", "Data", "suppliersMock.json"); + var json = System.IO.File.ReadAllText(jsonPath); + return JsonSerializer.Deserialize(json, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }) ?? new SupplierMockData(); + } + + private static string FormatLastOrder(string dateStr) + { + if (DateTime.TryParse(dateStr, out var date)) + { + return date.ToString("d. MMMM yyyy", new CultureInfo("da-DK")); + } + return dateStr; + } +} + +public class SupplierTableViewModel +{ + public required string SearchPlaceholder { get; init; } + public required string ExportButtonText { get; init; } + public required string CreateButtonText { get; init; } + public required string ColumnSupplier { get; init; } + public required string ColumnContact { get; init; } + public required string ColumnProducts { get; init; } + public required string ColumnLastOrder { get; init; } + public required string ColumnStatus { get; init; } + public required string EmptySearchText { get; init; } + public required IReadOnlyList Suppliers { get; init; } +} + +public class SupplierItemViewModel +{ + public required string Id { get; init; } + public required string Name { get; init; } + public required string City { get; init; } + public required string ContactPerson { get; init; } + public int ProductCount { get; init; } + public required string LastOrderDate { get; init; } + public bool IsActive { get; init; } + public required string StatusClass { get; init; } + public required string StatusText { get; init; } +} + +internal class SupplierMockData +{ + public List Suppliers { get; set; } = new(); +} + +internal class SupplierData +{ + public string Id { get; set; } = ""; + public string Name { get; set; } = ""; + public string City { get; set; } = ""; + public string ContactPerson { get; set; } = ""; + public int ProductCount { get; set; } + public string LastOrder { get; set; } = ""; + public bool IsActive { get; set; } +} diff --git a/PlanTempus.Application/Features/Suppliers/Data/suppliersMock.json b/PlanTempus.Application/Features/Suppliers/Data/suppliersMock.json new file mode 100644 index 0000000..53bb8e7 --- /dev/null +++ b/PlanTempus.Application/Features/Suppliers/Data/suppliersMock.json @@ -0,0 +1,112 @@ +{ + "suppliers": [ + { + "id": "beauty-group-denmark", + "name": "Beauty Group Denmark", + "city": "København", + "contactPerson": "Lars Hansen", + "productCount": 24, + "lastOrder": "2024-12-15", + "isActive": true + }, + { + "id": "salon-supplies-aps", + "name": "Salon Supplies ApS", + "city": "Aarhus", + "contactPerson": "Mette Nielsen", + "productCount": 18, + "lastOrder": "2024-12-22", + "isActive": true + }, + { + "id": "pro-hair-distribution", + "name": "Pro Hair Distribution", + "city": "Odense", + "contactPerson": "Anders Sørensen", + "productCount": 32, + "lastOrder": "2024-12-10", + "isActive": true + }, + { + "id": "nordic-beauty-import", + "name": "Nordic Beauty Import", + "city": "Aalborg", + "contactPerson": "Pia Kristensen", + "productCount": 15, + "lastOrder": "2024-11-28", + "isActive": true + }, + { + "id": "color-world-as", + "name": "Color World A/S", + "city": "Vejle", + "contactPerson": "Thomas Berg", + "productCount": 8, + "lastOrder": "2024-12-05", + "isActive": false + }, + { + "id": "tools-and-more", + "name": "Tools & More", + "city": "Roskilde", + "contactPerson": "Karen Olsen", + "productCount": 12, + "lastOrder": "2024-12-18", + "isActive": true + }, + { + "id": "scandinavian-cosmetics", + "name": "Scandinavian Cosmetics", + "city": "Helsingør", + "contactPerson": "Erik Madsen", + "productCount": 45, + "lastOrder": "2024-12-20", + "isActive": true + }, + { + "id": "hair-products-international", + "name": "Hair Products International", + "city": "Frederiksberg", + "contactPerson": "Sofie Andersen", + "productCount": 67, + "lastOrder": "2024-12-12", + "isActive": true + }, + { + "id": "danish-beauty-supply", + "name": "Danish Beauty Supply", + "city": "Esbjerg", + "contactPerson": "Michael Petersen", + "productCount": 21, + "lastOrder": "2024-11-15", + "isActive": false + }, + { + "id": "salon-essentials", + "name": "Salon Essentials", + "city": "Kolding", + "contactPerson": "Anne Marie Larsen", + "productCount": 38, + "lastOrder": "2024-12-19", + "isActive": true + }, + { + "id": "nordic-hair-care", + "name": "Nordic Hair Care", + "city": "Horsens", + "contactPerson": "Christian Holm", + "productCount": 29, + "lastOrder": "2024-12-08", + "isActive": true + }, + { + "id": "beauty-wholesale-dk", + "name": "Beauty Wholesale DK", + "city": "Silkeborg", + "contactPerson": "Louise Jensen", + "productCount": 53, + "lastOrder": "2024-12-21", + "isActive": true + } + ] +} diff --git a/PlanTempus.Application/Features/Suppliers/Pages/Index.cshtml b/PlanTempus.Application/Features/Suppliers/Pages/Index.cshtml new file mode 100644 index 0000000..ec69371 --- /dev/null +++ b/PlanTempus.Application/Features/Suppliers/Pages/Index.cshtml @@ -0,0 +1,39 @@ +@page "/leverandoerer" +@model PlanTempus.Application.Features.Suppliers.Pages.IndexModel +@{ + ViewData["Title"] = "Leverandører"; +} + + + + + +

Leverandører

+

Administrer leverandører og indkøb

+
+
+ + + + 12 + Leverandører i alt + + + 10 + Aktive + + + 45.230 kr + Indkøb denne måned + + + 3 + Afventende ordrer + + +
+
+ + + @await Component.InvokeAsync("SupplierTable") + diff --git a/PlanTempus.Application/Features/Suppliers/Pages/Index.cshtml.cs b/PlanTempus.Application/Features/Suppliers/Pages/Index.cshtml.cs new file mode 100644 index 0000000..3ad2944 --- /dev/null +++ b/PlanTempus.Application/Features/Suppliers/Pages/Index.cshtml.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace PlanTempus.Application.Features.Suppliers.Pages; + +public class IndexModel : PageModel +{ + public void OnGet() + { + } +} diff --git a/PlanTempus.Application/Features/_Shared/Pages/_Layout.cshtml b/PlanTempus.Application/Features/_Shared/Pages/_Layout.cshtml index 9a6e870..a9d88df 100644 --- a/PlanTempus.Application/Features/_Shared/Pages/_Layout.cshtml +++ b/PlanTempus.Application/Features/_Shared/Pages/_Layout.cshtml @@ -35,6 +35,7 @@ + @await RenderSectionAsync("Styles", required: false) diff --git a/PlanTempus.Application/wwwroot/css/components.css b/PlanTempus.Application/wwwroot/css/components.css index de0e1ed..500e821 100644 --- a/PlanTempus.Application/wwwroot/css/components.css +++ b/PlanTempus.Application/wwwroot/css/components.css @@ -353,7 +353,8 @@ swp-status-badge { color: var(--color-blue); } - &.employee { + &.employee, + &.inactive { background: var(--color-background-alt); color: var(--color-text-secondary); } diff --git a/PlanTempus.Application/wwwroot/css/suppliers.css b/PlanTempus.Application/wwwroot/css/suppliers.css new file mode 100644 index 0000000..a080019 --- /dev/null +++ b/PlanTempus.Application/wwwroot/css/suppliers.css @@ -0,0 +1,70 @@ +/** + * Suppliers - Page Styling + * + * Feature-specific styling only. + * Reuses: + * - swp-sticky-header, swp-header-content, swp-page-container (page.css) + * - swp-stats-row.cols-4, swp-stat-card (stats.css) + * - swp-action-bar, swp-search-input (components.css) + * - swp-data-table, swp-status-badge, swp-empty-state (components.css) + * - swp-btn (components.css) + */ + +/* =========================================== + SUPPLIER TABLE (uses swp-data-table from components.css) + =========================================== */ +swp-card.suppliers-list { + padding: 0; + overflow: hidden; +} + +/* Table columns: Leverandør(1fr) | Kontakt(150px) | Produkter(80px) | Sidste(120px) | Status(100px) */ +swp-card.suppliers-list swp-data-table { + grid-template-columns: minmax(200px, 1fr) 150px 80px 120px 100px; +} + +swp-card.suppliers-list swp-data-table-header, +swp-card.suppliers-list swp-data-table-row { + padding: 0 var(--spacing-10); +} + +swp-card.suppliers-list swp-data-table-header swp-data-table-cell { + padding-top: var(--spacing-5); + padding-bottom: var(--spacing-5); +} + +swp-card.suppliers-list swp-data-table-row { + cursor: pointer; +} + +swp-card.suppliers-list swp-data-table-cell { + padding: var(--spacing-5) 0; +} + +/* Supplier cell (name + city) */ +swp-supplier-cell { + display: flex; + flex-direction: column; + gap: 2px; +} + +swp-supplier-name { + font-weight: var(--font-weight-medium); + color: var(--color-text); +} + +swp-supplier-city { + font-size: var(--font-size-sm); + color: var(--color-text-secondary); +} + +/* Products column (center + mono) */ +swp-card.suppliers-list swp-data-table-row swp-data-table-cell:nth-child(3) { + font-family: var(--font-mono); + text-align: center; +} + +/* Last order column (muted) */ +swp-card.suppliers-list swp-data-table-row swp-data-table-cell:nth-child(4) { + color: var(--color-text-secondary); +} diff --git a/PlanTempus.Application/wwwroot/ts/app.ts b/PlanTempus.Application/wwwroot/ts/app.ts index db2d6f7..47dbed7 100644 --- a/PlanTempus.Application/wwwroot/ts/app.ts +++ b/PlanTempus.Application/wwwroot/ts/app.ts @@ -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(); } diff --git a/PlanTempus.Application/wwwroot/ts/modules/suppliers.ts b/PlanTempus.Application/wwwroot/ts/modules/suppliers.ts new file mode 100644 index 0000000..7cb0f09 --- /dev/null +++ b/PlanTempus.Application/wwwroot/ts/modules/suppliers.ts @@ -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 | 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('swp-card.suppliers-list swp-data-table-row[data-href]'); + if (!row) return; + + const href = row.dataset.href; + if (href) { + window.location.href = href; + } + }); + } +}