Procházet zdrojové kódy

Changed category to be nullable

FE_dev
bronjaermin před 2 roky
rodič
revize
bcf60a70f5

+ 3
- 4
src/AppRoutes.js Zobrazit soubor

// STATS_PAGE, // STATS_PAGE,
REGISTER_PAGE, REGISTER_PAGE,
// CREATE_AD_PAGE, // CREATE_AD_PAGE,
FILES_VIEW_PAGE,
// FILES_VIEW_PAGE,
ADD_FILE, ADD_FILE,
FILES_PAGE, FILES_PAGE,
} from "./constants/pages"; } from "./constants/pages";
// import StatsPage from "./pages/StatsPage/StatsPage"; // import StatsPage from "./pages/StatsPage/StatsPage";
import RegisterPage from "./pages/RegisterPage/RegisterPage"; import RegisterPage from "./pages/RegisterPage/RegisterPage";
// import CreateAdPage from "./pages/AdsPage/CreateAdPage"; // 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 AddFile from "./pages/FilesPage/AddFile";
import FilesPage from "./pages/FilesPage/FilesPage"; import FilesPage from "./pages/FilesPage/FilesPage";


}, [location]); }, [location]);
return ( return (
<Switch> <Switch>
<Route exact path={FILES_VIEW_PAGE} component={FilesViewPage} />
<Route exact path={BASE_PAGE} component={LoginPage} /> <Route exact path={BASE_PAGE} component={LoginPage} />
<Route path={NOT_FOUND_PAGE} component={NotFoundPage} /> <Route path={NOT_FOUND_PAGE} component={NotFoundPage} />
{/* <Route path={USERS_PAGE} component={UsersPage} /> */} {/* <Route path={USERS_PAGE} component={UsersPage} /> */}
{/* <PrivateRoute exact path={CANDIDATES_PAGE} component={CandidatesPage} /> {/* <PrivateRoute exact path={CANDIDATES_PAGE} component={CandidatesPage} />
<PrivateRoute exact path={CREATE_AD_PAGE} component={CreateAdPage} /> */} <PrivateRoute exact path={CREATE_AD_PAGE} component={CreateAdPage} /> */}
{/* <PrivateRoute exact path={FILES_PAGE} component={FilesPage} /> */} {/* <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={ADD_FILE} component={AddFile}/>
<PrivateRoute exact path={FILES_PAGE} component={FilesPage}/> <PrivateRoute exact path={FILES_PAGE} component={FilesPage}/>
{/* <PrivateRoute {/* <PrivateRoute

+ 2
- 2
src/assets/styles/components/_files.scss Zobrazit soubor

} }


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

+ 2
- 29
src/pages/FilesPage/AddFile.js Zobrazit soubor

const categories = useSelector(selectCategories); const categories = useSelector(selectCategories);
const tags = useSelector(selectTags); const tags = useSelector(selectTags);
const [selectedCategory, setSelectedCategory] = useState(null); const [selectedCategory, setSelectedCategory] = useState(null);
const [note,setNote] = useState("")
const { t } = useTranslation(); const { t } = useTranslation();


useEffect(() => { useEffect(() => {
setPdfFile(null); setPdfFile(null);
setTitle(""); setTitle("");
setShowMessage(true); setShowMessage(true);
setNote("")
}; };


useEffect(() => { useEffect(() => {
if ( if (
title === "" || title === "" ||
tagsIds.length === 0 || tagsIds.length === 0 ||
pdfFile === null ||
selectedCategory === null
pdfFile === null
) )
return; return;


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


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

return ( return (
<div className="files-page" style={{ paddingTop: "0px" }}> <div className="files-page" style={{ paddingTop: "0px" }}>
<div className="l-t-rectangle"></div> <div className="l-t-rectangle"></div>
<div className="r-b-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">
<div className="files-page-card-title"> <div className="files-page-card-title">
</div> </div>
</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 <div
className="add-file-message" className="add-file-message"
style={ style={

+ 500
- 0
src/pages/FilesPage/FileTable.js Zobrazit soubor

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 Zobrazit soubor

import { selectCategories } from "../../store/selectors/categoriesSelector"; import { selectCategories } from "../../store/selectors/categoriesSelector";
import table from "../../assets/images/table.png"; import table from "../../assets/images/table.png";
import { FILES_VIEW_PAGE } from "../../constants/pages"; import { FILES_VIEW_PAGE } from "../../constants/pages";
import FileTable from "./FileTable";


const FilesPage = ({ history }) => { const FilesPage = ({ history }) => {
const categories = useSelector(selectCategories); const categories = useSelector(selectCategories);
); );
}; };


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

return ( return (
<> <>
<div className="l-t-rectangle"></div> <div className="l-t-rectangle"></div>
<div className="r-b-rectangle"></div> <div className="r-b-rectangle"></div>
<div className="pl-144" style={{ paddingTop: "36px" }}> <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>
</div> </div>
</> </>

+ 5
- 10
src/pages/FilesPage/FilesViewPage.js Zobrazit soubor



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


</div> </div>
)} )}
</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 <Pagination
// size={matches ? "small" : "medium"}
size={"small"} size={"small"}
count={ count={
parseInt(data.total) <= 6 parseInt(data.total) <= 6

+ 0
- 1
src/store/saga/filesSaga.js Zobrazit soubor

for (let i = 0; i < payload.tagsIds.length; i++) for (let i = 0; i < payload.tagsIds.length; i++)
formData.append("tagsIds[]", payload.tagsIds[i]); formData.append("tagsIds[]", payload.tagsIds[i]);
formData.append("fileToUpload", payload.fileToUpload); formData.append("fileToUpload", payload.fileToUpload);
formData.append("note", payload.note);
const result = yield call(uploadFileRequest, formData); const result = yield call(uploadFileRequest, formData);
yield put(uploadFile(result.data)); yield put(uploadFile(result.data));
payload.onSuccessUploadFile(); payload.onSuccessUploadFile();

Načítá se…
Zrušit
Uložit