Refactor media upload handling in MediaPanel: remove direct upload logic and integrate useUploadStore for file enqueueing. Update translations for upload status and add UploadSnackbar component to display upload notifications in the UI.

This commit is contained in:
Leonid Pershin
2026-07-24 22:25:01 +03:00
parent 2523808e3b
commit 8f8ce5122a
7 changed files with 188 additions and 53 deletions
@@ -0,0 +1,72 @@
import { useTranslation } from 'react-i18next'
import { AlertCircle, Check, ChevronDown, ChevronUp, Clock, Loader2, X } from 'lucide-react'
import { cn } from '@/shared/lib/cn'
import { type UploadItem, useUploadStore } from './upload-store'
function StatusIcon({ status }: { status: UploadItem['status'] }) {
switch (status) {
case 'done':
return <Check className="h-3.5 w-3.5 text-primary" />
case 'error':
return <AlertCircle className="h-3.5 w-3.5 text-red-500" />
case 'uploading':
return <Loader2 className="h-3.5 w-3.5 animate-spin text-muted-foreground" />
default:
return <Clock className="h-3.5 w-3.5 text-muted-foreground" />
}
}
/** Глобальный индикатор загрузок: живёт вне страниц, поэтому загрузка идёт при любой навигации. */
export function UploadSnackbar() {
const { t } = useTranslation()
const { items, active, minimized, toggleMinimize, dismiss } = useUploadStore()
if (items.length === 0) return null
const done = items.filter((i) => i.status === 'done').length
const header = active
? t('admin.media.uploadingCount', { done, total: items.length })
: t('admin.media.uploadedCount', { count: done })
return (
<div className="crt-panel fixed bottom-4 right-4 z-50 w-80 max-w-[calc(100vw-2rem)] rounded-md shadow-lg">
<div className="flex items-center gap-2 border-b border-border px-3 py-2 text-sm">
<span className="truncate font-medium">{header}</span>
<button type="button" className="ml-auto opacity-70 hover:opacity-100" onClick={toggleMinimize}>
{minimized ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</button>
{!active && (
<button type="button" className="opacity-70 hover:opacity-100" onClick={dismiss}>
<X className="h-4 w-4" />
</button>
)}
</div>
{!minimized && (
<ul className="max-h-64 divide-y divide-border overflow-y-auto">
{items.map((item) => (
<li key={item.id} className="flex flex-col gap-1 px-3 py-2 text-xs">
<div className="flex items-center gap-2">
<StatusIcon status={item.status} />
<span className="truncate" title={item.name}>
{item.name}
</span>
{item.status === 'uploading' && (
<span className="ml-auto shrink-0 text-muted-foreground">{item.percent}%</span>
)}
</div>
{item.status === 'uploading' && (
<div className="h-1 w-full overflow-hidden rounded bg-muted">
<div
className={cn('h-full rounded bg-primary transition-[width]')}
style={{ width: `${item.percent}%` }}
/>
</div>
)}
</li>
))}
</ul>
)}
</div>
)
}