Files
tests/client/src/components/FolderViewer.tsx
2024-11-15 17:00:23 +09:00

308 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useDocuments, useDownload, useFolders } from '../hooks/swrHooks'
import { IDocument, IDocumentFolder } from '../interfaces/documents'
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, Divider, FileButton, Flex, Loader, MantineStyleProp, RingProgress, ScrollAreaAutosize, 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: MantineStyleProp = {
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)}
>
<Flex
style={FileItemStyle}
{...props}
>
<Folder />
{folder.name}
</Flex>
</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'>
<Flex
style={FileItemStyle}
onClick={() => handleDocumentClick(index)}
{...props}
>
<InsertDriveFile />
{doc.name}
</Flex>
<Flex>
<ActionIcon
onClick={() => {
if (!isLoading) {
setShouldFetch(true)
}
}}
variant='subtle'>
{isLoading ?
<Loader size='sm' />
:
<IconDownload />
}
</ActionIcon>
</Flex>
</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 (
<Loader />
)
}
return (
<ScrollAreaAutosize w={'100%'} h={'100%'} p={'sm'}>
<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 &&
<Flex direction='column' gap='sm'>
<Flex direction='column' gap='sm' p='sm' style={{
border: filesToUpload.length > 0 ? '1px dashed gray' : 'none',
borderRadius: '8px',
}}>
<Flex gap='sm'>
<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>
</>
}
</Flex>
<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>
}
</Flex>
</Flex>
}
<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>
</ScrollAreaAutosize>
)
}