application/components/assignment/assignment-calendar.js
import React from 'react';
import {browserHistory, Link} from 'react-router';
import {timeToFormat} from './../../util/strings';
import {connect} from 'react-redux';
import moment from 'moment';
import _ from 'lodash';
import Immutable from 'immutable';
import HTML5Backend from 'react-dnd-html5-backend'
import { DragDropContext } from 'react-dnd'
import CurrentUser from './../../current-user';
import BigCalendar from 'react-big-calendar';
import withDragAndDrop from './addons/dragAndDrop';
import Paper from 'material-ui/Paper';
import Dialog from 'material-ui/Dialog';
import DatePicker from 'material-ui/DatePicker';
import FlatButton from 'material-ui/FlatButton';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import FontIcon from 'material-ui/FontIcon';
import Subheader from 'material-ui/Subheader';
import {RadioButton, RadioButtonGroup} from 'material-ui/RadioButton';
import {Row, Col} from './../flexbox';
import ceoTheme from './../../theme';
import {IssueModal} from './../issue';
import {
assignmentsUpdate,
assignmentsCheckOut,
assignmentsCheckIn,
assignmentsFetch,
assignmentsFetchOne
} from './../../redux/actions/assignment-actions';
import {
issuesFetch,
issueUpdate
} from './../../redux/actions/issue-actions';
import {
snackbarShowMessage
} from './../../redux/actions/snackbar-actions';
import {
assignmentViewUpdate
} from './../../redux/actions/assignment-view-actions';
import AssignmentManager from './../../managers/assignment-manager';
import request from './../../util/request';
import LoadingIndicator from '../common/loading-indicator';
const localizer = BigCalendar.momentLocalizer(moment)
const DragAndDropCalendar = withDragAndDrop(BigCalendar);
function getUrlParameter(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
var results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
};
function Event({ event }) {
let colors = {
'photo': '#DA8AE5',
'article': '#01BCD4',
'issue': 'green',
'misc': 'orange'
}
const lock = <FontIcon style={{color:'white', verticalAlign:'middle', fontSize:'20px', marginLeft:'-2px'}} className='mui-icons'>lock</FontIcon>;
const lockBlack = <FontIcon style={{color:'black', verticalAlign:'middle', fontSize:'16px', marginLeft:'-2px', marginRight:'1px'}} className='mui-icons'>lock</FontIcon>;
let color = colors['misc'];
if (colors[event.type]) {
color = colors[event.type];
}
if (event.type == 'issue') {
return (
<div style={{
backgroundColor: color,
padding: '2px 5px',
}}>
{event.lock ? lock : ''}<strong style={{verticalAlign:'middle'}}>{event.title ? event.title : (moment(event.start).format('MMMM Do') + ' Issue')}</strong>
</div>
)
}
if (!event.unscheduled) {
return (
<div style={{
padding: '2px 5px',
color: 'black'
}}>
{event.lock ? lockBlack : <span style={{width:'10px', height:'10px', backgroundColor:color, display:'inline-block', marginRight:'3px'}}></span>}
<span style={{verticalAlign:'middle'}}>{event.title ? event.title : event.slug}</span>
</div>
)
}
return null;
}
function CustomToolbar(toolbar) {
const goToBack = () => {
toolbar.date.setMonth(toolbar.date.getMonth() - 1);
toolbar.onNavigate('prev');
};
const goToNext = () => {
toolbar.date.setMonth(toolbar.date.getMonth() + 1);
toolbar.onNavigate('next');
};
const goToCurrent = () => {
const now = new Date();
toolbar.date.setMonth(now.getMonth());
toolbar.date.setYear(now.getFullYear());
toolbar.onNavigate('current');
};
const label = () => {
const date = moment(toolbar.date);
return (
<h3 style={{margin:'0px'}}>{date.format('MMMM')} {date.format('YYYY')}</h3>
);
};
let calView = window.Store.getState().assignmentView.calview;
const swapView = () => {
if (window.Store.getState().assignmentView.calview == 'issue') {
window.Store.dispatch(assignmentViewUpdate({
'calview': 'assignment'
}))
.then(() => AssignmentManager.fetchFromView());
} else if (window.Store.getState().assignmentView.calview == 'assignment') {
window.Store.dispatch(assignmentViewUpdate({
'calview': 'issue'
}))
.then(() => AssignmentManager.fetchFromView());
}
}
return (
<div>
<Row className='rbc-toolbar'>
<Col xs={4}>
<FlatButton
label="BACK"
labelPosition="after"
onClick={goToBack}
icon={<FontIcon className='mui-icons'>keyboard_arrow_left</FontIcon>}
/>
<FlatButton
label="TODAY"
onClick={goToCurrent}
/>
<FlatButton
label="NEXT"
labelPosition="before"
onClick={goToNext}
icon={<FontIcon className='mui-icons'>keyboard_arrow_right</FontIcon>}
/>
</Col>
<Col xs={4} style={{textAlign:'center'}}>
{label()}
</Col>
<Col xs={4} style={{textAlign:'right'}}>
<span style={{fontSize:'14px', verticalAlign:'middle'}}>SORT BY: </span>
<FlatButton
label="ISSUE"
onClick={swapView}
disabled={calView == 'issue'}
icon={calView == 'issue'
? (<FontIcon className='mui-icons'>check_circle</FontIcon>)
: ''
}
/>
<FlatButton
label="DUE DATE"
onClick={swapView}
disabled={calView == 'assignment'}
icon={calView == 'assignment'
? (<FontIcon className='mui-icons'>check_circle</FontIcon>)
: ''
}
/>
</Col>
</Row>
</div>
);
};
class UnwrappedAssignmentCalendar extends React.Component {
constructor(props) {
super(props);
this.state = {
eventSetterOpen: false,
eventSetterDate: null,
eventSetterAssignment: null,
eventSetterIssue: null,
multiIssueDrop: [],
multiIssueSetterOpen: false,
multiIssueSetterId: null,
multiIssueSetterAssignment: null,
issueModalOpen: false,
issueModalDate: null,
unassigned: Immutable.fromJS([])
}
this.handleMove = this.handleMove.bind(this);
this.handleNavigate = this.handleNavigate.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handleSetterSave = this.handleSetterSave.bind(this);
this.handleAssignmentChange = this.handleAssignmentChange.bind(this);
this.handleDateChange = this.handleDateChange.bind(this);
this.handleMultiIssueSave = this.handleMultiIssueSave.bind(this);
this.handleMultiIssueChange = this.handleMultiIssueChange.bind(this);
this.handleIssueModalClose = this.handleIssueModalClose.bind(this);
this.fetchUpdate = this.fetchUpdate.bind(this);
}
componentWillMount() {
if (this.props.assignmentView.useDate) {
this.fetchUpdate();
} else {
this.props.dispatch(assignmentViewUpdate({
useDate: moment()
}))
.then(() => this.fetchUpdate());
}
}
fetchUpdate() {
const {dispatch} = this.props;
dispatch(issuesFetch({
'start': this.props.assignmentView.useDate.startOf('month').format('YYYY-MM-DD'),
'end': this.props.assignmentView.useDate.endOf('month').format('YYYY-MM-DD')
}))
.then(() => {
dispatch(assignmentsFetch({'type': 'unscheduled'}))
.then(() => {
this.setState({unassigned: this.props.assignments.items});
})
.then(() => {
if (this.props.assignmentView.calview == 'issue') {
let issues = [];
this.props.issues.items.map((issue) => {
issues.push(issue.get('uuid'));
});
dispatch(assignmentViewUpdate({
issues: issues,
type: 'any'
}))
.then(() => AssignmentManager.fetchFromView());
} else {
dispatch(assignmentViewUpdate({
start: this.props.assignmentView.useDate.startOf('month').format('YYYY-MM-DD'),
end: this.props.assignmentView.useDate.endOf('month').format('YYYY-MM-DD'),
issues: [],
type: 'any'
}))
.then(() => AssignmentManager.fetchFromView());
}
});
});
}
// shouldComponentUpdate(nextProps, nextState) {
// if (this.props.assignments != nextProps.assignments) {
// return true;
// }
// if (this.props.issueDueDates != nextProps.dueDates) {
// return true;
// }
// if (this.state.eventSetterOpen != nextState.eventSetterOpen) {
// return true;
// }
// return false;
// }
handleMove({ event, start, end }) {
let useEvent = this.props.assignments.items.find(item => item.get('uuid') == event.uuid);
if (this.props.assignmentView.calview == 'issue') {
const day = moment(start).format('MMDDYYYY');
let issues = this.props.issues.items.filter((issue) => {
if (moment(issue.get('published_at')).format('MMDDYYYY') == day) {
return true;
}
return false;
});
if (issues.size == 1) {
let issue = this.props.issues.items.find((item) => moment(item.get('published_at')).format('MMDDYYYY') == day);
this.handleAssignmentSave(useEvent, event.start, event.end, issue.get('id'));
} else if (issues.size > 1){
this.setState({multiIssueDrop: issues, multiIssueSetterOpen: true, multiIssueSetterAssignment: event.uuid});
}
} else if (this.props.assignmentView.calview == 'assignment') {
if (!event.lock && event.start !== start) {
this.handleAssignmentSave(useEvent, start, end);
event.unscheduled = false;
}
}
}
handleEventClick(event) {
if (event.type == 'issue') {
this.setState({eventSetterOpen: true, eventSetterIssue: event});
} else {
browserHistory.push('/ceo/redirect?next=assignment/' + event.uuid);
}
}
handleDayClick(day) {
if (this.props.calView == 'assignment') {
this.setState({eventSetterOpen: true, eventSetterDate: day.start});
} else {
this.setState({issueModalOpen: true, issueModalDate: day.start});
}
}
handleAssignmentSave(event, start, end, issueId) {
const {dispatch} = this.props;
let data = {};
if (!issueId) {
data.due_at = moment(start).utc();
} else {
data.issue = issueId;
}
if (event.get('lock')) {
console.log('handling the lock');
} else {
dispatch(assignmentsCheckOut(event.get('srn')))
.then(() => {
dispatch(assignmentsUpdate(event.get('uuid'), data));
})
.then(() => {
dispatch(snackbarShowMessage('Assignment updated'));
})
.then(() => {
this.handleCheckIn(event);
})
}
}
handleIssueSave(event, start, end) {
const {dispatch} = this.props;
let data = {};
//set datetime as utc
data.published_at = moment(start).utc();
dispatch(issueUpdate(event.uuid, data))
.then(() => dispatch(issuesFetch()))
.then(() => dispatch(snackbarShowMessage('Issue updated')));
}
handleNavigate(date) {
//do stuff when you navigate
const {dispatch} = this.props;
dispatch(assignmentViewUpdate({
useDate: moment(date)
}))
.then(() => this.fetchUpdate());
}
handleAssignmentChange(e, i, uuid) {
console.log(uuid);
this.setState({eventSetterAssignment: uuid});
}
handleDateChange(e, i, date) {
this.setState({eventSetterDate: date})
}
handleSetterSave() {
let event = this.state.unassigned.filter((item) => item.get('uuid') == this.state.eventSetterAssignment).first();
let issue = null;
if (this.props.assignmentView.calview == 'issue' && this.state.eventSetterIssue) {
issue = this.props.issues.items.find((item) => item.get('uuid') == this.state.eventSetterIssue.uuid);
}
if (this.state.eventSetterDate) {
event = event.set('start', this.state.eventSetterDate)
.set('end', this.state.eventSetterDate);
}
event = event.set('unscheduled', false);
if (event && event.type !== 'issue' && !issue) {
this.handleAssignmentSave(event, event.start, event.end);
} else if (event && issue) {
this.handleAssignmentSave(event, event.start, event.end, issue.get('id'));
}
this.handleClose();
}
handleMultiIssueChange(e, i, id) {
this.setState({multiIssueSetterId: id})
};
handleMultiIssueSave() {
const event = this.props.issueDueDates.find((item) => item.uuid == this.state.multiIssueSetterAssignment);
const id = this.state.multiIssueSetterId;
if (event && id) {
this.handleAssignmentSave(event, event.start, event.end, id);
}
this.handleClose();
}
handleClose() {
this.setState({eventSetterOpen: false, eventSetterAssignment: null, multiIssueSetterOpen: false, multiIssueSetterId: null});
}
handleCheckIn(event) {
const {dispatch} = this.props;
dispatch(assignmentsFetchOne(event.get('uuid')))
.then((response) => {
// console.log(response);
const assignment = this.props.assignments.items.find((item) => item.get('uuid') == event.get('uuid'));
dispatch(assignmentsCheckIn(assignment.get('uuid'), assignment.get('lock').get('uuid')));
});
}
handleIssueModalClose(e) {
this.setState({issueModalOpen: false, issueModalDate: null});
}
render() {
const formats = {
dateFormat: 'D'
}
let assignments = [];
if (this.props.assignmentView.calview == 'issue') {
this.props.issues.items.map((issue,i) => {
let event = {};
//event.allDay = true;
event.title = issue.get('label') ? issue.get('label') : issue.get('slug');
event.uuid = issue.get('uuid');
event.type = 'issue';
if (!CurrentUser.hasRole('Administrator')) {
event.lock = true;
} else {
event.lock = false;
}
if (issue.get('published_at')) {
event.start = moment.utc(issue.get('published_at')).local().toDate();
event.end = moment.utc(issue.get('published_at')).local().toDate();
} else {
event.unscheduled = true;
}
assignments.push(event);
let issueId = issue.get('id');
let filtered = this.props.assignments.items.filter((assignment,i) => {
if (assignment.get('issue_id') == issueId) {
return true;
}
return false;
})
filtered.map((assignment,i) => {
let event = {};
//event.allDay = true;
event.title = assignment.get('title') ? assignment.get('title') : assignment.get('slug');
event.type = assignment.get('type');
event.slug = assignment.get('slug');
event.uuid = assignment.get('uuid');
event.srn = assignment.get('srn');
// check if the assignment is locked
if (assignment.get('lock') && assignment.get('lock').get('user_id') !== CurrentUser.getId()) {
event.lock = assignment.get('lock').get('uuid');
event.lockUser = assignment.get('lock').get('user_id');
}
event.start = moment.utc(issue.get('published_at')).local().toDate();
event.end = moment.utc(issue.get('published_at')).local().toDate();
assignments.push(event);
})
});
} else {
this.props.assignments.items.map((item,i) => {
let event = {};
//event.allDay = true;
event.title = item.get('title') ? item.get('title') : item.get('slug');
event.type = item.get('type');
event.slug = item.get('slug');
event.uuid = item.get('uuid');
event.srn = item.get('srn');
event.issue = item.get('issue_id') ? item.get('issue_id') : null;
//need a way of differentiating viewmodes in backgroundWrapper
event.flag = true;
// check if the assignment is locked
if (item.get('lock') && item.get('lock').get('user_id') !== CurrentUser.getId()) {
event.lock = item.get('lock').get('uuid');
event.lockUser = item.get('lock').get('user_id');
}
// set the event unscheduled if it has no due_at
if (item.get('due_at')) {
event.start = moment.utc(item.get('due_at')).local().toDate();
event.end = moment.utc(item.get('due_at')).local().toDate();
} else {
event.unscheduled = true;
}
assignments.push(event);
});
}
const eventSetterActions = [
<FlatButton
label="Cancel"
primary={true}
onClick={this.handleClose}
/>,
<FlatButton
label="Submit"
primary={true}
disabled={this.state.eventSetterAssignment ? false : true}
onClick={this.handleSetterSave}
/>,
];
const multiIssueSetterActions = [
<FlatButton
label="Cancel"
primary={true}
onClick={this.handleClose}
/>,
<FlatButton
label="Submit"
primary={true}
disabled={this.state.multiIssueSetterId ? false : true}
onClick={this.handleMultiIssueSave}
/>,
];
return (
<Paper style={{height:window.innerHeight}}>
{
this.props.issues.isFetching || this.props.assignments.isFetching
? <LoadingIndicator centered={true} />
: ''
}
<DragAndDropCalendar
className={this.props.assignmentView.calView}
selectable
popup
views={['month', 'agenda']}
events={assignments}
onNavigate={this.handleNavigate.bind(this)}
onSelectEvent={this.handleEventClick.bind(this)}
onSelectSlot={this.handleDayClick.bind(this)}
onEventDrop={this.handleMove}
formats={formats}
localizer={localizer}
components={{
event: Event,
toolbar: CustomToolbar
}}
/>
<Dialog
title='Schedule an Assignment Due Date'
actions={eventSetterActions}
modal={true}
open={this.state.eventSetterOpen}
contentStyle={{width:'500px'}}
>
<div>
{
this.props.assignmentView.calview == 'issue'
? (
<SelectField
floatingLabelText={'Add Assignment to Issue'}
onChange={this.handleAssignmentChange}
value={this.state.eventSetterAssignment}
>
{
this.state.unassigned.map((item,i) => {
if (!item.get('issue_id') && !item.get('lock')) {
return (
<MenuItem key={i} value={item.get('uuid')} primaryText={item.get('title') ? item.get('title') : item.get('slug')} />
)
}
})
}
</SelectField>
)
: (
<div>
<DatePicker
floatingLabelText='Selected Due Date'
container='inline'
mode='landscape'
value={this.state.eventSetterDate}
onChange={this.handleDateChange}
firstDayOfWeek={0}
/>
<SelectField
floatingLabelText='Unscheduled Assignments'
onChange={this.handleAssignmentChange}
value={this.state.eventSetterAssignment}
>
{
assignments.map((event,i) => {
if (event.unscheduled && !event.lock) {
return (
<MenuItem key={i} value={event.uuid} primaryText={event.title} />
)
}
})
}
</SelectField>
</div>
)
}
</div>
</Dialog>
<Dialog
title='Which Issue do you want this Assignment linked to?'
actions={multiIssueSetterActions}
modal={true}
open={this.state.multiIssueSetterOpen}
contentStyle={{width:'500px'}}
>
<SelectField
floatingLabelText='Select an Issue'
onChange={this.handleMultiIssueChange}
value={this.state.multiIssueSetterId}
>
{
this.state.multiIssueDrop.map((issue,i) => {
return (
<MenuItem key={i} value={issue.get('id')} primaryText={issue.get('label') ? issue.get('label') : (moment(issue.get('published_at')).format('MMMM Do') + ' Issue')} />
)
})
}
</SelectField>
</Dialog>
{
this.state.issueModalDate
? (
<IssueModal onClose={this.handleIssueModalClose} open={this.state.issueModalOpen} issueDate={this.state.issueModalDate} />
)
: ''
}
</Paper>
);
}
}
const mapStateToProps = (state) => {
return {
assignments: state.assignments,
issues: state.issues,
workflows: state.workflows,
assignmentView: state.assignmentView
};
}
const AssignmentCalendar = connect(mapStateToProps)(UnwrappedAssignmentCalendar);
export default DragDropContext(HTML5Backend)(AssignmentCalendar);