fix(config): добавить новые зависимости и обновить конфигурацию компонентов
Docker images / prepare-release (push) Successful in 5s
Docker images / backend-image (push) Successful in 2m37s
Docker images / frontend-image (push) Successful in 2m22s
Docker images / updater-image (push) Successful in 38s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 8s
Docker images / prepare-release (push) Successful in 5s
Docker images / backend-image (push) Successful in 2m37s
Docker images / frontend-image (push) Successful in 2m22s
Docker images / updater-image (push) Successful in 38s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 8s
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
"use client"
|
||||
|
||||
import { useCallback, useState } from "react"
|
||||
import { UploadIcon } from "lucide-react"
|
||||
import { toast } from "sonner"
|
||||
import { useFileUpload } from "@/hooks/use-file-upload"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
interface FileImportDialogProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
title: string
|
||||
description?: string
|
||||
accept?: string
|
||||
multiple?: boolean
|
||||
onImport: (files: File[]) => void | Promise<void>
|
||||
}
|
||||
|
||||
function FileImportDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
title,
|
||||
description,
|
||||
accept = "*",
|
||||
multiple = false,
|
||||
onImport,
|
||||
}: FileImportDialogProps) {
|
||||
const [importing, setImporting] = useState(false)
|
||||
|
||||
const [{ files, isDragging, errors }, actions] = useFileUpload({
|
||||
accept,
|
||||
multiple,
|
||||
maxFiles: multiple ? 10 : 1,
|
||||
onError: (errs) => {
|
||||
errs.forEach((e) => toast.error(e))
|
||||
},
|
||||
})
|
||||
|
||||
const handleImport = useCallback(async () => {
|
||||
const rawFiles = files
|
||||
.map((f) => (f.file instanceof File ? f.file : null))
|
||||
.filter((f): f is File => f != null)
|
||||
if (rawFiles.length === 0) {
|
||||
toast.error("Выберите файл для импорта")
|
||||
return
|
||||
}
|
||||
setImporting(true)
|
||||
try {
|
||||
await onImport(rawFiles)
|
||||
actions.clearFiles()
|
||||
onOpenChange(false)
|
||||
toast.success("Импорт выполнен")
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : "Ошибка импорта")
|
||||
} finally {
|
||||
setImporting(false)
|
||||
}
|
||||
}, [actions, files, onImport, onOpenChange])
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
{description && <DialogDescription>{description}</DialogDescription>}
|
||||
</DialogHeader>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col items-center justify-center gap-3 rounded-lg border border-dashed p-8 text-center transition-colors",
|
||||
isDragging ? "border-primary bg-primary/5" : "border-border",
|
||||
)}
|
||||
onDragEnter={actions.handleDragEnter}
|
||||
onDragLeave={actions.handleDragLeave}
|
||||
onDragOver={actions.handleDragOver}
|
||||
onDrop={actions.handleDrop}
|
||||
>
|
||||
<UploadIcon className="size-8 text-muted-foreground/40" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Перетащите файл сюда или выберите на диске
|
||||
</p>
|
||||
<Button type="button" variant="outline" size="sm" onClick={actions.openFileDialog}>
|
||||
Выбрать файл
|
||||
</Button>
|
||||
<input {...actions.getInputProps()} className="sr-only" />
|
||||
{files.length > 0 && (
|
||||
<ul className="w-full text-left text-sm">
|
||||
{files.map((f) => (
|
||||
<li key={f.id} className="truncate font-mono text-xs">
|
||||
{f.file instanceof File ? f.file.name : f.file.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{errors.map((e) => (
|
||||
<p key={e} className="text-xs text-destructive">{e}</p>
|
||||
))}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button type="button" onClick={handleImport} disabled={importing || files.length === 0}>
|
||||
{importing ? "Импорт…" : "Импортировать"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export { FileImportDialog, type FileImportDialogProps }
|
||||
Reference in New Issue
Block a user