mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-22 11:45:52 +00:00
Canonicalize platform table empty states
This commit is contained in:
@@ -2386,6 +2386,12 @@ divide frame locally. Platform table consumers must preserve the owner split:
|
||||
frontend-primitives owns the repeated `PlatformTableShell` frame and guardrail
|
||||
registry, while platform and unified-resource consumers own the source-specific
|
||||
row fields, drawers, and resource semantics.
|
||||
Platform table empty states follow the same registry-backed ownership.
|
||||
`PlatformTableEmptyState` owns the repeated table-card empty-state shell for
|
||||
Docker, Kubernetes, Proxmox, Standalone, TrueNAS, vSphere, and future platform
|
||||
feature tables; source-specific consumers own only the empty-state icon, title,
|
||||
description, and actions. Platform feature tables must not import
|
||||
`EmptyState` directly or recreate a `Card`-wrapped empty-state shell.
|
||||
Inline detail table rows are also registry-backed. `InlineDetailTableRow`
|
||||
owns the row/cell/content shell and row-click containment for platform,
|
||||
workload, and infrastructure inline drawers; callers may pass row-specific
|
||||
|
||||
@@ -124,6 +124,11 @@ explicit uncovered and failed-latest-task states, rather than from source-detail
|
||||
row counts alone. PBS server/datastore rows may display backup counts, but the
|
||||
counts must come from the PBS backup API artifact identity, not from a
|
||||
datastore-capacity approximation.
|
||||
Those Proxmox backup workflows own recovery semantics, source-specific
|
||||
empty-state copy, and retry/filter actions, but their table empty-state frame
|
||||
must compose the frontend-primitives-owned `PlatformTableEmptyState` shell
|
||||
instead of importing `EmptyState` directly or wrapping it in a backup-local
|
||||
card.
|
||||
The Proxmox overview workload table's Backup column is the primary
|
||||
glance-level protection signal for current guests. It must remain visible,
|
||||
dense, and backed by the canonical guest `LastBackup` synchronization from PVE
|
||||
|
||||
@@ -215,6 +215,11 @@ filter semantics, drawer handoffs, and resource projections, while
|
||||
Future platform tables must keep that split: row data and platform semantics
|
||||
stay in the unified-resource consumer, and the repeated table shell stays in the
|
||||
shared frontend primitive.
|
||||
Platform table empty states use that same split. Unified-resource consumers own
|
||||
the source-specific empty-state vocabulary, action choice, and evidence rule
|
||||
that decides why a table is empty, but the table-card empty-state shell itself
|
||||
must compose the frontend-primitives-owned `PlatformTableEmptyState` primitive
|
||||
instead of importing `EmptyState` directly or wrapping it in a page-local card.
|
||||
The same split applies to inline detail row shells on platform, native-resource,
|
||||
workload, and infrastructure tables. Unified-resource consumers own row
|
||||
identity, source-specific drawer content, expansion state, and resource
|
||||
|
||||
@@ -137,6 +137,31 @@
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "platform-table-empty-state-local-empty-state",
|
||||
"category": "platform-table-empty-state",
|
||||
"summary": "Platform feature tables must compose PlatformTableEmptyState for their table/card empty-state shell instead of importing EmptyState directly.",
|
||||
"canonical": {
|
||||
"path": "src/features/platformPage/sharedPlatformPage.tsx",
|
||||
"export": "PlatformTableEmptyState"
|
||||
},
|
||||
"scopes": [
|
||||
"src/features/docker",
|
||||
"src/features/kubernetes",
|
||||
"src/features/proxmox",
|
||||
"src/features/standalone",
|
||||
"src/features/truenas",
|
||||
"src/features/vmware"
|
||||
],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["@/components/shared/EmptyState"],
|
||||
"legacyReason": "Retired migration debt. Platform table empty states must use PlatformTableEmptyState; no platform feature exceptions are currently allowed.",
|
||||
"allowedPaths": [],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
}
|
||||
],
|
||||
"requiredPatternGuards": [
|
||||
|
||||
@@ -102,9 +102,14 @@ import guestRowStateSource from '@/components/Workloads/useGuestRowState.ts?raw'
|
||||
import workloadSelectionStateSource from '@/components/Workloads/useWorkloadSelectionState.ts?raw';
|
||||
import dockerHostsTableSource from '@/features/docker/DockerHostsTable.tsx?raw';
|
||||
import kubernetesNodesTableSource from '@/features/kubernetes/KubernetesNodesTable.tsx?raw';
|
||||
import proxmoxBackupsTableSource from '@/features/proxmox/ProxmoxBackupsTable.tsx?raw';
|
||||
import proxmoxCephTableSource from '@/features/proxmox/ProxmoxCephTable.tsx?raw';
|
||||
import proxmoxCoverageTableSource from '@/features/proxmox/ProxmoxCoverageTable.tsx?raw';
|
||||
import proxmoxMailGatewayTableSource from '@/features/proxmox/ProxmoxMailGatewayTable.tsx?raw';
|
||||
import proxmoxNodesTableSource from '@/features/proxmox/ProxmoxNodesTable.tsx?raw';
|
||||
import proxmoxHostTableModelSource from '@/features/proxmox/proxmoxHostTableModel.ts?raw';
|
||||
import proxmoxRecoverableTableSource from '@/features/proxmox/ProxmoxRecoverableTable.tsx?raw';
|
||||
import proxmoxReplicationTableSource from '@/features/proxmox/ProxmoxReplicationTable.tsx?raw';
|
||||
import vsphereHostsTableSource from '@/features/vmware/VsphereHostsTable.tsx?raw';
|
||||
import agentsMachinesTableSource from '@/features/standalone/AgentsMachinesTable.tsx?raw';
|
||||
import agentMachineTableModelSource from '@/features/standalone/agentMachineTableModel.ts?raw';
|
||||
@@ -1005,6 +1010,59 @@ describe('shared primitive guardrails', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps platform table empty states on the shared shell template', () => {
|
||||
const registry = JSON.parse(sharedTemplateRegistrySource) as {
|
||||
patternGuards: Array<{
|
||||
id: string;
|
||||
canonical?: { path?: string; export?: string };
|
||||
allPatterns?: string[];
|
||||
scopes?: string[];
|
||||
allowedPaths?: string[];
|
||||
}>;
|
||||
};
|
||||
const registeredGuard = registry.patternGuards.find(
|
||||
(guard) => guard.id === 'platform-table-empty-state-local-empty-state',
|
||||
);
|
||||
|
||||
expect(registeredGuard?.canonical?.path).toBe(
|
||||
'src/features/platformPage/sharedPlatformPage.tsx',
|
||||
);
|
||||
expect(registeredGuard?.canonical?.export).toBe('PlatformTableEmptyState');
|
||||
expect(registeredGuard?.allPatterns).toEqual(['@/components/shared/EmptyState']);
|
||||
expect(registeredGuard?.allowedPaths ?? []).toHaveLength(0);
|
||||
expect(registeredGuard?.scopes).toEqual(
|
||||
expect.arrayContaining([
|
||||
'src/features/docker',
|
||||
'src/features/kubernetes',
|
||||
'src/features/proxmox',
|
||||
'src/features/standalone',
|
||||
'src/features/truenas',
|
||||
'src/features/vmware',
|
||||
]),
|
||||
);
|
||||
expect(sharedPlatformPageSource).toContain('export function PlatformTableEmptyState');
|
||||
expect(sharedPlatformPageSource).toContain('icon?: JSX.Element');
|
||||
expect(sharedPlatformPageSource).toContain("from '@/components/shared/EmptyState'");
|
||||
|
||||
for (const source of [
|
||||
dockerHostsTableSource,
|
||||
kubernetesNodesTableSource,
|
||||
proxmoxBackupsTableSource,
|
||||
proxmoxCephTableSource,
|
||||
proxmoxCoverageTableSource,
|
||||
proxmoxMailGatewayTableSource,
|
||||
proxmoxRecoverableTableSource,
|
||||
proxmoxReplicationTableSource,
|
||||
proxmoxNodesTableSource,
|
||||
vsphereHostsTableSource,
|
||||
]) {
|
||||
expect(source).toContain('PlatformTableEmptyState');
|
||||
expect(source).not.toContain("@/components/shared/EmptyState");
|
||||
expect(source).not.toContain('<EmptyState');
|
||||
expect(source).not.toContain('<Card padding="lg">');
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps platform row-detail disclosure controls on the shared toggle primitive', () => {
|
||||
const registry = JSON.parse(sharedTemplateRegistrySource) as {
|
||||
requiredPatternGuards?: Array<{
|
||||
|
||||
@@ -55,7 +55,7 @@ export function PlatformSectionTabs<TabId extends string>(props: {
|
||||
}
|
||||
|
||||
export function PlatformTableEmptyState(props: {
|
||||
icon: JSX.Element;
|
||||
icon?: JSX.Element;
|
||||
title: string;
|
||||
description: string;
|
||||
actions?: JSX.Element;
|
||||
|
||||
@@ -2,12 +2,11 @@ import { Show, createMemo, createResource, createSignal, type Component, type JS
|
||||
import ChevronRightIcon from 'lucide-solid/icons/chevron-right';
|
||||
import CalendarIcon from 'lucide-solid/icons/calendar';
|
||||
import ShieldCheckIcon from 'lucide-solid/icons/shield-check';
|
||||
import { Card } from '@/components/shared/Card';
|
||||
import { EmptyState } from '@/components/shared/EmptyState';
|
||||
import { useSearchParams } from '@solidjs/router';
|
||||
import { FilterBar, type FilterDef, type FilterSelectOption } from '@/components/shared/FilterBar';
|
||||
import { useBreakpoint } from '@/hooks/useBreakpoint';
|
||||
import { apiFetch } from '@/utils/apiClient';
|
||||
import { PlatformTableEmptyState } from '@/features/platformPage/sharedPlatformPage';
|
||||
import {
|
||||
getRecoveryFilterDateLabel,
|
||||
recoveryDateKeyFromTimestamp,
|
||||
@@ -438,34 +437,30 @@ export const ProxmoxBackupsTable: Component<{
|
||||
<Show
|
||||
when={!backups.error}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
icon={props.emptyIcon}
|
||||
title="Could not load Proxmox backup inventory"
|
||||
description={(backups.error as Error | undefined)?.message ?? 'Refresh to retry.'}
|
||||
actions={
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void refetch()}
|
||||
class="inline-flex min-h-10 items-center rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-surface-hover"
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
<PlatformTableEmptyState
|
||||
icon={props.emptyIcon}
|
||||
title="Could not load Proxmox backup inventory"
|
||||
description={(backups.error as Error | undefined)?.message ?? 'Refresh to retry.'}
|
||||
actions={
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void refetch()}
|
||||
class="inline-flex min-h-10 items-center rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-surface-hover"
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Show
|
||||
when={backups() !== undefined}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
icon={props.emptyIcon}
|
||||
title="Loading Proxmox backup inventory"
|
||||
description="Reading PBS snapshots, PVE backup files, guest snapshots, and recent backup tasks."
|
||||
/>
|
||||
</Card>
|
||||
<PlatformTableEmptyState
|
||||
icon={props.emptyIcon}
|
||||
title="Loading Proxmox backup inventory"
|
||||
description="Reading PBS snapshots, PVE backup files, guest snapshots, and recent backup tasks."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div class="space-y-3">
|
||||
|
||||
@@ -7,8 +7,6 @@ import {
|
||||
type Component,
|
||||
type JSX,
|
||||
} from 'solid-js';
|
||||
import { Card } from '@/components/shared/Card';
|
||||
import { EmptyState } from '@/components/shared/EmptyState';
|
||||
import { InlineDetailTableRow } from '@/components/shared/InlineDetailTableRow';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import type { StatusIndicatorVariant } from '@/utils/status';
|
||||
@@ -20,6 +18,7 @@ import {
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
type PlatformTableFilterOption,
|
||||
PlatformTableEmptyState,
|
||||
PlatformTableShell,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
import { PlatformResourceDetailToggleButton } from '@/features/platformPage/PlatformResourceDetailTableRow';
|
||||
@@ -207,13 +206,11 @@ export const ProxmoxCephTable: Component<{
|
||||
<Show
|
||||
when={total() > 0}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
icon={props.emptyIcon}
|
||||
title={props.emptyTitle}
|
||||
description={props.emptyDescription}
|
||||
/>
|
||||
</Card>
|
||||
<PlatformTableEmptyState
|
||||
icon={props.emptyIcon}
|
||||
title={props.emptyTitle}
|
||||
description={props.emptyDescription}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div class="space-y-3">
|
||||
@@ -232,13 +229,11 @@ export const ProxmoxCephTable: Component<{
|
||||
<Show
|
||||
when={filtered().length > 0}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
icon={props.emptyIcon}
|
||||
title="No clusters match current filters"
|
||||
description="Adjust the search or status filter to see more clusters."
|
||||
/>
|
||||
</Card>
|
||||
<PlatformTableEmptyState
|
||||
icon={props.emptyIcon}
|
||||
title="No clusters match current filters"
|
||||
description="Adjust the search or status filter to see more clusters."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<PlatformTableShell
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { For, Show, type Accessor, type JSX } from 'solid-js';
|
||||
|
||||
import { Card } from '@/components/shared/Card';
|
||||
import { EmptyState } from '@/components/shared/EmptyState';
|
||||
import { InlineDetailTableRow } from '@/components/shared/InlineDetailTableRow';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import { TableCell, TableRow } from '@/components/shared/Table';
|
||||
@@ -9,6 +7,7 @@ import { formatBytes } from '@/utils/format';
|
||||
import {
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
PlatformTableEmptyState,
|
||||
PlatformTableShell,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
import { PlatformResourceDetailToggleButton } from '@/features/platformPage/PlatformResourceDetailTableRow';
|
||||
@@ -101,21 +100,17 @@ export function ProxmoxCoverageTable(props: {
|
||||
<Show
|
||||
when={props.rows.length > 0}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
icon={props.emptyIcon}
|
||||
title={
|
||||
!props.hasAnyRows
|
||||
? props.emptyTitle
|
||||
: 'No workload coverage rows match current filters'
|
||||
}
|
||||
description={
|
||||
!props.hasAnyRows
|
||||
? props.emptyDescription
|
||||
: 'Adjust the search, posture filter, or selected day to see more workloads.'
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
<PlatformTableEmptyState
|
||||
icon={props.emptyIcon}
|
||||
title={
|
||||
!props.hasAnyRows ? props.emptyTitle : 'No workload coverage rows match current filters'
|
||||
}
|
||||
description={
|
||||
!props.hasAnyRows
|
||||
? props.emptyDescription
|
||||
: 'Adjust the search, posture filter, or selected day to see more workloads.'
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<PlatformTableShell
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { For, Show, createSignal, type Component, type JSX } from 'solid-js';
|
||||
import { Card } from '@/components/shared/Card';
|
||||
import { EmptyState } from '@/components/shared/EmptyState';
|
||||
import { InlineDetailTableRow } from '@/components/shared/InlineDetailTableRow';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import { TableCell, TableHead, TableRow } from '@/components/shared/Table';
|
||||
@@ -14,6 +12,7 @@ import {
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
type PlatformResourceStatusFilter,
|
||||
PlatformTableEmptyState,
|
||||
PlatformTableShell,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
import { PlatformResourceDetailToggleButton } from '@/features/platformPage/PlatformResourceDetailTableRow';
|
||||
@@ -58,9 +57,7 @@ export const ProxmoxMailGatewayTable: Component<{
|
||||
<Show
|
||||
when={props.resources.length > 0}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState title={props.emptyTitle} description={props.emptyDescription} />
|
||||
</Card>
|
||||
<PlatformTableEmptyState title={props.emptyTitle} description={props.emptyDescription} />
|
||||
}
|
||||
>
|
||||
<div class="space-y-3">
|
||||
@@ -79,12 +76,10 @@ export const ProxmoxMailGatewayTable: Component<{
|
||||
<Show
|
||||
when={tableState.filtered().length > 0}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
title="No instances match current filters"
|
||||
description="Adjust the search or status filter to see more instances."
|
||||
/>
|
||||
</Card>
|
||||
<PlatformTableEmptyState
|
||||
title="No instances match current filters"
|
||||
description="Adjust the search or status filter to see more instances."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<PlatformTableShell
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { For, Show, type Accessor, type JSX } from 'solid-js';
|
||||
|
||||
import { Card } from '@/components/shared/Card';
|
||||
import { EmptyState } from '@/components/shared/EmptyState';
|
||||
import { TableCell, TableHead, TableRow } from '@/components/shared/Table';
|
||||
import { formatBytes } from '@/utils/format';
|
||||
import {
|
||||
@@ -11,6 +9,7 @@ import {
|
||||
import {
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
PlatformTableEmptyState,
|
||||
PlatformTableShell,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
|
||||
@@ -136,21 +135,19 @@ export function ProxmoxRecoverableTable(props: {
|
||||
<Show
|
||||
when={props.artifacts.length > 0}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
icon={props.emptyIcon}
|
||||
title={
|
||||
!props.hasAnyArtifacts
|
||||
? props.emptyTitle
|
||||
: 'No recoverable artifacts match current filters'
|
||||
}
|
||||
description={
|
||||
!props.hasAnyArtifacts
|
||||
? props.emptyDescription
|
||||
: 'Adjust the search, source filter, or selected day to see more artifacts.'
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
<PlatformTableEmptyState
|
||||
icon={props.emptyIcon}
|
||||
title={
|
||||
!props.hasAnyArtifacts
|
||||
? props.emptyTitle
|
||||
: 'No recoverable artifacts match current filters'
|
||||
}
|
||||
description={
|
||||
!props.hasAnyArtifacts
|
||||
? props.emptyDescription
|
||||
: 'Adjust the search, source filter, or selected day to see more artifacts.'
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<PlatformTableShell
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { For, Show, createMemo, createSignal, type Component, type JSX } from 'solid-js';
|
||||
import ArrowRightIcon from 'lucide-solid/icons/arrow-right';
|
||||
import { Card } from '@/components/shared/Card';
|
||||
import { EmptyState } from '@/components/shared/EmptyState';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import type { StatusIndicatorVariant } from '@/utils/status';
|
||||
import { TableCell, TableHead, TableRow } from '@/components/shared/Table';
|
||||
@@ -12,6 +10,7 @@ import {
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
type PlatformTableFilterOption,
|
||||
PlatformTableEmptyState,
|
||||
PlatformTableShell,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
import type { ReplicationJob, ReplicationJobsResponse } from '@/types/api';
|
||||
@@ -184,46 +183,40 @@ export const ProxmoxReplicationTable: Component<{
|
||||
<Show
|
||||
when={!props.error}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
icon={props.emptyIcon}
|
||||
title="Could not load replication jobs"
|
||||
description={(props.error as Error | undefined)?.message ?? 'Refresh to retry.'}
|
||||
actions={
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => props.onRetry()}
|
||||
class="inline-flex min-h-10 items-center rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-surface-hover"
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
<PlatformTableEmptyState
|
||||
icon={props.emptyIcon}
|
||||
title="Could not load replication jobs"
|
||||
description={(props.error as Error | undefined)?.message ?? 'Refresh to retry.'}
|
||||
actions={
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => props.onRetry()}
|
||||
class="inline-flex min-h-10 items-center rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-surface-hover"
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Show
|
||||
when={props.jobs !== undefined}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
icon={props.emptyIcon}
|
||||
title="Loading replication jobs"
|
||||
description="Reading scheduled replication state from PVE."
|
||||
/>
|
||||
</Card>
|
||||
<PlatformTableEmptyState
|
||||
icon={props.emptyIcon}
|
||||
title="Loading replication jobs"
|
||||
description="Reading scheduled replication state from PVE."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Show
|
||||
when={total() > 0}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
icon={props.emptyIcon}
|
||||
title={props.emptyTitle}
|
||||
description={props.emptyDescription}
|
||||
/>
|
||||
</Card>
|
||||
<PlatformTableEmptyState
|
||||
icon={props.emptyIcon}
|
||||
title={props.emptyTitle}
|
||||
description={props.emptyDescription}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div class="space-y-3">
|
||||
@@ -242,13 +235,11 @@ export const ProxmoxReplicationTable: Component<{
|
||||
<Show
|
||||
when={filtered().length > 0}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
icon={props.emptyIcon}
|
||||
title="No replication jobs match current filters"
|
||||
description="Adjust the search or status filter to see more jobs."
|
||||
/>
|
||||
</Card>
|
||||
<PlatformTableEmptyState
|
||||
icon={props.emptyIcon}
|
||||
title="No replication jobs match current filters"
|
||||
description="Adjust the search or status filter to see more jobs."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<PlatformTableShell
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
PLATFORM_TABLE_BODY_CLASS,
|
||||
PLATFORM_TABLE_HEADER_ROW_CLASS,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
import { TABLE_CARD_FRAME_CLASS } from '@/components/shared/TableCard';
|
||||
import type { Resource } from '@/types/resource';
|
||||
import { getRecoveryFullDateLabel } from '@/utils/recoveryDatePresentation';
|
||||
|
||||
@@ -264,5 +265,17 @@ describe('ProxmoxBackupsTable', () => {
|
||||
await fireEvent.input(searchInput, { target: { value: 'no-such-guest' } });
|
||||
|
||||
expect(screen.queryByText('pbs-docker')).not.toBeInTheDocument();
|
||||
const emptyStateHeading = screen.getByRole('heading', {
|
||||
name: /no recoverable artifacts match current filters/i,
|
||||
});
|
||||
expect(emptyStateHeading).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Adjust the search, source filter, or selected day to see more artifacts.'),
|
||||
).toBeInTheDocument();
|
||||
expectClassTokens(
|
||||
emptyStateHeading.closest(`.${TABLE_CARD_FRAME_CLASS}`),
|
||||
TABLE_CARD_FRAME_CLASS,
|
||||
);
|
||||
expect(emptyStateHeading.closest('.border-dashed')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user