forked from VinokurovVE/tests
343 lines
12 KiB
TypeScript
343 lines
12 KiB
TypeScript
import { useDocuments, useDownload, useFolders } from '../hooks/swrHooks'
|
||
import { IDocument, IDocumentFolder } from '../interfaces/documents'
|
||
import { Box, Breadcrumbs, Button, CircularProgress, Divider, IconButton, Link, List, ListItemButton, SxProps } from '@mui/material'
|
||
import { Cancel, Close, Download, Folder, InsertDriveFile, Upload, UploadFile } from '@mui/icons-material'
|
||
import React, { useEffect, useRef, useState } from 'react'
|
||
import DocumentService from '../services/DocumentService'
|
||
import { mutate } from 'swr'
|
||
import FileViewer from './modals/FileViewer'
|
||
|
||
interface FolderProps {
|
||
folder: IDocumentFolder;
|
||
index: number;
|
||
handleFolderClick: (folder: IDocumentFolder) => void;
|
||
}
|
||
|
||
interface DocumentProps {
|
||
doc: IDocument;
|
||
index: number;
|
||
handleDocumentClick: (index: number) => void;
|
||
}
|
||
|
||
const FileItemStyle: SxProps = {
|
||
cursor: 'pointer',
|
||
display: 'flex',
|
||
width: '100%',
|
||
flexDirection: 'row',
|
||
gap: '8px',
|
||
alignItems: 'center',
|
||
padding: '8px'
|
||
}
|
||
|
||
function ItemFolder({ folder, handleFolderClick, ...props }: FolderProps) {
|
||
return (
|
||
<ListItemButton
|
||
onClick={() => handleFolderClick(folder)}
|
||
>
|
||
<Box
|
||
sx={FileItemStyle}
|
||
{...props}
|
||
>
|
||
<Folder />
|
||
{folder.name}
|
||
</Box>
|
||
</ListItemButton>
|
||
)
|
||
}
|
||
|
||
const handleSave = async (file: Blob, filename: string) => {
|
||
const link = document.createElement('a')
|
||
link.href = window.URL.createObjectURL(file)
|
||
link.download = filename
|
||
link.click()
|
||
link.remove()
|
||
window.URL.revokeObjectURL(link.href)
|
||
}
|
||
|
||
function ItemDocument({ doc, index, handleDocumentClick, ...props }: DocumentProps) {
|
||
const [shouldFetch, setShouldFetch] = useState(false)
|
||
|
||
const { file, isLoading } = useDownload(shouldFetch ? doc?.document_folder_id : null, shouldFetch ? doc?.id : null)
|
||
|
||
useEffect(() => {
|
||
if (shouldFetch) {
|
||
if (file) {
|
||
handleSave(file, doc.name)
|
||
setShouldFetch(false)
|
||
}
|
||
}
|
||
}, [shouldFetch, file])
|
||
|
||
return (
|
||
<ListItemButton>
|
||
<Box
|
||
sx={FileItemStyle}
|
||
onClick={() => handleDocumentClick(index)}
|
||
{...props}
|
||
>
|
||
<InsertDriveFile />
|
||
{doc.name}
|
||
</Box>
|
||
<Box>
|
||
<IconButton
|
||
onClick={() => {
|
||
if (!isLoading) {
|
||
setShouldFetch(true)
|
||
}
|
||
}}
|
||
sx={{ ml: 'auto' }}
|
||
>
|
||
{isLoading ?
|
||
<CircularProgress size={24} variant='indeterminate' />
|
||
:
|
||
<Download />
|
||
}
|
||
</IconButton>
|
||
</Box>
|
||
</ListItemButton>
|
||
)
|
||
}
|
||
|
||
export default function FolderViewer() {
|
||
const [currentFolder, setCurrentFolder] = useState<IDocumentFolder | null>(null)
|
||
const [breadcrumbs, setBreadcrumbs] = useState<IDocumentFolder[]>([])
|
||
const { folders, isLoading: foldersLoading } = useFolders()
|
||
const { documents, isLoading: documentsLoading } = useDocuments(currentFolder?.id)
|
||
const [uploadProgress, setUploadProgress] = useState(0)
|
||
const [isUploading, setIsUploading] = useState(false)
|
||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||
const [fileViewerModal, setFileViewerModal] = useState(false)
|
||
const [currentFileNo, setCurrentFileNo] = useState<number>(-1)
|
||
|
||
const [dragOver, setDragOver] = useState(false)
|
||
const [filesToUpload, setFilesToUpload] = useState<File[]>([])
|
||
|
||
const handleFolderClick = (folder: IDocumentFolder) => {
|
||
setCurrentFolder(folder)
|
||
setBreadcrumbs((prev) => [...prev, folder])
|
||
}
|
||
|
||
const handleDocumentClick = async (index: number) => {
|
||
setCurrentFileNo(index)
|
||
setFileViewerModal(true)
|
||
}
|
||
|
||
const handleBreadcrumbClick = (index: number) => {
|
||
const newBreadcrumbs = breadcrumbs.slice(0, index + 1);
|
||
setBreadcrumbs(newBreadcrumbs)
|
||
setCurrentFolder(newBreadcrumbs[newBreadcrumbs.length - 1])
|
||
}
|
||
|
||
const handleUploadClick = () => {
|
||
if (fileInputRef.current) {
|
||
fileInputRef.current.click()
|
||
}
|
||
}
|
||
|
||
const handleDragOver = (e: React.DragEvent) => {
|
||
e.preventDefault()
|
||
setDragOver(true)
|
||
}
|
||
|
||
const handleDragLeave = () => {
|
||
setDragOver(false)
|
||
}
|
||
|
||
const handleDrop = (e: React.DragEvent) => {
|
||
e.preventDefault()
|
||
setDragOver(false)
|
||
const files = Array.from(e.dataTransfer.files)
|
||
setFilesToUpload((prevFiles) => [...prevFiles, ...files])
|
||
}
|
||
|
||
const handleFileInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const files = Array.from(e.target.files || [])
|
||
setFilesToUpload((prevFiles) => [...prevFiles, ...files])
|
||
}
|
||
|
||
const uploadFiles = async () => {
|
||
setIsUploading(true)
|
||
if (filesToUpload.length > 0 && currentFolder && currentFolder.id) {
|
||
const formData = new FormData()
|
||
for (const file of filesToUpload) {
|
||
formData.append('files', file)
|
||
}
|
||
try {
|
||
await DocumentService.uploadFiles(currentFolder.id, formData, setUploadProgress);
|
||
setIsUploading(false);
|
||
setFilesToUpload([]);
|
||
mutate(`/info/documents/${currentFolder.id}`);
|
||
} catch (error) {
|
||
console.error(error);
|
||
setIsUploading(false);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (foldersLoading || documentsLoading) {
|
||
return (
|
||
<CircularProgress />
|
||
)
|
||
}
|
||
|
||
return (
|
||
<Box sx={{
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
gap: '16px'
|
||
}}>
|
||
<FileViewer
|
||
open={fileViewerModal}
|
||
setOpen={setFileViewerModal}
|
||
currentFileNo={currentFileNo}
|
||
setCurrentFileNo={setCurrentFileNo}
|
||
docs={documents}
|
||
/>
|
||
|
||
<Breadcrumbs>
|
||
<Link
|
||
underline='hover'
|
||
color='inherit'
|
||
onClick={() => {
|
||
setCurrentFolder(null)
|
||
setBreadcrumbs([])
|
||
}}
|
||
sx={{ cursor: 'pointer' }}
|
||
>
|
||
Главная
|
||
</Link>
|
||
|
||
{breadcrumbs.map((breadcrumb, index) => (
|
||
<Link
|
||
key={breadcrumb.id}
|
||
underline="hover"
|
||
color="inherit"
|
||
onClick={() => handleBreadcrumbClick(index)}
|
||
sx={{ cursor: 'pointer' }}
|
||
>
|
||
{breadcrumb.name}
|
||
</Link>
|
||
))}
|
||
</Breadcrumbs>
|
||
|
||
{currentFolder &&
|
||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
||
<Box sx={{
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
gap: '16px',
|
||
border: filesToUpload.length > 0 ? '1px dashed gray' : 'none',
|
||
borderRadius: '8px',
|
||
p: '16px'
|
||
}}>
|
||
<Box sx={{ display: 'flex', gap: '16px' }}>
|
||
<Button
|
||
LinkComponent="label"
|
||
role={undefined}
|
||
variant="outlined"
|
||
tabIndex={-1}
|
||
startIcon={
|
||
isUploading ? <CircularProgress sx={{ maxHeight: "20px", maxWidth: "20px" }} variant="determinate" value={uploadProgress} /> : <UploadFile />
|
||
}
|
||
onClick={handleUploadClick}
|
||
>
|
||
<input
|
||
type='file'
|
||
ref={fileInputRef}
|
||
style={{ display: 'none' }}
|
||
onChange={handleFileInput}
|
||
onClick={(e) => {
|
||
if (e.currentTarget) {
|
||
e.currentTarget.value = ''
|
||
}
|
||
}}
|
||
/>
|
||
Добавить
|
||
</Button>
|
||
|
||
{filesToUpload.length > 0 &&
|
||
<>
|
||
<Button
|
||
variant="contained"
|
||
color="primary"
|
||
startIcon={<Upload />}
|
||
onClick={uploadFiles}
|
||
>
|
||
Загрузить все
|
||
</Button>
|
||
|
||
<Button
|
||
variant='outlined'
|
||
startIcon={<Cancel />}
|
||
onClick={() => {
|
||
setFilesToUpload([])
|
||
}}
|
||
>
|
||
Отмена
|
||
</Button>
|
||
</>
|
||
}
|
||
</Box>
|
||
|
||
<Divider />
|
||
|
||
{filesToUpload.length > 0 &&
|
||
<Box>
|
||
{filesToUpload.map((file, index) => (
|
||
<Box key={index} sx={{ display: 'flex', alignItems: 'center', gap: '8px', marginTop: '8px' }}>
|
||
<Box>
|
||
<InsertDriveFile />
|
||
<span>{file.name}</span>
|
||
</Box>
|
||
|
||
<IconButton sx={{ ml: 'auto' }} onClick={() => {
|
||
setFilesToUpload(prev => {
|
||
return prev.filter((_, i) => i != index)
|
||
})
|
||
}}>
|
||
<Close />
|
||
</IconButton>
|
||
</Box>
|
||
))}
|
||
</Box>
|
||
}
|
||
</Box>
|
||
</Box>
|
||
}
|
||
|
||
<List
|
||
dense
|
||
onDragOver={handleDragOver}
|
||
onDragLeave={handleDragLeave}
|
||
onDrop={handleDrop}
|
||
sx={{
|
||
backgroundColor: dragOver ? 'rgba(0, 0, 0, 0.1)' : 'inherit'
|
||
}}
|
||
>
|
||
{currentFolder ? (
|
||
documents?.map((doc: IDocument, index: number) => (
|
||
<div key={`${doc.id}-${doc.name}`}>
|
||
<ItemDocument
|
||
doc={doc}
|
||
index={index}
|
||
handleDocumentClick={handleDocumentClick}
|
||
/>
|
||
{index < documents.length - 1 && <Divider />}
|
||
</div>
|
||
))
|
||
) : (
|
||
folders?.map((folder: IDocumentFolder, index: number) => (
|
||
<div key={`${folder.id}-${folder.name}`}>
|
||
<ItemFolder
|
||
folder={folder}
|
||
index={index}
|
||
handleFolderClick={handleFolderClick}
|
||
/>
|
||
{index < folders.length - 1 && <Divider />}
|
||
</div>
|
||
))
|
||
)}
|
||
</List>
|
||
</Box>
|
||
)
|
||
} |