forked from VinokurovVE/tests
318 lines
11 KiB
TypeScript
318 lines
11 KiB
TypeScript
import { useDocuments, useDownload, useFolders } from '../hooks/swrHooks'
|
||
import { IDocument, IDocumentFolder } from '../interfaces/documents'
|
||
import { Box, CircularProgress, Divider, SxProps } from '@mui/material'
|
||
import { Folder, InsertDriveFile } from '@mui/icons-material'
|
||
import React, { useEffect, useState } from 'react'
|
||
import DocumentService from '../services/DocumentService'
|
||
import { mutate } from 'swr'
|
||
import FileViewer from './modals/FileViewer'
|
||
import { ActionIcon, Anchor, Breadcrumbs, Button, FileButton, Flex, Loader, RingProgress, Table, Text } from '@mantine/core'
|
||
import { IconCancel, IconDownload, IconFile, IconFilePlus, IconFileUpload, IconX } from '@tabler/icons-react'
|
||
|
||
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 (
|
||
<Flex
|
||
onClick={() => handleFolderClick(folder)}
|
||
>
|
||
<Box
|
||
sx={FileItemStyle}
|
||
{...props}
|
||
>
|
||
<Folder />
|
||
{folder.name}
|
||
</Box>
|
||
</Flex>
|
||
)
|
||
}
|
||
|
||
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 (
|
||
<Flex align='center'>
|
||
<Box
|
||
sx={FileItemStyle}
|
||
onClick={() => handleDocumentClick(index)}
|
||
{...props}
|
||
>
|
||
<InsertDriveFile />
|
||
{doc.name}
|
||
</Box>
|
||
<Box>
|
||
<ActionIcon
|
||
onClick={() => {
|
||
if (!isLoading) {
|
||
setShouldFetch(true)
|
||
}
|
||
}}
|
||
variant='subtle'>
|
||
{isLoading ?
|
||
<Loader size='sm' />
|
||
:
|
||
<IconDownload />
|
||
}
|
||
</ActionIcon>
|
||
</Box>
|
||
</Flex>
|
||
)
|
||
}
|
||
|
||
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 [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 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 = (files: File[] | null) => {
|
||
if (files !== null) {
|
||
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',
|
||
p: '16px'
|
||
}}>
|
||
<FileViewer
|
||
open={fileViewerModal}
|
||
setOpen={setFileViewerModal}
|
||
currentFileNo={currentFileNo}
|
||
setCurrentFileNo={setCurrentFileNo}
|
||
docs={documents}
|
||
/>
|
||
|
||
<Breadcrumbs>
|
||
<Anchor
|
||
onClick={() => {
|
||
setCurrentFolder(null)
|
||
setBreadcrumbs([])
|
||
}}
|
||
>
|
||
Главная
|
||
</Anchor>
|
||
{breadcrumbs.map((breadcrumb, index) => (
|
||
<Anchor
|
||
key={breadcrumb.id}
|
||
onClick={() => handleBreadcrumbClick(index)}
|
||
>
|
||
{breadcrumb.name}
|
||
</Anchor>
|
||
))}
|
||
</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' }}>
|
||
<FileButton multiple onChange={handleFileInput}>
|
||
{(props) => <Button variant='filled' leftSection={isUploading ? <Loader /> : <IconFilePlus />} {...props}>Добавить</Button>}
|
||
</FileButton>
|
||
|
||
{filesToUpload.length > 0 &&
|
||
<>
|
||
<Button
|
||
variant='filled'
|
||
leftSection={isUploading ? <RingProgress sections={[{ value: uploadProgress, color: 'blue' }]} /> : <IconFileUpload />}
|
||
onClick={uploadFiles}
|
||
>
|
||
Загрузить все
|
||
</Button>
|
||
|
||
<Button
|
||
variant='outline'
|
||
leftSection={<IconCancel />}
|
||
onClick={() => {
|
||
setFilesToUpload([])
|
||
}}
|
||
>
|
||
Отмена
|
||
</Button>
|
||
</>
|
||
}
|
||
</Box>
|
||
|
||
<Divider />
|
||
|
||
{filesToUpload.length > 0 &&
|
||
<Flex direction='column'>
|
||
{filesToUpload.map((file, index) => (
|
||
<Flex key={index} p='8px'>
|
||
<Flex gap='sm' direction='row' align='center'>
|
||
<IconFile />
|
||
<Text>{file.name}</Text>
|
||
</Flex>
|
||
|
||
<ActionIcon onClick={() => {
|
||
setFilesToUpload(prev => {
|
||
return prev.filter((_, i) => i != index)
|
||
})
|
||
}} ml='auto' variant='subtle'>
|
||
<IconX />
|
||
</ActionIcon>
|
||
</Flex>
|
||
))}
|
||
</Flex>
|
||
}
|
||
</Box>
|
||
</Box>
|
||
}
|
||
|
||
<Table
|
||
onDragOver={handleDragOver}
|
||
onDragLeave={handleDragLeave}
|
||
onDrop={handleDrop}
|
||
bg={dragOver ? 'rgba(0, 0, 0, 0.1)' : 'inherit'}
|
||
highlightOnHover>
|
||
<Table.Thead>
|
||
<Table.Tr>
|
||
|
||
</Table.Tr>
|
||
</Table.Thead>
|
||
|
||
<Table.Tbody>
|
||
{currentFolder ? (
|
||
documents?.map((doc: IDocument, index: number) => (
|
||
<Table.Tr key={doc.id}>
|
||
<Table.Td>
|
||
<ItemDocument
|
||
doc={doc}
|
||
index={index}
|
||
handleDocumentClick={handleDocumentClick}
|
||
/>
|
||
</Table.Td>
|
||
</Table.Tr>
|
||
))
|
||
) : (
|
||
folders?.map((folder: IDocumentFolder, index: number) => (
|
||
<Table.Tr key={folder.id}>
|
||
<Table.Td>
|
||
<ItemFolder
|
||
folder={folder}
|
||
index={index}
|
||
handleFolderClick={handleFolderClick}
|
||
/>
|
||
</Table.Td>
|
||
</Table.Tr>
|
||
))
|
||
)}
|
||
</Table.Tbody>
|
||
</Table>
|
||
</Box>
|
||
)
|
||
} |