diff --git a/frontend/src/features/admin/storage/StoragePanel.tsx b/frontend/src/features/admin/storage/StoragePanel.tsx index d56003a..8872c84 100644 --- a/frontend/src/features/admin/storage/StoragePanel.tsx +++ b/frontend/src/features/admin/storage/StoragePanel.tsx @@ -7,6 +7,7 @@ import { qk } from '@/shared/api/query-keys' import { cn } from '@/shared/lib/cn' import { Button } from '@/shared/ui/button' import { Card, CardContent } from '@/shared/ui/card' +import { StoragePanelSkeleton } from './StoragePanelSkeleton' import { getStorageStats } from './api' import { formatBytes, percentOf } from './format' @@ -68,7 +69,7 @@ export function StoragePanel() { } } - if (isLoading || !data) return

{t('common.loading')}

+ if (isLoading || !data) return const { volumeTotalBytes: total, volumeFreeBytes: free, storageBytes: storage } = data const volumeKnown = total > 0 diff --git a/frontend/src/features/admin/storage/StoragePanelSkeleton.tsx b/frontend/src/features/admin/storage/StoragePanelSkeleton.tsx new file mode 100644 index 0000000..6193aed --- /dev/null +++ b/frontend/src/features/admin/storage/StoragePanelSkeleton.tsx @@ -0,0 +1,71 @@ +import { useTranslation } from 'react-i18next' +import { Card, CardContent } from '@/shared/ui/card' +import { Skeleton } from '@/shared/ui/skeleton' + +/** Строк в составе хранилища заранее не знаем — берём типичное число, чтобы не пустовала карточка. */ +const AREA_ROWS = 5 + +/** + * Скелетон панели хранилища: подсчёт размеров идёт по диску и занимает секунды. Повторяет вёрстку + * {@link StoragePanel} блок в блок, чтобы после загрузки ничего не переехало. + */ +export function StoragePanelSkeleton() { + const { t } = useTranslation() + + return ( +
+ + +
+
+ + +
+
+ + +
+
+ + + +
+ {Array.from({ length: 4 }, (_, i) => ( +
+ + + +
+ ))} +
+
+
+ + + +
+ + +
+ + + +
+ {Array.from({ length: AREA_ROWS }, (_, i) => ( +
+ +
+ + +
+ + + +
+ ))} +
+
+
+
+ ) +} diff --git a/frontend/src/shared/ui/skeleton.tsx b/frontend/src/shared/ui/skeleton.tsx new file mode 100644 index 0000000..63584b4 --- /dev/null +++ b/frontend/src/shared/ui/skeleton.tsx @@ -0,0 +1,19 @@ +import { type HTMLAttributes, forwardRef } from 'react' +import { cn } from '@/shared/lib/cn' + +/** + * Заглушка блока на время загрузки. Только форма — размеры задаёт вызывающий код, чтобы скелетон + * повторял реальную вёрстку и страница не прыгала при появлении данных. + */ +export const Skeleton = forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +) +Skeleton.displayName = 'Skeleton'