Enhance DataTableCard component with accent and icon support
- 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:
@@ -1,31 +1,57 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Card from '$lib/components/ui/card/index.js';
|
import * as Card from '$lib/components/ui/card/index.js';
|
||||||
import { cn } from '$lib/utils.js';
|
import { cn } from '$lib/utils.js';
|
||||||
|
import { kpiAccent, type KpiAccentKey } from '$lib/kpi-accents.js';
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
|
accent,
|
||||||
|
icon,
|
||||||
class: className,
|
class: className,
|
||||||
toolbar,
|
toolbar,
|
||||||
children
|
children
|
||||||
}: {
|
}: {
|
||||||
/** Без title — как в shadcn dashboard-01: сразу toolbar и таблица */
|
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
accent?: KpiAccentKey;
|
||||||
|
icon?: Snippet;
|
||||||
toolbar?: Snippet;
|
toolbar?: Snippet;
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
class?: string;
|
class?: string;
|
||||||
} = $props();
|
} = $props();
|
||||||
</script>
|
</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}
|
{#if title}
|
||||||
<Card.Header class="border-b border-border bg-muted/30 px-4 py-3.5">
|
<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>
|
<div class="flex gap-3">
|
||||||
{#if description}
|
{#if icon && accent}
|
||||||
<Card.Description class="text-xs sm:text-sm">{description}</Card.Description>
|
<div
|
||||||
{/if}
|
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>
|
</Card.Header>
|
||||||
{/if}
|
{/if}
|
||||||
{#if toolbar}
|
{#if toolbar}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
import GlobeIcon from '@lucide/svelte/icons/globe';
|
import GlobeIcon from '@lucide/svelte/icons/globe';
|
||||||
import CloudUploadIcon from '@lucide/svelte/icons/cloud-upload';
|
import CloudUploadIcon from '@lucide/svelte/icons/cloud-upload';
|
||||||
import LockIcon from '@lucide/svelte/icons/lock';
|
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';
|
import { nextSortDir, compareStrings, compareNum, type SortDir } from '$lib/table-sort.js';
|
||||||
|
|
||||||
let loading = $state(true);
|
let loading = $state(true);
|
||||||
@@ -619,7 +620,9 @@ async function handleAliasFilterChange(nextAlias: string) {
|
|||||||
class="mb-6"
|
class="mb-6"
|
||||||
title="Активные IP (агрегат)"
|
title="Активные IP (агрегат)"
|
||||||
description="С GeoIP при включённой базе на шлюзе"
|
description="С GeoIP при включённой базе на шлюзе"
|
||||||
|
accent="teal"
|
||||||
>
|
>
|
||||||
|
{#snippet icon()}<GlobeIcon class="size-5" aria-hidden="true" />{/snippet}
|
||||||
{#snippet toolbar()}
|
{#snippet toolbar()}
|
||||||
<DataTableToolbar>
|
<DataTableToolbar>
|
||||||
<div class="relative max-w-xs flex-1">
|
<div class="relative max-w-xs flex-1">
|
||||||
@@ -751,7 +754,9 @@ async function handleAliasFilterChange(nextAlias: string) {
|
|||||||
class="mb-6"
|
class="mb-6"
|
||||||
title="По нодам"
|
title="По нодам"
|
||||||
description="Health, версия, аптайм, bad connections"
|
description="Health, версия, аптайм, bad connections"
|
||||||
|
accent="violet"
|
||||||
>
|
>
|
||||||
|
{#snippet icon()}<ServerIcon class="size-5" aria-hidden="true" />{/snippet}
|
||||||
{#snippet toolbar()}
|
{#snippet toolbar()}
|
||||||
<DataTableToolbar>
|
<DataTableToolbar>
|
||||||
<div class="relative max-w-xs flex-1">
|
<div class="relative max-w-xs flex-1">
|
||||||
@@ -910,7 +915,9 @@ async function handleAliasFilterChange(nextAlias: string) {
|
|||||||
class="mt-6"
|
class="mt-6"
|
||||||
title="Топ пользователей по трафику"
|
title="Топ пользователей по трафику"
|
||||||
description="До 10 записей из summary"
|
description="До 10 записей из summary"
|
||||||
|
accent="info"
|
||||||
>
|
>
|
||||||
|
{#snippet icon()}<UsersIcon class="size-5" aria-hidden="true" />{/snippet}
|
||||||
{#snippet toolbar()}
|
{#snippet toolbar()}
|
||||||
<DataTableToolbar>
|
<DataTableToolbar>
|
||||||
<div class="relative max-w-xs flex-1">
|
<div class="relative max-w-xs flex-1">
|
||||||
|
|||||||
@@ -346,7 +346,8 @@
|
|||||||
</KpiStatCard>
|
</KpiStatCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DataTableCard title="Инциденты" description="Список и разбор">
|
<DataTableCard title="Инциденты" description="Список и разбор" accent="danger">
|
||||||
|
{#snippet icon()}<SirenIcon class="size-5" aria-hidden="true" />{/snippet}
|
||||||
{#snippet toolbar()}
|
{#snippet toolbar()}
|
||||||
<DataTableToolbar>
|
<DataTableToolbar>
|
||||||
<div class="relative max-w-md flex-1">
|
<div class="relative max-w-md flex-1">
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
import SearchIcon from '@lucide/svelte/icons/search';
|
import SearchIcon from '@lucide/svelte/icons/search';
|
||||||
import { nextSortDir, compareStrings, type SortDir } from '$lib/table-sort.js';
|
import { nextSortDir, compareStrings, type SortDir } from '$lib/table-sort.js';
|
||||||
import MapIcon from '@lucide/svelte/icons/map';
|
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 { Alert, AlertDescription, AlertTitle } from '$lib/components/ui/alert/index.js';
|
||||||
import { Button } from '$lib/components/ui/button/index.js';
|
import { Button } from '$lib/components/ui/button/index.js';
|
||||||
import { Badge } from '$lib/components/ui/badge/index.js';
|
import { Badge } from '$lib/components/ui/badge/index.js';
|
||||||
@@ -617,7 +618,8 @@ $effect(() => {
|
|||||||
</Alert>
|
</Alert>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<DataTableCard title="Уникальные IP" description="Снимок шлюза: активные и недавние списки">
|
<DataTableCard title="Уникальные IP" description="Снимок шлюза: активные и недавние списки" accent="teal">
|
||||||
|
{#snippet icon()}<GlobeIcon class="size-5" aria-hidden="true" />{/snippet}
|
||||||
{#snippet toolbar()}
|
{#snippet toolbar()}
|
||||||
<DataTableToolbar>
|
<DataTableToolbar>
|
||||||
<div class="relative max-w-md flex-1">
|
<div class="relative max-w-md flex-1">
|
||||||
|
|||||||
@@ -391,7 +391,9 @@
|
|||||||
<DataTableCard
|
<DataTableCard
|
||||||
title="Последние инциденты из снимка"
|
title="Последние инциденты из снимка"
|
||||||
description="Всего: {snapshot?.counts?.total ?? snapshot?.incidents?.length ?? 0}"
|
description="Всего: {snapshot?.counts?.total ?? snapshot?.incidents?.length ?? 0}"
|
||||||
|
accent="warning"
|
||||||
>
|
>
|
||||||
|
{#snippet icon()}<ActivityIcon class="size-5" aria-hidden="true" />{/snippet}
|
||||||
{#snippet toolbar()}
|
{#snippet toolbar()}
|
||||||
<DataTableToolbar>
|
<DataTableToolbar>
|
||||||
<div class="relative max-w-md flex-1">
|
<div class="relative max-w-md flex-1">
|
||||||
|
|||||||
@@ -260,7 +260,9 @@
|
|||||||
<DataTableCard
|
<DataTableCard
|
||||||
title="Ноды"
|
title="Ноды"
|
||||||
description="Контроллер и метрики. Ниже — компактные группы прокси (автозагрузка). «Детально» — обзор с графиками."
|
description="Контроллер и метрики. Ниже — компактные группы прокси (автозагрузка). «Детально» — обзор с графиками."
|
||||||
|
accent="violet"
|
||||||
>
|
>
|
||||||
|
{#snippet icon()}<ServerIcon class="size-5" aria-hidden="true" />{/snippet}
|
||||||
<ScrollArea class="w-full" orientation="both">
|
<ScrollArea class="w-full" orientation="both">
|
||||||
<Table.Root>
|
<Table.Root>
|
||||||
<Table.Header class="sticky top-0 z-10 bg-muted">
|
<Table.Header class="sticky top-0 z-10 bg-muted">
|
||||||
|
|||||||
@@ -440,7 +440,8 @@ let sumActiveIps = $derived.by(() =>
|
|||||||
</KpiStatCard>
|
</KpiStatCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DataTableCard>
|
<DataTableCard title="Пользователи" description="Слияние по имени между серверами" accent="violet">
|
||||||
|
{#snippet icon()}<UsersIcon class="size-5" aria-hidden="true" />{/snippet}
|
||||||
{#snippet toolbar()}
|
{#snippet toolbar()}
|
||||||
<div class="border-b border-border">
|
<div class="border-b border-border">
|
||||||
<div class="flex flex-wrap items-center justify-between gap-2 px-3 py-2.5">
|
<div class="flex flex-wrap items-center justify-between gap-2 px-3 py-2.5">
|
||||||
|
|||||||
Reference in New Issue
Block a user