AppStateESS/homestead

View on GitHub
javascript/StudentAddRoomDamages/roomDamage.jsx

Summary

Maintainability
D
2 days
Test Coverage
import React from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
import $ from 'jquery';

// Top level component responsible for handling most of the high level logic,
// storing relevant data, and making ajax calls.
class RoomDamageBox extends React.Component{
    // Sets up an initial state for the class, with default values.
    constructor(props){
        super(props);

        this.state = {room: {location: this.props.roomLocation},
        damages: undefined,
        newDamages: [],
        options: [],
        alert: undefined}

        this.componentWillMount = this.componentWillMount.bind(this);
        this.addUnsavedDamages = this.addUnsavedDamages.bind(this);
        this.saveDamages = this.saveDamages.bind(this);
        this.getRoomDamages = this.getRoomDamages.bind(this);
        this.getOptions = this.getOptions.bind(this);
        this.addRoomDamages = this.addRoomDamages.bind(this);
        this.removeRoomDamages = this.removeRoomDamages.bind(this);
    }
    componentWillMount(){
        this.getRoomDamages();
        this.getOptions();
    }
    addUnsavedDamages(type, sideInput, noteInput){
        var options = this.state.options

        var categ = '';
        var dmgTypeDesc = '';
        var found = false;

        for(var i = 0; i < options.length; i++){
            if(!found){
                var dmgTypeArr = options[i].DamageTypes;
                for(var j = 0; j < dmgTypeArr.length; j++){
                    if(dmgTypeArr[j].id == type){ // NB: Must be double equals, not triple
                        categ = options[i].category;
                        dmgTypeDesc = dmgTypeArr[j].description;
                        found = true;
                    }
                }
            }
        }

        var newDamageNode = {dmgTypeId: type, category: categ, description: dmgTypeDesc, side: sideInput, note: noteInput};
        var newDmgsArr = this.state.newDamages;
        newDmgsArr.push(newDamageNode);
        this.setState({newDamages: newDmgsArr});
    }
    saveDamages(){
        var newDamages = this.state.newDamages;
        //console.log(newDamages)
        for(var i = 0; i < newDamages.length; i++)
        {
            var damage = newDamages[i];
            this.addRoomDamages(damage.dmgTypeId, damage.side, damage.note);
        }
        this.setState({newDamages: []});
    }
    // Function responsible for setting up an ajax call to retrieve the current room damages.
    getRoomDamages(){
        var inputData = {roomPersistentId: this.props.roomPID};
        $.ajax({
            url: 'index.php?module=hms&action=RetrieveRoomDamage',
            type: 'GET',
            datatype: 'json',
            data: inputData,
            success: function(data)
            {
                var outputData = [];
                outputData = JSON.parse(data);
                this.setState({damages: outputData});
            }.bind(this),
            error: function(xhr, status, err)
            {
                alert("Failed to set status properly.")
                console.error(this.props.url, status, err.toString());
            }.bind(this)
        });
    }
    getOptions(){
        $.ajax({
            url: 'index.php?module=hms&action=AjaxGetRoomDamageTypes',
            type: 'GET',
            dataType: 'json',
            success: function(data) {
                this.setState({options: data});
            }.bind(this),
            error: function(xhr, status, err) {
                alert("Failed to grab the damages options for drop down")
                console.error(this.props.url, status, err.toString());
            }.bind(this)
        });
    }
    // Function responsible for setting up and executing the ajax call to add room damages.
    addRoomDamages(type, side, desc){
        //console.log(desc)
        $.ajax({
          //url: 'index.php?module=hms&action=AddRoomDamage&roomPersistentId='+roomPID+'&damageType='+type+'&side='+side+'&description='+desc+'&term='+term,
          url: 'index.php?module=hms&action=AddRoomDamage&roomPersistentId=' + this.props.roomPID + '&term=' + this.props.term,
          type: 'POST',
          data: {damageType: type,
                side: side,
                description: desc
            },
          success: function(data) {
              var newAlert = JSON.parse(data);
              this.setState({alert: newAlert});
              this.getRoomDamages();
          }.bind(this),
          error: function(xhr, status, err) {
            alert("Failed to add room damages to database properly. "+ err.toString())
            console.error(this.props.url, status, err.toString());
          }.bind(this)
        });
    }
    // Function responsible for removing room damages from the newDamages array
    removeRoomDamages(id){
        var newDmgsArr = this.state.newDamages;
        var len = newDmgsArr.length;
        var indexToSplice = -1;
        for(var i = 0; i < len; i++){
            if(newDmgsArr[i].id === id){
                indexToSplice = i;
            }
        }
        if(indexToSplice !== -1){
            newDmgsArr.splice(indexToSplice, 1);
        }
        this.setState({newDamages: newDmgsArr});
    }
    render(){
        return (
            <div>
                <h2>Room Damages <small>{this.state.room.location}</small></h2>
                <CurrentDamagesTable roomDamages={this.state.damages}/>
                <AddDamageBox options={this.state.options} addDamage={this.addUnsavedDamages} newDamages={this.state.newDamages}
                    removeRoomDamages={this.removeRoomDamages} alert={this.state.alert} saveDamages={this.saveDamages}/>
            </div>
        );
    }
}

