|
|
|
|
|
|
|
|
|
|
|
import React, { useState, useEffect } from "react"; |
|
|
|
|
|
import IconButton from "../../components/IconButton/IconButton"; |
|
|
|
|
|
import deleteIcon from "../../assets/images/delete.png"; |
|
|
|
|
|
import editIcon from "../../assets/images/edit.png"; |
|
|
|
|
|
import PropTypes from "prop-types"; |
|
|
|
|
|
import { useTranslation } from "react-i18next"; |
|
|
|
|
|
import { useSelector, useDispatch } from "react-redux"; |
|
|
|
|
|
import { |
|
|
|
|
|
deleteFileReq, |
|
|
|
|
|
getFileFiltersReq, |
|
|
|
|
|
getFilesReq, |
|
|
|
|
|
setContent, |
|
|
|
|
|
updateFileReq, |
|
|
|
|
|
} from "../../store/actions/files/fileActions"; |
|
|
|
|
|
import { Fade, Pagination } from "@mui/material"; |
|
|
|
|
|
import FilterButton from "../../components/Button/FilterButton"; |
|
|
|
|
|
import searchImage from "../../assets/images/search.png"; |
|
|
|
|
|
import DocsFilters from "../../components/Docs/DocsFilters"; |
|
|
|
|
|
import { ADD_FILE } from "../../constants/pages"; |
|
|
|
|
|
import ConfirmDialog from "../../components/MUI/ConfirmDialog"; |
|
|
|
|
|
import FileViewer from "react-file-viewer"; |
|
|
|
|
|
import CustomModal from "../../components/UI/CustomModal"; |
|
|
|
|
|
import xIcon from "../../assets/images/x.png"; |
|
|
|
|
|
import Button from "../../components/Button/Button"; |
|
|
|
|
|
import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; |
|
|
|
|
|
import { FETCH_FILES_LOADING } from "../../store/actions/files/fileActionConstants"; |
|
|
|
|
|
import { useHistory } from "react-router"; |
|
|
|
|
|
|
|
|
|
|
|
const FileTable = () => { |
|
|
|
|
|
const [file, setFile] = useState(null); |
|
|
|
|
|
const { data } = useSelector((s) => s.files); |
|
|
|
|
|
const { filters } = useSelector((s) => s.fileFilters); |
|
|
|
|
|
const [page, setPage] = useState(1); |
|
|
|
|
|
const [toggleFiltersDrawer, setToggleFiltersDrawer] = useState(false); |
|
|
|
|
|
const [isSearchFieldVisible, setIsSearchFieldVisible] = useState(false); |
|
|
|
|
|
const [fileForDelete, setFileForDelete] = useState({ |
|
|
|
|
|
open: false, |
|
|
|
|
|
title: "", |
|
|
|
|
|
streamId: null, |
|
|
|
|
|
}); |
|
|
|
|
|
const [openNoteModal, setOpenNoteModal] = useState({ |
|
|
|
|
|
open: false, |
|
|
|
|
|
note: null, |
|
|
|
|
|
streamId: "", |
|
|
|
|
|
}); |
|
|
|
|
|
const { t } = useTranslation(); |
|
|
|
|
|
const dispatch = useDispatch(); |
|
|
|
|
|
const history = useHistory(); |
|
|
|
|
|
|
|
|
|
|
|
const handleToggleFiltersDrawer = () => { |
|
|
|
|
|
setToggleFiltersDrawer((oldState) => !oldState); |
|
|
|
|
|
setFile(null); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
handleFilters(page); |
|
|
|
|
|
}, [file]); |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
dispatch(getFileFiltersReq()); |
|
|
|
|
|
dispatch( |
|
|
|
|
|
getFilesReq({ |
|
|
|
|
|
payload: { |
|
|
|
|
|
pageSize: 3, |
|
|
|
|
|
currentPage: page, |
|
|
|
|
|
categories: [null], |
|
|
|
|
|
extensions: [], |
|
|
|
|
|
tags: [], |
|
|
|
|
|
content: "", |
|
|
|
|
|
}, |
|
|
|
|
|
}) |
|
|
|
|
|
); |
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
const handleChange = (_, value) => { |
|
|
|
|
|
handleFilters(value); |
|
|
|
|
|
setPage(value); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const handleFilters = (value) => { |
|
|
|
|
|
var catFilters = [null]; |
|
|
|
|
|
|
|
|
|
|
|
var extFilters = []; |
|
|
|
|
|
filters.extensions |
|
|
|
|
|
?.filter((n) => n.isChecked) |
|
|
|
|
|
.forEach((m) => extFilters.push(m.name)); |
|
|
|
|
|
|
|
|
|
|
|
var tagFilters = []; |
|
|
|
|
|
filters.tags |
|
|
|
|
|
?.filter((n) => n.isChecked) |
|
|
|
|
|
.forEach((m) => tagFilters.push(m.name)); |
|
|
|
|
|
|
|
|
|
|
|
dispatch( |
|
|
|
|
|
getFilesReq({ |
|
|
|
|
|
payload: { |
|
|
|
|
|
pageSize: 3, |
|
|
|
|
|
currentPage: value, |
|
|
|
|
|
categories: catFilters, |
|
|
|
|
|
extensions: extFilters, |
|
|
|
|
|
tags: tagFilters, |
|
|
|
|
|
content: filters.content === null ? "" : filters.content, |
|
|
|
|
|
}, |
|
|
|
|
|
}) |
|
|
|
|
|
); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const isLoading = useSelector( |
|
|
|
|
|
selectIsLoadingByActionType(FETCH_FILES_LOADING) |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
const handleChangeVisibility = () => { |
|
|
|
|
|
setIsSearchFieldVisible(!isSearchFieldVisible); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const stopPropagation = (e) => { |
|
|
|
|
|
e.stopPropagation(); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const handleChangeContent = (value) => { |
|
|
|
|
|
dispatch(setContent(value)); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const deleteFileHandler = (stream_id) => { |
|
|
|
|
|
dispatch(deleteFileReq({ id: stream_id })); |
|
|
|
|
|
if (file !== null && stream_id === file.streamId) { |
|
|
|
|
|
setFile(null); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const handleKeyDown = (event) => { |
|
|
|
|
|
if (event.key === "Enter" && filters.content !== "") { |
|
|
|
|
|
var catFilters = [null]; |
|
|
|
|
|
|
|
|
|
|
|
var extFilters = []; |
|
|
|
|
|
filters.extensions |
|
|
|
|
|
?.filter((n) => n.isChecked) |
|
|
|
|
|
.forEach((m) => extFilters.push(m.name)); |
|
|
|
|
|
|
|
|
|
|
|
var tagFilters = []; |
|
|
|
|
|
filters.tags |
|
|
|
|
|
?.filter((n) => n.isChecked) |
|
|
|
|
|
.forEach((m) => tagFilters.push(m.name)); |
|
|
|
|
|
|
|
|
|
|
|
dispatch( |
|
|
|
|
|
getFilesReq({ |
|
|
|
|
|
payload: { |
|
|
|
|
|
pageSize: 3, |
|
|
|
|
|
currentPage: page, |
|
|
|
|
|
categories: catFilters, |
|
|
|
|
|
extensions: extFilters, |
|
|
|
|
|
tags: tagFilters, |
|
|
|
|
|
content: filters.content, |
|
|
|
|
|
}, |
|
|
|
|
|
}) |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const input = ( |
|
|
|
|
|
<div> |
|
|
|
|
|
<input |
|
|
|
|
|
placeholder="Pretrazi..." |
|
|
|
|
|
value={filters.content === undefined ? "" : filters.content} |
|
|
|
|
|
onChange={(e) => handleChangeContent(e.target.value)} |
|
|
|
|
|
onKeyDown={handleKeyDown} |
|
|
|
|
|
className="candidate-search-field" |
|
|
|
|
|
onClick={stopPropagation} |
|
|
|
|
|
role="input" |
|
|
|
|
|
/> |
|
|
|
|
|
</div> |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
const onError = (e) => { |
|
|
|
|
|
console.log(e, "error in file-viewer"); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const displayFile = (fileStream, streamId, fileType) => { |
|
|
|
|
|
setFile({ fileStream, streamId, fileType }); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const getPathForFile = (fileType, fileStream) => { |
|
|
|
|
|
if (fileType === "docx") { |
|
|
|
|
|
return `data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,${fileStream}`; |
|
|
|
|
|
} else if (fileType === "doc") { |
|
|
|
|
|
return `data:application/msword;base64,${fileStream}`; |
|
|
|
|
|
} else { |
|
|
|
|
|
return `data:application/pdf;base64,${fileStream}`; |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const updateFileNoteHandler = () => { |
|
|
|
|
|
dispatch( |
|
|
|
|
|
updateFileReq({ |
|
|
|
|
|
id: openNoteModal.streamId, |
|
|
|
|
|
data: openNoteModal.note, |
|
|
|
|
|
onSuccessOpenNoteModal, |
|
|
|
|
|
}) |
|
|
|
|
|
); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const onSuccessOpenNoteModal = () => { |
|
|
|
|
|
setOpenNoteModal({ |
|
|
|
|
|
open: false, |
|
|
|
|
|
note: null, |
|
|
|
|
|
streamId: "", |
|
|
|
|
|
}); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
return isLoading ? ( |
|
|
|
|
|
<div> |
|
|
|
|
|
<div className="l-t-rectangle"></div> |
|
|
|
|
|
<div className="r-b-rectangle"></div> |
|
|
|
|
|
<div className="loader-container h-withHeader"> |
|
|
|
|
|
<span>Loading</span> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
) : ( |
|
|
|
|
|
<> |
|
|
|
|
|
<ConfirmDialog |
|
|
|
|
|
open={fileForDelete.open} |
|
|
|
|
|
title="Brisanje dokumenta" |
|
|
|
|
|
subtitle={fileForDelete.title} |
|
|
|
|
|
imgSrc={deleteIcon} |
|
|
|
|
|
content="Da li ste sigurni za brisanje dokumenta?" |
|
|
|
|
|
onClose={() => { |
|
|
|
|
|
setFileForDelete({ open: false, title: "", streamId: null }); |
|
|
|
|
|
}} |
|
|
|
|
|
onConfirm={() => { |
|
|
|
|
|
deleteFileHandler(fileForDelete.streamId); |
|
|
|
|
|
setFileForDelete({ open: false, title: "", streamId: null }); |
|
|
|
|
|
}} |
|
|
|
|
|
/> |
|
|
|
|
|
<CustomModal |
|
|
|
|
|
classes="files-custom-modal" |
|
|
|
|
|
open={openNoteModal.open} |
|
|
|
|
|
onCloseModal={() => |
|
|
|
|
|
setOpenNoteModal({ open: false, note: null, streamId: "" }) |
|
|
|
|
|
} |
|
|
|
|
|
> |
|
|
|
|
|
<div className="add-pattern-modal-header"> |
|
|
|
|
|
<div className="add-pattern-modal-header-title"> |
|
|
|
|
|
<div className="add-pattern-modal-header-title-image"> |
|
|
|
|
|
<img src={editIcon} alt="plus" /> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div className="add-pattern-modal-header-title-title"> |
|
|
|
|
|
<p>Edit text</p> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div className="add-pattern-modal-header-title-sub"> |
|
|
|
|
|
<sub> | Note</sub> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div |
|
|
|
|
|
className="add-pattern-modal-header-close" |
|
|
|
|
|
onClick={() => |
|
|
|
|
|
setOpenNoteModal({ open: false, note: null, streamId: "" }) |
|
|
|
|
|
} |
|
|
|
|
|
> |
|
|
|
|
|
<img src={xIcon} alt="close" /> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div className="files-edit-note"> |
|
|
|
|
|
<textarea |
|
|
|
|
|
className="files-note" |
|
|
|
|
|
value={openNoteModal.note} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setOpenNoteModal((oldState) => ({ |
|
|
|
|
|
...oldState, |
|
|
|
|
|
note: e.target.value, |
|
|
|
|
|
})) |
|
|
|
|
|
} |
|
|
|
|
|
/> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div className="files-custom-modal-buttons"> |
|
|
|
|
|
<Button |
|
|
|
|
|
type="button" |
|
|
|
|
|
variant="contained" |
|
|
|
|
|
className="c-btn c-btn--primary-outlined" |
|
|
|
|
|
onClick={() => |
|
|
|
|
|
setOpenNoteModal({ open: false, note: null, streamId: "" }) |
|
|
|
|
|
} |
|
|
|
|
|
> |
|
|
|
|
|
Close |
|
|
|
|
|
</Button> |
|
|
|
|
|
<Button |
|
|
|
|
|
type="button" |
|
|
|
|
|
variant="contained" |
|
|
|
|
|
className="c-btn c-btn--primary" |
|
|
|
|
|
onClick={updateFileNoteHandler} |
|
|
|
|
|
> |
|
|
|
|
|
Save Changes |
|
|
|
|
|
</Button> |
|
|
|
|
|
</div> |
|
|
|
|
|
</CustomModal> |
|
|
|
|
|
<DocsFilters |
|
|
|
|
|
open={toggleFiltersDrawer} |
|
|
|
|
|
handleClose={handleToggleFiltersDrawer} |
|
|
|
|
|
setPage={setPage} |
|
|
|
|
|
category={null} |
|
|
|
|
|
/> |
|
|
|
|
|
<div |
|
|
|
|
|
className="flex-center" |
|
|
|
|
|
style={{ |
|
|
|
|
|
paddingTop: "36px", |
|
|
|
|
|
justifyContent: "space-between", |
|
|
|
|
|
postion: "relative", |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
<h1 className="page-heading">Pregled dokumenata</h1> |
|
|
|
|
|
<div style={{ postion: "relative" }}> |
|
|
|
|
|
<Fade in={isSearchFieldVisible} timeout={500} className="proba"> |
|
|
|
|
|
{input} |
|
|
|
|
|
</Fade> |
|
|
|
|
|
<Fade in={isSearchFieldVisible} timeout={500}> |
|
|
|
|
|
<div |
|
|
|
|
|
style={{ |
|
|
|
|
|
position: "absolute", |
|
|
|
|
|
zIndex: 10000, |
|
|
|
|
|
right: 95, |
|
|
|
|
|
marginTop: 0, |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
<img src={searchImage} /> |
|
|
|
|
|
</div> |
|
|
|
|
|
</Fade> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div className="flex-center"> |
|
|
|
|
|
<IconButton |
|
|
|
|
|
className="c-btn c-btn--primary-outlined candidate-btn search-field userPageBtn ml-20px no-padding custom-filter-button" |
|
|
|
|
|
onClick={handleChangeVisibility} |
|
|
|
|
|
> |
|
|
|
|
|
{t("candidates.search")} |
|
|
|
|
|
<img src={searchImage} alt="filter" className="candidates-image" /> |
|
|
|
|
|
</IconButton> |
|
|
|
|
|
<FilterButton onShowFilters={handleToggleFiltersDrawer} /> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div |
|
|
|
|
|
style={{ |
|
|
|
|
|
display: "flex", |
|
|
|
|
|
marginTop: "39px", |
|
|
|
|
|
flexDirection: "column", |
|
|
|
|
|
justifyContent: "space-between", |
|
|
|
|
|
minHeight: "500px", |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
<div className="table-cont"> |
|
|
|
|
|
<div style={{ display: "flex", height: "300px" }}> |
|
|
|
|
|
<table |
|
|
|
|
|
className={"usersTable-users mini"} |
|
|
|
|
|
style={{ |
|
|
|
|
|
width: file === null ? "100%" : "800px", |
|
|
|
|
|
height: "50%", |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
<thead> |
|
|
|
|
|
<tr |
|
|
|
|
|
className="headingRow headingRowFiles" |
|
|
|
|
|
style={{ cursor: "pointer" }} |
|
|
|
|
|
> |
|
|
|
|
|
<th>Putanja dokumenta</th> |
|
|
|
|
|
<th>Naziv dokumenta</th> |
|
|
|
|
|
<th>Tip dokumenta</th> |
|
|
|
|
|
{file === null ? <th>Veličina dokumenta</th> : ""} |
|
|
|
|
|
<th>Obrisi dokument</th> |
|
|
|
|
|
<th>Note</th> |
|
|
|
|
|
<th>Preuzmi dokument</th> |
|
|
|
|
|
</tr> |
|
|
|
|
|
</thead> |
|
|
|
|
|
<tbody> |
|
|
|
|
|
{data.data?.map((n, index) => ( |
|
|
|
|
|
<tr |
|
|
|
|
|
key={index} |
|
|
|
|
|
className="secondaryRow" |
|
|
|
|
|
onClick={() => |
|
|
|
|
|
displayFile(n.file_stream, n.stream_id, n.file_type) |
|
|
|
|
|
} |
|
|
|
|
|
> |
|
|
|
|
|
<td className="docs-name">{n.fileName}</td> |
|
|
|
|
|
<td className="docs-name">{n.title}</td> |
|
|
|
|
|
<td>{n.file_type && n.file_type}</td> |
|
|
|
|
|
{file === null ? ( |
|
|
|
|
|
<td className="profession">{n.cached_file_size}kB</td> |
|
|
|
|
|
) : ( |
|
|
|
|
|
"" |
|
|
|
|
|
)} |
|
|
|
|
|
<td className="profession"> |
|
|
|
|
|
<IconButton |
|
|
|
|
|
className="c-btn c-btn--primary-outlined files-view-page-delete-btn" |
|
|
|
|
|
onClick={() => |
|
|
|
|
|
setFileForDelete({ |
|
|
|
|
|
open: true, |
|
|
|
|
|
title: n.title, |
|
|
|
|
|
streamId: n.stream_id, |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
> |
|
|
|
|
|
<img |
|
|
|
|
|
style={{ width: "12px", height: "12px" }} |
|
|
|
|
|
src={deleteIcon} |
|
|
|
|
|
/> |
|
|
|
|
|
</IconButton> |
|
|
|
|
|
</td> |
|
|
|
|
|
<td className="profession"> |
|
|
|
|
|
<IconButton |
|
|
|
|
|
className="c-btn c-btn--primary-outlined files-view-page-delete-btn" |
|
|
|
|
|
onClick={() => { |
|
|
|
|
|
setOpenNoteModal({ |
|
|
|
|
|
open: true, |
|
|
|
|
|
note: n.note, |
|
|
|
|
|
streamId: n.stream_id, |
|
|
|
|
|
}); |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
<img |
|
|
|
|
|
style={{ width: "12px", height: "12px" }} |
|
|
|
|
|
src={editIcon} |
|
|
|
|
|
/> |
|
|
|
|
|
</IconButton> |
|
|
|
|
|
</td> |
|
|
|
|
|
<td> |
|
|
|
|
|
<div onClick={stopPropagation}> |
|
|
|
|
|
<a |
|
|
|
|
|
className="applicant-cv-button" |
|
|
|
|
|
style={{ |
|
|
|
|
|
width: "100px", |
|
|
|
|
|
height: "40px", |
|
|
|
|
|
padding: 8, |
|
|
|
|
|
}} |
|
|
|
|
|
download={n.title} |
|
|
|
|
|
href={getPathForFile(n.file_type, n.file_stream)} |
|
|
|
|
|
> |
|
|
|
|
|
{t("common.download")} |
|
|
|
|
|
</a> |
|
|
|
|
|
</div> |
|
|
|
|
|
</td> |
|
|
|
|
|
</tr> |
|
|
|
|
|
))} |
|
|
|
|
|
</tbody> |
|
|
|
|
|
</table> |
|
|
|
|
|
{file && ( |
|
|
|
|
|
<div |
|
|
|
|
|
style={{ |
|
|
|
|
|
width: "500px", |
|
|
|
|
|
marginLeft: "30px", |
|
|
|
|
|
overflowY: "visible", |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
<FileViewer |
|
|
|
|
|
fileType={file.fileType} |
|
|
|
|
|
filePath={getPathForFile(file.fileType, file.fileStream)} |
|
|
|
|
|
onError={onError} |
|
|
|
|
|
/> |
|
|
|
|
|
</div> |
|
|
|
|
|
)} |
|
|
|
|
|
</div> |
|
|
|
|
|
<Pagination |
|
|
|
|
|
size={"small"} |
|
|
|
|
|
count={ |
|
|
|
|
|
parseInt(data.total) <= 6 |
|
|
|
|
|
? 1 |
|
|
|
|
|
: Math.ceil(parseInt(data.total) / 6) |
|
|
|
|
|
} |
|
|
|
|
|
color="primary" |
|
|
|
|
|
className="candidates-pagination" |
|
|
|
|
|
onChange={handleChange} |
|
|
|
|
|
shape="rounded" |
|
|
|
|
|
page={page} |
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
|
|
<IconButton |
|
|
|
|
|
className="c-btn ads-page-btn c-btn--primary add-ad-btn filesPage" |
|
|
|
|
|
onClick={() => history.push(ADD_FILE)} |
|
|
|
|
|
> |
|
|
|
|
|
+ Fajl |
|
|
|
|
|
</IconButton> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div |
|
|
|
|
|
style={{ |
|
|
|
|
|
display: "flex", |
|
|
|
|
|
justifyContent: "flex-end", |
|
|
|
|
|
marginBottom: "35px", |
|
|
|
|
|
}} |
|
|
|
|
|
></div> |
|
|
|
|
|
</div> |
|
|
|
|
|
</> |
|
|
|
|
|
); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
FileTable.propTypes = { |
|
|
|
|
|
// history: PropTypes.shape({ |
|
|
|
|
|
// replace: PropTypes.func, |
|
|
|
|
|
// push: PropTypes.func, |
|
|
|
|
|
// location: PropTypes.shape({ |
|
|
|
|
|
// pathname: PropTypes.string, |
|
|
|
|
|
// }), |
|
|
|
|
|
// }), |
|
|
|
|
|
files: PropTypes.array, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
export default FileTable; |