refactor(frontend): migrate badge patterns to Badge component (8 files)

Replaced inline rounded-full badge patterns and removed 5 duplicated helper functions:
- getStatusBadgeClass (translate/+page, validation-tasks/[policyId])
- getRunStatusBadgeClass (validation-tasks/[policyId])
- getStateClass (LaunchConfirmationPanel)
- getStateTone (CompiledSQLPreview)

All badges now use <Badge variant="..."> from $lib/ui.
This commit is contained in:
2026-06-17 14:21:08 +03:00
parent abd50c92ed
commit 4b3770b311
8 changed files with 45 additions and 134 deletions

View File

@@ -22,6 +22,7 @@
<script lang="ts">
import { t } from "$lib/i18n/index.svelte.js";
import { requestDatasetReviewApi } from "$lib/api/datasetReview.js";
import { Badge } from "$lib/ui";
let {
sessionId = "",
@@ -71,21 +72,7 @@
return $t.dataset_review?.preview?.state?.[state] || state;
}
function getStateTone(state) {
if (state === "ready") {
return "bg-success-light text-success";
}
if (state === "stale") {
return "bg-warning-light text-warning";
}
if (state === "failed") {
return "bg-destructive-light text-destructive";
}
if (state === "pending") {
return "bg-primary-light text-primary";
}
return "bg-surface-muted text-text";
}
function getCompilerLabel() {
if (compiledBySuperset) {
@@ -197,9 +184,9 @@
</div>
<div class="flex flex-wrap items-center gap-2">
<span class={`rounded-full px-3 py-1 text-xs font-medium ${getStateTone(effectiveState)}`}>
<Badge variant={effectiveState === "ready" ? "success" : effectiveState === "stale" ? "warning" : effectiveState === "failed" ? "destructive" : effectiveState === "pending" ? "primary" : "muted"}>
{$t.dataset_review?.preview?.state_label}: {getStateLabel(effectiveState)}
</span>
</Badge>
<button
type="button"
class="inline-flex items-center justify-center rounded-xl bg-primary px-4 py-2 text-sm font-medium text-white transition hover:bg-primary-hover disabled:cursor-not-allowed disabled:bg-border"

View File

@@ -20,6 +20,7 @@
<script lang="ts">
import { t } from "$lib/i18n/index.svelte.js";
import { requestDatasetReviewApi } from "$lib/api/datasetReview.js";
import { Badge } from "$lib/ui";
let {
sessionId = "",
@@ -377,23 +378,11 @@
</div>
<div class="flex flex-wrap gap-2">
<span
class={`rounded-full px-3 py-1 text-xs font-medium ${
reviewState === "Incomplete"
? "bg-destructive-light text-destructive"
: reviewState === "WarningApproval"
? "bg-warning-light text-warning"
: "bg-success-light text-success"
}`}
>
<Badge variant={reviewState === "Incomplete" ? "destructive" : reviewState === "WarningApproval" ? "warning" : "success"}>
{$t.dataset_review?.mapping?.state_label}: {$t.dataset_review?.mapping?.state?.[reviewState]}
</span>
<span class="rounded-full bg-surface-muted px-3 py-1 text-xs font-medium text-text">
{$t.dataset_review?.mapping?.pending_approvals_label}: {pendingApprovalCount}
</span>
<span class="rounded-full bg-surface-muted px-3 py-1 text-xs font-medium text-text">
{$t.dataset_review?.mapping?.required_values_label}: {missingRequiredCount}
</span>
</Badge>
<Badge variant="muted">{$t.dataset_review?.mapping?.pending_approvals_label}: {pendingApprovalCount}</Badge>
<Badge variant="muted">{$t.dataset_review?.mapping?.required_values_label}: {missingRequiredCount}</Badge>
<button
type="button"
class="inline-flex items-center justify-center rounded-xl border border-border-strong px-4 py-2 text-sm font-medium text-text transition hover:bg-surface-page disabled:cursor-not-allowed disabled:opacity-50"
@@ -459,24 +448,24 @@
<h3 class="text-base font-semibold text-text">
{importedFilter?.display_name || importedFilter?.filter_name || mapping.mapping_id}
</h3>
<span class="rounded-full bg-surface-muted px-2 py-0.5 text-xs text-text">
<Badge variant="muted" size="sm">
{$t.dataset_review?.mapping?.to_variable_label}: {templateVariable?.variable_name ||
mapping.variable_id}
</span>
</Badge>
{#if templateVariable?.is_required}
<span class="rounded-full bg-destructive-light px-2 py-0.5 text-xs text-destructive">
<Badge variant="destructive" size="sm">
{$t.dataset_review?.mapping?.required_badge}
</span>
</Badge>
{/if}
{#if rowState.needsExplicitApproval}
<span class="rounded-full bg-warning-light px-2 py-0.5 text-xs text-warning">
<Badge variant="warning" size="sm">
{$t.dataset_review?.mapping?.approval_required_badge}
</span>
</Badge>
{/if}
{#if mapping.approval_state === "approved"}
<span class="rounded-full bg-success-light px-2 py-0.5 text-xs text-success">
<Badge variant="success" size="sm">
{$t.dataset_review?.mapping?.approved_badge}
</span>
</Badge>
{/if}
</div>

View File

@@ -20,6 +20,7 @@
<script lang="ts">
import { t } from "$lib/i18n/index.svelte.js";
import { requestDatasetReviewApi } from "$lib/api/datasetReview.js";
import { Badge } from "$lib/ui";
let {
sessionId = "",
@@ -148,15 +149,7 @@
return $t.dataset_review?.launch?.state?.[state] || state;
}
function getStateClass(state) {
if (state === "Submitted") {
return "bg-success-light text-success";
}
if (state === "Ready") {
return "bg-primary-light text-primary";
}
return "bg-destructive-light text-destructive";
}
function jumpTo(target) {
onjump({ target });
@@ -211,9 +204,9 @@
</p>
</div>
<span class={`rounded-full px-3 py-1 text-xs font-medium ${getStateClass(panelState)}`}>
<Badge variant={panelState === "Submitted" ? "success" : panelState === "Ready" ? "primary" : "destructive"}>
{$t.dataset_review?.launch?.state_label}: {getStateLabel(panelState)}
</span>
</Badge>
</div>
{#if panelState === "Blocked"}

View File

@@ -15,6 +15,7 @@
<script lang="ts">
import { t } from "$lib/i18n/index.svelte.js";
import { Badge } from "$lib/ui";
let {
findings = [],
@@ -179,9 +180,7 @@
<h3 class="text-sm font-semibold text-text">
{$t.dataset_review?.findings?.blocking_title}
</h3>
<span class="rounded-full bg-destructive-light px-2.5 py-1 text-xs font-medium text-destructive">
{blockingFindings.length}
</span>
<Badge variant="destructive">{blockingFindings.length}</Badge>
</div>
{#if blockingFindings.length === 0}
@@ -271,9 +270,7 @@
<h3 class="text-sm font-semibold text-text">
{$t.dataset_review?.findings?.warning_title}
</h3>
<span class="rounded-full bg-warning-light px-2.5 py-1 text-xs font-medium text-warning">
{warningFindings.length}
</span>
<Badge variant="warning">{warningFindings.length}</Badge>
</div>
{#if warningFindings.length === 0}
@@ -363,9 +360,7 @@
<h3 class="text-sm font-semibold text-text">
{$t.dataset_review?.findings?.informational_title}
</h3>
<span class="rounded-full bg-surface-muted px-2.5 py-1 text-xs font-medium text-text">
{informationalFindings.length}
</span>
<Badge variant="muted">{informationalFindings.length}</Badge>
</div>
{#if informationalFindings.length === 0}

View File

@@ -4,6 +4,7 @@
<!-- @LAYER UI -->
<script lang="ts">
import { t } from "$lib/i18n/index.svelte.js";
import { Badge } from "$lib/ui";
let { session, currentWorkspaceState, readinessLabel, getWorkspaceStateLabel, profile } = $props();
</script>
@@ -23,20 +24,12 @@
<div class="flex flex-wrap items-center gap-2">
{#if session}
<span class="rounded-full bg-surface-muted px-3 py-1 text-xs font-medium text-text">
{session.source_kind || $t.dataset_review?.workspace?.source_badge_fallback}
</span>
<span class="rounded-full bg-surface-muted px-3 py-1 text-xs font-medium text-text">
{profile?.dataset_name || session.dataset_ref}
</span>
<Badge variant="muted">{session.source_kind || $t.dataset_review?.workspace?.source_badge_fallback}</Badge>
<Badge variant="muted">{profile?.dataset_name || session.dataset_ref}</Badge>
{/if}
<span class="rounded-full bg-surface-muted px-3 py-1 text-xs font-medium text-text">
{$t.dataset_review?.workspace?.state_label}: {getWorkspaceStateLabel(currentWorkspaceState)}
</span>
<Badge variant="muted">{$t.dataset_review?.workspace?.state_label}: {getWorkspaceStateLabel(currentWorkspaceState)}</Badge>
{#if session}
<span class="rounded-full bg-primary-light px-3 py-1 text-xs font-medium text-primary">
{$t.dataset_review?.workspace?.readiness_label}: {readinessLabel}
</span>
<Badge variant="primary">{$t.dataset_review?.workspace?.readiness_label}: {readinessLabel}</Badge>
{/if}
</div>
</div>

View File

@@ -26,7 +26,7 @@
import { page } from "$app/state";
import { goto } from "$app/navigation";
import { t } from "$lib/i18n/index.svelte.js";
import { Button } from "$lib/ui";
import { Badge, Button } from "$lib/ui";
import SourceIntakePanel from "$lib/components/dataset-review/SourceIntakePanel.svelte";
import ValidationFindingsPanel from "$lib/components/dataset-review/ValidationFindingsPanel.svelte";
import SemanticLayerReview from "$lib/components/dataset-review/SemanticLayerReview.svelte";
@@ -207,20 +207,11 @@
<div class="mt-0.5 text-sm font-semibold text-primary">{getRecommendedActionLabel($t, m.session?.recommended_action)}</div>
</div>
{#if m.session?.readiness_state === "run_ready"}
<span class="inline-flex items-center gap-1.5 rounded-full bg-success-light px-3 py-1 text-xs font-medium text-success">
<span class="h-1.5 w-1.5 rounded-full bg-success"></span>
Готов к запуску
</span>
<Badge variant="success" dot={true}>Готов к запуску</Badge>
{:else if m.session?.readiness_state === "review_ready"}
<span class="inline-flex items-center gap-1.5 rounded-full bg-primary-light px-3 py-1 text-xs font-medium text-primary">
<span class="h-1.5 w-1.5 rounded-full bg-primary-ring"></span>
Готов к проверке
</span>
<Badge variant="primary" dot={true}>Готов к проверке</Badge>
{:else if m.session?.readiness_state === "recovery_required" || m.session?.readiness_state === "partially_ready"}
<span class="inline-flex items-center gap-1.5 rounded-full bg-warning-light px-3 py-1 text-xs font-medium text-warning">
<span class="h-1.5 w-1.5 rounded-full bg-warning"></span>
Частичное восстановление
</span>
<Badge variant="warning" dot={true}>Частичное восстановление</Badge>
{/if}
</div>
</div>
@@ -229,7 +220,7 @@
<div class="flex items-center gap-2">
<svg class="h-4 w-4 text-text-subtle" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"></path></svg>
<span class="text-xs font-semibold uppercase tracking-wide text-text-muted">{$t.dataset_review?.workspace?.recovered_filters_title || "Фильтры из дашборда"}</span>
<span class="rounded-full bg-primary-light px-2 py-0.5 text-xs font-medium text-primary">{m.importedFilters.length}</span>
<Badge variant="primary" size="sm">{m.importedFilters.length}</Badge>
</div>
<div class="mt-2 flex flex-wrap gap-2">
{#each m.importedFilters.slice(0, 6) as filter}

View File

@@ -6,7 +6,7 @@
import { ROUTES } from '$lib/routes';
import { t, _ } from '$lib/i18n/index.svelte.js';
import { addToast } from '$lib/toasts.svelte.js';
import { Button, EmptyState } from '$lib/ui';
import { Badge, Button, EmptyState } from '$lib/ui';
import { fetchJobs, deleteJob, duplicateJob } from '$lib/api/translate.js';
/** @type {string} idle | loading | empty | populated | error */
@@ -98,18 +98,7 @@
}
}
/** @param {string} status */
function getStatusBadgeClass(status) {
const map = {
DRAFT: 'bg-surface-muted text-text',
READY: 'bg-primary-light text-primary',
RUNNING: 'bg-warning-light text-warning',
COMPLETED: 'bg-success-light text-success',
FAILED: 'bg-destructive-light text-destructive',
CANCELLED: 'bg-surface-muted text-text-muted',
};
return map[status] || 'bg-surface-muted text-text';
}
</script>
<div class="container mx-auto px-4 py-6">
@@ -196,6 +185,7 @@
{:else if uxState === 'populated'}
<div class="grid gap-4">
{#each jobs as job}
{@const statusVariant = ({ DRAFT: "muted", READY: "primary", RUNNING: "warning", COMPLETED: "success", FAILED: "destructive", CANCELLED: "muted" })[job.status] || "muted"}
<div
onclick={() => navigateToConfig(job.id)}
class="bg-surface-card border border-border rounded-lg p-4 hover:shadow-md hover:border-border-strong transition-all cursor-pointer"
@@ -204,9 +194,7 @@
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3 mb-1">
<h3 class="text-lg font-semibold text-text truncate">{job.name}</h3>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {getStatusBadgeClass(job.status)}">
{job.status}
</span>
<Badge variant={statusVariant}>{job.status}</Badge>
</div>
{#if job.description}
<p class="text-sm text-text-muted truncate mb-2">{job.description}</p>

View File

@@ -14,7 +14,7 @@
import { goto } from '$app/navigation';
import { ROUTES } from '$lib/routes';
import { t } from '$lib/i18n/index.svelte.js';
import { EmptyState } from '$lib/ui';
import { Badge, EmptyState } from '$lib/ui';
import { addToast } from '$lib/toasts.svelte.js';
import { triggerValidationRun as triggerRun, deleteValidationTask as deleteTask, getValidationRuns as fetchRuns } from '$lib/api.js';
import { page } from '$app/state';
@@ -43,22 +43,7 @@
// Derived status
let isScheduled = $derived(task?.schedule_days?.length > 0);
/** @param {string} status */
function getStatusBadgeClass(status) {
const map = {
PASS: 'bg-success-light text-success',
WARN: 'bg-warning-light text-warning',
FAIL: 'bg-destructive-light text-destructive',
UNKNOWN: 'bg-surface-muted text-text-muted',
RUNNING: 'bg-primary-light text-primary',
PENDING: 'bg-warning-light text-warning',
COMPLETED: 'bg-success-light text-success',
};
return map[status] || 'bg-surface-muted text-text-muted';
}
/** @param {string} status */
const getRunStatusBadgeClass = getStatusBadgeClass;
/** @param {number} ms */
function formatDuration(ms) {
@@ -166,22 +151,14 @@
<div class="flex items-center gap-3 mb-2">
<h1 class="text-2xl font-bold text-text truncate">{task?.name || '-'}</h1>
{#if task?.is_active !== false}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-success-light text-success">
{$t.validation?.active || 'Active'}
</span>
<Badge variant="success">{$t.validation?.active || 'Active'}</Badge>
{:else}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-surface-muted text-text-muted">
{$t.validation?.inactive || 'Inactive'}
</span>
<Badge variant="muted">{$t.validation?.inactive || 'Inactive'}</Badge>
{/if}
{#if isScheduled}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary-light text-primary">
{$t.validation?.scheduled || 'Scheduled'}
</span>
<Badge variant="primary">{$t.validation?.scheduled || 'Scheduled'}</Badge>
{:else}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-surface-muted text-text-muted">
{$t.validation?.manual_only || 'Manual only'}
</span>
<Badge variant="muted">{$t.validation?.manual_only || 'Manual only'}</Badge>
{/if}
</div>
<div class="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-text-muted">
@@ -342,9 +319,7 @@
{getTriggerLabel(run.trigger)}
</td>
<td class="px-4 py-3 whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {getRunStatusBadgeClass(run.status)}">
{run.status || 'UNKNOWN'}
</span>
<Badge variant={({ PASS: "success", WARN: "warning", FAIL: "destructive", UNKNOWN: "muted", RUNNING: "primary", PENDING: "warning", COMPLETED: "success" })[run.status] || "muted"}>{run.status || 'UNKNOWN'}</Badge>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-text-muted">
{getAggregateSummary(run)}