Advertisement
Guest User

Untitled

a guest
Jun 16th, 2019
70
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 21.28 KB | None | 0 0
  1. import React from "react";
  2. import {MuiThemeProvider, withStyles} from "@material-ui/core/styles";
  3. import {theme} from "../saasintegrations/theme.js";
  4. import InfoTab from "./saas/InfoTab.jsx";
  5. import MediaTab from "./tabs/media_tab/MediaTab.jsx";
  6. import UsersTab from "./tabs/saas_users/UsersTab";
  7. import ContractsTab from "./tabs/contracts_tab/ContractsTab.jsx";
  8. import HistoryTab from "./tabs/history_tab/HistoryTabWrapper.jsx";
  9. import Snackbar from "./componentsUtils/Snackbar.jsx";
  10. import DialogTitle from "@material-ui/core/DialogTitle";
  11. import Dialog from "@material-ui/core/Dialog";
  12. import Tabs from "@material-ui/core/Tabs";
  13. import Tab from "@material-ui/core/Tab";
  14. import Close from "@material-ui/icons/Close";
  15. import AssociateCatalogPopup from "./saas/CatalogAssociationPopup/CatalogAssociationPopup.jsx";
  16. import {OomnitzaDb} from "../api/api.coffee";
  17. import {OomnitzaUtility} from "../util/util.coffee";
  18. import LinkedIcon from "@material-ui/icons/Link";
  19. import UnlinkedIcon from "../saas/LinkCatalog";
  20. import _ from "lodash";
  21. import moment from "moment";
  22. import env from "../util/Environment.js";
  23. import {
  24. tabs,
  25. defaultSaasFieldsConfig,
  26. hiddenFields,
  27. getMeta,
  28. getContractsMeta,
  29. getDetails,
  30. getMedia,
  31. getMediaById,
  32. sendMedia,
  33. deleteMedia,
  34. updateSaas,
  35. wfTriggerCheck,
  36. getContracts,
  37. getFieldList,
  38. getLinkedStatus,
  39. isAutocomplete,
  40. getFieldsConfiguredOnSB,
  41. getSaasFromHistory,
  42. getInitialVisibleFields
  43. } from "./SaasDetailsUtils";
  44. import {ARCHIVE, NEW, EDIT, VERSION} from "./constants";
  45.  
  46. const db = new OomnitzaDb();
  47. const maxWidth = 1360;
  48. const maxHeight = 944;
  49.  
  50. const styles = {
  51. associationPanel: {
  52. marginRight: 15,
  53. display: "flex",
  54. alignItems: "center",
  55. fontSize: 14,
  56. color: "#8a8a8a"
  57. },
  58. dialogTitle: {
  59. height: 25,
  60. display: "flex",
  61. justifyContent: "space-between",
  62. alignItems: "center",
  63. backgroundColor: "#f5f5f5"
  64. },
  65. dialogSaasName: {
  66. maxWidth: 600,
  67. whiteSpace: "nowrap",
  68. overflow: "hidden",
  69. textOverflow: "ellipsis",
  70. fontSize: 20,
  71. fontWeight: "bold",
  72. color: "#222"
  73. },
  74. closeIcon: {
  75. color: "#8a8a8a",
  76. cursor: "pointer"
  77. },
  78. linkIconLinked: {
  79. marginLeft: 12,
  80. cursor: "pointer",
  81. color: "#99cc34"
  82. },
  83. linkIconUnlinked: {
  84. marginLeft: 12,
  85. cursor: "pointer",
  86. color: "#8a8a8a",
  87. "&:hover": {
  88. color: "#1f1f1f"
  89. }
  90. },
  91. linkedCatalogName: {
  92. maxWidth: 200,
  93. whiteSpace: "nowrap",
  94. overflow: "hidden",
  95. textOverflow: "ellipsis",
  96. color: "#000000",
  97. },
  98. paper: {
  99. position: "relative",
  100. maxWidth: maxWidth,
  101. maxHeight: maxHeight,
  102. overflow: "hidden",
  103. height: "calc(100% - 40px)",
  104. width: "100%",
  105. "& *": {
  106. fontFamily: "Roboto"
  107. }
  108. },
  109. tabsContainer: {
  110. margin: "0 12px"
  111. },
  112. tabsRoot: {
  113. backgroundColor: "#f5f5f5"
  114. },
  115. selectedTab: {
  116. color: "#1f1f1f"
  117. },
  118. disabledTab: {
  119. color: "#e0e0e0"
  120. },
  121. rootTab: {
  122. minWidth: 0,
  123. fontSize: "14px",
  124. fontWeight: 500,
  125. },
  126. snackBarError: {
  127. marginBottom: 20
  128. }
  129. };
  130.  
  131. class SaasDetailsPopup extends React.Component {
  132. constructor(props) {
  133. super(props);
  134. this.state = {
  135. openStats: false,
  136. mode: props.mode,
  137. error: false,
  138. errorMessage: "",
  139. open: props.open,
  140. showActions: false,
  141. currentTab: this.props.initPage,
  142. meta: {},
  143. origFields: [],
  144. origData: {},
  145. fields: [],
  146. errorModel: {},
  147. data: [],
  148. tabsDisabled: false,
  149. media: [],
  150. history: [],
  151. contracts: [],
  152. contractsMeta: {},
  153. workflowList: [],
  154. showAssociatePopup: false,
  155. dateFormat: "MM/DD/YY",
  156. visibleFields: getInitialVisibleFields()
  157. };
  158. this.loadData();
  159. }
  160.  
  161. componentWillReceiveProps(nextProps) {
  162. this.setState({ //TODO navigation to users tab goes here
  163. open: nextProps.open,
  164. media: [],
  165. history: [],
  166. contracts: [],
  167. showActions: false
  168. });
  169. }
  170.  
  171. render() {
  172. const {classes, saas_id} = this.props;
  173. const {currentTab, open, data, showAssociatePopup, error, errorMessage} = this.state;
  174. return (<MuiThemeProvider theme={theme}>
  175. <Dialog
  176. aria-labelledby="simple-dialog-title"
  177. open={open}
  178. classes={{
  179. paper: classes.paper
  180. }}
  181. >
  182. {showAssociatePopup
  183. &&
  184. <AssociateCatalogPopup
  185. saas_id={saas_id}
  186. handleChangeCatalogLink={this.handleChangeCatalogLink}
  187. handleCancelClick={this.toggleAssociatePopup}/>
  188. }
  189. <DialogTitle
  190. id="simple-dialog-title"
  191. disableTypography
  192. classes={{
  193. root: classes.dialogTitle
  194. }}
  195. >
  196. <h2 className={classes.dialogSaasName}>
  197. {data.saas_name}
  198. </h2>
  199. <div style={{display: "flex", alignItems: "center"}}>
  200. {this.getAssociationCatalogControl()}
  201. <Close onClick={this.handleClose} className={classes.closeIcon}/>
  202. </div>
  203. </DialogTitle>
  204. <Tabs
  205. value={currentTab}
  206. indicatorColor="primary"
  207. style={{color: "#09c"}}
  208. onChange={this.changeTab}
  209. classes={{
  210. flexContainer: classes.tabsContainer,
  211. root: classes.tabsRoot
  212. }}
  213. >
  214. {this.getTabs()}
  215. </Tabs>
  216. {this.getTabsContent(currentTab)}
  217. <Snackbar
  218. style={{marginBottom: 20}}
  219. open={error}
  220. message={errorMessage}
  221. handleClose={this.onCloseErrorMsg}
  222. />
  223. </Dialog>
  224. </MuiThemeProvider>);
  225. }
  226.  
  227. toggleAssociatePopup = () => {
  228. const showAssociatePopup = !this.state.showAssociatePopup;
  229. this.setState({showAssociatePopup});
  230. };
  231.  
  232. handleChangeCatalogLink = () => {
  233. this.toggleAssociatePopup();
  234. this.loadData();
  235. };
  236.  
  237. getAssociationCatalogControl = () => {
  238. const {classes} = this.props;
  239. const {data, mode} = this.state;
  240. const linked = getLinkedStatus(data);
  241. return (
  242. <div className={classes.associationPanel}>
  243. {linked
  244. ?
  245. <p>Linked with: <span className={classes.linkedCatalogName}> {data.saas_name}</span></p>
  246. :
  247. <span>This software is not associated with catalog</span>}
  248. {mode === EDIT
  249. &&
  250. linked
  251. ?
  252. <LinkedIcon className={classes.linkIconLinked} onClick={this.toggleAssociatePopup}/>
  253. :
  254. <UnlinkedIcon className={classes.linkIconUnlinked} onClick={this.toggleAssociatePopup}/>
  255. }
  256. </div>);
  257. };
  258.  
  259. loadData = () => {
  260. const {mode, saas_id} = this.props;
  261. $.when(
  262. getMeta(),
  263. getDetails(saas_id),
  264. getContractsMeta(),
  265. this.getDateFormat(),
  266. ).then(([meta], [data], [contractsMeta], dateFormat) => {
  267. const errorModel = getErrorsModel(meta);
  268. this.setState({
  269. meta,
  270. contractsMeta,
  271. data,
  272. origData: _.cloneDeep(data),
  273. errorModel,
  274. dateFormat,
  275. });
  276. getFieldsConfiguredOnSB("SAAS", getInitialVisibleFields())
  277. .then(visibleFields => {
  278. //const fields = getFieldList(meta, data, mode, visibleFields);
  279. this.setState({visibleFields});
  280. });
  281. });
  282. };
  283.  
  284. changeTab = (event, value) => {
  285. this.setState({currentTab: value});
  286. };
  287.  
  288. getTabs = () => {
  289. const {classes: {rootTab: root, selectedTab: selected, disabledTab: disabled}} = this.props;
  290. const {tabsDisabled, currentTab} = this.state;
  291.  
  292. return tabs.map(({label, id}) => {
  293. return <Tab
  294. key={id}
  295. value={id}
  296. disabled={currentTab !== id && tabsDisabled}
  297. label={label}
  298. classes={{root, selected, disabled}}
  299. />;
  300. });
  301. };
  302.  
  303. getTabsContent = (currentTab) => {
  304. const {
  305. showActions, fields, media, history, workflowList, errorModel, data, contracts, contractsMeta,
  306. dateFormat, openStats, error
  307. } = this.state;
  308. const {initData, saas_id} = this.props;
  309. let filteredFields = this.filterByRelationalAndCleanUpErrors(fields);
  310. if (currentTab === "info") {
  311. return <InfoTab
  312. fields={filteredFields}
  313. {...data}
  314. errorModel={errorModel}
  315. image={`/api/v3/saas/${saas_id}/thumbnail?size=215`}
  316. width={"240px"}
  317. workflowList={workflowList}
  318. onBlur={this.validateField}
  319. showActions={showActions}
  320. handleChange={this.handleChange}
  321. handleCancel={this.handleCancel}
  322. handleSave={this.handleSave}
  323. onAutocomplete={this.onAutocomplete}
  324. onAddValue={this.onAddValue}
  325. />;
  326. } else if (currentTab === "media") {
  327. return <MediaTab
  328. getMedia={this.getMedia}
  329. sendMedia={this.sendMedia}
  330. deleteMedia={this.deleteMedia}
  331. toggleDisplayPhoto={this.toggleDisplayPhoto}
  332. mediaList={media}
  333. />;
  334. } else if (currentTab === "history") {
  335. return <HistoryTab
  336. previewVersion={this.previewVersionFromHistory}
  337. getHistoryList={this.getHistoryList}
  338. historyList={history}
  339. />;
  340. } else if (currentTab === "users") {
  341. return <UsersTab
  342. error={error}
  343. openStats={openStats}
  344. onCloseStats={this.onCloseStats}
  345. onOpenStats={this.onOpenStats}
  346. refreshSaas={this.refreshSaas}
  347. usageStatistic={data.usage_statistic}
  348. saasId={data.saas_id}
  349. dateFormat={dateFormat}
  350. initData={initData}
  351. failCallback={this.onOpenErrorMsg}
  352. />;
  353. } else if (currentTab === "contracts") {
  354. return <ContractsTab
  355. saasId={data.saas_id}
  356. clearContractsList={this.clearContractsList}
  357. getContracts={this.getContracts.bind(this)}
  358. contracts={contracts}
  359. contractMeta={contractsMeta}
  360. />;
  361. }
  362. };
  363.  
  364. onAddValue = (id, value) => {
  365. this.handleChange({id, value: {value, label: value}});
  366. };
  367.  
  368. onAutocomplete = (fieldId, value, data_type) => {
  369. let {searchUsers, searchLocations, searchField} = this.props;
  370. if (data_type === "USERS") {
  371. return searchUsers(value);
  372. } else if (data_type === "LOCATION") {
  373. return searchLocations(value);
  374. }
  375. return searchField(fieldId, value);
  376. };
  377.  
  378. handleChange = ({id, value}) => {
  379. let currentFields = _.map(this.state.fields, ((field) => {
  380. if (field.id === id) {
  381. return _.extend({}, field, {value: _.isNull(value) ? "" : value});
  382. }
  383. return field;
  384. }));
  385. const isDataSame = this.compareFieldSets(this.filterByRelationalAndCleanUpErrors(currentFields), this.state.origFields);
  386. this.setState({fields: currentFields, showActions: !isDataSame, tabsDisabled: !isDataSame}, () => {
  387. this.validateField(id, "change");
  388. this.wfTriggerCheck(id, value);
  389. });
  390. };
  391.  
  392. compareFieldSets = (current, original) => {
  393. const origMap = original.reduce(((obj, {value, id}) => {
  394. obj[id] = value;
  395. return obj;
  396. }), {});
  397. return _.every(current, (field) => {
  398. return _.isEqual(field.value, origMap[field.id]);
  399. });
  400. };
  401.  
  402. validateField = (id, event) => {
  403. let {errorModel} = this.state;
  404. _.map(this.filterByRelationalAndCleanUpErrors(this.state.fields), ((field) => {
  405. if (field.id === id) {
  406. errorModel[field.id] = this.getErrorMessage(id, field, event);
  407. }
  408. }));
  409. this.setState({errorModel: errorModel});
  410. };
  411.  
  412. getErrorMessage = (id, field, event) => {
  413. if (this.shouldValidate(field, event) && this.isMandatory(field)) {
  414. return "This is a mandatory field";
  415. }
  416. return "";
  417. };
  418.  
  419. shouldValidate = (field, event) => {
  420. let {errorModel} = this.state;
  421. return event === "blur" || (event === "change" && !!errorModel[field.id]);
  422. };
  423.  
  424. isMandatory = (field) => {
  425. return !_.trim(field.value) && field.mandatory;
  426. };
  427.  
  428. validateOnSave = () => {
  429. let {errorModel} = this.state;
  430. _.map(this.filterByRelationalAndCleanUpErrors(this.state.fields), ((field) => {
  431. if (this.isMandatory(field)) {
  432. errorModel[field.id] = "This is a mandatory field";
  433. } else {
  434. errorModel[field.id] = "";
  435. }
  436. }));
  437. this.setState({errorModel: errorModel});
  438. };
  439.  
  440. handleCancel = () => {
  441. this.setState({
  442. fields: _.cloneDeep(this.state.origFields),
  443. errorModel: {},
  444. showActions: false,
  445. tabsDisabled: false,
  446. workflowList: []
  447. });
  448. };
  449.  
  450. handleClose = () => {
  451. this.setState({open: false});
  452. };
  453.  
  454. handleSave = () => {
  455. const {saas_id} = this.props;
  456. this.validateOnSave();
  457. if (this.noErrors()) {
  458. let data = this.filterFieldOnSave();
  459. updateSaas(saas_id, data).then(() => {
  460. this.setState({showActions: false, tabsDisabled: false}, this.loadData);
  461. }).fail(oom.util.failCallback);
  462. }
  463. };
  464.  
  465. noErrors = () => {
  466. return _.every(this.state.errorModel, (field) => {
  467. return !field;
  468. });
  469. };
  470.  
  471. wfTriggerCheck = (id, value) => {
  472. const {saas_id} = this.props;
  473. return wfTriggerCheck(saas_id, id, value).then((response) => {
  474. this.setState({workflowList: response});
  475. });
  476. };
  477.  
  478. filterByRelationalAndCleanUpErrors = (fields) => {
  479. const {errorModel} = this.state;
  480. return _.filter(fields, (field) => {
  481. let shouldShow = this.shouldShowByRelational(field, fields);
  482. if (!shouldShow) {
  483. errorModel[field.id] = "";
  484. }
  485. return shouldShow;
  486. });
  487. };
  488.  
  489. shouldShowByRelational = ({dependencies}, currentFields) => {
  490. const fieldValueMap = createMap(currentFields);
  491. if (!_.isEmpty(dependencies)) {
  492. return _.every(dependencies, (values, id) => {
  493. let {value, data_type} = fieldValueMap[id];
  494. if (data_type === "LOCATION") {
  495. return _.includes(values, _.get(value, "value", value));
  496. }
  497. return _.includes(values, value);
  498. });
  499. }
  500. return true;
  501. };
  502.  
  503. filterFieldOnSave = () => {
  504. const {fields, origFields} = this.state;
  505. let origFieldsMap = origFields.reduce(((obj, {value, id}) => {
  506. obj[id] = value;
  507. return obj;
  508. }), {});
  509.  
  510. let transformedValues = _.map(this.filterByRelationalAndCleanUpErrors(fields), ({value, id, data_type, searchhelp_type}) => {
  511. let newValue = value;
  512. if (isAutocomplete(data_type)) {
  513. newValue = _.has(value, "value") ? {id: value.value, label: value.label} : value;
  514. } else if (isCustomAutocomplete(searchhelp_type)) {
  515. newValue = _.get(value, "value", value);
  516. } else if (data_type === "DATE") {
  517. newValue = moment(value).unix();
  518. }
  519. return {id, value: newValue};
  520. });
  521.  
  522. let changedFields = _.filter(transformedValues, ({value, id}) => {
  523. return !_.isEqual(value, origFieldsMap[id]);
  524. });
  525.  
  526. return changedFields.reduce(((obj, {value, id}) => {
  527. obj[id] = value;
  528. return obj;
  529. }), {});
  530. };
  531.  
  532. getMedia = () => {
  533. const {saas_id} = this.props;
  534. return getMedia(saas_id).then((media) => this.setState({media}));
  535. };
  536.  
  537. sendMedia = (data) => {
  538. const {saas_id} = this.props;
  539. return sendMedia(saas_id, data);
  540. };
  541. deleteMedia = (id) => {
  542. const {saas_id} = this.props;
  543. return deleteMedia(saas_id, id);
  544. };
  545. toggleDisplayPhoto = (id) => {
  546. const {saas_id} = this.props;
  547. const {media} = this.state;
  548.  
  549. return db.v3.query.patch(["saas", saas_id, "media", id, "default_display"], {})
  550. .then(() => {
  551. return getMediaById(saas_id, id)
  552. })
  553. .then(mediaItem => {
  554. this.setState({media: media.map((item) => id === item.uid ? mediaItem : item)})
  555. });
  556. };
  557. getHistoryList = (skipCount, params) => {
  558. const {saas_id} = this.props;
  559. const queryParams = ["limit=100", `skip=${skipCount}`, ...params];
  560. return db.v3.query.get(["saas", saas_id, "changes-history"], queryParams)
  561. .then(({data: history, facets}) => {
  562. if (skipCount) {
  563. this.setState({history: this.state.history.concat(history)}); //TODO uniq - если накладыватся элементы списка
  564. } else {
  565. this.setState({history});
  566. }
  567. return [facets, skipCount];
  568. });
  569. };
  570.  
  571. previewVersionFromHistory = (version) => {
  572. const {saas_id} = this.props;
  573. const {meta} = this.state;
  574. return getSaasFromHistory(saas_id, version).then((data) => {
  575. this.setState({
  576. mode: VERSION,
  577. tabsDisabled: true,
  578. currentTab: "info"
  579. });
  580. getFieldsConfiguredOnSB("SAAS", defaultSaasFieldsConfig).then(visibleFields => {
  581. const fields = getFieldList(meta, data, mode, visibleFields);
  582. this.setState({
  583. fields,
  584. origFields: _.cloneDeep(fields)
  585. });
  586. }).fail(oom.util.failCallback);
  587. })
  588. };
  589.  
  590. refreshSaas = () => {
  591. return getDetails().then(data => {
  592. this.setState({data})
  593. });
  594. };
  595.  
  596. onOpenStats = () => {
  597. this.setState({openStats: true});
  598. };
  599.  
  600. onCloseStats = () => {
  601. this.setState({openStats: false});
  602. };
  603.  
  604. getContracts = (skipCount = 0) => {
  605. const {saas_id} = this.props;
  606. return getContracts(saas_id, skipCount)
  607. .then(contracts => {
  608. if (skipCount) { //TODO uniq - если накладыватся элементы списка
  609. this.setState({contracts: this.state.contracts.concat(contracts)});
  610. } else {
  611. this.setState({contracts});
  612. }
  613. });
  614. };
  615.  
  616. clearContractsList = () => {
  617. this.setState({contracts: []});
  618. };
  619.  
  620. onOpenErrorMsg = (errorMessage) => {
  621. this.setState({error: true, errorMessage})
  622. };
  623.  
  624. onCloseErrorMsg = () => {
  625. this.setState({error: false, errorMessage: ""})
  626. };
  627.  
  628. getDateFormat = () => {
  629. return env.getDateFormat({moment: true});
  630. };
  631. }
  632.  
  633. function isCustomAutocomplete(searchhelp_type) {
  634. return searchhelp_type === "dynamic";
  635. }
  636.  
  637. function getErrorsModel(meta) {
  638. let errorModel = {};
  639. _.map(_.keys(_.omit(meta, hiddenFields)), (key) => {
  640. errorModel[key] = "";
  641. });
  642. return errorModel;
  643. }
  644.  
  645. function createMap(fieldList) {
  646. return fieldList.reduce(((obj, {value, data_type, id}) => {
  647. if (data_type === "CHECKBOX") {
  648. obj[id] = {value: value == "1" ? "Checked" : "Unchecked", data_type};
  649. } else {
  650. obj[id] = {value, data_type};
  651. }
  652. return obj;
  653. }), {});
  654. }
  655.  
  656. export default withStyles(styles)(SaasDetailsPopup);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement