Enhance DataTableCard component with accent and icon support
Publish telemt-api gateway Docker image / test (push) Successful in 26s
Publish telemt-api gateway Docker image / build-and-push (push) Successful in 2m24s

- Added accent and icon properties to the DataTableCard component for improved visual distinction and context.
- Updated multiple pages to utilize the new accent feature, enhancing the user interface with color-coded cards.
- Incorporated icons in DataTableCard instances across various routes for better representation of content.
This commit is contained in:
Denozordec
2026-04-10 17:19:49 +07:00
parent bfe79ca601
commit 7c6892ce0e
7 changed files with 50 additions and 9 deletions
@@ -1,31 +1,57 @@
<script lang="ts">
import * as Card from '$lib/components/ui/card/index.js';
import { cn } from '$lib/utils.js';
import { kpiAccent, type KpiAccentKey } from '$lib/kpi-accents.js';
import type { Snippet } from 'svelte';
let {
title,
description,
accent,
icon,
class: className,
toolbar,
children
}: {
/** Без title — как в shadcn dashboard-01: сразу toolbar и таблица */
title?: string;
description?: string;
accent?: KpiAccentKey;
icon?: Snippet;
toolbar?: Snippet;
children: Snippet;
class?: string;
} = $props();
</script>
<Card.Root density="compact" class={cn('overflow-hidden rounded-lg border border-border shadow-sm', className)}>
<Card.Root
density="compact"
class={cn(
'overflow-hidden rounded-lg border border-border shadow-sm',
accent && 'border-l-4',
accent && kpiAccent[accent].border,
className
)}
>
{#if title}
<Card.Header class="border-b border-border bg-muted/30 px-4 py-3.5">
<Card.Title class="text-base font-semibold">{title}</Card.Title>
{#if description}
<Card.Description class="text-xs sm:text-sm">{description}</Card.Description>
{/if}
<div class="flex gap-3">
{#if icon && accent}
<div
class={cn(
'flex size-10 shrink-0 items-center justify-center rounded-lg',
kpiAccent[accent].iconWrap
)}
>
{@render icon()}
</div>
{/if}
<div class="min-w-0 flex-1 space-y-0.5">
<Card.Title class="text-base font-semibold">{title}</Card.Title>
{#if description}
<Card.Description class="text-xs sm:text-sm">{description}</Card.Description>
{/if}
</div>
</div>
</Card.Header>
{/if}
{#if toolbar}
+7
View File
@@ -45,6 +45,7 @@
import GlobeIcon from '@lucide/svelte/icons/globe';
import CloudUploadIcon from '@lucide/svelte/icons/cloud-upload';
import LockIcon from '@lucide/svelte/icons/lock';
import UsersIcon from '@lucide/svelte/icons/users';
import { nextSortDir, compareStrings, compareNum, type SortDir } from '$lib/table-sort.js';
let loading = $state(true);
@@ -619,7 +620,9 @@ async function handleAliasFilterChange(nextAlias: string) {
class="mb-6"
title="Активные IP (агрегат)"
description="С GeoIP при включённой базе на шлюзе"
accent="teal"
>
{#snippet icon()}<GlobeIcon class="size-5" aria-hidden="true" />{/snippet}
{#snippet toolbar()}
<DataTableToolbar>
<div class="relative max-w-xs flex-1">
@@ -751,7 +754,9 @@ async function handleAliasFilterChange(nextAlias: string) {
class="mb-6"
title="По нодам"
description="Health, версия, аптайм, bad connections"
accent="violet"
>
{#snippet icon()}<ServerIcon class="size-5" aria-hidden="true" />{/snippet}
{#snippet toolbar()}
<DataTableToolbar>
<div class="relative max-w-xs flex-1">
@@ -910,7 +915,9 @@ async function handleAliasFilterChange(nextAlias: string) {
class="mt-6"
title="Топ пользователей по трафику"
description="До 10 записей из summary"
accent="info"
>
{#snippet icon()}<UsersIcon class="size-5" aria-hidden="true" />{/snippet}
{#snippet toolbar()}
<DataTableToolbar>
<div class="relative max-w-xs flex-1">
+2 -1
View File
@@ -346,7 +346,8 @@
</KpiStatCard>
</div>
<DataTableCard title="Инциденты" description="Список и разбор">
<DataTableCard title="Инциденты" description="Список и разбор" accent="danger">
{#snippet icon()}<SirenIcon class="size-5" aria-hidden="true" />{/snippet}
{#snippet toolbar()}
<DataTableToolbar>
<div class="relative max-w-md flex-1">
+3 -1
View File
@@ -17,6 +17,7 @@
import SearchIcon from '@lucide/svelte/icons/search';
import { nextSortDir, compareStrings, type SortDir } from '$lib/table-sort.js';
import MapIcon from '@lucide/svelte/icons/map';
import GlobeIcon from '@lucide/svelte/icons/globe';
import { Alert, AlertDescription, AlertTitle } from '$lib/components/ui/alert/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { Badge } from '$lib/components/ui/badge/index.js';
@@ -617,7 +618,8 @@ $effect(() => {
</Alert>
{/if}
<DataTableCard title="Уникальные IP" description="Снимок шлюза: активные и недавние списки">
<DataTableCard title="Уникальные IP" description="Снимок шлюза: активные и недавние списки" accent="teal">
{#snippet icon()}<GlobeIcon class="size-5" aria-hidden="true" />{/snippet}
{#snippet toolbar()}
<DataTableToolbar>
<div class="relative max-w-md flex-1">
+2
View File
@@ -391,7 +391,9 @@
<DataTableCard
title="Последние инциденты из снимка"
description="Всего: {snapshot?.counts?.total ?? snapshot?.incidents?.length ?? 0}"
accent="warning"
>
{#snippet icon()}<ActivityIcon class="size-5" aria-hidden="true" />{/snippet}
{#snippet toolbar()}
<DataTableToolbar>
<div class="relative max-w-md flex-1">
+2
View File
@@ -260,7 +260,9 @@
<DataTableCard
title="Ноды"
description="Контроллер и метрики. Ниже — компактные группы прокси (автозагрузка). «Детально» — обзор с графиками."
accent="violet"
>
{#snippet icon()}<ServerIcon class="size-5" aria-hidden="true" />{/snippet}
<ScrollArea class="w-full" orientation="both">
<Table.Root>
<Table.Header class="sticky top-0 z-10 bg-muted">
+2 -1
View File
@@ -440,7 +440,8 @@ let sumActiveIps = $derived.by(() =>
</KpiStatCard>
</div>
<DataTableCard>
<DataTableCard title="Пользователи" description="Слияние по имени между серверами" accent="violet">
{#snippet icon()}<UsersIcon class="size-5" aria-hidden="true" />{/snippet}
{#snippet toolbar()}
<div class="border-b border-border">
<div class="flex flex-wrap items-center justify-between gap-2 px-3 py-2.5">