refactor(frontend): migrate remaining EmptyState patterns (8 files)

Replaced hand-rolled border-dashed empty states with <EmptyState>:
- HealthMatrix, ProviderConfig, ApiKeysTab, dashboards/+page,
  git/+page, MetricsTable, ColumnsTable, DashboardDataGrid

Table empty states wrapped in <tr><td colspan={N}>. Custom SVG icons
preserved via {#snippet icon()}.
This commit is contained in:
2026-06-17 15:22:05 +03:00
parent bd9a8cba79
commit 286133957b
8 changed files with 41 additions and 59 deletions

View File

@@ -31,7 +31,7 @@
<script lang="ts">
// [SECTION: IMPORTS]
import { t } from '$lib/i18n/index.svelte.js';
import { Button, Input } from "$lib/ui";
import { Button, EmptyState, Input } from "$lib/ui";
// [SECTION: TYPE EXPORTS]
export interface Column {
@@ -209,12 +209,13 @@
<!-- ── Empty State ── -->
{:else if !loading && displayData.length === 0}
<div class="flex flex-col items-center justify-center py-12 text-text-subtle rounded-lg border border-dashed border-border">
<svg class="w-12 h-12 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
<p class="text-sm">{emptyText || $t.common?.no_data || "No items"}</p>
</div>
<EmptyState title={emptyText || $t.common?.no_data || "No items"}>
{#snippet icon()}
<svg class="w-12 h-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
{/snippet}
</EmptyState>
<!-- ── Table ── -->
{:else}

View File

@@ -9,6 +9,7 @@
<!-- @UX_STATE Active -> One card highlighted (the current filter). -->
<script lang="ts">
import { t } from '$lib/i18n/index.svelte.js';
import { EmptyState } from "$lib/ui";
let {
pass_count = 0,
@@ -116,12 +117,16 @@
</div>
{#if total === 0}
<div class="mt-4 p-8 text-center border-2 border-dashed border-border rounded-xl text-text-subtle">
<svg class="w-10 h-10 mx-auto mb-2 text-text-subtle" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
<p>{$t.health?.no_records_for_environment || 'No validation records for this environment'}</p>
</div>
<EmptyState
title={$t.health?.no_records_for_environment || 'No validation records for this environment'}
class="mt-4"
>
{#snippet icon()}
<svg class="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
{/snippet}
</EmptyState>
{/if}
{/if}
</div>

View File

@@ -16,7 +16,7 @@ import { SvelteSet } from "svelte/reactivity";
import { requestApi } from "$lib/api";
import { addToast } from "$lib/toasts.svelte.js";
import HelpTooltip from "$lib/ui/HelpTooltip.svelte";
import { ConfirmDialog } from "$lib/ui";
import { ConfirmDialog, EmptyState } from "$lib/ui";
let {
providers = [],
@@ -757,11 +757,7 @@ import { SvelteSet } from "svelte/reactivity";
</div>
</div>
{:else}
<div
class="text-center py-8 text-text-muted border-2 border-dashed rounded-lg"
>
{$t.llm.no_providers}
</div>
<EmptyState title={$t.llm.no_providers} />
{/each}
</div>
</div>

View File

@@ -18,7 +18,7 @@
import { t } from '$lib/i18n/index.svelte.js';
import { api } from '$lib/api.js';
import { addToast } from '$lib/toasts.svelte.js';
import { ConfirmDialog } from '$lib/ui';
import { ConfirmDialog, EmptyState } from '$lib/ui';
let keys = $state([]);
let environments = $state([]);
@@ -321,10 +321,7 @@
{/each}
</div>
{:else if keys.length === 0}
<!-- Empty State -->
<div class="text-center py-8 text-text-muted text-sm border-2 border-dashed border-border rounded-lg">
{$t.api_keys?.empty || 'No API keys configured.'}
</div>
<EmptyState title={$t.api_keys?.empty || 'No API keys configured.'} />
{:else}
<!-- Keys Table -->
<div class="overflow-x-auto border border-border rounded-lg">

View File

@@ -25,7 +25,7 @@
import DashboardMaintenanceBadge from "$lib/components/DashboardMaintenanceBadge.svelte";
import { environmentContextStore, initializeEnvironmentContext, setSelectedEnvironment } from "$lib/stores/environmentContext.svelte.js";
import { Button, Icon, Pagination, Skeleton } from "$lib/ui";
import { Button, EmptyState, Icon, Pagination, Skeleton } from "$lib/ui";
import ColumnFilterPopover from "./ColumnFilterPopover.svelte";
import { formatDate, getSortIndicator } from "./dashboard-helpers.js";
import { DashboardHubModel } from "$lib/models/DashboardHubModel.svelte.ts";
@@ -234,34 +234,15 @@
</div>
</div>
{:else if m.dashboards.length === 0}
<!-- Empty State -->
<div class="py-12 text-center text-text-muted">
<svg
class="w-16 h-16 mx-auto mb-4 text-text-subtle"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M3 3h18v18H3V3zm16 16V5H5v14h14z" />
</svg>
{#if m.effectiveProfileFilter?.applied}
<p>{$t.profile?.filter_empty_state || "No dashboards found for active profile filters. Try adjusting your filter settings."}</p>
<div class="mt-4">
<button
type="button"
class="rounded border border-primary bg-surface-card px-3 py-1.5 text-xs font-medium text-primary hover:bg-primary-light"
onclick={() => m.handleTemporaryShowAll()}
>
{$t.profile?.filter_show_all_temporarily || "Show all dashboards temporarily"}
</button>
</div>
{:else}
<p>{$t.dashboard?.empty}</p>
{/if}
</div>
{#if m.effectiveProfileFilter?.applied}
<EmptyState
title={$t.profile?.filter_empty_state || "No dashboards found for active profile filters. Try adjusting your filter settings."}
actionLabel={$t.profile?.filter_show_all_temporarily || "Show all dashboards temporarily"}
onAction={() => m.handleTemporaryShowAll()}
/>
{:else}
<EmptyState title={$t.dashboard?.empty} />
{/if}
{:else}
<!-- Toolbar -->
<div class="flex items-center justify-between mb-4 gap-4">

View File

@@ -9,6 +9,7 @@
<script lang="ts">
import { t } from "$lib/i18n/index.svelte.js";
import { api } from "$lib/api.js";
import { EmptyState } from "$lib/ui";
let { columns = [], datasetId, envId } = $props();
@@ -71,7 +72,7 @@
</thead>
<tbody>
{#if columns.length === 0}
<tr><td colspan="4" class="py-4 text-center text-text-subtle">{$t.datasets?.detail_empty ?? 'No data'}</td></tr>
<tr><td colspan="4"><EmptyState title={$t.datasets?.detail_empty ?? 'No data'} /></td></tr>
{:else}
{#each columns as col (col.id)}
<tr class="border-b border-border hover:bg-surface-muted {editingRowId && editingRowId !== col.id ? 'opacity-50' : ''}">

View File

@@ -9,6 +9,7 @@
<script lang="ts">
import { t } from "$lib/i18n/index.svelte.js";
import { api } from "$lib/api.js";
import { EmptyState } from "$lib/ui";
let { metrics = [], datasetId, envId } = $props();
@@ -59,7 +60,7 @@
</thead>
<tbody>
{#if metrics.length === 0}
<tr><td colspan="3" class="py-4 text-center text-text-subtle">{$t.datasets?.detail_empty ?? 'No data'}</td></tr>
<tr><td colspan="3"><EmptyState title={$t.datasets?.detail_empty ?? 'No data'} /></td></tr>
{:else}
{#each metrics as metric (metric.id)}
<tr class="border-b border-border hover:bg-surface-muted {editingRowId && editingRowId !== metric.id ? 'opacity-50' : ''}">

View File

@@ -21,7 +21,7 @@ import { SvelteSet } from "svelte/reactivity";
import { gitService } from '../../services/gitService.js';
import type { DashboardMetadata } from '../../types/dashboard';
import { t } from '$lib/i18n/index.svelte.js';
import { Card, PageHeader, Select } from '$lib/ui';
import { Card, EmptyState, PageHeader, Select } from '$lib/ui';
import {
environmentContextStore,
initializeEnvironmentContext,
@@ -244,11 +244,11 @@ import { SvelteSet } from "svelte/reactivity";
repositoriesOnly={activeTab === 'repos'}
/>
{:else}
<p class="text-text-muted italic">
{activeTab === 'repos'
<EmptyState
title={activeTab === 'repos'
? ($t.git?.no_repositories_selected || "No repositories found")
: ($t.dashboard?.no_dashboards || "No dashboards")}
</p>
/>
{/if}
</Card>
{/if}