808c52484c
Replace the "coming soon" stub with a real dashboard wired to `GET /storage`. modern-sk visuals: a layered disk-capacity gauge (library share vs other-used vs free), stat tiles (tracks/artists/albums/playtime/ footprint/avg size), per-format size bars, metadata-health badges, source breakdown, a popularity-weighted top-genres cloud, and playful fun facts. - types: full `StorageStats` shape + `toStorageStats` snake→camel mapper - endpoint: re-point `getStorageStats` to `GET /storage` with transform - lib: `formatLongDuration` for big playtime spans - i18n: `storage.*` keys (en + ru) - three list states (loading / error / empty) per the UI invariant Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
48 lines
1.7 KiB
TypeScript
48 lines
1.7 KiB
TypeScript
export function formatDuration(ms: number): string {
|
|
const totalSec = Math.floor(ms / 1000);
|
|
const h = Math.floor(totalSec / 3600);
|
|
const m = Math.floor((totalSec % 3600) / 60);
|
|
const s = totalSec % 60;
|
|
if (h > 0)
|
|
return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
|
|
return `${m}:${String(s).padStart(2, '0')}`;
|
|
}
|
|
|
|
export function formatFileSize(bytes: number): string {
|
|
if (bytes < 1024) return `${bytes} B`;
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
if (bytes < 1024 * 1024 * 1024)
|
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
}
|
|
|
|
export function formatDateTime(iso: string | undefined): string | undefined {
|
|
if (!iso) return undefined;
|
|
const d = new Date(iso);
|
|
if (Number.isNaN(d.getTime())) return undefined;
|
|
return new Intl.DateTimeFormat(undefined, {
|
|
dateStyle: 'medium',
|
|
timeStyle: 'short',
|
|
}).format(d);
|
|
}
|
|
|
|
/** Human "X days Y hours" style for big spans (e.g. total library playtime).
|
|
* Shows the two most-significant non-zero units; falls back to "0m". */
|
|
export function formatLongDuration(seconds: number): string {
|
|
if (seconds <= 0) return '0m';
|
|
const days = Math.floor(seconds / 86400);
|
|
const hours = Math.floor((seconds % 86400) / 3600);
|
|
const mins = Math.floor((seconds % 3600) / 60);
|
|
const parts: string[] = [];
|
|
if (days) parts.push(`${days}d`);
|
|
if (hours) parts.push(`${hours}h`);
|
|
if (mins && parts.length < 2) parts.push(`${mins}m`);
|
|
return parts.slice(0, 2).join(' ') || '0m';
|
|
}
|
|
|
|
export function formatCount(n: number): string {
|
|
if (n < 1000) return String(n);
|
|
if (n < 1_000_000) return `${(n / 1000).toFixed(1)}K`;
|
|
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
}
|