Files
tests/client/src/components/FolderViewer.tsx
cracklesparkle 974fc12b34 mantine
2024-10-09 16:51:37 +09:00

318 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 { 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>
)
}