Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

FilesViewPage.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. import { Button, Pagination } from "@mui/material";
  2. import React from "react";
  3. import { useEffect } from "react";
  4. import { useState } from "react";
  5. import { useSelector } from "react-redux";
  6. import { useDispatch } from "react-redux";
  7. import FilterButton from "../../components/Button/FilterButton";
  8. import DocsFilters from "../../components/Docs/DocsFilters";
  9. import { FETCH_FILES_LOADING } from "../../store/actions/files/fileActionConstants";
  10. import {
  11. deleteFileReq,
  12. getFileFiltersReq,
  13. getFilesReq,
  14. updateFileReq,
  15. } from "../../store/actions/files/fileActions";
  16. import { setContent } from "../../store/actions/files/fileActions";
  17. import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors";
  18. import Fade from "@mui/material/Fade";
  19. import { useTranslation } from "react-i18next";
  20. import searchImage from "../../assets/images/search.png";
  21. import IconButton from "../../components/IconButton/IconButton";
  22. import { ADD_FILE, FILES_PAGE } from "../../constants/pages";
  23. import PropTypes from "prop-types";
  24. import FileViewer from "react-file-viewer";
  25. import { useLocation } from "react-router";
  26. import deleteIcon from "../../assets/images/delete.png";
  27. import editIcon from "../../assets/images/edit.png";
  28. import xIcon from "../../assets/images/x.png";
  29. import ConfirmDialog from "../../components/MUI/ConfirmDialog";
  30. import CustomModal from "../../components/UI/CustomModal";
  31. const FilesViewPage = ({ history }) => {
  32. const [toggleFiltersDrawer, setToggleFiltersDrawer] = useState(false);
  33. const [page, setPage] = useState(1);
  34. const { filters } = useSelector((s) => s.fileFilters);
  35. const { data } = useSelector((s) => s.files);
  36. const { t } = useTranslation();
  37. const [isSearchFieldVisible, setIsSearchFieldVisible] = useState(false);
  38. const [file, setFile] = useState(null);
  39. const [openNoteModal, setOpenNoteModal] = useState({
  40. open: false,
  41. note: null,
  42. streamId: "",
  43. });
  44. const [fileForDelete, setFileForDelete] = useState({
  45. open: false,
  46. title: "",
  47. streamId: null,
  48. });
  49. const { state } = useLocation();
  50. const dispatch = useDispatch();
  51. if (state === undefined) {
  52. history.replace(FILES_PAGE);
  53. }
  54. const handleToggleFiltersDrawer = () => {
  55. setToggleFiltersDrawer((oldState) => !oldState);
  56. setFile(null);
  57. };
  58. useEffect(() => {
  59. handleFilters(page);
  60. }, [file]);
  61. useEffect(() => {
  62. dispatch(getFileFiltersReq());
  63. dispatch(
  64. getFilesReq({
  65. payload: {
  66. pageSize: 6,
  67. currentPage: page,
  68. categories: [state && state.category],
  69. extensions: [],
  70. tags: [],
  71. content: "",
  72. },
  73. })
  74. );
  75. }, []);
  76. const handleChange = (_, value) => {
  77. handleFilters(value);
  78. setPage(value);
  79. };
  80. const handleFilters = (value) => {
  81. var catFilters = [state && state.category];
  82. var extFilters = [];
  83. filters.extensions
  84. ?.filter((n) => n.isChecked)
  85. .forEach((m) => extFilters.push(m.name));
  86. var tagFilters = [];
  87. filters.tags
  88. ?.filter((n) => n.isChecked)
  89. .forEach((m) => tagFilters.push(m.name));
  90. dispatch(
  91. getFilesReq({
  92. payload: {
  93. pageSize: 6,
  94. currentPage: value,
  95. categories: catFilters,
  96. extensions: extFilters,
  97. tags: tagFilters,
  98. content: filters.content === null ? "" : filters.content,
  99. },
  100. })
  101. );
  102. };
  103. const isLoading = useSelector(
  104. selectIsLoadingByActionType(FETCH_FILES_LOADING)
  105. );
  106. const handleChangeVisibility = () => {
  107. setIsSearchFieldVisible(!isSearchFieldVisible);
  108. };
  109. const stopPropagation = (e) => {
  110. e.stopPropagation();
  111. };
  112. const handleChangeContent = (value) => {
  113. dispatch(setContent(value));
  114. };
  115. const deleteFileHandler = (stream_id) => {
  116. dispatch(deleteFileReq({ id: stream_id }));
  117. if (file !== null && stream_id === file.streamId) {
  118. setFile(null);
  119. }
  120. };
  121. const handleKeyDown = (event) => {
  122. if (event.key === "Enter" && filters.content !== "") {
  123. var catFilters = [state && state.category];
  124. var extFilters = [];
  125. filters.extensions
  126. ?.filter((n) => n.isChecked)
  127. .forEach((m) => extFilters.push(m.name));
  128. var tagFilters = [];
  129. filters.tags
  130. ?.filter((n) => n.isChecked)
  131. .forEach((m) => tagFilters.push(m.name));
  132. dispatch(
  133. getFilesReq({
  134. payload: {
  135. pageSize: 6,
  136. currentPage: page,
  137. categories: catFilters,
  138. extensions: extFilters,
  139. tags: tagFilters,
  140. content: filters.content,
  141. },
  142. })
  143. );
  144. }
  145. };
  146. const input = (
  147. <div>
  148. <input
  149. placeholder="Pretrazi..."
  150. value={filters.content === undefined ? "" : filters.content}
  151. onChange={(e) => handleChangeContent(e.target.value)}
  152. onKeyDown={handleKeyDown}
  153. className="candidate-search-field"
  154. onClick={stopPropagation}
  155. role="input"
  156. />
  157. </div>
  158. );
  159. const onError = (e) => {
  160. console.log(e, "error in file-viewer");
  161. };
  162. const displayFile = (fileStream, streamId, fileType) => {
  163. setFile({ fileStream, streamId, fileType });
  164. };
  165. const getHrefForDownload = (fileType, fileStream) => {
  166. if (fileType === "docx") {
  167. return `data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,${fileStream}`;
  168. } else if (fileType === "doc") {
  169. return `data:application/msword;base64,${fileStream}`;
  170. } else {
  171. return `data:application/pdf;base64,${fileStream}`;
  172. }
  173. };
  174. const updateFileNoteHandler = () => {
  175. dispatch(
  176. updateFileReq({ id: openNoteModal.streamId, data: openNoteModal.note, onSuccessOpenNoteModal })
  177. );
  178. };
  179. const onSuccessOpenNoteModal = () => {
  180. setOpenNoteModal({
  181. open: false,
  182. note: null,
  183. streamId: "",
  184. });
  185. };
  186. return isLoading ? (
  187. <div>
  188. <div className="l-t-rectangle"></div>
  189. <div className="r-b-rectangle"></div>
  190. <div className="loader-container h-withHeader">
  191. <span>Loading</span>
  192. </div>
  193. </div>
  194. ) : (
  195. <>
  196. <ConfirmDialog
  197. open={fileForDelete.open}
  198. title="Brisanje dokumenta"
  199. subtitle={fileForDelete.title}
  200. imgSrc={deleteIcon}
  201. content="Da li ste sigurni za brisanje dokumenta?"
  202. onClose={() => {
  203. setFileForDelete({ open: false, title: "", streamId: null });
  204. }}
  205. onConfirm={() => {
  206. deleteFileHandler(fileForDelete.streamId);
  207. setFileForDelete({ open: false, title: "", streamId: null });
  208. }}
  209. />
  210. <CustomModal
  211. classes="files-custom-modal"
  212. open={openNoteModal.open}
  213. onCloseModal={() =>
  214. setOpenNoteModal({ open: false, note: null, streamId: "" })
  215. }
  216. >
  217. <div className="add-pattern-modal-header">
  218. <div className="add-pattern-modal-header-title">
  219. <div className="add-pattern-modal-header-title-image">
  220. <img src={editIcon} alt="plus" />
  221. </div>
  222. <div className="add-pattern-modal-header-title-title">
  223. <p>Edit text</p>
  224. </div>
  225. <div className="add-pattern-modal-header-title-sub">
  226. <sub> | Note</sub>
  227. </div>
  228. </div>
  229. <div
  230. className="add-pattern-modal-header-close"
  231. onClick={() =>
  232. setOpenNoteModal({ open: false, note: null, streamId: "" })
  233. }
  234. >
  235. <img src={xIcon} alt="close" />
  236. </div>
  237. </div>
  238. <div className="files-edit-note">
  239. <textarea
  240. className="files-note"
  241. value={openNoteModal.note}
  242. onChange={(e) =>
  243. setOpenNoteModal((oldState) => ({
  244. ...oldState,
  245. note: e.target.value,
  246. }))
  247. }
  248. />
  249. </div>
  250. <div className="files-custom-modal-buttons">
  251. <Button
  252. type="button"
  253. variant="contained"
  254. className="c-btn c-btn--primary-outlined"
  255. onClick={() =>
  256. setOpenNoteModal({ open: false, note: null, streamId: "" })
  257. }
  258. >
  259. Close
  260. </Button>
  261. <Button
  262. type="button"
  263. variant="contained"
  264. className="c-btn c-btn--primary"
  265. onClick={updateFileNoteHandler}
  266. >
  267. Save Changes
  268. </Button>
  269. </div>
  270. </CustomModal>
  271. <div onClick={() => setIsSearchFieldVisible(false)}>
  272. <div className="l-t-rectangle"></div>
  273. <div className="r-b-rectangle"></div>
  274. <DocsFilters
  275. open={toggleFiltersDrawer}
  276. handleClose={handleToggleFiltersDrawer}
  277. setPage={setPage}
  278. category={state && state.category}
  279. />
  280. <div
  281. className="pl-144 flex-center"
  282. style={{
  283. paddingTop: "36px",
  284. justifyContent: "space-between",
  285. postion: "relative",
  286. }}
  287. >
  288. <h1 className="page-heading">Pregled dokumenata</h1>
  289. <div style={{ postion: "relative" }}>
  290. <Fade in={isSearchFieldVisible} timeout={500} className="proba">
  291. {input}
  292. </Fade>
  293. <Fade in={isSearchFieldVisible} timeout={500}>
  294. <div
  295. style={{
  296. position: "absolute",
  297. zIndex: 10000,
  298. right: 95,
  299. marginTop: 0,
  300. }}
  301. >
  302. <img src={searchImage} />
  303. </div>
  304. </Fade>
  305. </div>
  306. <div className="flex-center">
  307. <IconButton
  308. className="c-btn c-btn--primary-outlined candidate-btn search-field userPageBtn ml-20px no-padding custom-filter-button"
  309. onClick={handleChangeVisibility}
  310. >
  311. {t("candidates.search")}
  312. <img
  313. src={searchImage}
  314. alt="filter"
  315. className="candidates-image"
  316. />
  317. </IconButton>
  318. <FilterButton onShowFilters={handleToggleFiltersDrawer} />
  319. </div>
  320. </div>
  321. <div
  322. className="pl-144"
  323. style={{
  324. display: "flex",
  325. marginTop: "39px",
  326. flexDirection: "column",
  327. justifyContent: "space-between",
  328. minHeight: "500px",
  329. }}
  330. >
  331. <div className="table-cont">
  332. <div style={{ display: "flex", height: "300px" }}>
  333. <table
  334. className={"usersTable-users mini"}
  335. style={{
  336. width: file === null ? "100%" : "800px",
  337. height: "50%",
  338. }}
  339. >
  340. <thead>
  341. <tr
  342. className="headingRow headingRowFiles"
  343. style={{ cursor: "pointer" }}
  344. >
  345. <th>Putanja dokumenta</th>
  346. <th>Naziv dokumenta</th>
  347. <th>Tip dokumenta</th>
  348. {file === null ? <th>Veličina dokumenta</th> : ""}
  349. <th>Obrisi dokument</th>
  350. <th>Note</th>
  351. <th>Preuzmi dokument</th>
  352. </tr>
  353. </thead>
  354. <tbody>
  355. {data &&
  356. data.data?.length > 0 &&
  357. data.data?.map((n, index) => (
  358. <tr
  359. key={index}
  360. className="secondaryRow"
  361. onClick={() =>
  362. displayFile(n.file_stream, n.stream_id, n.file_type)
  363. }
  364. >
  365. <td className="docs-name">{n.fileName}</td>
  366. <td className="docs-name">{n.title}</td>
  367. <td>{n.file_type && n.file_type}</td>
  368. {file === null ? (
  369. <td className="profession">{n.cached_file_size}kB</td>
  370. ) : (
  371. ""
  372. )}
  373. <td className="profession">
  374. <IconButton
  375. className="c-btn c-btn--primary-outlined files-view-page-delete-btn"
  376. onClick={() =>
  377. setFileForDelete({
  378. open: true,
  379. title: n.title,
  380. streamId: n.stream_id,
  381. })
  382. }
  383. >
  384. <img
  385. style={{ width: "12px", height: "12px" }}
  386. src={deleteIcon}
  387. />
  388. </IconButton>
  389. </td>
  390. <td className="profession">
  391. <IconButton
  392. className="c-btn c-btn--primary-outlined files-view-page-delete-btn"
  393. onClick={() => {
  394. setOpenNoteModal({
  395. open: true,
  396. note: n.note,
  397. streamId: n.stream_id,
  398. });
  399. }}
  400. >
  401. <img
  402. style={{ width: "12px", height: "12px" }}
  403. src={editIcon}
  404. />
  405. </IconButton>
  406. </td>
  407. <td>
  408. <div onClick={stopPropagation}>
  409. <a
  410. className="applicant-cv-button"
  411. style={{
  412. width: "100px",
  413. height: "40px",
  414. padding: 8,
  415. }}
  416. download={n.title}
  417. href={getHrefForDownload(
  418. n.file_type,
  419. n.file_stream
  420. )}
  421. >
  422. {t("common.download")}
  423. </a>
  424. </div>
  425. </td>
  426. </tr>
  427. ))}
  428. </tbody>
  429. </table>
  430. {file && (
  431. <div
  432. style={{
  433. width: "500px",
  434. marginLeft: "30px",
  435. overflowY: "visible",
  436. }}
  437. >
  438. <FileViewer
  439. fileType={file.fileType}
  440. filePath={`data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,${file.fileStream}`}
  441. onError={onError}
  442. />
  443. </div>
  444. )}
  445. </div>
  446. {/* <a
  447. className="applicant-cv-button"
  448. download={"Proba2.docx"}
  449. href={`data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,${file2}`}
  450. title="Download pdf document"
  451. >
  452. {t("common.download")} file
  453. </a> */}
  454. <Pagination
  455. // size={matches ? "small" : "medium"}
  456. size={"small"}
  457. count={
  458. parseInt(data.total) <= 6
  459. ? 1
  460. : Math.ceil(parseInt(data.total) / 6)
  461. }
  462. color="primary"
  463. className="candidates-pagination"
  464. onChange={handleChange}
  465. shape="rounded"
  466. page={page}
  467. />
  468. <IconButton
  469. className="c-btn ads-page-btn c-btn--primary add-ad-btn filesPage"
  470. onClick={() => history.push(ADD_FILE)}
  471. >
  472. + Fajl
  473. </IconButton>
  474. </div>
  475. <div
  476. style={{
  477. display: "flex",
  478. justifyContent: "flex-end",
  479. marginBottom: "35px",
  480. }}
  481. ></div>
  482. </div>
  483. </div>
  484. </>
  485. );
  486. };
  487. FilesViewPage.propTypes = {
  488. history: PropTypes.shape({
  489. replace: PropTypes.func,
  490. push: PropTypes.func,
  491. location: PropTypes.shape({
  492. pathname: PropTypes.string,
  493. }),
  494. }),
  495. };
  496. export default FilesViewPage;