Files
universal_is/client/src/components/FolderViewer.tsx

374 lines
15 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 React, { useEffect, useState } from 'react'
import DocumentService from '../services/DocumentService'
import { mutate } from 'swr'
import FileViewer from './modals/FileViewer'
import { IconCancel, IconDownload, IconFileUpload, IconX } from '@tabler/icons-react'
import { Breadcrumb, BreadcrumbButton, BreadcrumbDivider, BreadcrumbItem, Button, createTableColumn, DataGrid, DataGridBody, DataGridCell, DataGridHeader, DataGridHeaderCell, DataGridRow, Divider, Field, ProgressBar, Spinner, TableCellLayout } from '@fluentui/react-components'
import { DocumentAdd20Regular, DocumentColor, DocumentRegular, DocumentTextColor, FolderRegular, ImageColor, TableColor } from '@fluentui/react-icons'
interface DocumentProps {
doc: IDocument;
}
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 getFileExtension(filename: string): string {
return filename.split('.').pop()?.toLowerCase() || '';
}
function handleDocFormatIcon(docName: string) {
const ext = getFileExtension(docName);
switch (ext) {
case 'docx':
return <DocumentTextColor />
case 'pdf':
return <DocumentTextColor color='red' />
case 'xlsx':
return <TableColor />
case 'jpg':
return <ImageColor />
default:
return <DocumentRegular />
}
}
function ItemDocument({ doc }: 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, doc.name])
return (
<Button icon={isLoading ?
<Spinner size='tiny' />
:
<IconDownload />
} appearance='subtle' onClick={(e) => {
e.stopPropagation()
if (!isLoading) {
setShouldFetch(true)
}
}} />
)
}
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);
}
}
}
const fileInputRef = React.useRef<HTMLInputElement | null>(null);
const handleClick = () => {
fileInputRef.current?.click();
};
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
// do what Mantine's onChange did
console.log("Selected files:", Array.from(e.target.files));
handleFileInput(Array.from(e.target.files))
}
};
if (foldersLoading || documentsLoading) {
return (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height: '100%',
padding: '1rem',
}}>
<Spinner />
</div>
)
}
return (
<div style={{
width: '100%',
height: '100%',
padding: '1rem',
}}>
{fileViewerModal &&
<FileViewer
open={fileViewerModal}
setOpen={setFileViewerModal}
currentFileNo={currentFileNo}
setCurrentFileNo={setCurrentFileNo}
docs={documents}
/>
}
<div style={{
display: 'flex',
flexDirection: 'column',
height: '100%',
width: '100%',
gap: '1rem'
}}>
<Breadcrumb>
<BreadcrumbItem>
<BreadcrumbButton onClick={() => {
setCurrentFolder(null)
setBreadcrumbs([])
}}>Главная</BreadcrumbButton>
</BreadcrumbItem>
{breadcrumbs.map((breadcrumb, index) => (
<>
<BreadcrumbDivider />
<BreadcrumbItem key={breadcrumb.id}>
<BreadcrumbButton icon={<FolderRegular />} onClick={() => {
handleBreadcrumbClick(index)
}}>{breadcrumb.name}</BreadcrumbButton>
</BreadcrumbItem>
</>
))}
</Breadcrumb>
{currentFolder &&
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '1rem'
}}>
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '1rem',
padding: '1rem',
border: filesToUpload.length > 0 ? '1px dashed gray' : 'none',
borderRadius: '8px',
}}>
<div style={{ display: 'flex', gap: '1rem' }}>
<input
type="file"
multiple
ref={fileInputRef}
style={{ display: "none" }}
onChange={handleFileChange}
/>
<Button appearance="primary" icon={<DocumentAdd20Regular />} onClick={handleClick}>
Добавить
</Button>
{filesToUpload.length > 0 &&
<>
<Button
appearance='primary'
icon={<IconFileUpload />}
onClick={uploadFiles}
disabled={isUploading}
>
Загрузить все
</Button>
<Button
appearance='outline'
icon={<IconCancel />}
onClick={() => {
setFilesToUpload([])
}}
>
Отмена
</Button>
</>
}
</div>
{isUploading &&
<Field validationMessage={"Загрузка файлов..."} validationState='none'>
<ProgressBar value={uploadProgress} />
</Field>
}
<Divider />
{filesToUpload.length > 0 &&
<div style={{
display: 'flex',
flexDirection: 'column'
}}>
{filesToUpload.map((file, index) => (
<div style={{
display: 'flex',
}} key={index}>
<Button appearance='transparent' icon={<DocumentColor />}>
{file.name}
</Button>
<Button style={{ marginLeft: 'auto' }} appearance='subtle' icon={<IconX />} onClick={() => {
setFilesToUpload(prev => {
return prev.filter((_, i) => i != index)
})
}} />
</div>
))}
</div>
}
</div>
</div>
}
<div style={{
width: '100%',
overflow: 'auto'
}}>
<DataGrid
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
style={{ backgroundColor: dragOver ? 'rgba(0, 0, 0, 0.1)' : 'inherit' }}
items={currentFolder
? (documents ?? []).map((doc: IDocument) => ({ kind: "doc", data: doc }))
: (folders ?? []).map((folder: IDocumentFolder) => ({ kind: "folder", data: folder }))}
columns={[
createTableColumn({
columnId: "name",
renderHeaderCell: () => "Название",
renderCell: (item) => (
<TableCellLayout truncate media={item.kind === "doc" ? handleDocFormatIcon(item.data.name) : <FolderRegular />}>
{item.data.name}
</TableCellLayout>
),
}),
createTableColumn({
columnId: "date",
renderHeaderCell: () => "Дата создания",
renderCell: (item) =>
new Date(item.data.create_date).toLocaleDateString(),
}),
createTableColumn({
columnId: "actions",
renderHeaderCell: () => "",
renderCell: (item) => {
if (item.kind === "doc") {
// replace with your <ItemDocument doc={doc} />
return <ItemDocument doc={item.data} />;
}
return null;
},
}),
]}
focusMode="cell"
resizableColumns
getRowId={(item) => item.data.id}
>
<DataGridHeader>
<DataGridRow>
{({ renderHeaderCell }) => (
<DataGridHeaderCell>{renderHeaderCell()}</DataGridHeaderCell>
)}
</DataGridRow>
</DataGridHeader>
<DataGridBody>
{({ item, rowId }: { item: { kind: string, data: any }, rowId: any }) => (
<DataGridRow
key={rowId}
style={{ cursor: "pointer" }}
onClick={() => {
if (item.kind === "doc") {
const index = documents?.findIndex((d: any) => d.id === item.data.id) ?? -1;
handleDocumentClick(index);
} else {
handleFolderClick(item.data as IDocumentFolder);
}
}}
>
{({ renderCell }) => <DataGridCell>{renderCell(item)}</DataGridCell>}
</DataGridRow>
)}
</DataGridBody>
</DataGrid>
</div>
</div>
</div>
)
}