mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
refactor(recovery): strengthen inventory workspace treatment
This commit is contained in:
@@ -473,6 +473,11 @@ protected estate into one endless slab; it should page or otherwise bound the
|
||||
primary inventory table so the workspace, filters, and adjacent activity panel
|
||||
remain readable as one monitoring surface instead of dissolving into a raw list
|
||||
dump.
|
||||
That same protected-inventory surface should carry compact operator orientation
|
||||
inside the table shell itself. `RecoveryProtectedInventorySection.tsx` should
|
||||
expose the current bounded range, page, and sort state near the primary grid,
|
||||
and the first column should carry enough secondary item metadata to read as a
|
||||
monitored inventory row rather than a bare export line.
|
||||
That same hierarchy rule also applies to the activity panel below the primary
|
||||
workspace. `RecoveryActivitySection.tsx` should read as timeline analysis for
|
||||
the selected history window, not as a second copy of the page-level posture
|
||||
|
||||
@@ -169,6 +169,31 @@ export const RecoveryProtectedInventorySection: Component<
|
||||
return sortedRollups().slice(start, start + PROTECTED_ITEMS_PAGE_SIZE);
|
||||
});
|
||||
|
||||
const pageStart = createMemo(() =>
|
||||
sortedRollups().length === 0 ? 0 : (protectedPage() - 1) * PROTECTED_ITEMS_PAGE_SIZE + 1,
|
||||
);
|
||||
|
||||
const pageEnd = createMemo(() =>
|
||||
Math.min(protectedPage() * PROTECTED_ITEMS_PAGE_SIZE, sortedRollups().length),
|
||||
);
|
||||
|
||||
const protectedSortLabel = createMemo(() => {
|
||||
switch (protectedSortCol()) {
|
||||
case 'item':
|
||||
return 'Item';
|
||||
case 'type':
|
||||
return 'Item Type';
|
||||
case 'platform':
|
||||
return 'Platform';
|
||||
case 'lastBackup':
|
||||
return 'Latest Point';
|
||||
case 'outcome':
|
||||
return 'Outcome';
|
||||
default:
|
||||
return 'Latest Point';
|
||||
}
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
props.queryFilter();
|
||||
props.platformFilter();
|
||||
@@ -325,6 +350,27 @@ export const RecoveryProtectedInventorySection: Component<
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={sortedRollups().length > 0}>
|
||||
<div class="border-b border-border-subtle/80 bg-surface-alt/20 px-4 py-3">
|
||||
<div class="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div class="flex flex-wrap items-center gap-2 text-xs">
|
||||
<span class="rounded-full border border-border-subtle bg-surface px-2.5 py-1 text-base-content">
|
||||
Showing {pageStart()} - {pageEnd()}
|
||||
</span>
|
||||
<span class="rounded-full border border-border-subtle bg-surface px-2.5 py-1 text-base-content">
|
||||
Page {protectedPage()} / {protectedTotalPages()}
|
||||
</span>
|
||||
<span class="rounded-full border border-border-subtle bg-surface px-2.5 py-1 text-base-content">
|
||||
Sorted by {protectedSortLabel()}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs text-muted">
|
||||
Open any item to drill directly into its recovery events.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={props.loading() && props.filteredRollups().length === 0}>
|
||||
<div class="px-6 py-6 text-sm text-muted">
|
||||
{getRecoveryProtectedItemsLoadingState().text}
|
||||
@@ -427,7 +473,7 @@ export const RecoveryProtectedInventorySection: Component<
|
||||
onClick={() => props.onSelectRollup(rollup.rollupId)}
|
||||
>
|
||||
<TableCell
|
||||
class={`relative max-w-[420px] truncate whitespace-nowrap px-3 py-3 text-base-content ${
|
||||
class={`relative max-w-[420px] px-3 py-3 text-base-content ${
|
||||
issueTone === 'rose' || issueTone === 'blue' ? 'font-medium' : ''
|
||||
}`}
|
||||
title={label}
|
||||
@@ -435,18 +481,32 @@ export const RecoveryProtectedInventorySection: Component<
|
||||
<Show when={issueTone !== 'none'}>
|
||||
<span class={`absolute inset-y-0 left-0 w-0.5 ${issueRailClass}`} />
|
||||
</Show>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="truncate">{label}</span>
|
||||
<Show when={neverSucceeded}>
|
||||
<span class={getRecoveryRollupStatusPillClass('never-succeeded')}>
|
||||
{getRecoveryRollupStatusPillLabel('never-succeeded')}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={!neverSucceeded && stale}>
|
||||
<span class={getRecoveryRollupStatusPillClass('stale')}>
|
||||
{getRecoveryRollupStatusPillLabel('stale')}
|
||||
</span>
|
||||
</Show>
|
||||
<div class="flex min-w-0 flex-col gap-1.5 pl-0.5">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<span class="truncate text-sm font-medium">{label}</span>
|
||||
<Show when={neverSucceeded}>
|
||||
<span class={getRecoveryRollupStatusPillClass('never-succeeded')}>
|
||||
{getRecoveryRollupStatusPillLabel('never-succeeded')}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={!neverSucceeded && stale}>
|
||||
<span class={getRecoveryRollupStatusPillClass('stale')}>
|
||||
{getRecoveryRollupStatusPillLabel('stale')}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-x-2 gap-y-1 text-[11px] text-muted">
|
||||
<Show when={itemTypePresentation?.label}>
|
||||
<span>{itemTypePresentation?.label}</span>
|
||||
</Show>
|
||||
<Show when={platforms.length > 0}>
|
||||
<span>
|
||||
{platforms
|
||||
.map((platform) => getSourcePlatformLabel(platform))
|
||||
.join(' • ')}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
@@ -456,7 +516,7 @@ export const RecoveryProtectedInventorySection: Component<
|
||||
fallback={<span class="text-muted">—</span>}
|
||||
>
|
||||
<span
|
||||
class={`inline-flex min-w-[4.5rem] justify-center rounded px-1.5 py-px text-[10px] font-medium ${
|
||||
class={`inline-flex min-w-[4.75rem] justify-center rounded-full border px-2 py-1 text-[10px] font-medium ${
|
||||
itemTypePresentation?.badgeClasses || 'bg-surface-alt text-base-content'
|
||||
}`}
|
||||
>
|
||||
@@ -525,9 +585,7 @@ export const RecoveryProtectedInventorySection: Component<
|
||||
fallback={<span>Showing 0 of 0 protected items</span>}
|
||||
>
|
||||
<span>
|
||||
Showing {(protectedPage() - 1) * PROTECTED_ITEMS_PAGE_SIZE + 1} -{' '}
|
||||
{Math.min(protectedPage() * PROTECTED_ITEMS_PAGE_SIZE, sortedRollups().length)} of{' '}
|
||||
{sortedRollups().length} protected items
|
||||
Showing {pageStart()} - {pageEnd()} of {sortedRollups().length} protected items
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -213,7 +213,7 @@ describe('Recovery', () => {
|
||||
expect(within(inventoryTable).getByText('Item')).toBeInTheDocument();
|
||||
expect(within(inventoryTable).getByText('Item Type')).toBeInTheDocument();
|
||||
expect(within(inventoryTable).getByText('Platform')).toBeInTheDocument();
|
||||
expect(within(inventoryTable).getByText('VM')).toBeInTheDocument();
|
||||
expect(within(inventoryTable).getAllByText('VM').length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText('Backups By Date')).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(await screen.findByText('VM 123'));
|
||||
@@ -742,13 +742,17 @@ describe('Recovery', () => {
|
||||
render(() => <Recovery />);
|
||||
|
||||
expect(await screen.findByText('Showing 1 - 24 of 26 protected items')).toBeInTheDocument();
|
||||
expect(screen.getByText('Page 1 / 2')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('Page 1 / 2').length).toBeGreaterThan(0);
|
||||
expect(screen.getByText('Sorted by Latest Point')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Open any item to drill directly into its recovery events.'),
|
||||
).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Showing 25 - 26 of 26 protected items')).toBeInTheDocument();
|
||||
expect(screen.getByText('Page 2 / 2')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('Page 2 / 2').length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user