Home Reference Source

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);