Packages.jsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. import React, { useEffect, useState } from 'react';
  2. import { addControlPanelContentFocusedElement, removeControlPanelContentFocusedElement } from '../../actions/ControlPanelContent/controlPanelContentActions';
  3. import { addActiveElement, removeFocusedElement } from '../../actions/MainNavigation/mainNavigationActions';
  4. import { bulkAction, getPackageList, handleAction } from '../../ControlPanelService/Package';
  5. import DropdownFilter from '../../components/MainNav/Toolbar/DropdownFilter/DropdownFilter';
  6. import * as MainNavigation from '../../actions/MainNavigation/mainNavigationActions';
  7. import SearchInput from '../../components/MainNav/Toolbar/SearchInput/SearchInput';
  8. import { addFavorite, deleteFavorite } from '../../ControlPanelService/Favorites';
  9. import LeftButton from '../../components/MainNav/Toolbar/LeftButton/LeftButton';
  10. import Checkbox from '../../components/MainNav/Toolbar/Checkbox/Checkbox';
  11. import Select from '../../components/MainNav/Toolbar/Select/Select';
  12. import Toolbar from '../../components/MainNav/Toolbar/Toolbar';
  13. import Modal from '../../components/ControlPanel/Modal/Modal';
  14. import Package from '../../components/Package/Package';
  15. import Spinner from '../../components/Spinner/Spinner';
  16. import { useDispatch, useSelector } from 'react-redux';
  17. import './Packages.scss';
  18. import { Helmet } from 'react-helmet';
  19. import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
  20. const Packages = props => {
  21. const { i18n } = useSelector(state => state.session);
  22. const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
  23. const { focusedElement } = useSelector(state => state.mainNavigation);
  24. const dispatch = useDispatch();
  25. const [loading, setLoading] = useState(false);
  26. const [modal, setModal] = useState({
  27. text: '',
  28. visible: false,
  29. actionUrl: ''
  30. });
  31. const [state, setState] = useState({
  32. packages: [],
  33. packagesFav: [],
  34. toggledAll: false,
  35. sorting: i18n.Date,
  36. order: "descending",
  37. selection: [],
  38. totalAmount: ''
  39. });
  40. useEffect(() => {
  41. dispatch(addActiveElement('/list/package/'));
  42. dispatch(removeFocusedElement());
  43. dispatch(removeControlPanelContentFocusedElement());
  44. fetchData().then(() => setLoading(false));
  45. return () => {
  46. dispatch(removeControlPanelContentFocusedElement());
  47. }
  48. }, []);
  49. useEffect(() => {
  50. window.addEventListener("keydown", handleContentSelection);
  51. window.addEventListener("keydown", handleFocusedElementShortcuts);
  52. return () => {
  53. window.removeEventListener("keydown", handleContentSelection);
  54. window.removeEventListener("keydown", handleFocusedElementShortcuts);
  55. };
  56. }, [controlPanelFocusedElement, focusedElement, state.packages]);
  57. const handleContentSelection = event => {
  58. if (event.keyCode === 38 || event.keyCode === 40) {
  59. if (focusedElement) {
  60. dispatch(MainNavigation.removeFocusedElement());
  61. }
  62. }
  63. if (event.keyCode === 38) {
  64. event.preventDefault();
  65. handleArrowUp();
  66. } else if (event.keyCode === 40) {
  67. event.preventDefault();
  68. handleArrowDown();
  69. }
  70. }
  71. const initFocusedElement = packages => {
  72. packages[0]['FOCUSED'] = packages[0]['NAME'];
  73. setState({ ...state, packages });
  74. dispatch(addControlPanelContentFocusedElement(packages[0]['NAME']));
  75. }
  76. const handleArrowDown = () => {
  77. let packages = [...state.packages];
  78. if (focusedElement) {
  79. MainNavigation.removeFocusedElement();
  80. }
  81. if (controlPanelFocusedElement === '') {
  82. initFocusedElement(packages);
  83. return;
  84. }
  85. let focusedElementPosition = packages.findIndex(pack => pack.NAME === controlPanelFocusedElement);
  86. if (focusedElementPosition !== packages.length - 1) {
  87. let nextFocusedElement = packages[focusedElementPosition + 1];
  88. packages[focusedElementPosition]['FOCUSED'] = '';
  89. nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
  90. document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
  91. setState({ ...state, packages });
  92. dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
  93. }
  94. }
  95. const handleArrowUp = () => {
  96. let packages = [...state.packages];
  97. if (focusedElement) {
  98. MainNavigation.removeFocusedElement();
  99. }
  100. if (controlPanelFocusedElement === '') {
  101. initFocusedElement(packages);
  102. return;
  103. }
  104. let focusedElementPosition = packages.findIndex(pack => pack.NAME === controlPanelFocusedElement);
  105. if (focusedElementPosition !== 0) {
  106. let nextFocusedElement = packages[focusedElementPosition - 1];
  107. packages[focusedElementPosition]['FOCUSED'] = '';
  108. nextFocusedElement['FOCUSED'] = nextFocusedElement['NAME'];
  109. document.getElementById(nextFocusedElement['NAME']).scrollIntoView({ behavior: "smooth", block: "center" });
  110. setState({ ...state, packages });
  111. dispatch(addControlPanelContentFocusedElement(nextFocusedElement['NAME']));
  112. }
  113. }
  114. const handleFocusedElementShortcuts = event => {
  115. let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus');
  116. if (controlPanelFocusedElement && !isSearchInputFocused) {
  117. switch (event.keyCode) {
  118. case 8: return handleDelete();
  119. case 13: return handleEdit();
  120. default: break;
  121. }
  122. }
  123. }
  124. const handleEdit = () => {
  125. props.history.push(`/edit/package/?package=${controlPanelFocusedElement}`);
  126. }
  127. const handleDelete = () => {
  128. const { packages } = state;
  129. let currentPackageData = packages.filter(pack => pack.NAME === controlPanelFocusedElement)[0];
  130. displayModal(currentPackageData.delete_conf, `/api/v1/delete/package/index.php?package=${controlPanelFocusedElement}`);
  131. }
  132. const fetchData = () => {
  133. setLoading(true);
  134. return new Promise((resolve, reject) => {
  135. getPackageList()
  136. .then(result => {
  137. setState({
  138. ...state,
  139. packages: reformatData(result.data.data),
  140. packagesFav: result.data.packagesFav,
  141. totalAmount: result.data.totalAmount,
  142. selection: [],
  143. toggledAll: false
  144. });
  145. resolve();
  146. })
  147. .catch(err => console.error(err));
  148. });
  149. }
  150. const reformatData = data => {
  151. let packages = [];
  152. for (let i in data) {
  153. data[i]['NAME'] = i;
  154. data[i]['FOCUSED'] = controlPanelFocusedElement === i;
  155. packages.push(data[i]);
  156. }
  157. return packages;
  158. }
  159. const changeSorting = (sorting, order) => {
  160. setState({
  161. ...state,
  162. sorting,
  163. order
  164. });
  165. }
  166. const packages = () => {
  167. const { packages } = state;
  168. const packagesFav = { ...state.packagesFav };
  169. const result = [];
  170. packages.forEach(pack => {
  171. pack.FOCUSED = controlPanelFocusedElement === pack.NAME;
  172. if (packagesFav[pack.NAME]) {
  173. pack.STARRED = packagesFav[pack.NAME];
  174. } else {
  175. pack.STARRED = 0;
  176. }
  177. result.push(pack);
  178. });
  179. let sortedResult = sortArray(result);
  180. return sortedResult.map((item, index) => {
  181. return <Package data={item} key={index} toggleFav={toggleFav} checkItem={checkItem} handleModal={displayModal} />;
  182. });
  183. }
  184. const checkItem = name => {
  185. const { selection, packages } = state;
  186. let duplicate = [...selection];
  187. let packagesDuplicate = packages;
  188. let checkedItem = duplicate.indexOf(name);
  189. let incomingItem = packagesDuplicate.findIndex(pack => pack.NAME === name);
  190. packagesDuplicate[incomingItem].isChecked = !packagesDuplicate[incomingItem].isChecked;
  191. if (checkedItem !== -1) {
  192. duplicate.splice(checkedItem, 1);
  193. } else {
  194. duplicate.push(name);
  195. }
  196. setState({ ...state, packages: packagesDuplicate, selection: duplicate });
  197. }
  198. const sortArray = array => {
  199. const { order, sorting } = state;
  200. let sortingColumn = sortBy(sorting);
  201. if (order === "descending") {
  202. return array.sort((a, b) => (a[sortingColumn] < b[sortingColumn]) ? 1 : ((b[sortingColumn] < a[sortingColumn]) ? -1 : 0));
  203. } else {
  204. return array.sort((a, b) => (a[sortingColumn] > b[sortingColumn]) ? 1 : ((b[sortingColumn] > a[sortingColumn]) ? -1 : 0));
  205. }
  206. }
  207. const sortBy = sorting => {
  208. const { Date, Starred } = i18n;
  209. switch (sorting) {
  210. case Date: return 'DATE';
  211. case i18n['Package Name']: return 'NAME';
  212. case Starred: return 'STARRED';
  213. default: break;
  214. }
  215. }
  216. const toggleFav = (value, type) => {
  217. const { packagesFav } = state;
  218. let packagesFavDuplicate = packagesFav;
  219. if (type === 'add') {
  220. packagesFavDuplicate[value] = 1;
  221. addFavorite(value, 'package')
  222. .then(() => {
  223. setState({ ...state, packagesFav: packagesFavDuplicate });
  224. })
  225. .catch(err => {
  226. console.error(err);
  227. });
  228. } else {
  229. packagesFavDuplicate[value] = undefined;
  230. deleteFavorite(value, 'package')
  231. .then(() => {
  232. setState({ ...state, packagesFav: packagesFavDuplicate });
  233. })
  234. .catch(err => {
  235. console.error(err);
  236. });
  237. }
  238. }
  239. const toggleAll = toggled => {
  240. const packagesDuplicate = [...state.packages];
  241. if (toggled) {
  242. let packageNames = [];
  243. let packages = packagesDuplicate.map(pack => {
  244. packageNames.push(pack.NAME);
  245. pack.isChecked = true;
  246. return pack;
  247. });
  248. setState({ ...state, packages, selection: packageNames, toggledAll: toggled });
  249. } else {
  250. let packages = packagesDuplicate.map(pack => {
  251. pack.isChecked = false;
  252. return pack;
  253. });
  254. setState({ ...state, packages, selection: [], toggledAll: toggled });
  255. }
  256. }
  257. const bulk = action => {
  258. const { selection } = state;
  259. if (selection.length && action) {
  260. setLoading(true);
  261. bulkAction(action, selection)
  262. .then(result => {
  263. if (result.status === 200) {
  264. fetchData().then(() => {
  265. refreshMenuCounters();
  266. toggleAll(false);
  267. });
  268. }
  269. })
  270. .catch(err => console.error(err));
  271. }
  272. }
  273. const displayModal = (text, actionUrl) => {
  274. setModal({ ...modal, visible: !modal.visible, text, actionUrl })
  275. }
  276. const modalConfirmHandler = () => {
  277. if (!modal.actionUrl) {
  278. return modalCancelHandler();
  279. }
  280. modalCancelHandler();
  281. setLoading(true);
  282. handleAction(modal.actionUrl)
  283. .then(res => {
  284. if (res.data.error) {
  285. setLoading(false);
  286. return displayModal(res.data.error, '');
  287. }
  288. fetchData().then(() => refreshMenuCounters())
  289. })
  290. .catch(err => { setLoading(false); console.error(err); });
  291. }
  292. const refreshMenuCounters = () => {
  293. dispatch(refreshCounters()).then(() => setLoading(false));
  294. }
  295. const modalCancelHandler = () => {
  296. setModal({ ...modal, visible: false, text: '', actionUrl: '' })
  297. }
  298. return (
  299. <div className="packages">
  300. <Helmet>
  301. <title>{`Vesta - ${i18n.PACKAGE}`}</title>
  302. </Helmet>
  303. <Toolbar mobile={false} >
  304. <LeftButton name={i18n['Add Package']} href="/add/package/" showLeftMenu={true} />
  305. <div className="r-menu">
  306. <div className="input-group input-group-sm">
  307. <Checkbox toggleAll={toggleAll} toggled={state.toggledAll} />
  308. <Select list='packagesList' bulkAction={bulk} />
  309. <DropdownFilter changeSorting={changeSorting} sorting={state.sorting} order={state.order} list="packagesList" />
  310. <SearchInput handleSearchTerm={term => props.changeSearchTerm(term)} />
  311. </div>
  312. </div>
  313. </Toolbar>
  314. <div className="packages-wrapper">
  315. {
  316. loading
  317. ? <Spinner />
  318. : (<>
  319. {packages()}
  320. <div className="total">{state.totalAmount}</div>
  321. </>)
  322. }
  323. </div>
  324. <Modal
  325. onSave={modalConfirmHandler}
  326. onCancel={modalCancelHandler}
  327. show={modal.visible}
  328. text={modal.text} />
  329. </div>
  330. );
  331. }
  332. export default Packages;