diff --git a/frontend/src/features/admin/media/UploadSnackbar.tsx b/frontend/src/features/admin/media/UploadSnackbar.tsx
index a97a910..d3868c8 100644
--- a/frontend/src/features/admin/media/UploadSnackbar.tsx
+++ b/frontend/src/features/admin/media/UploadSnackbar.tsx
@@ -6,46 +6,71 @@ import { type UploadItem, useUploadStore } from './upload-store'
function StatusIcon({ status }: { status: UploadItem['status'] }) {
switch (status) {
case 'done':
- return
+ return
case 'error':
- return
+ return
case 'uploading':
- return
+ return
default:
- return
+ return
}
}
+function barWidth(item: UploadItem): number {
+ if (item.status === 'done' || item.status === 'error') return 100
+ if (item.status === 'uploading') return item.percent
+ return 0
+}
+
/** Глобальный индикатор загрузок: живёт вне страниц, поэтому загрузка идёт при любой навигации. */
export function UploadSnackbar() {
const { t } = useTranslation()
- const { items, active, minimized, toggleMinimize, dismiss } = useUploadStore()
+ const { items, active, minimized, skipped, toggleMinimize, dismiss } = useUploadStore()
- if (items.length === 0) return null
+ if (items.length === 0 && skipped === 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 })
+ const hasItems = items.length > 0
+ const header = hasItems
+ ? active
+ ? t('admin.media.uploadingCount', { done, total: items.length })
+ : t('admin.media.uploadedCount', { count: done })
+ : t('admin.media.skippedDuplicates', { count: skipped })
return (
-
+
{header}
-
+ {hasItems && (
+
+ )}
{!active && (
-
- {!minimized && (
-
+ {hasItems && skipped > 0 && (
+
+ {t('admin.media.skippedDuplicates', { count: skipped })}
+
+ )}
+
+ {hasItems && !minimized && (
+
diff --git a/frontend/src/features/admin/media/upload-store.ts b/frontend/src/features/admin/media/upload-store.ts
index 518e31f..ffbbfb1 100644
--- a/frontend/src/features/admin/media/upload-store.ts
+++ b/frontend/src/features/admin/media/upload-store.ts
@@ -1,7 +1,5 @@
import { create } from 'zustand'
import { queryClient } from '@/shared/api/query-client'
-import i18n from '@/shared/lib/i18n'
-import { toast } from '@/shared/ui/toast-store'
import { listMedia, uploadMedia } from './api'
export type UploadItem = {
@@ -15,6 +13,7 @@ type UploadStore = {
items: UploadItem[]
active: boolean
minimized: boolean
+ skipped: number
enqueue: (files: File[]) => Promise
toggleMinimize: () => void
dismiss: () => void
@@ -35,14 +34,12 @@ async function pump() {
running = true
useUploadStore.setState({ active: true, minimized: false })
- let uploaded = 0
while (queue.length > 0) {
const job = queue.shift()!
patch(job.id, { status: 'uploading', percent: 0 })
try {
await uploadMedia(job.file, (percent) => patch(job.id, { percent }))
patch(job.id, { status: 'done', percent: 100 })
- uploaded++
void queryClient.invalidateQueries({ queryKey: ['admin', 'media'] })
} catch {
patch(job.id, { status: 'error' })
@@ -51,13 +48,13 @@ async function pump() {
running = false
useUploadStore.setState({ active: false })
- if (uploaded > 0) toast.success(i18n.t('admin.media.uploadedCount', { count: uploaded }))
}
export const useUploadStore = create((set) => ({
items: [],
active: false,
minimized: false,
+ skipped: 0,
enqueue: async (files) => {
// Пропускаем дубликаты: уже в библиотеке (не проваленные) и уже в текущей очереди.
@@ -82,18 +79,20 @@ export const useUploadStore = create((set) => ({
const toAdd = files.filter((f) => !existing.has(f.name) && !inFlight.has(f.name))
const skipped = files.length - toAdd.length
- if (toAdd.length > 0) {
- const newItems: UploadItem[] = toAdd.map((file) => {
- const id = `u${++counter}`
- queue.push({ id, file })
- return { id, name: file.name, percent: 0, status: 'queued' }
- })
- set((s) => ({ items: [...s.items, ...newItems], minimized: false }))
- void pump()
- }
- if (skipped > 0) toast.message(i18n.t('admin.media.skippedDuplicates', { count: skipped }))
+ const newItems: UploadItem[] = toAdd.map((file) => {
+ const id = `u${++counter}`
+ queue.push({ id, file })
+ return { id, name: file.name, percent: 0, status: 'queued' }
+ })
+
+ set((s) => ({
+ items: newItems.length ? [...s.items, ...newItems] : s.items,
+ skipped: s.skipped + skipped,
+ minimized: newItems.length ? false : s.minimized,
+ }))
+ if (newItems.length) void pump()
},
toggleMinimize: () => set((s) => ({ minimized: !s.minimized })),
- dismiss: () => set({ items: [] }),
+ dismiss: () => set({ items: [], skipped: 0 }),
}))