Ver código fonte

Changed category to be nullable

FE_dev
bronjaermin 2 anos atrás
pai
commit
bcf60a70f5

+ 3
- 4
src/AppRoutes.js Ver arquivo

@@ -25,7 +25,7 @@ import {
// STATS_PAGE,
REGISTER_PAGE,
// CREATE_AD_PAGE,
FILES_VIEW_PAGE,
// FILES_VIEW_PAGE,
ADD_FILE,
FILES_PAGE,
} from "./constants/pages";
@@ -51,7 +51,7 @@ import UserDetails from "./pages/UsersPage/UserDetails";
// import StatsPage from "./pages/StatsPage/StatsPage";
import RegisterPage from "./pages/RegisterPage/RegisterPage";
// import CreateAdPage from "./pages/AdsPage/CreateAdPage";
import FilesViewPage from "./pages/FilesPage/FilesViewPage";
// import FilesViewPage from "./pages/FilesPage/FilesViewPage";
import AddFile from "./pages/FilesPage/AddFile";
import FilesPage from "./pages/FilesPage/FilesPage";

@@ -67,7 +67,6 @@ const AppRoutes = () => {
}, [location]);
return (
<Switch>
<Route exact path={FILES_VIEW_PAGE} component={FilesViewPage} />
<Route exact path={BASE_PAGE} component={LoginPage} />
<Route path={NOT_FOUND_PAGE} component={NotFoundPage} />
{/* <Route path={USERS_PAGE} component={UsersPage} /> */}
@@ -86,7 +85,7 @@ const AppRoutes = () => {
{/* <PrivateRoute exact path={CANDIDATES_PAGE} component={CandidatesPage} />
<PrivateRoute exact path={CREATE_AD_PAGE} component={CreateAdPage} /> */}
{/* <PrivateRoute exact path={FILES_PAGE} component={FilesPage} /> */}
<PrivateRoute exact path={FILES_VIEW_PAGE} component={FilesViewPage} />
{/* <PrivateRoute exact path={FILES_VIEW_PAGE} component={FilesViewPage} /> */}
<PrivateRoute exact path={ADD_FILE} component={AddFile}/>
<PrivateRoute exact path={FILES_PAGE} component={FilesPage}/>
{/* <PrivateRoute

+ 2
- 2
src/assets/styles/components/_files.scss Ver arquivo

@@ -68,7 +68,7 @@
}

.files-page-categories-category {
width: calc(100% / 4) !important;
width: calc(100% / 6) !important;
margin: 0 !important;
padding: 16px;
}
@@ -76,7 +76,7 @@
.files-page-category-button {
display: flex;
flex-direction: row;
justify-content: center;
justify-content: flex-start;
align-items: center;
padding: 18px 72px;
gap: 10px;

+ 2
- 29
src/pages/FilesPage/AddFile.js Ver arquivo

@@ -26,7 +26,6 @@ const AddFile = ({ history }) => {
const categories = useSelector(selectCategories);
const tags = useSelector(selectTags);
const [selectedCategory, setSelectedCategory] = useState(null);
const [note,setNote] = useState("")
const { t } = useTranslation();

useEffect(() => {
@@ -39,7 +38,6 @@ const AddFile = ({ history }) => {
setPdfFile(null);
setTitle("");
setShowMessage(true);
setNote("")
};

useEffect(() => {
@@ -78,45 +76,25 @@ const AddFile = ({ history }) => {
if (
title === "" ||
tagsIds.length === 0 ||
pdfFile === null ||
selectedCategory === null
pdfFile === null
)
return;

dispatch(
uploadFileReq({
title,
categoryId: selectedCategory,
categoryId: selectedCategory === null ? -1 : selectedCategory,
tagsIds,
fileToUpload: pdfFile,
note:note,
onSuccessUploadFile,
})
);
};

const handleChangeNote = (e) => {
setNote(e.target.value)
}

return (
<div className="files-page" style={{ paddingTop: "0px" }}>
<div className="l-t-rectangle"></div>
<div className="r-b-rectangle"></div>
{/* <div className="files-page-card">
<div className="files-page-card-title">
<h1>Title</h1>
</div>
<div className="files-page-card-content">
<input
type="text"
className="create-ad-form-control-first-step-input"
onChange={(e) => setTitle(e.target.value)}
value={title}
placeholder="Document Title"
/>
</div>
</div> */}

<div className="files-page-card">
<div className="files-page-card-title">
@@ -228,11 +206,6 @@ const AddFile = ({ history }) => {
</div>
</div>

<div className="files-page-card">
<div className="files-page-card-title"></div>
<textarea className="files-note" placeholder="Note..." value={note} onChange={(e) => handleChangeNote(e)}/>
</div>

<div
className="add-file-message"
style={

+ 500
- 0
src/pages/FilesPage/FileTable.js Ver arquivo

@@ -0,0 +1,500 @@
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;

+ 45
- 26
src/pages/FilesPage/FilesPage.js Ver arquivo

@@ -9,6 +9,7 @@ import {
import { selectCategories } from "../../store/selectors/categoriesSelector";
import table from "../../assets/images/table.png";
import { FILES_VIEW_PAGE } from "../../constants/pages";
import FileTable from "./FileTable";

const FilesPage = ({ history }) => {
const categories = useSelector(selectCategories);
@@ -33,38 +34,56 @@ const FilesPage = ({ history }) => {
);
};

const getNameHandler = (name) => {
if (name.length > 15) return name.substr(0, 15) + "...";
return name;
};

return (
<>
<div className="l-t-rectangle"></div>
<div className="r-b-rectangle"></div>
<div className="pl-144" style={{ paddingTop: "36px" }}>
<div style={{ marginBottom: "39px" }}>
<h1 className="page-heading">Kategorije</h1>
</div>
<div className="files-page-categories">
{categories &&
categories.map((category) => (
<div className="files-page-categories-category" key={category.id}>
<IconButton
className="c-btn c-btn--primary-outlined files-page-category-button"
data-testid="pattern-details-send-email"
onClick={() => getChildCategories(category.id, category.name)}
// onClick={() =>
//
// }
<div style={{ marginBottom: "50px" }}>
<div style={{ marginBottom: "30px" }}>
<h1 className="page-heading">Folderi</h1>
</div>
<div className="files-page-categories">
{categories &&
categories.map((category) => (
<div
className="files-page-categories-category"
key={category.id}
>
<img
style={{
marginRight: "5px",
width: "12px",
height: "12px",
}}
src={table}
/>
{category.name}
</IconButton>
</div>
))}
<IconButton
className="c-btn c-btn--primary-outlined files-page-category-button"
data-testid="pattern-details-send-email"
onClick={() =>
getChildCategories(category.id, category.name)
}
>
<img
style={{
marginRight: "5px",
width: "12px",
height: "12px",
}}
src={table}
/>
{getNameHandler(category.name)}
</IconButton>
</div>
))}
</div>
</div>

<div style={{ marginBottom: "50px" }}>
<div style={{ marginBottom: "30px" }}>
<h1 className="page-heading">Fajlovi</h1>
</div>
<div className="files-page-categories">
<FileTable />
</div>
</div>
</div>
</>

+ 5
- 10
src/pages/FilesPage/FilesViewPage.js Ver arquivo

@@ -198,7 +198,11 @@ const FilesViewPage = ({ history }) => {

const updateFileNoteHandler = () => {
dispatch(
updateFileReq({ id: openNoteModal.streamId, data: openNoteModal.note, onSuccessOpenNoteModal })
updateFileReq({
id: openNoteModal.streamId,
data: openNoteModal.note,
onSuccessOpenNoteModal,
})
);
};

@@ -470,16 +474,7 @@ const FilesViewPage = ({ history }) => {
</div>
)}
</div>
{/* <a
className="applicant-cv-button"
download={"Proba2.docx"}
href={`data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,${file2}`}
title="Download pdf document"
>
{t("common.download")} file
</a> */}
<Pagination
// size={matches ? "small" : "medium"}
size={"small"}
count={
parseInt(data.total) <= 6

+ 0
- 1
src/store/saga/filesSaga.js Ver arquivo

@@ -37,7 +37,6 @@ export function* uploadFileSaga({ payload }) {
for (let i = 0; i < payload.tagsIds.length; i++)
formData.append("tagsIds[]", payload.tagsIds[i]);
formData.append("fileToUpload", payload.fileToUpload);
formData.append("note", payload.note);
const result = yield call(uploadFileRequest, formData);
yield put(uploadFile(result.data));
payload.onSuccessUploadFile();

Carregando…
Cancelar
Salvar