// Component responsible for displaying the table of the current saved damages
class CurrentDamagesTable extends React.Component{
    render(){
        if(this.props.roomDamages !== undefined) {
            var rows = this.props.roomDamages.map(function(node){
                return (
                    <tr key={node.id}>
                        <td>{node.category}</td>
                        <td>{node.description}</td>
                        <td>{node.side}</td>
                        <td>{node.term}</td>
                        <td>{node.reported_on}</td>
                        <td>
                            <a href="javascript:;" title={node.note}>
                                <i className="fa fa-comment"></i>
                            </a>
                        </td>
                    </tr>
                );
            });
        } else {
            var emptyMsg = (<p>No reported damages for your room.</p>)
        }

        return (
            <div className="row">
                <div className="col-md-8">
                    <div className="panel panel-default">
                        <div className="panel-heading">
                            <h4>Existing Damages</h4>
                        </div>
                        <div className="panel-body">
                            <p>Damages that we already know about are listed below. You won't be charged for these when you check-out.</p>
                            <table className="table table-striped table-hover">
                                <thead>
                                    <tr>
                                        <th>Category</th>
                                        <th>Damage Type</th>
                                        <th>Side</th>
                                        <th>Term</th>
                                        <th>Reported On</th>
                                        <th></th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {rows}
                                </tbody>
                            </table>
                            {emptyMsg}
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}


// Component responsible for collecting the relevant information for adding room damage, as
// well as the button to add it to the UnsavedDamagesTable table.
class AddDamageBox extends React.Component{
    constructor(props){
        super(props);

        this.state = {dmgTypeValid: undefined, descValid: undefined}
        this.add = this.add.bind(this);
    }
    add(){

        var dmgType = this.refs.damageType.value;
        var side = this.refs.side.value;
        var desc = this.refs.desc.value;

        var dmgTypeUnset = (dmgType === 0);
        var descUnset = (desc === "");

        // If the form data is valid, then update state
        if(!dmgTypeUnset && !descUnset){
            // Reset the form for the next damage
            this.refs.damageType.value = '0';
            this.refs.side.value = 'Both';
            this.refs.desc.value = '';

            this.props.addDamage(dmgType, side, desc);
        }

        // Otherwise, update state to indicate an error
        this.setState({dmgTypeInvalid: dmgTypeUnset, descInvalid: descUnset});

    }
    render(){
        var options = Array({category:"Welcome", id: 0, description: "Select the type of damage"});//{id: 0, description: "Select the type of Damage"}

        var data = this.props.options;
        for(var i = 0; i < data.length; i++) {
          options.push(data[i]);
        }

        var selectOptions = options.map(function(node){
            if(node.category === "Welcome") {
                return (<option key={node.id} value={node.id}>{node.description}</option>);
            } else {
              var dmgTypes = node.DamageTypes;
              var options = [];
              for(var i = 0; i < dmgTypes.length;i++) {
                var object = dmgTypes[i];
                options[i+1] = <option key={object.id} value={object.id}>{object.description}</option>;
              }

              return(<optgroup key={node.category} label={node.category}>
                {options}
              </optgroup>);
            }
        });

        var dmgTypeError = false;
        var descError = false;

        if(this.state.dmgTypeInvalid !== undefined) {
            dmgTypeError = this.state.dmgTypeInvalid;
        }

        if(this.state.descInvalid !== undefined) {
            descError = this.state.descInvalid;
        }

        var dmgTypeClasses = classNames({
            'form-group': true,
            'has-error': dmgTypeError
        });

        var descClasses = classNames({
            'form-group': true,
            'has-error': descError
        });

        return (
            <div className='row'>
                <div className="col-md-8">
                    <div className="panel panel-default">
                        <div className="panel-heading">
                            <h4>New Room Damages</h4>
                        </div>

                        <div className="panel-body">
                            <div className="row">
                                <div className="col-md-10">
                                    <p>Add any additional damages you've found in your room. The left/right side of the room are from the perspective of facing into the room from the hallway.</p>
                                </div>
                            </div>

                            <AlertBox alert={this.props.alert}/>

                            <UnsavedDamagesTable newRoomDamages={this.props.newDamages} removeRow={this.props.removeRoomDamages} saveDamages={this.props.saveDamages}/>
                            <hr/>
                            <div className="row">
                                <div className="col-md-5">
                                    <div className={dmgTypeClasses}>
                                        <label>Damage Type:</label>
                                        <select className="form-control" ref="damageType">
                                            {selectOptions}
                                        </select>
                                    </div>
                                </div>
                                <div className="col-md-3">
                                    <div className="form-group">
                                        <label>Side of Room:</label>
                                        <select className="form-control" ref="side">
                                            <option>Both</option>
                                            <option>Left</option>
                                            <option>Right</option>
                                        </select>
                                    </div>
                                </div>
                                <div className="col-md-4">
                                    <div className={descClasses}>
                                        <label>Description</label>
                                        <input type="text" className="form-control" ref="desc"></input>
                                    </div>
                                </div>
                            </div>
                            <div className="form-group">
                                <button onClick={this.add} className="btn btn-md btn-success pull-right"><i className="fa fa-plus"></i> Add Damage</button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}

class AlertBox extends React.Component{
    render(){
        if(this.props.alert === undefined){
            return (
                <div></div>
            );
        }
        else{
            //console.log(this.props.alert)
            if(this.props.alert.status === "success"){
                return (
                    <div className="alert alert-success">
                        <i className="fa fa-check fa-2x"></i> <span>Room Damages successfully added.</span>
                    </div>
                );
            }
            else{
                return (
                    <div className="alert alert-danger">
                        <i className="fa fa-times fa-2x"></i> <span>{this.props.alert.status}</span>
                    </div>
                );
            }
        }
    }
}

// Component responsible for displaying the table of the unsaved damages that have been
// added but not saved by the user.
class UnsavedDamagesTable extends React.Component{
    constructor(props){
        super(props);

        this.removeRow = this.removeRow.bind(this);
    }
    removeRow(id){
        this.props.removeRow(id);
    }
    render(){
        //console.log(this.props.newRoomDamages)
        if(this.props.newRoomDamages.length !== 0){
            var data = this.props.newRoomDamages;
            var removeRow = this.removeRow
            var rows = data.map(function(node){
                return (
                    <UnsavedDamageRow key={node.dmgTypeId + node.category + node.side} node={node} removeRow={removeRow}/>
                );
            });
            return (
                <div>
                    <h5><strong>New Unsaved Damages</strong></h5>
                    <table className="table table-striped table-hover">
                        <thead>
                            <tr>
                                <th>Category</th>
                                <th>Damage Type</th>
                                <th>Side</th>
                                <th></th>
                            </tr>
                        </thead>
                        <tbody>
                            {rows}
                        </tbody>
                    </table>
                    <div className="row">
                        <div className="col-md-12">
                            <button onClick={this.props.saveDamages} className="btn btn-lg btn-primary pull-right">Save New Damages</button>
                        </div>
                    </div>
                </div>
            );
        }
        else {
            return (<div></div>);
        }
    }
}

class UnsavedDamageRow extends React.Component{
    constructor(props){
        super(props);

        this.removeRow = this.removeRow.bind(this);
    }
    removeRow(){
        this.props.removeRow(this.props.node.id);
    }
    render(){
        var node = this.props.node;
        var commentStyle = {'marginRight': '5px'};
        return (
            <tr>
                <td>{node.category}</td>
                <td>{node.description}</td>
                <td>{node.side}</td>
                <td>
                    <a style={commentStyle} className="pull-left" href="javascript:;" title={node.note}>
                        <i className="fa fa-comment"></i>
                    </a>
                    <button onClick={this.removeRow} className="close pull-right">
                      <i className="fa fa-trash"></i>
                    </button>
                </td>
            </tr>
        )
    }
}



//Inserts all the react components within the given element.
ReactDOM.render(<RoomDamageBox roomLocation={window.roomLocation} roomPID={window.roomPID} term={window.term}/>, document.getElementById('roomDamage'));