import type { components } from '@pipu/api-client'; import { unwrap } from '@pipu/core'; import { useQuery } from '@tanstack/react-query'; import { useId } from 'react'; import { useTranslation } from 'react-i18next'; import { useSession } from '../../app/session'; import { formatBytes } from '../../shared/lib/format'; import { cx } from '../../shared/ui/ui'; import s from '../page.module.css'; import u from './uploads.module.css'; export type UploadStatus = components['schemas']['UploadStatus']; export type UploadFile = components['schemas']['UploadFile']; const TONE: Record = { uploading: 'info', processing: 'info', pending_review: 'warn', approved: 'info', importing: 'info', imported: 'ok', import_failed: 'bad', duplicate: 'muted', invalid: 'bad', rejected: 'bad', withdrawn: 'muted', expired: 'muted', }; /** Статусы, которые ещё меняются сами (обработка, импорт) — список опрашивается. */ export const MOVING: UploadStatus[] = ['uploading', 'processing', 'approved', 'importing']; export function UploadStatusBadge({ status }: { status: UploadStatus }) { const { t } = useTranslation('admin'); return {t(`uploads.status.${status}`)}; } export function useUploadQuota() { const session = useSession(); return useQuery({ queryKey: ['upload-quota'], queryFn: async () => unwrap(await session.client.GET('/api/v1/uploads/quota')), }); } /** «Очередь: 6,2 ГБ из 10 ГБ» — видят модераторы и админ (docs/uploads.md#3). */ export function QuotaMeter() { const { t, i18n } = useTranslation('admin'); const quota = useUploadQuota(); const id = useId(); if (!quota.data) return null; const q = quota.data; const ratio = q.limit_bytes > 0 ? Math.min(q.used_bytes / q.limit_bytes, 1) : 1; const fmt = (n: number) => formatBytes(n, i18n.language); return (
{t('uploads.quota', { used: fmt(q.used_bytes), limit: fmt(q.limit_bytes) })} {q.per_user_limit_bytes !== null && ` · ${t('uploads.quota_mine', { used: fmt(q.user_used_bytes), limit: fmt(q.per_user_limit_bytes) })}`}
); } /** Понятный текст причины: код из errors: или текст отклонения админа. */ export function useReasonText(): (reason: string | null) => string | null { const { t, i18n } = useTranslation(['admin', 'errors']); const tr = t as unknown as (k: string) => string; return (reason) => { if (!reason) return null; const code = reason.split(':')[0]!.trim(); const key = `errors:${code}`; if (i18n.exists(key)) { const rest = reason.slice(code.length + 1).trim(); return rest ? `${tr(key)}: ${rest}` : tr(key); } return reason; }; }