9 changed files with 489 additions and 90 deletions
-
1frontend_reactjs/src/components/DataTable.tsx
-
191frontend_reactjs/src/components/FolderViewer.tsx
-
128frontend_reactjs/src/components/modals/CreateCompanyModal.tsx
-
109frontend_reactjs/src/components/modals/CreateDepartmentModal.tsx
-
43frontend_reactjs/src/hooks/swrHooks.ts
-
2frontend_reactjs/src/http/axiosInstance.ts
-
5frontend_reactjs/src/interfaces/documents.ts
-
83frontend_reactjs/src/pages/Documents.tsx
-
17frontend_reactjs/src/services/DocumentService.ts
@ -0,0 +1,191 @@ |
|||
import { useDocuments, useFolders } from '../hooks/swrHooks' |
|||
import { IDocument, IDocumentFolder } from '../interfaces/documents' |
|||
import { Box, Breadcrumbs, Button, Card, CardActionArea, CircularProgress, LinearProgress, Link } from '@mui/material' |
|||
import { Folder, InsertDriveFile, Upload, UploadFile } from '@mui/icons-material' |
|||
import { useState } from 'react' |
|||
import DocumentService from '../services/DocumentService' |
|||
|
|||
interface FolderProps { |
|||
folder: IDocumentFolder; |
|||
handleFolderClick: (folder: IDocumentFolder) => void; |
|||
} |
|||
|
|||
function ItemFolder({ folder, handleFolderClick, ...props }: FolderProps) { |
|||
return ( |
|||
<Card |
|||
key={folder.id} |
|||
> |
|||
<CardActionArea> |
|||
<Box |
|||
sx={{ |
|||
cursor: 'pointer', |
|||
display: 'flex', |
|||
flexDirection: 'row', |
|||
gap: '8px', |
|||
alignItems: 'center', |
|||
padding: '8px' |
|||
}} |
|||
onClick={() => handleFolderClick(folder)} |
|||
{...props} |
|||
> |
|||
<Folder /> |
|||
{folder.name} |
|||
</Box> |
|||
</CardActionArea> |
|||
</Card> |
|||
) |
|||
} |
|||
|
|||
interface DocumentProps { |
|||
doc: IDocument; |
|||
handleDocumentClick: (doc: IDocument) => void; |
|||
} |
|||
|
|||
function ItemDocument({ doc, handleDocumentClick, ...props }: DocumentProps) { |
|||
return ( |
|||
<Card |
|||
key={doc.id} |
|||
> |
|||
<CardActionArea> |
|||
<Box |
|||
sx={{ |
|||
cursor: 'pointer', |
|||
display: 'flex', |
|||
flexDirection: 'row', |
|||
gap: '8px', |
|||
alignItems: 'center', |
|||
padding: '8px', |
|||
}} |
|||
onClick={() => { |
|||
handleDocumentClick(doc) |
|||
}} |
|||
{...props} |
|||
> |
|||
<InsertDriveFile /> |
|||
{doc.name} |
|||
</Box> |
|||
</CardActionArea> |
|||
</Card> |
|||
) |
|||
} |
|||
|
|||
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 handleFolderClick = (folder: IDocumentFolder) => { |
|||
setCurrentFolder(folder) |
|||
setBreadcrumbs((prev) => [...prev, folder]) |
|||
} |
|||
|
|||
const handleDocumentClick = async (doc: IDocument) => { |
|||
try { |
|||
const response = await DocumentService.downloadDoc(doc.document_folder_id, doc.id); |
|||
const url = window.URL.createObjectURL(new Blob([response.data])); |
|||
const link = document.createElement('a'); |
|||
link.href = url; |
|||
link.setAttribute('download', doc.name); |
|||
document.body.appendChild(link); |
|||
link.click(); |
|||
link.remove(); |
|||
} catch (error) { |
|||
console.error(error); |
|||
} |
|||
} |
|||
|
|||
const handleBreadcrumbClick = (index: number) => { |
|||
const newBreadcrumbs = breadcrumbs.slice(0, index + 1); |
|||
setBreadcrumbs(newBreadcrumbs) |
|||
setCurrentFolder(newBreadcrumbs[newBreadcrumbs.length - 1]) |
|||
} |
|||
|
|||
// const handleFileUpload = (event) => {
|
|||
// const file = event.target.files[0]
|
|||
|
|||
// }
|
|||
|
|||
if (foldersLoading || documentsLoading) { |
|||
return ( |
|||
<CircularProgress /> |
|||
) |
|||
} |
|||
|
|||
return ( |
|||
<Box sx={{ |
|||
display: 'flex', |
|||
flexDirection: 'column', |
|||
gap: '24px' |
|||
}}> |
|||
<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> |
|||
|
|||
<Box> |
|||
{currentFolder && |
|||
<Button |
|||
variant="outlined" |
|||
startIcon={<UploadFile />} |
|||
onClick={() => { |
|||
console.log(currentFolder.id) |
|||
}} |
|||
> |
|||
Загрузить |
|||
</Button> |
|||
} |
|||
|
|||
</Box> |
|||
|
|||
<Box |
|||
sx={{ |
|||
display: 'flex', |
|||
flexDirection: 'column', |
|||
flexWrap: 'wrap', |
|||
gap: '16px' |
|||
}}> |
|||
{currentFolder ? ( |
|||
documents?.map((doc: IDocument) => ( |
|||
<ItemDocument |
|||
key={`doc-${doc.id}`} |
|||
doc={doc} |
|||
handleDocumentClick={handleDocumentClick} |
|||
/> |
|||
)) |
|||
) : ( |
|||
folders?.map((folder: IDocumentFolder) => ( |
|||
<ItemFolder |
|||
key={`folder-${folder.id}`} |
|||
folder={folder} |
|||
handleFolderClick={handleFolderClick} |
|||
/> |
|||
)) |
|||
)} |
|||
</Box> |
|||
</Box> |
|||
) |
|||
} |
@ -0,0 +1,128 @@ |
|||
import { SubmitHandler, useForm } from 'react-hook-form'; |
|||
import { AxiosResponse } from 'axios'; |
|||
import { ApiResponse } from '../../interfaces/auth'; |
|||
import RoleService from '../../services/RoleService'; |
|||
import { Box, Button, MenuItem, Modal, Select, TextField, Typography } from '@mui/material'; |
|||
import { ICompany } from '../../interfaces/documents'; |
|||
import { useCompanies } from '../../hooks/swrHooks'; |
|||
|
|||
interface Props { |
|||
open: boolean; |
|||
setOpen: (state: boolean) => void; |
|||
} |
|||
|
|||
const style = { |
|||
position: 'absolute' as 'absolute', |
|||
top: '50%', |
|||
left: '50%', |
|||
transform: 'translate(-50%, -50%)', |
|||
width: 400, |
|||
bgcolor: 'background.paper', |
|||
boxShadow: 24, |
|||
borderRadius: 2, |
|||
p: 4, |
|||
display: "flex", |
|||
flexDirection: "column", |
|||
gap: "8px" |
|||
} |
|||
|
|||
export default function CreateCompanyModal({ |
|||
open, |
|||
setOpen |
|||
}: Props) { |
|||
const { companies } = useCompanies() |
|||
|
|||
const { register, handleSubmit, formState: { errors } } = useForm<ICompany>({ |
|||
defaultValues: { |
|||
name: '', |
|||
fullname: '', |
|||
description: '', |
|||
} |
|||
}) |
|||
|
|||
const onSubmit: SubmitHandler<ICompany> = async (data) => { |
|||
try { |
|||
const response: AxiosResponse<ApiResponse> = await RoleService.createRole(data) |
|||
console.log(response.data) |
|||
} catch (error) { |
|||
console.error(error) |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<Modal |
|||
open={open} |
|||
onClose={() => setOpen(false)} |
|||
aria-labelledby="modal-modal-title" |
|||
aria-describedby="modal-modal-description" |
|||
> |
|||
<form onSubmit={handleSubmit(onSubmit)}> |
|||
|
|||
<Box sx={style}> |
|||
<Typography variant="h6" component="h6" gutterBottom> |
|||
Создание компании |
|||
</Typography> |
|||
|
|||
<TextField |
|||
fullWidth |
|||
margin="normal" |
|||
label="Наименование" |
|||
required |
|||
{...register('name', { required: 'Наименование обязательно' })} |
|||
error={!!errors.name} |
|||
helperText={errors.name?.message} |
|||
/> |
|||
|
|||
<TextField |
|||
fullWidth |
|||
margin="normal" |
|||
label="Полное наименование" |
|||
required |
|||
{...register('fullname', { required: 'Полное наименование обязательно' })} |
|||
error={!!errors.fullname} |
|||
helperText={errors.fullname?.message} |
|||
/> |
|||
|
|||
<TextField |
|||
fullWidth |
|||
margin="normal" |
|||
label="Описание" |
|||
{...register('description')} |
|||
error={!!errors.description} |
|||
helperText={errors.description?.message} |
|||
/> |
|||
|
|||
{companies} |
|||
|
|||
<Select |
|||
label="owner_id" |
|||
{...register('owner_id', { required: '' })} |
|||
error={!!errors.owner_id} |
|||
> |
|||
{/* {companies && |
|||
companies.map((item, index) => { |
|||
<MenuItem> |
|||
{index} |
|||
</MenuItem> |
|||
}) |
|||
} */} |
|||
</Select> |
|||
|
|||
<Box sx={{ |
|||
display: "flex", |
|||
justifyContent: "space-between", |
|||
gap: "8px" |
|||
}}> |
|||
<Button type="submit" variant="contained" color="primary"> |
|||
Добавить роль |
|||
</Button> |
|||
|
|||
<Button type="button" variant="outlined" color="primary" onClick={() => setOpen(false)}> |
|||
Отмена |
|||
</Button> |
|||
</Box> |
|||
</Box> |
|||
</form> |
|||
</Modal> |
|||
) |
|||
} |
@ -0,0 +1,109 @@ |
|||
import { SubmitHandler, useForm } from 'react-hook-form'; |
|||
import { AxiosResponse } from 'axios'; |
|||
import { ApiResponse } from '../../interfaces/auth'; |
|||
import RoleService from '../../services/RoleService'; |
|||
import { Box, Button, Modal, TextField, Typography } from '@mui/material'; |
|||
import { ICompany } from '../../interfaces/documents'; |
|||
|
|||
interface Props { |
|||
open: boolean; |
|||
setOpen: (state: boolean) => void; |
|||
} |
|||
|
|||
const style = { |
|||
position: 'absolute' as 'absolute', |
|||
top: '50%', |
|||
left: '50%', |
|||
transform: 'translate(-50%, -50%)', |
|||
width: 400, |
|||
bgcolor: 'background.paper', |
|||
boxShadow: 24, |
|||
borderRadius: 2, |
|||
p: 4, |
|||
display: "flex", |
|||
flexDirection: "column", |
|||
gap: "8px" |
|||
} |
|||
|
|||
export default function CreateDepartmentModal({ |
|||
open, |
|||
setOpen |
|||
}: Props) { |
|||
const { register, handleSubmit, formState: { errors } } = useForm<ICompany>({ |
|||
defaultValues: { |
|||
name: '', |
|||
fullname: '', |
|||
description: '', |
|||
} |
|||
}) |
|||
|
|||
const onSubmit: SubmitHandler<ICompany> = async (data) => { |
|||
try { |
|||
const response: AxiosResponse<ApiResponse> = await RoleService.createRole(data) |
|||
console.log(response.data) |
|||
} catch (error) { |
|||
console.error(error) |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<Modal |
|||
open={open} |
|||
onClose={() => setOpen(false)} |
|||
aria-labelledby="modal-modal-title" |
|||
aria-describedby="modal-modal-description" |
|||
> |
|||
<form onSubmit={handleSubmit(onSubmit)}> |
|||
|
|||
<Box sx={style}> |
|||
<Typography variant="h6" component="h6" gutterBottom> |
|||
Создание компании |
|||
</Typography> |
|||
|
|||
<TextField |
|||
fullWidth |
|||
margin="normal" |
|||
label="Наименование" |
|||
required |
|||
{...register('name', { required: 'Наименование обязательно' })} |
|||
error={!!errors.name} |
|||
helperText={errors.name?.message} |
|||
/> |
|||
|
|||
<TextField |
|||
fullWidth |
|||
margin="normal" |
|||
label="Полное наименование" |
|||
required |
|||
{...register('fullname', { required: 'Полное наименование обязательно' })} |
|||
error={!!errors.fullname} |
|||
helperText={errors.fullname?.message} |
|||
/> |
|||
|
|||
<TextField |
|||
fullWidth |
|||
margin="normal" |
|||
label="Описание" |
|||
{...register('description')} |
|||
error={!!errors.description} |
|||
helperText={errors.description?.message} |
|||
/> |
|||
|
|||
<Box sx={{ |
|||
display: "flex", |
|||
justifyContent: "space-between", |
|||
gap: "8px" |
|||
}}> |
|||
<Button type="submit" variant="contained" color="primary"> |
|||
Добавить роль |
|||
</Button> |
|||
|
|||
<Button type="button" variant="outlined" color="primary" onClick={() => setOpen(false)}> |
|||
Отмена |
|||
</Button> |
|||
</Box> |
|||
</Box> |
|||
</form> |
|||
</Modal> |
|||
) |
|||
} |
Reference in new issue
xxxxxxxxxx