Enhance StoragePanel with improved volume color coding and metric display
ci / build-backend (push) Successful in 2m43s
ci / build-frontend (push) Successful in 1m5s
ci / tests (push) Successful in 2m42s
ci / sonar (push) Successful in 6m12s

Added a color coding system for volume segments in the StoragePanel to visually distinguish between owned, foreign, and free storage. Updated the Metric component to include a swatch for better visual association with the corresponding volume. Enhanced comments for clarity and improved the display logic for free storage, ensuring users can easily identify low space conditions.
This commit is contained in:
Leonid Pershin
2026-07-27 23:54:05 +03:00
parent 16b3c485d7
commit 9f86016a2c
@@ -10,6 +10,14 @@ import { Card, CardContent } from '@/shared/ui/card'
import { getStorageStats } from './api' import { getStorageStats } from './api'
import { formatBytes, percentOf } from './format' import { formatBytes, percentOf } from './format'
/** Доли тома: своё, чужое и свободное. Свободное краснеет у порога — там его и надо заметить. */
const VOLUME_COLORS = {
ours: 'bg-primary',
foreign: 'bg-muted-foreground/50',
free: 'bg-emerald-500/25',
lowFree: 'bg-destructive/50',
}
/** Цвета областей — те же и в полосе, и в списке: полоса без легенды не читается. */ /** Цвета областей — те же и в полосе, и в списке: полоса без легенды не читается. */
const AREA_COLORS: Record<StorageArea, string> = { const AREA_COLORS: Record<StorageArea, string> = {
Assets: 'bg-emerald-500', Assets: 'bg-emerald-500',
@@ -101,18 +109,24 @@ export function StoragePanel() {
{volumeKnown ? ( {volumeKnown ? (
<> <>
{/* Полоса тома: своё, чужое, свободное. */} {/* Полоса тома: своё, чужое, свободное. Свободное — такая же доля, а не пустой
остаток полосы: иначе непонятно, кончился ли диск или просто кончилась заливка. */}
<div className="flex h-4 overflow-hidden rounded-full bg-muted/40"> <div className="flex h-4 overflow-hidden rounded-full bg-muted/40">
<div <div
className="bg-primary" className={VOLUME_COLORS.ours}
style={{ width: `${percentOf(storage, total)}%` }} style={{ width: `${percentOf(storage, total)}%` }}
title={`${t('admin.storage.ours')} · ${formatBytes(storage)}`} title={`${t('admin.storage.ours')} · ${formatBytes(storage)}`}
/> />
<div <div
className="bg-muted-foreground/50" className={VOLUME_COLORS.foreign}
style={{ width: `${percentOf(foreign, total)}%` }} style={{ width: `${percentOf(foreign, total)}%` }}
title={`${t('admin.storage.foreign')} · ${formatBytes(foreign)}`} title={`${t('admin.storage.foreign')} · ${formatBytes(foreign)}`}
/> />
<div
className={lowSpace ? VOLUME_COLORS.lowFree : VOLUME_COLORS.free}
style={{ width: `${percentOf(free, total)}%` }}
title={`${t('admin.storage.free')} · ${formatBytes(free)}`}
/>
</div> </div>
<div className="grid gap-3 text-sm sm:grid-cols-4"> <div className="grid gap-3 text-sm sm:grid-cols-4">
@@ -122,17 +136,20 @@ export function StoragePanel() {
value={formatBytes(storage)} value={formatBytes(storage)}
hint={`${percentOf(storage, total).toFixed(1)}%`} hint={`${percentOf(storage, total).toFixed(1)}%`}
accent="text-primary" accent="text-primary"
swatch={VOLUME_COLORS.ours}
/> />
<Metric <Metric
label={t('admin.storage.foreign')} label={t('admin.storage.foreign')}
value={formatBytes(foreign)} value={formatBytes(foreign)}
hint={t('admin.storage.foreignHint')} hint={t('admin.storage.foreignHint')}
swatch={VOLUME_COLORS.foreign}
/> />
<Metric <Metric
label={t('admin.storage.free')} label={t('admin.storage.free')}
value={formatBytes(free)} value={formatBytes(free)}
hint={`${percentOf(free, total).toFixed(1)}%`} hint={`${percentOf(free, total).toFixed(1)}%`}
accent={lowSpace ? 'text-destructive' : 'text-emerald-500'} accent={lowSpace ? 'text-destructive' : 'text-emerald-500'}
swatch={lowSpace ? VOLUME_COLORS.lowFree : VOLUME_COLORS.free}
/> />
</div> </div>
</> </>
@@ -196,10 +213,21 @@ function Metric({
value, value,
hint, hint,
accent, accent,
}: Readonly<{ label: string; value: string; hint?: string; accent?: string }>) { swatch,
}: Readonly<{
label: string
value: string
hint?: string
accent?: string
/** Цвет доли на полосе тома: без него подпись и сегмент не связать глазами. */
swatch?: string
}>) {
return ( return (
<div className="flex flex-col"> <div className="flex flex-col">
<span className="text-xs uppercase tracking-wide text-muted-foreground">{label}</span> <span className="flex items-center gap-1.5 text-xs uppercase tracking-wide text-muted-foreground">
{swatch && <span className={cn('h-2.5 w-2.5 shrink-0 rounded-sm', swatch)} />}
{label}
</span>
<span className={cn('font-mono text-lg tabular-nums', accent)}>{value}</span> <span className={cn('font-mono text-lg tabular-nums', accent)}>{value}</span>
{hint && <span className="text-xs text-muted-foreground">{hint}</span>} {hint && <span className="text-xs text-muted-foreground">{hint}</span>}
</div> </div>