chore(resources): remove Largest 5 and Recently changed cards from Volumes tab (#981)

The two landing cards above the volumes table did not earn their space
and made the Volumes view feel cluttered. Drop the cards, the unused
TabLanding component, and the helpers that only fed it.
This commit is contained in:
Anso
2026-05-07 23:45:05 -04:00
committed by GitHub
parent 0be479100c
commit 4867234eac
2 changed files with 1 additions and 155 deletions
+1 -46
View File
@@ -1,4 +1,4 @@
import { useState, useEffect, useMemo } from 'react';
import { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger, TabsHighlight, TabsHighlightItem } from "@/components/ui/tabs";
@@ -37,22 +37,9 @@ import { FootprintTreemap } from './resources/FootprintTreemap';
import { ImageDetailsSheet } from './resources/ImageDetailsSheet';
import { VolumeBrowserSheet } from './resources/VolumeBrowserSheet';
import { NetworkDetailSheet, type NetworkInspectData } from './resources/NetworkDetailSheet';
import { TabLanding, type TabLandingEntry } from './resources/TabLanding';
const NetworkTopologyView = lazy(() => import('./NetworkTopologyView'));
const RECENT_WINDOW_MS = 24 * 60 * 60 * 1000;
function ageLabel(ms: number): string {
const minutes = Math.max(0, Math.round((Date.now() - ms) / 60000));
if (minutes < 1) return 'just now';
if (minutes < 60) return `${minutes}m ago`;
const hours = Math.round(minutes / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.round(hours / 24);
return `${days}d ago`;
}
// ── Interfaces ─────────────────────────────────────────────────────────────────
interface UsageData {
@@ -627,28 +614,6 @@ export default function ResourcesView() {
setConfirmPrune({ target: 'images', scope: 'all' });
};
const volumeLandings = useMemo(() => {
const largest: TabLandingEntry[] = [...volumes]
.sort((a, b) => b.Size - a.Size)
.slice(0, 5)
.map(vol => ({
key: vol.Name,
primary: vol.Name,
secondary: vol.Size > 0 ? formatBytes(vol.Size) : '-',
}));
const now = Date.now();
const recent: TabLandingEntry[] = volumes
.filter(v => !!v.CreatedAt && now - new Date(v.CreatedAt).getTime() <= RECENT_WINDOW_MS)
.sort((a, b) => new Date(b.CreatedAt ?? 0).getTime() - new Date(a.CreatedAt ?? 0).getTime())
.slice(0, 5)
.map(vol => ({
key: vol.Name,
primary: vol.Name,
secondary: vol.CreatedAt ? ageLabel(new Date(vol.CreatedAt).getTime()) : '-',
}));
return { largest, recent };
}, [volumes]);
return (
<div className="p-6 h-full overflow-auto text-foreground flex flex-col gap-6 animate-in fade-in-0 duration-300">
@@ -896,16 +861,6 @@ export default function ResourcesView() {
{/* Volumes */}
<TabsContent value="volumes" className="m-0 border-0 p-0 animate-in fade-in-0 duration-200">
{!isLoading && volumes.length > 0 && (
<TabLanding
largestSubtitle={`by size · ${volumes.length} total`}
largestEntries={volumeLandings.largest}
largestEmpty="No volumes on disk."
recentSubtitle="last 24h"
recentEntries={volumeLandings.recent}
recentEmpty="No volumes created in the last 24h."
/>
)}
<FilterToggle
value={volumeFilter}
onChange={setVolumeFilter}
@@ -1,109 +0,0 @@
import { useMemo } from 'react';
import { cn } from '@/lib/utils';
export interface TabLandingEntry {
key: string;
primary: string;
secondary: string;
}
interface LandingCardProps {
label: string;
subtitle?: string;
entries: TabLandingEntry[];
emptyLabel: string;
onEntryClick?: (entry: TabLandingEntry) => void;
accent?: 'brand' | 'warning';
}
function LandingCard({ label, subtitle, entries, emptyLabel, onEntryClick, accent = 'brand' }: LandingCardProps) {
const accentClass = accent === 'warning' ? 'text-warning' : 'text-brand';
return (
<div className="flex flex-col rounded-md border border-card-border border-t-card-border-top bg-card shadow-card-bevel">
<div className="flex items-baseline gap-2 px-3 pt-2.5 pb-1.5">
<span className={cn('font-mono text-[10px] uppercase tracking-[0.22em]', accentClass)}>
{label}
</span>
{subtitle ? (
<span className="font-mono text-[10px] text-stat-subtitle">{subtitle}</span>
) : null}
</div>
{entries.length === 0 ? (
<div className="px-3 pb-3 text-[11px] font-mono text-stat-subtitle/80">
{emptyLabel}
</div>
) : (
<ul className="flex flex-col divide-y divide-border/40 px-1 pb-1">
{entries.map(entry => (
<li key={entry.key}>
<button
type="button"
onClick={() => onEntryClick?.(entry)}
disabled={!onEntryClick}
className={cn(
'flex w-full items-center justify-between gap-3 rounded-sm px-2 py-1 text-left transition-colors',
onEntryClick ? 'hover:bg-muted/40 cursor-pointer' : 'cursor-default',
)}
>
<span className="truncate font-mono text-[11px] text-stat-value">{entry.primary}</span>
<span className="shrink-0 font-mono text-[10px] tabular-nums text-stat-subtitle">
{entry.secondary}
</span>
</button>
</li>
))}
</ul>
)}
</div>
);
}
interface TabLandingProps {
largestLabel?: string;
largestSubtitle?: string;
largestEntries: TabLandingEntry[];
largestEmpty: string;
recentLabel?: string;
recentSubtitle?: string;
recentEntries: TabLandingEntry[];
recentEmpty: string;
onLargestClick?: (entry: TabLandingEntry) => void;
onRecentClick?: (entry: TabLandingEntry) => void;
}
export function TabLanding({
largestLabel = 'Largest 5',
largestSubtitle,
largestEntries,
largestEmpty,
recentLabel = 'Recently changed',
recentSubtitle,
recentEntries,
recentEmpty,
onLargestClick,
onRecentClick,
}: TabLandingProps) {
const largestTop = useMemo(() => largestEntries.slice(0, 5), [largestEntries]);
const recentTop = useMemo(() => recentEntries.slice(0, 5), [recentEntries]);
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 p-3 border-b">
<LandingCard
label={largestLabel}
subtitle={largestSubtitle}
entries={largestTop}
emptyLabel={largestEmpty}
onEntryClick={onLargestClick}
accent="brand"
/>
<LandingCard
label={recentLabel}
subtitle={recentSubtitle}
entries={recentTop}
emptyLabel={recentEmpty}
onEntryClick={onRecentClick}
accent="warning"
/>
</div>
);
}