refactor(frontend): migrate skeleton patterns to Skeleton component (7 files)

77 animate-pulse instances replaced with <Skeleton> from $lib/ui:
- dashboards/+page (48 instances — loading grid)
- datasets/[id]/+page (6), maintenance/+page (6), settings/+page (5)
- reports/llm/[taskId]/+page (3), dashboards/[id]/+page (8)
- validation-tasks/+page (1 — table skeleton with row variant)
This commit is contained in:
2026-06-17 14:21:16 +03:00
parent 4b3770b311
commit 3acf1f6b5a
7 changed files with 49 additions and 57 deletions

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 } from "$lib/ui";
import { Button, Skeleton } from "$lib/ui";
import ColumnFilterPopover from "./ColumnFilterPopover.svelte";
import { formatDate, getPaginationRange, getSortIndicator } from "./dashboard-helpers.js";
import { DashboardHubModel } from "$lib/models/DashboardHubModel.svelte.ts";
@@ -158,28 +158,28 @@
class="grid gap-4 px-6 py-3 bg-surface-muted border-b border-border font-semibold text-[11px] uppercase tracking-wide text-text-muted"
style="width: max(100%, 1460px); grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(70px,0.5fr) minmax(110px,0.7fr) minmax(280px,2fr) minmax(150px,1fr) 124px;"
>
<div class="h-4 rounded bg-surface-muted animate-pulse"></div>
<div class="h-4 rounded bg-surface-muted animate-pulse"></div>
<div class="h-4 rounded bg-surface-muted animate-pulse"></div>
<div class="h-4 rounded bg-surface-muted animate-pulse"></div>
<div class="h-4 rounded bg-surface-muted animate-pulse"></div>
<div class="h-4 rounded bg-surface-muted animate-pulse"></div>
<div class="h-4 rounded bg-surface-muted animate-pulse"></div>
<div class="h-4 rounded bg-surface-muted animate-pulse"></div>
<Skeleton variant="line" />
<Skeleton variant="line" />
<Skeleton variant="line" />
<Skeleton variant="line" />
<Skeleton variant="line" />
<Skeleton variant="line" />
<Skeleton variant="line" />
<Skeleton variant="line" />
</div>
{#each Array(5) as _}
<div
class="grid gap-4 px-6 py-4 border-b border-border last:border-b-0 hover:bg-surface-muted transition-colors"
style="width: max(100%, 1460px); grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(70px,0.5fr) minmax(110px,0.7fr) minmax(280px,2fr) minmax(150px,1fr) 124px;"
>
<div class="h-4 rounded bg-surface-muted animate-pulse"></div>
<div class="h-4 rounded bg-surface-muted animate-pulse"></div>
<div class="h-4 rounded bg-surface-muted animate-pulse"></div>
<div class="h-4 rounded bg-surface-muted animate-pulse"></div>
<div class="h-4 rounded bg-surface-muted animate-pulse"></div>
<div class="h-4 rounded bg-surface-muted animate-pulse"></div>
<div class="h-4 rounded bg-surface-muted animate-pulse"></div>
<div class="h-4 rounded bg-surface-muted animate-pulse"></div>
<Skeleton variant="line" />
<Skeleton variant="line" />
<Skeleton variant="line" />
<Skeleton variant="line" />
<Skeleton variant="line" />
<Skeleton variant="line" />
<Skeleton variant="line" />
<Skeleton variant="line" />
</div>
{/each}
</div>

View File

@@ -18,6 +18,7 @@
import { openDrawerForTaskIfPreferred } from '$lib/stores/taskDrawer.svelte.js';
import { DashboardDetailModel } from '$lib/models/DashboardDetailModel.svelte';
import { ROUTES } from '$lib/routes.js';
import { Skeleton } from '$lib/ui';
import CommitHistory from '$lib/components/git/CommitHistory.svelte';
import GitManager from '$lib/components/git/GitManager.svelte';
@@ -67,10 +68,10 @@
{#if m.isLoading}
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
{#each Array(3) as _}
<div class="h-24 animate-pulse rounded-xl border border-border bg-surface-card"></div>
<Skeleton variant="card" height="h-24" class="rounded-xl border border-border bg-surface-card" />
{/each}
</div>
<div class="h-64 animate-pulse rounded-xl border border-border bg-surface-card"></div>
<Skeleton variant="card" height="h-64" class="rounded-xl border border-border bg-surface-card" />
{:else if m.dashboard}
<div class="grid grid-cols-1 gap-6 xl:grid-cols-5">
<!-- Thumbnail -->
@@ -80,7 +81,7 @@
<button class="rounded-md border border-border-strong px-2 py-1 text-xs text-text hover:bg-surface-page" onclick={() => m.loadThumbnail(true)} disabled={m.isThumbnailLoading}>{$t.common?.refresh}</button>
</div>
{#if m.isThumbnailLoading}
<div class="h-56 animate-pulse rounded-lg bg-surface-muted"></div>
<Skeleton variant="card" height="h-56" />
{:else if m.thumbnailUrl}
<img src={m.thumbnailUrl} alt="Dashboard thumbnail" class="h-56 w-full rounded-lg border border-border object-cover" />
{:else}
@@ -136,7 +137,7 @@
<button class="rounded-md border border-border-strong px-2 py-1 text-xs text-text hover:bg-surface-page" onclick={() => m.loadValidationHistory(m.dashboard?.id ?? m.dashboardRef)} disabled={m.isValidationHistoryLoading}>{$t.common?.refresh}</button>
</div>
{#if m.isValidationHistoryLoading}
<div class="space-y-2">{#each Array(3) as _}<div class="h-10 animate-pulse rounded bg-surface-muted"></div>{/each}</div>
<div class="space-y-2">{#each Array(3) as _}<Skeleton variant="line" height="h-10" />{/each}</div>
{:else if m.validationHistoryError}
<div class="rounded-lg border border-destructive-ring bg-destructive-light px-3 py-2 text-sm text-destructive">{m.validationHistoryError}</div>
{:else if m.validationHistory.length === 0}

View File

@@ -12,7 +12,7 @@
import { onMount } from 'svelte';
import { page } from '$app/state';
import { t } from '$lib/i18n/index.svelte.js';
import { Button } from '$lib/ui';
import { Button, Skeleton } from '$lib/ui';
import { DatasetDetailModel } from '$lib/models/DatasetDetailModel.svelte';
let datasetId = $derived(page.params.id);
@@ -65,21 +65,21 @@
{#if model.isLoading}
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="bg-surface-card border border-border rounded-lg p-6">
<div class="animate-pulse bg-surface-muted rounded h-6 w-1/2 mb-4"></div>
<Skeleton variant="line" width="w-1/2" height="h-6" class="mb-4" />
{#each Array(5) as _}
<div class="flex justify-between py-2 border-b border-border last:border-0">
<div class="animate-pulse bg-surface-muted rounded h-4 w-20"></div>
<div class="animate-pulse bg-surface-muted rounded h-4 w-32"></div>
<Skeleton variant="line" width="w-20" />
<Skeleton variant="line" width="w-32" />
</div>
{/each}
</div>
<div class="bg-surface-card border border-border rounded-lg p-6 lg:col-span-2">
<div class="animate-pulse bg-surface-muted rounded h-6 w-1/3 mb-4"></div>
<Skeleton variant="line" width="w-1/3" height="h-6" class="mb-4" />
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
{#each Array(4) as _}
<div class="p-3 border border-border rounded-lg">
<div class="animate-pulse bg-surface-muted rounded h-4 w-full mb-2"></div>
<div class="animate-pulse bg-surface-muted rounded h-3 w-16"></div>
<Skeleton variant="line" class="mb-2" />
<Skeleton variant="line" width="w-16" height="h-3" />
</div>
{/each}
</div>

View File

@@ -17,6 +17,7 @@
import MaintenanceEventsTable from "$lib/components/MaintenanceEventsTable.svelte";
import StartMaintenanceForm from "$lib/components/StartMaintenanceForm.svelte";
import { t } from "$lib/i18n/index.svelte.js";
import { Skeleton } from "$lib/ui";
let pageLoading = $state(true);
@@ -54,17 +55,17 @@
<!-- Loading skeleton -->
<div class="space-y-6">
<div class="rounded-lg border border-border bg-surface-card p-4">
<div class="animate-pulse bg-surface-muted h-6 w-40 rounded mb-4"></div>
<Skeleton variant="line" width="w-40" height="h-6" class="mb-4" />
<div class="grid grid-cols-2 gap-4">
<div class="animate-pulse bg-surface-muted h-10 rounded"></div>
<div class="animate-pulse bg-surface-muted h-10 rounded"></div>
<Skeleton variant="line" height="h-10" />
<Skeleton variant="line" height="h-10" />
</div>
<div class="mt-4 animate-pulse bg-surface-muted h-20 rounded"></div>
<Skeleton variant="card" height="h-20" class="mt-4" />
</div>
<div class="border border-border rounded-lg overflow-hidden">
<div class="animate-pulse bg-surface-muted h-8 w-full"></div>
<Skeleton variant="line" height="h-8" />
{#each [1, 2, 3] as _, i (i)}
<div class="animate-pulse bg-surface-muted h-12 w-full border-t border-border"></div>
<Skeleton variant="line" height="h-12" class="border-t border-border" />
{/each}
</div>
</div>

View File

@@ -16,6 +16,7 @@
import { t } from '$lib/i18n/index.svelte.js';
import { openDrawerForTask } from '$lib/stores/taskDrawer.svelte.js';
import Icon from '$lib/ui/Icon.svelte';
import { Skeleton } from '$lib/ui';
import { LLMReportModel } from '$lib/models/LLMReportModel.svelte';
let taskId = $derived(page.params.taskId);
@@ -92,9 +93,9 @@
{#if model.screenState === 'loading'}
<div class="space-y-3">
<div class="h-24 animate-pulse rounded-xl border border-border bg-surface-card"></div>
<div class="h-64 animate-pulse rounded-xl border border-border bg-surface-card"></div>
<div class="h-64 animate-pulse rounded-xl border border-border bg-surface-card"></div>
<Skeleton variant="card" height="h-24" class="rounded-xl border border-border bg-surface-card" />
<Skeleton variant="card" height="h-64" class="rounded-xl border border-border bg-surface-card" />
<Skeleton variant="card" height="h-64" class="rounded-xl border border-border bg-surface-card" />
</div>
{:else if model.task}
<!-- Stats row -->

View File

@@ -26,6 +26,7 @@
import { t } from "$lib/i18n/index.svelte.js";
import { api } from "$lib/api.js";
import { addToast } from "$lib/toasts.svelte.js";
import { Skeleton } from "$lib/ui";
import {
normalizeTab,
@@ -137,11 +138,11 @@
{#if isLoading}
<div class="rounded-xl border border-border bg-surface-card p-6 shadow-sm">
<div class="space-y-3">
<div class="h-8 animate-pulse rounded bg-surface-muted"></div>
<div class="h-8 animate-pulse rounded bg-surface-muted"></div>
<div class="h-8 animate-pulse rounded bg-surface-muted"></div>
<div class="h-8 animate-pulse rounded bg-surface-muted"></div>
<div class="h-8 animate-pulse rounded bg-surface-muted"></div>
<Skeleton variant="line" height="h-8" />
<Skeleton variant="line" height="h-8" />
<Skeleton variant="line" height="h-8" />
<Skeleton variant="line" height="h-8" />
<Skeleton variant="line" height="h-8" />
</div>
</div>
{:else if settings}

View File

@@ -10,7 +10,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { t } from '$lib/i18n/index.svelte.js';
import { EmptyState } from '$lib/ui';
import { EmptyState, Skeleton } from '$lib/ui';
import { ValidationTasksListModel } from '$lib/models/ValidationTasksListModel.svelte';
let { data } = $props();
@@ -92,20 +92,8 @@
<!-- Loading -->
{#if m.isLoading}
<div class="bg-surface-card border border-border rounded-lg overflow-hidden">
<table class="min-w-full divide-y divide-border">
<thead class="bg-surface-muted"><tr>
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">{$t.validation?.task_name}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">{$t.validation?.environment}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">{$t.validation?.schedule}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">{$t.validation?.last_run}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">{$t.validation?.status}</th>
<th class="px-6 py-3 text-right text-xs font-medium text-text-muted uppercase tracking-wider">{$t.validation?.actions}</th>
</tr></thead>
<tbody class="bg-surface-card divide-y divide-border">
{#each Array(5) as _, i (i)}<tr class="animate-pulse"><td class="px-6 py-4"><div class="h-4 w-40 bg-surface-muted rounded"></div></td><td class="px-6 py-4"><div class="h-4 w-24 bg-surface-muted rounded"></div></td><td class="px-6 py-4"><div class="h-4 w-20 bg-surface-muted rounded"></div></td><td class="px-6 py-4"><div class="h-4 w-32 bg-surface-muted rounded"></div></td><td class="px-6 py-4"><div class="h-5 w-14 bg-surface-muted rounded-full"></div></td><td class="px-6 py-4"><div class="h-4 w-24 bg-surface-muted rounded ml-auto"></div></td></tr>{/each}
</tbody>
</table>
<div class="bg-surface-card border border-border rounded-lg overflow-hidden p-6">
<Skeleton variant="row" cols={6} rows={5} />
</div>
<!-- Empty -->