Home Reference Source

application/components/assignment/assignment-item.js

  1. import React from 'react';
  2. import _ from 'lodash';
  3.  
  4. import {connect} from 'react-redux';
  5. import {Toolbar, ToolbarGroup, ToolbarSeparator, ToolbarTitle} from 'material-ui/Toolbar';
  6. import {List, ListItem} from 'material-ui/List'
  7. import RaisedButton from 'material-ui/RaisedButton';
  8. import FlatButton from 'material-ui/FlatButton';
  9. import SelectField from 'material-ui/SelectField';
  10. import MenuItem from 'material-ui/MenuItem';
  11. import TextField from 'material-ui/TextField';
  12. import FontIcon from 'material-ui/FontIcon';
  13. import DatePicker from 'material-ui/DatePicker';
  14. import TimePicker from 'material-ui/TimePicker';
  15. import Paper from 'material-ui/Paper';
  16. import Divider from 'material-ui/Divider';
  17. import Subheader from 'material-ui/Subheader';
  18.  
  19. import {browserHistory, Link} from 'react-router';
  20. import Config from './../../config';
  21.  
  22. import Immutable from 'immutable';
  23. import moment from 'moment';
  24.  
  25. import CurrentUser from './../../current-user';
  26. import {sluggify} from './../../util/strings';
  27.  
  28. import {timeToFormat, htmlDecode} from './../../util/strings';
  29.  
  30. import LoadingIndicator from './../common/loading-indicator';
  31. import UserSearchBox from './../common/user-search-box';
  32. import SimpleEditor from './../common/simple-editor';
  33.  
  34. import BaseView from './../base-view';
  35. import DeleteButton from './../common/delete-button';
  36. import ContentSearchModal from './../common/content-search-modal';
  37.  
  38. import {Row, Col} from './../flexbox';
  39.  
  40. import {rawToHtml} from '../../util/rawToHtml';
  41.  
  42. import {ContentRow} from './../content/content-table';
  43.  
  44. import AssignmentDiscussion from './assignment-discussion';
  45.  
  46. import {
  47. workflowsMaybeFetch,
  48. workflowsFilterAssignmentOnly,
  49. workflowsFilterPrintOnly
  50. } from './../../redux/actions/workflow-actions';
  51.  
  52. import {
  53. issuesFetch,
  54. issuesMaybeFetch,
  55. issuesFilterOpen
  56. } from './../../redux/actions/issue-actions';
  57.  
  58. import {
  59. snackbarShowMessage,
  60. snackbarClose
  61. } from './../../redux/actions/snackbar-actions';
  62.  
  63. import {
  64. alertShowMessage
  65. } from './../../redux/actions/alert-actions';
  66.  
  67. import {
  68. globalHistoryPush,
  69. globalHistoryPop
  70. } from './../../redux/actions/global-history-actions';
  71.  
  72. import {
  73. assignmentsMaybeFetchCache,
  74. assignmentsFetchOne,
  75. assignmentsCheckIn,
  76. assignmentsCheckOut,
  77. assignmentsRemove,
  78. assignmentsUpdate,
  79. assignmentsCreate,
  80. assignmentsClearLock
  81. } from './../../redux/actions/assignment-actions';
  82.  
  83. import {
  84. usersMaybeFetch,
  85. usersFetchOne,
  86. usersFetch,
  87. usersRemove,
  88. userSetSelected,
  89. userUpdate,
  90. userCreate
  91. } from './../../redux/actions/user-actions';
  92.  
  93. import {
  94. applicationIsDirty
  95. } from './../../redux/actions/application-actions';
  96.  
  97. import LockButtons from './../lock-buttons';
  98.  
  99. class AssignmentItem extends BaseView {
  100.  
  101. constructor(props) {
  102. super(props);
  103.  
  104. this.getUuid = this.getUuid.bind(this);
  105. this.isNew = this.isNew.bind(this);
  106. this.isLocked = this.isLocked.bind(this);
  107. this.isUserEditable = this.isUserEditable.bind(this);
  108.  
  109. this.handleFormChange = this.handleFormChange.bind(this);
  110. this.handleEditorChange = this.handleEditorChange.bind(this);
  111.  
  112. this.handleCheckout = this.handleCheckout.bind(this);
  113. this.handleCheckin = this.handleCheckin.bind(this);
  114. this.handleSaveCheckIn = this.handleSaveCheckIn.bind(this);
  115. this.handleSave = this.handleSave.bind(this);
  116.  
  117. this.freezeAssignment = this.freezeAssignment.bind(this);
  118. this.thawAssignment = this.thawAssignment.bind(this);
  119.  
  120. this.state = {
  121. contentIsOpen: false,
  122. dirty: false,
  123. didDelete: false,
  124. edited: {
  125. users: [],
  126. children: []
  127. },
  128. actionStartDate: null,
  129. actionStartTime: null,
  130. actionEndDate: null,
  131. actionEndTime: null
  132. }
  133. }
  134.  
  135. getUuid() {
  136. if (this.props.uuid) {
  137. return this.props.uuid;
  138. }
  139.  
  140. if (this.props.params && this.props.params.id) {
  141. return this.props.params.id;
  142. }
  143.  
  144. return false;
  145. }
  146.  
  147. isNew() {
  148. if (!this.getUuid() || this.getUuid() === 'new') {
  149. return true;
  150. }
  151.  
  152. return false;
  153. }
  154.  
  155. isLocked() {
  156. if (this.isNew()) {
  157. return false;
  158. }
  159.  
  160. if (this.state.edited.lock) {
  161. return true;
  162. }
  163.  
  164. return false;
  165. }
  166.  
  167. isUserEditable() {
  168. if (this.isNew()) {
  169. return true;
  170. }
  171.  
  172. if (!this.state.edited.lock) {
  173. return false;
  174. }
  175.  
  176. if (this.state.edited.lock.user_id === CurrentUser.getId()) {
  177. return true;
  178. }
  179.  
  180. return false;
  181. }
  182.  
  183. getRouter() {
  184. return this.props.router;
  185. }
  186.  
  187. componentWillMount() {
  188. const {dispatch} = this.props;
  189.  
  190. dispatch(workflowsMaybeFetch())
  191. .then(() => dispatch(workflowsFilterAssignmentOnly()))
  192. .then(() => dispatch(workflowsFilterPrintOnly()))
  193. .then(() => dispatch(issuesFetch()))
  194. .then(() => dispatch(issuesFilterOpen()))
  195. .then(() => dispatch(usersMaybeFetch()));
  196.  
  197. if (!this.isNew()) {
  198. dispatch(assignmentsFetchOne(this.getUuid()))
  199. .then(() => {
  200. let assignment = this.props.assignments.items.find((item) => item.get('uuid') == this.getUuid());
  201.  
  202. let dirty = false;
  203.  
  204. // pause here for a second and see if we have a modified version in the global history state
  205. const savedState = this.props.globalHistory.items.find((item) => item.get('uuid') == this.getUuid());
  206.  
  207. if (savedState && savedState.size && savedState.get('isDirty')) {
  208. // we have a freeze dried state. reconstitute that
  209. // then set the dirty state to 'true'
  210. assignment = this.thawAssignment(savedState.get('cachedObject'));
  211. dirty = true;
  212. }
  213.  
  214. this.setState({'edited': assignment.toJS(), 'dirty': dirty});
  215. })
  216. .then(() => {
  217. if (this.state.edited.action_start_time) {
  218. this.setState({
  219. 'actionStartDate': moment.utc(this.state.edited.action_start_time).local().toDate(),
  220. 'actionStartTime': moment.utc(this.state.edited.action_start_time).local().toDate(),
  221. 'actionEndDate': moment.utc(this.state.edited.action_end_time).local().toDate(),
  222. 'actionEndTime': moment.utc(this.state.edited.action_end_time).local().toDate(),
  223. })
  224. }
  225. });
  226. } else {
  227. this.setState({
  228. 'edited': {
  229. 'children': [],
  230. 'users': []
  231. }
  232. });
  233. }
  234.  
  235. this.setState({'publishIsOpen': false});
  236. }
  237.  
  238. componentDidMount() {
  239. this.getRouter().setRouteLeaveHook(this.props.route, this.routerWillLeave.bind(this));
  240.  
  241. if (this.refs.slugTextField && !this.refs.slugTextField.input.value) {
  242. setTimeout(() => this.refs.slugTextField.focus(), 0);
  243. }
  244. }
  245.  
  246. routerWillLeave(nextLocation) {
  247. if (!this.isNew() && !this.state.didDelete) {
  248. const {dispatch} = this.props;
  249. dispatch(globalHistoryPush({
  250. 'label': this.state.edited.title ? this.state.edited.title : this.state.edited.slug,
  251. 'type': 'assignment',
  252. 'contentType': 'assignment',
  253. 'url': '/ceo/assignment/' + this.state.edited.uuid,
  254. 'uuid': this.state.edited.uuid,
  255. 'isDirty': this.state.dirty,
  256. 'cachedObject': (this.state.dirty ? this.freezeAssignment(this.state.edited) : {})
  257. }));
  258. }
  259.  
  260. if (this.isNew() && this.isUserEditable() && this.state.dirty && this.state.dirty === true) {
  261. return 'You have not saved this assignment! Any unsaved changes will be lost once you click OK. Click cancel and then save to preserve your work. Are you still sure you want to leave this item?';
  262. }
  263. }
  264.  
  265. freezeAssignment(assignment) {
  266. let toFreeze = assignment;
  267.  
  268. let rawContent = this.state.editorContent ? this.state.editorContent : toFreeze.content;
  269. toFreeze.content = rawContent;
  270.  
  271. return toFreeze;
  272. }
  273.  
  274. thawAssignment(assignment) {
  275. let toThaw = assignment;
  276.  
  277. this.state.editorContent = toThaw.get('content') ? toThaw.get('content') : '';
  278.  
  279. return toThaw;
  280. }
  281.  
  282. // callbacks to handle the user selection
  283. onSelectUser(user) {
  284. let assignment = this.state.edited;
  285. const existing = _.find(assignment.users, {'uuid': user.get('uuid')});
  286. if (!existing) {
  287. assignment.users.push(user.toJS());
  288. this.setState({'edited': assignment, dirty: true});
  289. this.props.dispatch(applicationIsDirty(true));
  290. }
  291. }
  292.  
  293. onRemoveUser(user) {
  294. let assignment = this.state.edited;
  295. assignment.users = assignment.users.filter((item, i) => {
  296. return user.uuid != item.uuid;
  297. });
  298.  
  299. this.setState({'edited': assignment, dirty: true});
  300. this.props.dispatch(applicationIsDirty(true));
  301. }
  302.  
  303. dueDateChange(empty, timestamp) {
  304. let assignment = this.state.edited;
  305.  
  306. assignment.due_at = moment(timestamp).utc().format('YYYY-MM-DD HH:mm:ss');
  307.  
  308. this.setState({'edited': assignment, dirty: true});
  309. this.props.dispatch(applicationIsDirty(true));
  310. }
  311.  
  312. actionDateChange(context, empty, date) {
  313. if (context == 'start') {
  314. this.setState({actionStartDate: date});
  315. this.setState({actionEndDate: date});
  316. }
  317. }
  318.  
  319. actionTimeChange(context, empty, time) {
  320. if (context == 'start') {
  321. this.setState({actionStartTime: time});
  322. } else if (context == 'end') {
  323. this.setState({actionEndTime: time});
  324. }
  325.  
  326. }
  327.  
  328. // state management
  329. handleSave(andCheckin = false) {
  330. const {dispatch} = this.props;
  331.  
  332. // all of the start and end times for assignment actions
  333. let actionStartDate = null;
  334. let actionStartTime = null;
  335. let actionEndDate = null;
  336. let actionEndTime = null;
  337. if (this.state.actionStartDate) {
  338. actionStartDate = new moment(this.state.actionStartDate).utc();
  339. }
  340. if (this.state.actionStartTime) {
  341. actionStartTime = new moment(this.state.actionStartTime).utc();
  342. }
  343. if (this.state.actionEndDate) {
  344. actionEndDate = new moment(this.state.actionEndDate).utc();
  345. }
  346. if (this.state.actionEndTime) {
  347. actionEndTime = new moment(this.state.actionEndTime).utc();
  348. }
  349.  
  350. let data = {
  351. 'title': this.state.edited.title,
  352. 'slug': this.state.edited.slug,
  353. 'content': this.state.editorContent,
  354. 'type': this.state.edited.type,
  355. 'contact_name': this.state.edited.contact_name,
  356. 'contact_phone': this.state.edited.contact_phone,
  357. 'location': this.state.edited.location
  358. };
  359. if (this.state.edited.issue) {
  360. data['issue'] = this.state.edited.issue.id;
  361. }
  362.  
  363. //clean up and set start_time
  364. if (actionStartDate && actionStartTime) {
  365. data.action_start_time = actionStartDate.format('L') + ' ' + actionStartTime.format('HH:mm:ss');
  366. } else if (actionStartDate && !actionStartTime) {
  367. data.action_start_time = actionStartDate.format('L') + ' ' + new moment().endOf('day').utc().format('HH:mm:ss');
  368. }
  369.  
  370. //clean up and set end_time
  371. if (actionEndDate && actionEndTime) {
  372. data.action_end_time = actionEndDate.format('L') + ' ' + actionEndTime.format('HH:mm:ss');
  373. } else if (actionEndDate && !actionEndTime && actionStartTime) {
  374. data.action_end_time = actionEndDate.format('L') + ' ' + actionStartTime.format('HH:mm:ss');
  375. } else if (actionEndDate && !actionEndTime && !actionStartTime && data.action_start_time) {
  376. data.action_end_time = data.action_start_time;
  377. }
  378.  
  379. if (parseInt(this.state.edited.workflow_id)) {
  380. data['workflow_id'] = parseInt(this.state.edited.workflow_id);
  381. }
  382.  
  383. if (this.state.edited.users && this.state.edited.users.length) {
  384. data['users'] = this.state.edited.users.map((user, i) => {
  385. return user.uuid;
  386. });
  387. }
  388.  
  389. if (this.state.edited.children && this.state.edited.children.length) {
  390. data['children'] = this.state.edited.children.map((content, i) => {
  391. return content.uuid;
  392. })
  393. }
  394.  
  395. if (this.state.edited.due_at) {
  396. data['due_at'] = this.state.edited.due_at;
  397. }
  398.  
  399. if (!this.isUserEditable()) {
  400. return;
  401. }
  402.  
  403. if (!this.isNew()) {
  404. dispatch(assignmentsUpdate(this.getUuid(), data))
  405. .then(() => {
  406. const assignment = this.props.assignments.items.find((item) => item.get('uuid') == this.state.edited.uuid);
  407. this.setState({'edited': assignment.toJS(), dirty: false});
  408. dispatch(applicationIsDirty(false));
  409. })
  410. .then(() => {
  411. dispatch(snackbarShowMessage('Assignment updated'));
  412. })
  413. .then(() => {
  414. if (andCheckin !== true) {
  415. return;
  416. }
  417.  
  418. this.handleCheckin();
  419. })
  420. .catch(() => {
  421. dispatch(snackbarClose());
  422. dispatch(alertShowMessage({
  423. 'title': 'Oops',
  424. 'message': (
  425. <p>There was a problem saving your assignment. Usually this means you don't have permission to perform an action, like publishing or deleting.</p>
  426. )
  427. }));
  428. this.setState({'dirty': true});
  429. dispatch(applicationIsDirty(true));
  430. });
  431. } else {
  432.  
  433. // Create is slightly different
  434. // first, create it
  435. dispatch(assignmentsCreate(data))
  436. .then(() => {
  437. // then grab the newly created item and pass it on
  438. const assignment = this.props.assignments.items.find((item) => item.get('uuid') == this.props.assignments.selected);
  439. dispatch(snackbarShowMessage('Assignment created'));
  440. return assignment;
  441. })
  442. .then((assignment) => {
  443. // update the state with the newly created object
  444. dispatch(applicationIsDirty(false));
  445. this.setState({
  446. 'edited': assignment.toJS(),
  447. dirty: false
  448. }, () => {
  449. // after that's done check it out
  450. this.handleCheckout({})
  451. .then(() => {
  452. // when the checkout is complete, update the URL
  453. // if you don't wait, you can get a race condition that can
  454. // result in a no-op warning
  455. browserHistory.push('/ceo/assignment/' + this.props.assignments.selected);
  456. });
  457. });
  458. })
  459. .catch(() => {
  460. dispatch(snackbarClose());
  461. dispatch(alertShowMessage({
  462. 'title': 'Oops',
  463. 'message': (
  464. <p>There was a problem saving your assignment. Usually this means you don't have permission to perform an action, like publishing or deleting.</p>
  465. )
  466. }));
  467. this.setState({'dirty': true});
  468. dispatch(applicationIsDirty(true));
  469. });
  470.  
  471. }
  472. }
  473.  
  474. handleFormChange(e) {
  475. const val = e.target.value;
  476. const key = e.target.getAttribute('for');
  477.  
  478. let assignment = this.state.edited;
  479.  
  480. assignment[key] = val;
  481.  
  482. if (key === 'slug') {
  483. assignment[key] = sluggify(val, true);
  484. }
  485.  
  486. this.setState({'edited': assignment, dirty: true});
  487. this.props.dispatch(applicationIsDirty(true));
  488. }
  489.  
  490. handleSelectChange(e, index, val) {
  491. let assignment = this.state.edited;
  492.  
  493. assignment.type = val;
  494. this.setState({'edited': assignment, dirty: true});
  495. this.props.dispatch(applicationIsDirty(true));
  496. }
  497.  
  498. handleWorkflowChange(e, index, val) {
  499. let assignment = this.state.edited;
  500.  
  501. assignment.workflow_id = val;
  502. this.setState({'edited': assignment, dirty: true});
  503. this.props.dispatch(applicationIsDirty(true));
  504. }
  505.  
  506. handleEditorChange(raw, editorState, name) {
  507. this.setState({'editorContent': this.toHtml(editorState.getCurrentContent()), dirty: true});
  508. this.props.dispatch(applicationIsDirty(true));
  509. }
  510.  
  511. handleIssueChange(e, index, val) {
  512. const issue = this.props.issues.items.find((item, i) => item.get('uuid') == val);
  513. if (!issue) {
  514. return;
  515. }
  516.  
  517. let assignment = this.state.edited;
  518. assignment.issue = issue.toJS();
  519.  
  520. this.setState({'edited': assignment, dirty: true});
  521. this.props.dispatch(applicationIsDirty(true));
  522. }
  523.  
  524. handleCheckout(e) {
  525. const {dispatch} = this.props;
  526. return dispatch(assignmentsCheckOut(this.state.edited.srn))
  527. .then(() => dispatch(assignmentsFetchOne(this.state.edited.uuid)))
  528. .then(() => {
  529. const assignment = this.props.assignments.items.find((item) => item.get('uuid') == this.state.edited.uuid);
  530. this.setState({'edited': assignment.toJS()});
  531. });
  532. }
  533.  
  534. handleCheckin(e) {
  535. const {dispatch} = this.props;
  536. dispatch(assignmentsCheckIn(this.getUuid(), this.state.edited.lock.uuid))
  537. .then(() => dispatch(assignmentsFetchOne(this.getUuid())))
  538. .then(() => {
  539. const assignment = this.props.assignments.items.find((item) => item.get('uuid') == this.state.edited.uuid);
  540. this.setState({'edited': assignment.toJS()});
  541. });
  542. }
  543.  
  544. handleSaveCheckIn(e) {
  545. this.handleSave(true);
  546. }
  547.  
  548. handleDelete(e) {
  549. const {dispatch} = this.props;
  550. dispatch(assignmentsRemove(this.getUuid()))
  551. .then(() => dispatch(globalHistoryPop(this.getUuid())))
  552. .then(() => this.setState({'didDelete': true}, () => {
  553. browserHistory.push('/ceo/assignment');
  554. dispatch(snackbarShowMessage('Assignment removed'));
  555. }))
  556. .catch(() => {
  557. dispatch(snackbarClose());
  558. dispatch(alertShowMessage({
  559. 'title': 'Oops',
  560. 'message': (
  561. <p>There was a problem removing your assignment. You may not have permission to delete.</p>
  562. )
  563. }));
  564. this.setState({'dirty': true});
  565. dispatch(applicationIsDirty(true));
  566. });
  567.  
  568. }
  569.  
  570. handleClearLock(e) {
  571. if (!CurrentUser.hasRole('Administrator')) {
  572. return;
  573. }
  574.  
  575. if (!confirm('Are you sure you want to clear this lock? You may overwrite pending changes.')) {
  576. return;
  577. }
  578.  
  579. const {dispatch} = this.props;
  580. return dispatch(assignmentsClearLock(CurrentUser.getUuid(), this.state.edited.lock.uuid))
  581. .then(() => dispatch(assignmentsFetchOne(this.getUuid())))
  582. .then(() => {
  583. const assignment = this.props.assignments.items.find((item) => item.get('uuid') == this.state.edited.uuid);
  584. this.setState({'edited': assignment.toJS()});
  585. });
  586. }
  587.  
  588. toHtml(state) {
  589. return rawToHtml(state);
  590. }
  591.  
  592. handleSelectContent(content) {
  593. let assignment = this.state.edited;
  594. const existing = _.find(assignment.children, {'uuid': content.get('uuid')});
  595. const {dispatch} = this.props;
  596.  
  597. if (!existing) {
  598. dispatch(snackbarShowMessage('Content added'));
  599. assignment.children.push(content.toJS());
  600. this.setState({'edited': assignment, dirty: true});
  601. dispatch(applicationIsDirty(true));
  602. }
  603. }
  604.  
  605. handleRemoveContent(content, e) {
  606. let assignment = this.state.edited;
  607. const {dispatch} = this.props;
  608. assignment.children = assignment.children.filter((item, i) => {
  609. return content.uuid != item.uuid;
  610. });
  611.  
  612. dispatch(snackbarShowMessage('Content removed'));
  613. this.setState({'edited': assignment, dirty: true});
  614. dispatch(applicationIsDirty(true));
  615. }
  616.  
  617. handleOpenContent() {
  618. this.setState({'contentIsOpen': true});
  619. }
  620.  
  621. handleCloseContent() {
  622. this.setState({'contentIsOpen': false});
  623. }
  624.  
  625. render() {
  626.  
  627. if (this.getUuid() && !this.props.assignments.items.size) {
  628. return <LoadingIndicator />;
  629. }
  630.  
  631. let dueDate = null;
  632.  
  633. if (this.state.edited.due_at) {
  634. dueDate = moment.utc(this.state.edited.due_at, 'YYYY-MM-DD HH:mm:ss').local().toDate();
  635. }
  636.  
  637. let assignment_types = [];
  638. if (Config && Config.get('assignment_types')) {
  639. assignment_types = Config.get('assignment_types');
  640. }
  641.  
  642. const presetIssue = (this.props.location && this.props.location.query.issue)
  643. ? decodeURIComponent(this.props.location.query.issue)
  644. : null;
  645.  
  646. if (presetIssue) {
  647. let issue = this.props.issues.items.find((item) => item.get('uuid') == presetIssue);
  648.  
  649. if (issue)
  650. this.state.edited.issue = issue.toJS();
  651. }
  652.  
  653. // Allows existing items attached to closed issues
  654. // to still show the issue in the select drop down
  655. let useIssue = null;
  656. let didUseIssue = false;
  657. if (this.state.edited
  658. && this.state.edited.issue
  659. && !parseInt(this.state.edited.issue.status) ) {
  660.  
  661. let tempIssue = this.state.edited.issue;
  662. let useLabel = [];
  663. if (tempIssue.published_at) {
  664. useLabel.push(moment(new Date(tempIssue.published_at)).local().format('LL'));
  665. }
  666. if (tempIssue.label) {
  667. useLabel.push(tempIssue.label);
  668. }
  669.  
  670. useLabel = useLabel.join(' - ');
  671. if (useLabel.length > 25) {
  672. useLabel = useLabel.substring(0, 22) + '...';
  673. }
  674. tempIssue.decorated_label = useLabel;
  675. useIssue = tempIssue;
  676. }
  677.  
  678. let creator = this.props.users.items.find((user) => {
  679. return this.state.edited.creator_id == user.get('id');
  680. })
  681.  
  682. return (
  683. <div className='assignment-item-root'>
  684. <Row>
  685. <Col xs={12}>
  686. <Paper className='toolbar' style={{ paddingTop:'10px', paddingBottom:'10px' }}>
  687. <Row middle='xs'>
  688. <Col xs={6}>
  689. <LockButtons
  690. onSave={this.handleSave}
  691. onSaveCheckIn={this.handleSaveCheckIn}
  692. onCheckIn={this.handleCheckin}
  693. onCheckOut={this.handleCheckout}
  694. userEditable={this.isUserEditable()}
  695. lockable={this.state.edited}
  696. />
  697. </Col>
  698. </Row>
  699. </Paper>
  700.  
  701. <Row>
  702. <Col xs={9}>
  703. <Paper className='padded clear-bottom'>
  704. <TextField
  705. style={{display:'none'}}
  706. htmlFor='slug'
  707. fullWidth={true}
  708. floatingLabelText='Slug'
  709. disabled={this.isUserEditable() ? false : true}
  710. onChange={this.handleFormChange}
  711. value={this.state.edited.slug ? this.state.edited.slug : ''}
  712. ref='slugTextField'
  713. />
  714. <TextField
  715. htmlFor='title'
  716. fullWidth={true}
  717. floatingLabelText='Title'
  718. disabled={this.isUserEditable() ? false : true}
  719. onChange={this.handleFormChange}
  720. value={this.state.edited.title ? this.state.edited.title : ''}
  721. />
  722. <div style={{marginTop:'1em'}}>
  723. <Subheader style={{paddingLeft:'0px'}}>Description</Subheader>
  724. <SimpleEditor
  725. value={this.state.edited.content}
  726. onChange={this.handleEditorChange}
  727. readOnly={this.isUserEditable() ? false : true}
  728. />
  729. </div>
  730. <Row middle='xs'>
  731. <Col xs={6}>
  732. <TextField
  733. htmlFor='contact_name'
  734. fullWidth={true}
  735. floatingLabelText='Contact Name'
  736. disabled={this.isUserEditable() ? false : true}
  737. onChange={this.handleFormChange}
  738. value={this.state.edited.contact_name ? this.state.edited.contact_name : ''}
  739. />
  740. </Col>
  741. <Col xs={6}>
  742. <TextField
  743. htmlFor='contact_phone'
  744. fullWidth={true}
  745. floatingLabelText='Contact Phone'
  746. disabled={this.isUserEditable() ? false : true}
  747. onChange={this.handleFormChange}
  748. value={this.state.edited.contact_phone ? this.state.edited.contact_phone : ''}
  749. />
  750. </Col>
  751. </Row>
  752. <Row middle='xs'>
  753. <Col xs={6}>
  754. <Row middle='xs'>
  755. <Col xs={12}>
  756. <DatePicker
  757. fullWidth={true}
  758. floatingLabelText='Start Date'
  759. disabled={this.isUserEditable() ? false : true}
  760. onChange={this.actionDateChange.bind(this, 'start')}
  761. value={this.state.actionStartDate}
  762. firstDayOfWeek={0}
  763. />
  764. </Col>
  765. </Row>
  766. </Col>
  767. <Col xs={6}>
  768. {
  769. new Date(this.state.actionStartTime).valueOf() == new Date(this.state.actionEndTime).valueOf()
  770. ? (
  771. <Row middle='xs'>
  772. <Col xs={12}>
  773. <TimePicker
  774. fullWidth={true}
  775. floatingLabelText='Start Time'
  776. disabled={this.isUserEditable() && this.state.actionStartDate ? false : true}
  777. onChange={this.actionTimeChange.bind(this, 'start')}
  778. value={this.state.actionStartTime}
  779. defaultTime={this.state.actionStartDate}
  780. />
  781. </Col>
  782. </Row>
  783. )
  784. : (
  785. <Row middle='xs'>
  786. <Col xs={6}>
  787. <TimePicker
  788. fullWidth={true}
  789. floatingLabelText='Start Time'
  790. disabled={this.isUserEditable() && this.state.actionStartDate ? false : true}
  791. onChange={this.actionTimeChange.bind(this, 'start')}
  792. value={this.state.actionStartTime}
  793. defaultTime={this.state.actionStartDate}
  794. />
  795. </Col>
  796. <Col xs={6}>
  797. <TimePicker
  798. fullWidth={true}
  799. floatingLabelText='End Time'
  800. disabled={this.isUserEditable() && this.state.actionStartTime ? false : true}
  801. onChange={this.actionTimeChange.bind(this, 'end')}
  802. value={this.state.actionEndTime}
  803. />
  804. </Col>
  805. </Row>
  806. )
  807. }
  808. </Col>
  809. <Col xs={12}>
  810. <TextField
  811. htmlFor='location'
  812. fullWidth={true}
  813. floatingLabelText='Location'
  814. disabled={this.isUserEditable() ? false : true}
  815. onChange={this.handleFormChange}
  816. value={this.state.edited.location ? this.state.edited.location : ''}
  817. />
  818. </Col>
  819. </Row>
  820. </Paper>
  821.  
  822. <Paper className='padded clear-bottom'>
  823. <Subheader style={{paddingLeft:'0px'}}>Linked Content</Subheader>
  824.  
  825. {this.state.edited.children.map((item, i) => {
  826. return (
  827.  
  828. <Row key={i}>
  829. <Col xs={12} style={{marginBottom:'6px'}}>
  830. <ContentRow
  831. item={Immutable.fromJS(item)}
  832. closeIcon={
  833. <FontIcon
  834. className='mui-icons'
  835. style={{'cursor': 'pointer'}}
  836. >
  837. close
  838. </FontIcon>
  839. }
  840. closeAction={
  841. this.handleRemoveContent.bind(this, item)
  842. }
  843. disabled={this.isUserEditable() ? false : true}
  844. />
  845. </Col>
  846. </Row>
  847. )
  848. })}
  849. <Row>
  850. <Col xs={12}>
  851. <RaisedButton
  852. onClick={this.handleOpenContent.bind(this)}
  853. secondary={true}
  854. label='Attach Content'
  855. style={{marginRight:'10px'}}
  856. disabled={this.isUserEditable() ? false : true}
  857. />
  858. <RaisedButton
  859. containerElement={<Link to={'/ceo/content/new?assignment='+this.getUuid()} />}
  860. label='Create Content'
  861. disabled={this.isUserEditable() ? false : true}
  862. />
  863. </Col>
  864. </Row>
  865. </Paper>
  866. {
  867. this.getUuid()
  868. ? (
  869. <Paper className='padded clear-bottom'>
  870. <AssignmentDiscussion assignment={this.getUuid()} readOnly={this.isUserEditable() ? false : true} />
  871. </Paper>
  872. )
  873. : ''
  874. }
  875. </Col>
  876. <Col xs={3}>
  877. <Paper className='padded clear-bottom'>
  878.  
  879. <SelectField
  880. fullWidth={true}
  881. style={{'maxWidth': '100%'}}
  882. floatingLabelText='Assignment Type'
  883. disabled={this.isUserEditable() ? false : true}
  884. onChange={this.handleSelectChange.bind(this)}
  885. value={this.state.edited.type}
  886. >
  887. {assignment_types.map((type, i) => {
  888. return <MenuItem key={i} value={type} primaryText={type} />;
  889. })}
  890. </SelectField>
  891.  
  892. <SelectField
  893. autoWidth={true}
  894. style={{maxWidth: '100%'}}
  895. value={parseInt(this.state.edited.workflow_id) ? this.state.edited.workflow_id : null}
  896. disabled={this.isUserEditable() ? false : true}
  897. onChange={this.handleWorkflowChange.bind(this)}
  898. floatingLabelText='Workflow Status'
  899. >
  900. {this.props.workflows.filters.assignmentOnly.map((id, i) => {
  901. const wf = this.props.workflows.items.find((workflow) => {
  902. return workflow.get('uuid') == id;
  903. })
  904. return <MenuItem key={i} value={wf.get('id')} primaryText={wf.get('name')} />
  905. })}
  906. </SelectField>
  907.  
  908. <DatePicker
  909. autoOk={true}
  910. fullWidth={true}
  911. hintText="Due Date"
  912. value={dueDate}
  913. disabled={this.isUserEditable() ? false : true}
  914. onChange={this.dueDateChange.bind(this)}
  915. floatingLabelText='Due Date'
  916. style={{'maxWidth': '100%'}}
  917. firstDayOfWeek={0}
  918. />
  919.  
  920. <SelectField
  921. floatingLabelText='Issue'
  922. value={this.state.edited.issue ? this.state.edited.issue.uuid : ''}
  923. disabled={this.isUserEditable() ? false : true}
  924. onChange={this.handleIssueChange.bind(this)}
  925. style={{width:'100%'}}
  926. >
  927. {this.props.issues.items.map((item, i) => {
  928. // const item = this.props.issues.items.find((item, i) => item.get('uuid') == id);
  929. if (useIssue && item.get('uuid') == useIssue.uuid) {
  930. didUseIssue = true;
  931. }
  932.  
  933. let label = [];
  934. if (item.get('published_at')) {
  935. label.push(moment(new Date(item.get('published_at'))).local().format('LL'));
  936. }
  937. if (item.get('label')) {
  938. label.push(item.get('label'));
  939. }
  940.  
  941. label = label.join(' - ');
  942. if (label.length > 25) {
  943. label = label.substring(0, 22) + '...';
  944. }
  945.  
  946. return (
  947. <MenuItem key={i} value={item.get('uuid')} primaryText={label} />
  948. );
  949. })}
  950. {
  951. useIssue && !didUseIssue
  952. ? <MenuItem value={useIssue.uuid} primaryText={useIssue.decorated_label} />
  953. : ''
  954. }
  955. </SelectField>
  956.  
  957. </Paper>
  958.  
  959. <Paper className='clear-bottom'>
  960. <List>
  961. <Subheader className='fixed-label'>Assigned Users</Subheader>
  962. <ListItem disabled={true} style={{marginTop:'-16px', paddingTop:'0px', paddingBottom:'0px'}}>
  963. <UserSearchBox
  964. onSelectUser={this.onSelectUser.bind(this)}
  965. disabled={this.isUserEditable() ? false : true}
  966. />
  967. </ListItem>
  968.  
  969. {this.state.edited.users.map((user, i) => {
  970. return (
  971. <ListItem
  972. key={i}
  973. disabled={true}
  974. primaryText={user.name}
  975. secondaryText={user.phone ? ('tel: '+user.phone) : ''}
  976. rightIcon={
  977. <FontIcon
  978. className='mui-icons '
  979. style={{'cursor': 'pointer'}}
  980. onClick={this.onRemoveUser.bind(this, user)}
  981. >close</FontIcon>
  982. }
  983. >
  984. </ListItem>
  985. );
  986. })}
  987. {
  988. creator
  989. ? (
  990. <div>
  991. <Subheader>Created By</Subheader>
  992. <ListItem
  993. style={{paddingTop:'0px'}}
  994. disabled={true}
  995. primaryText={creator.get('name')}
  996. secondaryText={creator.get('phone') ? ('tel: '+creator.get('phone')) : ''}
  997. />
  998. </div>
  999. )
  1000. : ''
  1001. }
  1002. </List>
  1003. </Paper>
  1004. <div className="clear-bottom">
  1005. <DeleteButton
  1006. className='full'
  1007. fullWidth={true}
  1008. disabled={this.isNew() || !this.isUserEditable() ? true : false}
  1009. onDelete={this.handleDelete.bind(this)} />
  1010. </div>
  1011.  
  1012. {
  1013. this.isLocked()
  1014. ? (
  1015. <div className='clear-bottom'>
  1016. <RaisedButton
  1017. label='Clear Lock'
  1018. style={{width:'100%'}}
  1019. icon={<FontIcon className='mui-icons'>clear</FontIcon>}
  1020. onClick={this.handleClearLock.bind(this)}
  1021. disabled={CurrentUser.hasRole('Administrator') && !this.isNew() ? false : true}
  1022. />
  1023. </div>
  1024. )
  1025. : ''
  1026. }
  1027.  
  1028. </Col>
  1029. </Row>
  1030.  
  1031. <ContentSearchModal
  1032. isOpen={this.state.contentIsOpen}
  1033. onRequestClose={this.handleCloseContent.bind(this)}
  1034. onSelectContent={this.handleSelectContent.bind(this)}
  1035. />
  1036. </Col>
  1037. </Row>
  1038. </div>
  1039. );
  1040. }
  1041. }
  1042.  
  1043. export default connect((state) => {
  1044. return {
  1045. assignments: state.assignments,
  1046. workflows: state.workflows,
  1047. issues: state.issues,
  1048. users: state.users,
  1049. globalHistory: state.globalHistory
  1050. }
  1051. })(AssignmentItem);