diff --git a/frontend/src/lib/models/DashboardHubModel.svelte.ts b/frontend/src/lib/models/DashboardHubModel.svelte.ts index 4ead8847..431c7a87 100644 --- a/frontend/src/lib/models/DashboardHubModel.svelte.ts +++ b/frontend/src/lib/models/DashboardHubModel.svelte.ts @@ -56,6 +56,8 @@ export class DashboardHubModel { // ── Core state ──────────────────────────────────────────────── selectedEnv: string | null = $state(null); allDashboards: import("./Dashboards.FiltersModel.svelte.ts").DashboardRow[] = $state([]); + /** Larger dataset for filter options (loaded via loadDashboardSearchOptions, up to 100) */ + filterOptionsDashboards: import("./Dashboards.FiltersModel.svelte.ts").DashboardRow[] = $state([]); dashboards: import("./Dashboards.FiltersModel.svelte.ts").DashboardRow[] = $state([]); filteredDashboards: import("./Dashboards.FiltersModel.svelte.ts").DashboardRow[] = $state([]); isLoading: boolean = $state(true); @@ -63,6 +65,8 @@ export class DashboardHubModel { // ── Validation popover ──────────────────────────────────────── validationStatuses: Record = $state({}); + /** Version counter bumped after each fetchValidationStatuses — triggers grid re-render */ + validationVersion: number = $state(0); openValidationPopover: string | null = $state(null); validationPopoverPosition: { left: number; top: number } = $state({ left: 0, top: 0 }); validationStatusLoading: boolean = $state(false); @@ -181,11 +185,15 @@ export class DashboardHubModel { } getFilterOptions(column: any): string[] { - return this.filters.getFilterOptions(this.allDashboards as any, this.validationStatuses); + const data = this.filterOptionsDashboards.length > this.allDashboards.length + ? this.filterOptionsDashboards : this.allDashboards; + return this.filters.getFilterOptions(column, data as any, this.validationStatuses); } getVisibleFilterOptions(column: any): string[] { - return this.filters.getVisibleFilterOptions(column, this.allDashboards as any, this.validationStatuses); + const data = this.filterOptionsDashboards.length > this.allDashboards.length + ? this.filterOptionsDashboards : this.allDashboards; + return this.filters.getVisibleFilterOptions(column, data as any, this.validationStatuses); } toggleFilterDropdown(column: any, event: Event | undefined, panelWidth?: number): void { @@ -356,7 +364,7 @@ export class DashboardHubModel { branch: d.git_status?.branch || null, hasRepo: d.git_status?.has_repo === true, hasChangesForCommit: d.git_status?.has_changes_for_commit === true, - } : { status: "pending", branch: null, hasRepo: null, hasChangesForCommit: false }, + } : { status: "no_repo", branch: null, hasRepo: null, hasChangesForCommit: false }, lastTask: d.last_task ? { status: normalizeTaskStatus(d.last_task.status), validationStatus: normalizeValidationStatus(d.last_task.validation_status), @@ -387,10 +395,29 @@ export class DashboardHubModel { search: query || undefined, page_size: 100, page_context: 'other', apply_profile_default: false, override_show_all: true, }); - this.searchableDashboardOptions = (response?.dashboards || []).map((d: any) => ({ + const raw = response?.dashboards || []; + this.searchableDashboardOptions = raw.map((d: any) => ({ id: d.id, name: d.title, subtitle: d.slug || '', hint: d.last_modified ? formatDate(d.last_modified) : '', })); + // Also store for filter options (lightweight DashboardRow mapping) + this.filterOptionsDashboards = raw.map((d: any) => { + const owners = normalizeOwners(d.owners); + const hasGitStatus = !!d.git_status; + return { + id: d.id, title: d.title, slug: d.slug, + changedOn: d.last_modified || null, + changedOnLabel: formatDate(d.last_modified), + owners, + actorLabel: owners.length > 0 ? owners.join(", ") : "-", + git: hasGitStatus ? { + status: d.git_status?.sync_status?.toLowerCase() || "no_repo", + branch: d.git_status?.branch || null, + hasRepo: d.git_status?.has_repo === true, + hasChangesForCommit: d.git_status?.has_changes_for_commit === true, + } : { status: "no_repo", branch: null, hasRepo: null, hasChangesForCommit: false }, + } as any; + }); } catch { this.searchableDashboardOptions = []; console.warn('[DashboardHub] loadDashboardSearchOptions failed'); @@ -405,7 +432,7 @@ export class DashboardHubModel { const ids = this.dashboards.map((d: any) => d.id).filter(Boolean); if (ids.length === 0) return; const result = await api.getValidationStatusBatch(this.selectedEnv, ids.join(",")); - if (result && typeof result === "object") this.validationStatuses = result; + if (result && typeof result === "object") { this.validationStatuses = result; this.validationVersion++; } } catch { /* graceful no-op */ } finally { this.validationStatusLoading = false; } } @@ -433,7 +460,7 @@ export class DashboardHubModel { this.total = result.total; this.totalPages = result.totalPages; this.selection.updateSelectionState(this.filteredDashboards as any, this.dashboards as any); - void this.hydrateVisibleGitStatusesBatch(); + void this.hydrateVisibleGitStatusesBatch().catch(() => {}); void this.fetchValidationStatuses(); } diff --git a/frontend/src/lib/models/Dashboards.FiltersModel.svelte.ts b/frontend/src/lib/models/Dashboards.FiltersModel.svelte.ts index e19977dd..fd3f2316 100644 --- a/frontend/src/lib/models/Dashboards.FiltersModel.svelte.ts +++ b/frontend/src/lib/models/Dashboards.FiltersModel.svelte.ts @@ -98,22 +98,22 @@ export class DashboardsFiltersModel { getColumnCellValue(dashboard: DashboardRow, column: FilterColumn, validationStatuses: Record): string { if (column === "title") return dashboard.title || "-"; - if (column === "git_status") return String(dashboard.git?.status || "pending").toLowerCase(); + if (column === "git_status") return String(dashboard.git?.status || "no_repo").toLowerCase(); if (column === "validation_status") return this.getValidationLabelForCell(dashboard, validationStatuses); if (column === "changed_on") return dashboard.changedOn ? String(dashboard.changedOn).slice(0, 10) : "-"; if (column === "actor") return dashboard.actorLabel || "-"; return "-"; } - getFilterOptions(allDashboards: DashboardRow[], validationStatuses: Record): string[] { + getFilterOptions(column: FilterColumn, allDashboards: DashboardRow[], validationStatuses: Record): string[] { return Array.from(new SvelteSet( - allDashboards.map(d => this.getColumnCellValue(d, "title", validationStatuses)).filter(Boolean) + allDashboards.map(d => this.getColumnCellValue(d, column, validationStatuses)).filter(Boolean) )).sort((a, b) => a.localeCompare(b, "ru")); } getVisibleFilterOptions(column: FilterColumn, allDashboards: DashboardRow[], validationStatuses: Record): string[] { const searchText = (this.columnFilterSearch[column] || "").toLowerCase(); - return this.getFilterOptions(allDashboards, validationStatuses).filter(v => v.toLowerCase().includes(searchText)); + return this.getFilterOptions(column, allDashboards, validationStatuses).filter(v => v.toLowerCase().includes(searchText)); } toggleFilterDropdown(column: FilterColumn, event: Event | undefined, panelWidth = 256): void {