Files
MikrotikManager/components/file-import-dialog.tsx
T
Denozordec a1a9124f3d
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
fix(config): добавить новые зависимости и обновить конфигурацию компонентов
2026-06-30 21:30:40 +07:00

121 lines
3.7 KiB
TypeScript

"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 }