- {tableData.length > 10 &&
-
Displaying {displaying.start} - {displaying.end} of {tableData.length} available drones:
-
}
- {/* displaying end */}
-
- {/* table end */}
+
+
+ {drones.total ?
+ (
+
+
+
Displaying {displayFrom} - {displayTo} of {drones.total} {dronesTypeText}:
+
- {tableData.length > 10 &&
-
-
Show
-
-
+
+
+
+
+ Image
+
+ {
+ updateDroneTable({sortBy: sortBy === '-serialNumber' ? 'serialNumber' : '-serialNumber'});
+ }}
+ >
+ Drone Serial Number
+
+
+
+ {
+ updateDroneTable({sortBy: sortBy === '-name' ? 'name' : '-name'});
+ }}
+ >
+ Drone Name
+
+
+
+ {
+ updateDroneTable({sortBy: sortBy === '-type' ? 'type' : '-type'});
+ }}
+ >
+ Drone Type
+
+
+
+ {
+ updateDroneTable({sortBy: sortBy === '-mileage' ? 'mileage' : '-mileage'});
+ }}
+ >
+ Mileage
+
+
+
+
+
+
+ {drones.items.map((drone) => (
+
+
+
+ {drone.thumbnailUrl ?
+ (
+
+ ) : (
+
+ )
+ }
+
+
+ {drone.serialNumber}
+ {drone.name}
+ {drone.type}
+ {drone.mileage}
+
+
+
+
+ ))}
+
+
-
per page
-
- }
- {/* show-per-page end */}
-
+
+
+ {
+ updateDroneTable({limit: value});
+ }}
+ />
+
+
+
{
+ updateDroneTable({offset: Math.ceil(selected * limit)});
+ }}
+ />
+
+
+
+
+ ) : (
+
{noDronesText}
+ )
+ }
);
}
}
-
MyDronesTable.propTypes = {
- tableData: PropTypes.array.isRequired,
- items: PropTypes.object.isRequired,
- itemPerPage: PropTypes.func.isRequired,
- displayingHandle: PropTypes.func.isRequired,
- displaying: PropTypes.object.isRequired,
-
+ currentTab: PropTypes.string.isRequired,
+ updateDroneTable: PropTypes.func.isRequired,
+ availableDrones: PropTypes.object.isRequired,
+ onMissionDrones: PropTypes.object.isRequired,
+ offset: PropTypes.number.isRequired,
+ limit: PropTypes.number.isRequired,
+ sortBy: PropTypes.string.isRequired,
+ deleteDrone: PropTypes.func.isRequired,
};
export default CSSModules(MyDronesTable, styles);
diff --git a/src/routes/MyDrone/components/MyDronesTable/MyDronesTable.scss b/src/routes/MyDrone/components/MyDronesTable/MyDronesTable.scss
index 4c1c6ce..5210a09 100644
--- a/src/routes/MyDrone/components/MyDronesTable/MyDronesTable.scss
+++ b/src/routes/MyDrone/components/MyDronesTable/MyDronesTable.scss
@@ -1,15 +1,76 @@
.my-drones-table {
- padding: 20px;
position: relative;
}
+.react-table {
+ margin: 25px 18px 0;
+}
+
+.table {
+ width: 100%;
+
+ td,
+ th {
+ padding: 0;
+ }
+}
+
+.thead {
+ background-color: #1e526c;
+}
+
+.th-inner {
+ color: #fff;
+ font-size: 14px;
+ font-weight: 400;
+ padding: 14px 27px 16px;
+ text-align: left;
+}
+
+.th-inner--sort-asc,
+.th-inner--sort-desc {
+ @extend .th-inner;
+ cursor: pointer;
+
+ &:after {
+ background: url('https://github.com/icon-sort-desc.png') no-repeat;
+ content: '';
+ display: inline-block;
+ height: 7px;
+ margin-left: 8px;
+ width: 11px;
+ }
+}
+
+.th-inner--sort-asc {
+ &:after {
+ transform: rotate(180deg);
+ }
+}
+
+.tr {
+ border-bottom: 1px solid #e7e8ea;
+}
+
+.td-inner {
+ font-size: 14px;
+ padding: 16px 27px;
+
+ > a {
+ color: #3b73b9;
+ }
+}
+
+.td-inner_date {
+ white-space: nowrap;
+}
+
.table-head {
display: flex;
- padding-bottom: 20px;
- padding-top: 5px;
.display {
font-size: 14px;
color: #131313;
+ margin: 30px 20px 0;
}
.filter-btn {
margin-left: auto;
@@ -33,106 +94,103 @@
}
}
-.show-per-page {
- position: absolute;
- display: flex;
- align-items: center;
- bottom: 27px;
- left: 20px;
+.navigation {
+ margin: 25px 20px;
}
-.perPage-select {
- width: 52px;
- margin: 0 10px;
+.navigation:after {
+ clear: both;
+ content: '';
+ display: table;
}
-:global {
- .Dropdown-root {
- position: relative;
- }
- .Dropdown-control {
- position: relative;
- overflow: hidden;
- background-color: white;
- border: 1px solid #e3e3e3;
- border-radius: 5px;
- box-sizing: border-box;
- color: #282828;
- cursor: default;
- outline: none;
- padding: 2px 12px 2px 10px;
- transition: all 200ms ease;
- }
+.pagination {
+ float: right;
+}
- .Dropdown-control:hover {
- box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
- }
+.perpage {
+ float: left;
+}
- .Dropdown-arrow {
- background: url("https://github.com/styles/img/icon-dropdown-caret-sm.png") no-repeat;
- width: 15px;
- height: 9px;
- border: none;
- position: absolute;
- top: 10px;
- right: 3px;
- }
+.actions {
+ display: flex;
+ justify-content: center;
+ align-items: baseline;
- .is-open .Dropdown-arrow {
- border-color: transparent transparent #999;
- border-width: 0 5px 5px;
- }
+ > li {
+ list-style: none;
+ color: #babfca;
+ font-size: 11px;
+ font-weight: bold;
+ height: 50px;
+ cursor: pointer;
+ margin-left: 40px;
- .Dropdown-menu {
- background-color: white;
- border: 1px solid #ccc;
- box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
- box-sizing: border-box;
- margin-top: -1px;
- max-height: 200px;
- overflow-y: auto;
- position: absolute;
- top: 100%;
- width: 100%;
- z-index: 1000;
- -webkit-overflow-scrolling: touch;
- }
+ &:first-child {
+ margin-left: 0;
+ }
+ }
+}
- .Dropdown-menu .Dropdown-group > .Dropdown-title{
- padding: 8px 10px;
- color: rgba(51, 51, 51, 1);
- font-weight: bold;
- text-transform: capitalize;
- }
+%action_link {
+ color: #babfca;
+ display: block;
+ font-size: 11px;
+ font-weight: bold;
+ text-align: center;
- .Dropdown-option {
- box-sizing: border-box;
- color: rgba(51, 51, 51, 0.8);
- cursor: pointer;
- display: block;
- padding: 8px 10px;
- }
+ &:hover {
+ color: #babfca;
+ }
+}
- .Dropdown-option:last-child {
- border-bottom-right-radius: 2px;
- border-bottom-left-radius: 2px;
- }
+.view-detail,
+.edit,
+.delete {
+ @extend %action_link;
+}
- .Dropdown-option:hover {
- background-color: #f2f9fc;
- color: #333;
- }
- .Dropdown-option.is-selected {
- background-color: #f2f9fc;
- color: #333;
- }
+%action_icon {
+ background-position: 0 0;
+ background-repeat: no-repeat;
+ content: '';
+ display: block;
+ margin: 0 auto;
+}
- .Dropdown-noresults {
- box-sizing: border-box;
- color: #ccc;
- cursor: default;
- display: block;
- padding: 8px 10px;
- }
+.view-detail:before {
+ @extend %action_icon;
+
+ width: 27px;
+ height: 17px;
+ background: url('https://github.com/icon-view-detail.png');
+}
+
+.edit:before {
+ @extend %action_icon;
+
+ width: 20px;
+ height: 20px;
+ background: url('https://github.com/icon-edit-row.png');
+}
+
+.delete:before {
+ @extend %action_icon;
+
+ width: 15px;
+ height: 22px;
+ background: url('https://github.com/icon-delete-row.png');
+}
+
+.no-drones {
+ padding: 30px 20px;
+}
+
+.no-image {
+ background: url('https://github.com/icon-drone-black.png') no-repeat center;
+ opacity: 0.5;
+ border: 1px solid rgb(77, 77, 77);
+ height: 95px;
+ width: 148px;
}
diff --git a/src/routes/MyDrone/components/MyDronesTabs/MyDronesTabs.jsx b/src/routes/MyDrone/components/MyDronesTabs/MyDronesTabs.jsx
index e1ef7bd..4ed638a 100644
--- a/src/routes/MyDrone/components/MyDronesTabs/MyDronesTabs.jsx
+++ b/src/routes/MyDrone/components/MyDronesTabs/MyDronesTabs.jsx
@@ -1,54 +1,35 @@
import React, {PropTypes} from 'react';
import CSSModules from 'react-css-modules';
-import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
import styles from './MyDronesTabs.scss';
-import MyDronesTable from '../MyDronesTable';
-
-Tabs.setUseDefaultStyles(false);
/*
* MyDronesTabs
*/
-
-export const MyDronesTabs = ({availableDrones, onMissionDrones, itemPerPage, items, displayingHandle, displaying}) => (
-
-
-
- Available({availableDrones.length} )
- On Mission({onMissionDrones.length} )
-
-
-
-
-
-
-
-
-
-
-
+export const MyDronesTabs = ({currentTab, updateDroneTable, availableDrones, onMissionDrones}) => (
+
+
{
+ currentTab !== 'available' && updateDroneTable({currentTab: 'available'});
+ }}
+ >Available ({availableDrones.total})
+
+
{
+ currentTab !== 'onMission' && updateDroneTable({currentTab: 'onMission'});
+ }}
+ >On Mission ({onMissionDrones.total})
+
+
);
MyDronesTabs.propTypes = {
- availableDrones: PropTypes.array.isRequired,
- onMissionDrones: PropTypes.array.isRequired,
- items: PropTypes.object.isRequired,
- itemPerPage: PropTypes.func.isRequired,
- displayingHandle: PropTypes.func.isRequired,
- displaying: PropTypes.object.isRequired,
+ currentTab: PropTypes.string.isRequired,
+ updateDroneTable: PropTypes.func.isRequired,
+ availableDrones: PropTypes.object.isRequired,
+ onMissionDrones: PropTypes.object.isRequired,
};
diff --git a/src/routes/MyDrone/components/MyDronesTabs/MyDronesTabs.scss b/src/routes/MyDrone/components/MyDronesTabs/MyDronesTabs.scss
index ea175db..240402b 100644
--- a/src/routes/MyDrone/components/MyDronesTabs/MyDronesTabs.scss
+++ b/src/routes/MyDrone/components/MyDronesTabs/MyDronesTabs.scss
@@ -1,5 +1,31 @@
.my-drones-tabs {
- :global {
+ border-bottom: 1px solid #e7e8ea;
+ padding-left: 15px;
+ overflow: hidden;
+}
+
+.tab {
+ background-color: #e7e8ea;
+ border-radius: 5px 5px 0 0;
+ cursor: pointer;
+ float: left;
+ height: 46px;
+ font-size: 14px;
+ font-weight: 700;
+ line-height: 45px;
+ margin-left: 7px;
+ text-align: center;
+ width: 150px;
+ &:first-child {
+ margin-left: 0;
}
}
+
+.tab_active {
+ background-color: #315b95;
+ color: #fff;
+ cursor: default;
+
+ @extend .tab;
+}
diff --git a/src/routes/MyDrone/components/ProviderMap/ProviderMap.jsx b/src/routes/MyDrone/components/ProviderMap/ProviderMap.jsx
index 30df989..7fdde1c 100644
--- a/src/routes/MyDrone/components/ProviderMap/ProviderMap.jsx
+++ b/src/routes/MyDrone/components/ProviderMap/ProviderMap.jsx
@@ -3,8 +3,25 @@ import CSSModules from 'react-css-modules';
import MapLegends from '../MapLegends';
import styles from './ProviderMap.scss';
-const getImage = (name) => `${window.location.origin}/img/${name}`;
+const statusToImage = {
+ 'idle-busy': 'icon-error-drone.png',
+ 'in-motion': 'icon-booked-drone.png',
+ 'idle-ready': 'icon-standby-drone.png',
+};
+const getMarkerIcon = (status) => `${window.location.origin}/img/${statusToImage[status]}`;
+
+const mapConfig = {
+ zoom: 13,
+ center: {
+ lat: -6.202180076671433,
+ lng: 106.83877944946289,
+ },
+ mapTypeControl: false,
+ zoomControl: false,
+ streetViewControl: false,
+ clickableIcons: false,
+};
/*
* ProviderMap
@@ -13,51 +30,31 @@ const getImage = (name) => `${window.location.origin}/img/${name}`;
class ProviderMap extends React.Component {
componentDidMount() {
- const {myDrons} = this.props;
-
- this.map = new google.maps.Map(this.node, {
- zoom: 7,
- center: myDrons[0],
- mapTypeControl: false,
- zoomControl: false,
- streetViewControl: false,
- });
-
- this.start = new google.maps.Marker({
- icon: getImage('icon-standby-drone.png'),
- position: myDrons[0],
- map: this.map,
- });
-
- this.end = new google.maps.Marker({
- icon: getImage('icon-booked-drone.png'),
- position: myDrons[1],
- map: this.map,
- });
-
- this.drone = new google.maps.Marker({
- icon: getImage('icon-error-drone.png'),
- position: myDrons[2],
- map: this.map,
- });
-
- this.start = new google.maps.Marker({
- icon: getImage('icon-standby-drone.png'),
- position: myDrons[3],
- map: this.map,
- });
-
- this.end = new google.maps.Marker({
- icon: getImage('icon-booked-drone.png'),
- position: myDrons[4],
- map: this.map,
- });
-
- this.drone = new google.maps.Marker({
- icon: getImage('icon-error-drone.png'),
- position: myDrons[5],
- map: this.map,
- });
+ this.map = new google.maps.Map(this.node, mapConfig);
+ this.droneMarkers = [];
+
+ // add all markers to the map
+ for (const droneCurrentLocation of this.props.dronesCurrentLocations) {
+ if (droneCurrentLocation.currentLocation.length >= 2) {
+ const droneMarker = new google.maps.Marker({
+ icon: getMarkerIcon(droneCurrentLocation.status),
+ position: {
+ lng: droneCurrentLocation.currentLocation[0],
+ lat: droneCurrentLocation.currentLocation[1],
+ },
+ map: this.map,
+ });
+ this.droneMarkers.push(droneMarker);
+ }
+ }
+
+ // zoom map to fit all markers
+ const markersBounds = new google.maps.LatLngBounds();
+ for (const droneMarker of this.droneMarkers) {
+ markersBounds.extend(droneMarker.getPosition());
+ }
+ this.map.setCenter(markersBounds.getCenter());
+ this.map.fitBounds(markersBounds);
}
shouldComponentUpdate() { // eslint-disable-line lodash/prefer-constant
@@ -76,7 +73,7 @@ class ProviderMap extends React.Component {
}
ProviderMap.propTypes = {
- myDrons: PropTypes.array.isRequired,
+ dronesCurrentLocations: PropTypes.array.isRequired,
};
diff --git a/src/routes/MyDrone/containers/MyDroneContainer.js b/src/routes/MyDrone/containers/MyDroneContainer.js
index 92d5f83..251190f 100644
--- a/src/routes/MyDrone/containers/MyDroneContainer.js
+++ b/src/routes/MyDrone/containers/MyDroneContainer.js
@@ -1,6 +1,12 @@
-import {connect} from 'react-redux';
+import {asyncConnect} from 'redux-connect';
+import {actions} from '../modules/MyDrone';
+
import MyDroneView from '../components/MyDroneView';
+const resolve = [{
+ promise: ({store}) => store.dispatch(actions.load()),
+}];
+
const mapState = (state) => state.myDrone;
-export default connect(mapState, {})(MyDroneView);
+export default asyncConnect(resolve, mapState, actions)(MyDroneView);
diff --git a/src/routes/MyDrone/containers/MyDronesTableContainer.js b/src/routes/MyDrone/containers/MyDronesTableContainer.js
new file mode 100644
index 0000000..84c9dc4
--- /dev/null
+++ b/src/routes/MyDrone/containers/MyDronesTableContainer.js
@@ -0,0 +1,7 @@
+import {connect} from 'react-redux';
+import {actions} from '../modules/MyDrone';
+import MyDronesTable from '../components/MyDronesTable';
+
+const mapState = (state) => state.myDrone;
+
+export default connect(mapState, actions)(MyDronesTable);
diff --git a/src/routes/MyDrone/containers/MyDronesTabsContainer.js b/src/routes/MyDrone/containers/MyDronesTabsContainer.js
index 5403c48..f41c629 100644
--- a/src/routes/MyDrone/containers/MyDronesTabsContainer.js
+++ b/src/routes/MyDrone/containers/MyDronesTabsContainer.js
@@ -1,16 +1,7 @@
import {connect} from 'react-redux';
+import {actions} from '../modules/MyDrone';
import MyDronesTabs from '../components/MyDronesTabs';
-import {itemPerPageAction, displayedRowsAction} from '../modules/MyDrone';
-
const mapState = (state) => state.myDrone;
-const mapDispatchToProps = (dispatch) => ({
- itemPerPage: (items) => {
- dispatch(itemPerPageAction(items));
- },
- displayingHandle: (items) => {
- dispatch(displayedRowsAction(items));
- },
-});
-export default connect(mapState, mapDispatchToProps)(MyDronesTabs);
+export default connect(mapState, actions)(MyDronesTabs);
diff --git a/src/routes/MyDrone/modules/MyDrone.js b/src/routes/MyDrone/modules/MyDrone.js
index 2ab9a65..e669f19 100644
--- a/src/routes/MyDrone/modules/MyDrone.js
+++ b/src/routes/MyDrone/modules/MyDrone.js
@@ -1,191 +1,127 @@
-import {handleActions, createAction} from 'redux-actions';
-import Reactable from 'reactable';
-
-const unsafe = Reactable.unsafe;
-
-const rowActions =
- '
';
-const getImage = () => `${window.location.origin}/img/`;
+import {handleActions} from 'redux-actions';
+import _ from 'lodash';
+import APIService from 'services/APIService';
+import {toastr} from 'react-redux-toastr';
+
+// ------------------------------------
+// Constants
+// ------------------------------------
+export const LOADED = 'MyDrone/LOADED';
+export const UPDATE_DRONE_TABLE = 'MyDrone/UPDATE_DRONE_TABLE';
+export const SET_LIMIT = 'MyDrone/SET_LIMIT';
+export const SET_OFFSET = 'MyDrone/SET_OFFSET';
+export const SET_SORT_BY = 'MyDrone/SET_SORT_BY';
+export const SET_CURRENT_TAB = 'MyDrone/SET_CURRENT_TAB';
// ------------------------------------
// Actions
// ------------------------------------
-export const itemPerPageAction = createAction('CHANGE_ITEM_SIZE');
-export const displayedRowsAction = createAction('DISPLAYED_ROWS');
+export const load = () => async(dispatch, getState) => {
+ const query = _.pick(getState().myDrone, ['limit', 'offset', 'sortBy']);
-export const sendRequest = (values) => new Promise((resolve) => {
- alert(JSON.stringify(values, null, 2));
- resolve();
-});
+ const dronesCurrentLocations = await APIService.fetchDronesCurrentLocations();
+ const availableDrones = await APIService.searchProviderDrones({...query, statuses: 'idle-ready'});
+ const onMissionDrones = await APIService.searchProviderDrones({...query, statuses: 'idle-busy,in-motion'});
+
+ dispatch({
+ type: LOADED,
+ payload: {
+ dronesCurrentLocations,
+ availableDrones,
+ onMissionDrones,
+ },
+ });
+};
+
+export const updateDroneTable = (filter) => async(dispatch, getState) => {
+ const prevState = getState().myDrone;
+ const newState = {...prevState, ...filter};
+ const {currentTab, limit, sortBy} = newState;
+ let {offset} = newState;
+
+ if (_.has(filter, 'currentTab')) {
+ // reset page to 0 when change tab
+ offset = 0;
+ dispatch({type: SET_OFFSET, payload: offset});
+
+ dispatch({type: SET_CURRENT_TAB, payload: currentTab});
+ }
+ if (_.has(filter, 'limit')) {
+ // adjust page number (offset) when change per page quantity (limit)
+ offset = Math.floor(prevState.offset / limit);
+ dispatch({type: SET_OFFSET, payload: offset});
+
+ dispatch({type: SET_LIMIT, payload: limit});
+ }
+ if (_.has(filter, 'offset')) {
+ dispatch({type: SET_OFFSET, payload: offset});
+ }
+ if (_.has(filter, 'sortBy')) {
+ dispatch({type: SET_SORT_BY, payload: sortBy});
+ }
+
+ const query = {limit, offset, sortBy};
+ const payload = {};
+
+ if (currentTab === 'available') {
+ payload.availableDrones = await APIService.searchProviderDrones({...query, statuses: 'idle-ready'});
+ } else if (currentTab === 'onMission') {
+ payload.onMissionDrones = await APIService.searchProviderDrones({...query, statuses: 'idle-busy,in-motion'});
+ }
+
+ dispatch({type: UPDATE_DRONE_TABLE, payload});
+};
+export const deleteDrone = (id) => async(dispatch, getState) => {
+ const currentState = getState().myDrone;
+ const query = _.pick(currentState, ['limit', 'offset', 'sortBy']);
+ const currentTab = currentState.currentTab;
+ const payload = {};
+ const totalDronsOnCurrentTab = currentTab === 'available' ? currentState.availableDrones.total : currentState.onMissionDrones.total;
+
+ await APIService.deleteProviderDrone(id);
+
+ toastr.success('Drone deleted');
+
+ // if we delete the last drone on the page on the current tab, switch page to previous one
+ if (totalDronsOnCurrentTab === query.offset + 1) {
+ query.offset = Math.max(query.offset - query.limit, 0);
+ dispatch({type: SET_OFFSET, payload: query.offset});
+ }
+
+ if (currentTab === 'available') {
+ payload.availableDrones = await APIService.searchProviderDrones({...query, statuses: 'idle-ready'});
+ } else if (currentTab === 'onMission') {
+ payload.onMissionDrones = await APIService.searchProviderDrones({...query, statuses: 'idle-busy,in-motion'});
+ }
+
+ dispatch({type: UPDATE_DRONE_TABLE, payload});
+};
export const actions = {
- itemPerPageAction,
- displayedRowsAction,
+ load,
+ updateDroneTable,
+ deleteDrone,
};
// ------------------------------------
// Reducer
// ------------------------------------
export default handleActions({
- [itemPerPageAction]: (state, action) => ({
- ...state, items: action.payload,
- }),
- [displayedRowsAction]: (state, action) => ({
- ...state, displaying: action.payload,
+ [LOADED]: (state, action) => ({
+ ...state, ...action.payload,
}),
+ [SET_LIMIT]: (state, action) => ({...state, limit: action.payload}),
+ [SET_OFFSET]: (state, action) => ({...state, offset: action.payload}),
+ [SET_SORT_BY]: (state, action) => ({...state, sortBy: action.payload}),
+ [SET_CURRENT_TAB]: (state, action) => ({...state, currentTab: action.payload}),
+ [UPDATE_DRONE_TABLE]: (state, action) => ({...state, ...action.payload}),
}, {
- // initial data
- items: {value: 10, label: '10'},
- displaying: {start: 1, end: 10, currentPage: 0},
- myDrons: [
- {
- lat: -6.195168,
- lng: 106.446533,
- status: 'Stand By',
- },
- {
- lat: -5.145657,
- lng: 104.47998,
- status: 'Booked',
- },
- {
- lat: -7.079088,
- lng: 107.215576,
- status: 'Error',
- },
- {
- lat: -6.500899,
- lng: 107.797852,
- status: 'Stand By',
- },
- {
- lat: -6.937333,
- lng: 108.643799,
- status: 'Booked',
- },
- {
- lat: -7.591218,
- lng: 108.028564,
- status: 'Error',
- },
- {
- lat: -5.462896,
- lng: 107.775879,
- status: 'Error',
- },
- ],
- availableDrones: [
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type gorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type corem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type xorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type sorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type worem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type iorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type korem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type rorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type morem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type gorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type corem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type xorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type sorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type worem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type iorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type korem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type rorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type morem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type gorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type corem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type xorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type sorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type worem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type iorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type korem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type rorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type morem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type gorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type corem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type xorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type sorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type worem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type lorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type iorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type korem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type rorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type morem', Mileage: '999.99 miles', '': unsafe(rowActions)},
-
- ],
- onMissionDrones: [
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type korem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type rorem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- {Image: unsafe(`
`), 'Drone Serial Number': '123456789ABC', 'Drone Name': 'Drone name lorem ipsum', 'Drone Type': 'Drone type morem', Mileage: '999.99 miles', '': unsafe(rowActions)},
- ],
+ currentTab: 'available',
+ limit: 10,
+ offset: 0,
+ sortBy: 'serialNumber',
+ dronesCurrentLocations: [],
+ availableDrones: {total: 0, items: []},
+ onMissionDrones: {total: 0, items: []},
});
diff --git a/src/routes/MyRequest/components/AssignDrone/AssignDrone.jsx b/src/routes/MyRequest/components/AssignDrone/AssignDrone.jsx
new file mode 100644
index 0000000..86a01d9
--- /dev/null
+++ b/src/routes/MyRequest/components/AssignDrone/AssignDrone.jsx
@@ -0,0 +1,109 @@
+import React, {PropTypes, Component} from 'react';
+import CSSModules from 'react-css-modules';
+import cn from 'classnames';
+import Modal from 'react-modal';
+import styles from './AssignDrone.scss';
+
+const customStyles = {
+ overlay: {
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: 'rgba(9, 9, 9, 0.58)',
+ zIndex: '9999',
+ },
+ content: {
+ top: '50%',
+ left: '50%',
+ right: 'auto',
+ bottom: 'auto',
+ marginRight: '-50%',
+ transform: 'translate(-50%, -50%)',
+ width: '450px',
+ height: '500px',
+ textAlign: 'center',
+ borderRadius: '5px',
+ fontWeight: 'bold',
+ fontSize: '20px',
+ zIndex: '99999',
+ padding: '0',
+ },
+};
+
+class AssignDrone extends Component {
+ constructor() {
+ super();
+
+ this.state = {
+ selectedDrone: null,
+ };
+ this.selectDrone = this.selectDrone.bind(this);
+ }
+
+ selectDrone(i) {
+ const {afterSelect} = this.props;
+
+ this.setState({
+ selectedDrone: i,
+ }, () => afterSelect(i));
+ }
+
+ render() {
+ const {drones, isOpen, closeModal, confirmAssign} = this.props;
+ return (
+
+
+
+
+ Assign drone for the mission
+
+
+ {
+ (drones && drones.length > 0) ?
+ (
+
+ {
+ drones.map((d, i) => (
+ this.selectDrone(i)} styleName={this.state.selectedDrone === i ? 'selected' : null}>
+ {d.name}
+
+ )
+ )
+ }
+
+ ) :
+ (
+
+ No available drones for now!
+
+ )
+ }
+
+
+
{
+ if (this.state.selectedDrone !== null) {
+ confirmAssign();
+ }
+ }
+ }
+ >Confirm
+
+
+
+ );
+ }
+}
+
+AssignDrone.propTypes = {
+ afterSelect: PropTypes.func,
+ drones: PropTypes.array,
+ isOpen: PropTypes.bool.isRequired,
+ closeModal: PropTypes.func,
+ confirmAssign: PropTypes.func,
+};
+
+export default CSSModules(AssignDrone, styles, {allowMultiple: true});
diff --git a/src/routes/MyRequest/components/AssignDrone/AssignDrone.scss b/src/routes/MyRequest/components/AssignDrone/AssignDrone.scss
new file mode 100644
index 0000000..f972af1
--- /dev/null
+++ b/src/routes/MyRequest/components/AssignDrone/AssignDrone.scss
@@ -0,0 +1,63 @@
+:global{
+ .ReactModal__Body--open{
+ overflow: hidden;
+ }
+}
+.title{
+ height: 50px;
+ background-color: #E1E2E5;
+ line-height: 50px;
+ padding-left:20px;
+ border-bottom: 1px solid #333;
+}
+.icon-close{
+ background-image: url("https://github.com/icon-close-modal.png");
+ position: absolute;
+ width: 24px;
+ height: 24px;
+ top:12px;
+ right:12px;
+ cursor: pointer;
+}
+.body{
+ padding-top:20px;
+ height: calc(100% - 110px);
+ overflow: auto;
+ ul{
+ text-align: left;
+ list-style:none;
+ padding:0;
+ margin:0;
+ li{
+ padding: 10px 20px;
+ cursor: pointer;
+ &.selected{
+ background-color: #224488;
+ color:#fff;
+ }
+ }
+ }
+}
+.no-drones{
+ text-align: center;
+}
+.foot{
+ height: 60px;
+ display: flex;
+ align-items:center;
+ justify-content: flex-end;
+ background-color: #E1E2E5;
+ padding: 0 20px;
+ .btn-confirm{
+ color:#fff;
+ background-color: #224488;
+ height: 36px;
+ line-height: 36px;
+ padding:0 16px;
+ border-radius: 4px;
+ cursor: pointer;
+ &.disabled{
+ cursor: not-allowed;
+ }
+ }
+}
diff --git a/src/routes/MyRequest/components/AssignDrone/index.js b/src/routes/MyRequest/components/AssignDrone/index.js
new file mode 100644
index 0000000..cb5c427
--- /dev/null
+++ b/src/routes/MyRequest/components/AssignDrone/index.js
@@ -0,0 +1,3 @@
+import AssignDrone from './AssignDrone';
+
+export default AssignDrone;
diff --git a/src/routes/MyRequest/components/MyRequestItems/MyRequestItems.jsx b/src/routes/MyRequest/components/MyRequestItems/MyRequestItems.jsx
index b143dfb..ea6ec96 100644
--- a/src/routes/MyRequest/components/MyRequestItems/MyRequestItems.jsx
+++ b/src/routes/MyRequest/components/MyRequestItems/MyRequestItems.jsx
@@ -16,18 +16,30 @@ class MyRequestItems extends React.Component {
_toggleDetail(i) {
if (_.includes(this.state.openedItems, i)) {
- this.state.openedItems.push(i);
- } else {
this.state.openedItems.splice(this.state.openedItems.indexOf(i), 1);
+ } else {
+ this.state.openedItems.push(i);
}
this.setState({openedItems: this.state.openedItems});
}
render() {
+ const {requestItems, currentStatus, assignDrone, rejectRequest, completeRequest, getDrones} = this.props;
return (
- {this.props.requestItems.map((requestItem, i) => (
-
+ {requestItems.map((requestItem, i) => (
+
))}
);
@@ -36,6 +48,11 @@ class MyRequestItems extends React.Component {
MyRequestItems.propTypes = {
requestItems: PropTypes.array.isRequired,
+ currentStatus: PropTypes.string.isRequired,
+ assignDrone: PropTypes.func.isRequired,
+ rejectRequest: PropTypes.func.isRequired,
+ completeRequest: PropTypes.func.isRequired,
+ getDrones: PropTypes.func.isRequired,
};
export default CSSModules(MyRequestItems, styles);
diff --git a/src/routes/MyRequest/components/MyRequestView.jsx b/src/routes/MyRequest/components/MyRequestView.jsx
index 766a63d..01ade37 100644
--- a/src/routes/MyRequest/components/MyRequestView.jsx
+++ b/src/routes/MyRequest/components/MyRequestView.jsx
@@ -1,49 +1,150 @@
-import React, {PropTypes} from 'react';
+import React, {PropTypes, Component} from 'react';
import CSSModules from 'react-css-modules';
import Tabs from 'components/Tabs';
import Pagination from 'components/Pagination';
+import SelectPerPage from 'components/SelectPerPage';
import styles from './MyRequestView.scss';
import MyRequestFilter from './MyRequestFilter';
-import MyRequestItemsContainer from '../containers/MyRequestItemsContainer';
-
-const tabList = [{
- name: 'New/Pending (5)',
-}, {
- name: 'Scheduled (3)',
-}, {
- name: 'In Progress (3)',
-}, {
- name: 'Completed (3)',
-}];
-
-export const MyRequestView = ({activeTab}) => (
-
-
Requests
-
-
-
-
-
{
- /* eslint-disable no-alert */
- alert('Filter Pressed!');
- /* eslint-enable no-alert */
- }}
- />
-
-
-
+import MyRequestItems from './MyRequestItems';
+
+const tabNames = ['New/Pending', 'Scheduled', 'In Progress', 'Completed'];
+
+class MyRequestView extends Component {
+ constructor() {
+ super();
+
+ this.state = {
+ activeTab: 0,
+ page: 0,
+ limit: 10,
+ };
+
+ this.onLimitChange = this.onLimitChange.bind(this);
+ this.onSelectTab = this.onSelectTab.bind(this);
+ this.getTabList = this.getTabList.bind(this);
+ this.loadData = this.loadData.bind(this);
+ this.onPageChange = this.onPageChange.bind(this);
+ }
+
+ componentDidMount() {
+ this.loadData();
+ }
+
+ onSelectTab(i) {
+ this.setState({
+ activeTab: i,
+ page: 0,
+ }, this.loadData);
+ }
+
+ onLimitChange(v) {
+ this.setState({
+ limit: v.value,
+ page: 0,
+ }, this.loadData);
+ }
+
+ onPageChange(v) {
+ this.setState({
+ page: v.selected,
+ }, this.loadData);
+ }
+
+ loadData() {
+ const {loadRequests, statusArr} = this.props;
+ loadRequests(statusArr[this.state.activeTab], this.state.limit, this.state.limit * this.state.page);
+ }
+
+ getTabList() {
+ const {totals, statusArr} = this.props;
+ return tabNames.map((name, i) => ({name: `${name} (${totals[statusArr[i]]})`}));
+ }
+
+ render() {
+ const {totals, statusArr, requestItems, assignDrone, rejectRequest, completeRequest, loadTotals, getDrones} = this.props;
+ return (
+
+
Requests
+
+
+
+
+ {
+ totals[statusArr[this.state.activeTab]] > 0 ?
+ (
+
{
+ /* eslint-disable no-alert */
+ alert('Filter Pressed!');
+ /* eslint-enable no-alert */
+ }}
+ />
+ ) :
+ (
+ No requests.
+ )
+ }
+ {
+
+ }
+ assignDrone(requestId, droneId).then(
+ () => {
+ this.loadData();
+ loadTotals();
+ }
+ )}
+ rejectRequest={(id) => rejectRequest(id).then(
+ () => {
+ this.loadData();
+ loadTotals();
+ }
+ )}
+ completeRequest={(id) => completeRequest(id).then(
+ () => {
+ this.loadData();
+ loadTotals();
+ }
+ )}
+ getDrones={getDrones}
+ />
+
+
-
-
-);
+ );
+ }
+}
MyRequestView.propTypes = {
- activeTab: PropTypes.number,
+ loadRequests: PropTypes.func.isRequired,
+ statusArr: PropTypes.array.isRequired,
+ totals: PropTypes.object,
+ requestItems: PropTypes.object,
+ assignDrone: PropTypes.func.isRequired,
+ rejectRequest: PropTypes.func.isRequired,
+ completeRequest: PropTypes.func.isRequired,
+ loadTotals: PropTypes.func.isRequired,
+ getDrones: PropTypes.func.isRequired,
};
export default CSSModules(MyRequestView, styles);
diff --git a/src/routes/MyRequest/components/MyRequestView.scss b/src/routes/MyRequest/components/MyRequestView.scss
index bf02f33..b024739 100644
--- a/src/routes/MyRequest/components/MyRequestView.scss
+++ b/src/routes/MyRequest/components/MyRequestView.scss
@@ -38,6 +38,10 @@
border-top: 1px solid #D6D6D6;
}
+ .no-data{
+ padding: 35px 30px 30px 22px;
+ }
+
:global {
ul {
margin: 0;
@@ -48,3 +52,20 @@
}
+.navigation {
+ margin: 25px 20px;
+}
+
+.navigation:after {
+ clear: both;
+ content: '';
+ display: table;
+}
+
+.pagination {
+ float: right;
+}
+
+.perpage {
+ float: left;
+}
diff --git a/src/routes/MyRequest/components/RequestDetails/RequestDetails.jsx b/src/routes/MyRequest/components/RequestDetails/RequestDetails.jsx
index f110b6b..520f94c 100644
--- a/src/routes/MyRequest/components/RequestDetails/RequestDetails.jsx
+++ b/src/routes/MyRequest/components/RequestDetails/RequestDetails.jsx
@@ -1,7 +1,9 @@
import React, {PropTypes} from 'react';
import CSSModules from 'react-css-modules';
+import moment from 'moment';
import styles from './RequestDetails.scss';
-import RequestMapContainer from '../../containers/RequestMapContainer';
+import GoogleMapRoute from 'routes/StatusDetail/components/StatusDetailMapRoute/GoogleMapRoute';
+
export const RequestDetails = ({requestItem, index, _toggleDetail}) => (
@@ -14,45 +16,80 @@ export const RequestDetails = ({requestItem, index, _toggleDetail}) => (
{requestItem.packageType}
- Pick Up Location:
- {requestItem.pickUpLocation}
-
-
- What is being delivered:
- {requestItem.deliveryObject}
-
-
- Drop off location
- {requestItem.dropOffLocation}
-
-
- Weight:
- {requestItem.weight}
-
-
- Distance
- {requestItem.distance}
+ Title:
+ {requestItem.title ? requestItem.title : 'N/A'}
- Requested delivery time
- {requestItem.requestedDeliveryTime}
-
-
- Payout:
- {requestItem.payout}
+ {requestItem.serviceType === 'Delivery' ? 'What is being delivered:' : 'Description'}
+ {requestItem.whatToBeDelivered ? requestItem.whatToBeDelivered : 'N/A'}
+ {
+ requestItem.serviceType === 'Delivery' ?
+ (
+ Pick Up Location:
+ {requestItem.pickUpLocation ? requestItem.pickUpLocation : 'N/A'}
+ ) : null
+ }
+ {
+ requestItem.serviceType === 'Delivery' ?
+ (
+ Drop off location
+ {requestItem.dropOffLocation ? requestItem.dropOffLocation : 'N/A'}
+
+ ) : null
+ }
+ {
+ requestItem.serviceType === 'Delivery' ?
+ (
+ Weight:
+
+ {
+ requestItem.weight ? // eslint-disable-line no-nested-ternary
+ (
+ requestItem.weight === 1 ?
+ `${requestItem.weight.toFixed(2)} lb` :
+ `${requestItem.weight.toFixed(2)} lbs`
+ ) : 'N/A'
+ }
+
+ ) : null
+ }
+ {
+ requestItem.serviceType === 'Delivery' ?
+ (
+ Distance
+
+ {
+ requestItem.distance ? // eslint-disable-line no-nested-ternary
+ (
+ requestItem.distance === 1 ?
+ `${requestItem.distance.toFixed(2)} mile` :
+ `${requestItem.distance.toFixed(2)} miles`
+ ) :
+ 'N/A'
+ }
+
+ ) : null
+ }
+ {
+ requestItem.serviceType === 'Delivery' ?
+ (
+ Requested delivery time:
+ {requestItem.requestedDeliveryTime ? moment(requestItem.requestedDeliveryTime).format('DD MMM YYYY, HH:MM A') : 'N/A'}
+ ) : null
+ }
Customer Contact Info
-
+
Name:
- {requestItem.customer.name}
+ {`${requestItem.customer.firstName} ${requestItem.customer.lastName}`}
Phone:
@@ -60,7 +97,7 @@ export const RequestDetails = ({requestItem, index, _toggleDetail}) => (
Address:
- {requestItem.customer.address}
+ {`${requestItem.customer.address.line1}, ${requestItem.customer.address.city}, ${requestItem.customer.address.state} ${requestItem.customer.address.postalCode}`}
Email:
@@ -72,7 +109,17 @@ export const RequestDetails = ({requestItem, index, _toggleDetail}) => (
_toggleDetail(index)} styleName="close" />
-
+
+ }
+ mapElement={
+
+ }
+ startLocation={requestItem.startLocation}
+ endLocation={requestItem.endLocation}
+ zones={requestItem.zones}
+ />
Expand Map
diff --git a/src/routes/MyRequest/components/RequestItem/AssignDrone b/src/routes/MyRequest/components/RequestItem/AssignDrone
new file mode 100644
index 0000000..e69de29
diff --git a/src/routes/MyRequest/components/RequestItem/RequestItem.jsx b/src/routes/MyRequest/components/RequestItem/RequestItem.jsx
index cf2f133..5f8da32 100644
--- a/src/routes/MyRequest/components/RequestItem/RequestItem.jsx
+++ b/src/routes/MyRequest/components/RequestItem/RequestItem.jsx
@@ -1,10 +1,11 @@
import React, {PropTypes} from 'react';
import CSSModules from 'react-css-modules';
+import moment from 'moment';
import styles from './RequestItem.scss';
import RequestItemControls from '../RequestItemControls';
import RequestDetails from '../RequestDetails';
-export const RequestItem = ({requestItem, index, isOpen, _toggleDetail}) => (
+export const RequestItem = ({requestItem, index, isOpen, _toggleDetail, currentStatus, assignDrone, rejectRequest, completeRequest, getDrones}) => (
@@ -12,28 +13,48 @@ export const RequestItem = ({requestItem, index, isOpen, _toggleDetail}) => (
Request ID:
{requestItem.requestId}
-
- Delivery Date:
- {requestItem.deliveryDate}
-
+ {
+ requestItem.serviceType === 'Delivery' ?
+ (
+ Delivery Date:
+ {requestItem.deliveryDate ? moment(requestItem.deliveryDate).format('DD MMM YYYY HH:MM A') : 'N/A'}
+ ) : null
+ }
Distance:
- {requestItem.distance}
+
+ {
+ requestItem.distance ? // eslint-disable-line no-nested-ternary
+ (requestItem.distance === 1 ?
+ `${requestItem.distance.toFixed(2)} mile` :
+ `${requestItem.distance.toFixed(2)} miles`) :
+ 'N/A'
+ }
+
Service Type:
- {requestItem.serviceType}
-
-
- Delivery Location:
- {requestItem.deliveryLocation}
-
-
- Payout:
- $ {requestItem.payout}
+ {requestItem.serviceType ? requestItem.serviceType : 'N/A'}
+ {
+ requestItem.serviceType === 'Delivery' ?
+ (
+ Delivery Location:
+ {requestItem.deliveryLocation ? requestItem.deliveryLocation : 'N/A'}
+ ) : null
+ }
-
+
assignDrone(requestItem.requestId, droneId)}
+ rejectRequest={() => rejectRequest(requestItem.requestId)}
+ completeRequest={() => completeRequest(requestItem.requestId)}
+ getDrones={getDrones}
+ requestId={requestItem.requestId}
+ />
{(() => {
if (isOpen) {
@@ -49,6 +70,11 @@ RequestItem.propTypes = {
index: PropTypes.number.isRequired,
isOpen: PropTypes.bool.isRequired,
_toggleDetail: PropTypes.func.isRequired,
+ currentStatus: PropTypes.string.isRequired,
+ assignDrone: PropTypes.func.isRequired,
+ rejectRequest: PropTypes.func.isRequired,
+ completeRequest: PropTypes.func.isRequired,
+ getDrones: PropTypes.func.isRequired,
};
export default CSSModules(RequestItem, styles);
diff --git a/src/routes/MyRequest/components/RequestItemControls/RequestItemControls.jsx b/src/routes/MyRequest/components/RequestItemControls/RequestItemControls.jsx
index eedc14c..9d63185 100644
--- a/src/routes/MyRequest/components/RequestItemControls/RequestItemControls.jsx
+++ b/src/routes/MyRequest/components/RequestItemControls/RequestItemControls.jsx
@@ -1,19 +1,332 @@
-import React, {PropTypes} from 'react';
+import React, {PropTypes, Component} from 'react';
import CSSModules from 'react-css-modules';
+import Modal from 'react-modal';
+import AssignDrone from '../AssignDrone';
+import Spinner from 'components/Spinner';
import styles from './RequestItemControls.scss';
+import {browserHistory} from 'react-router';
-export const RequestItemControls = ({_toggleDetail, isOpen, index}) => (
-
-
_toggleDetail(index)}>View Detail
-
Accept
-
Reject
-
-);
+const customStyles = {
+ overlay: {
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: 'rgba(9, 9, 9, 0.58)',
+ zIndex: '9999',
+ },
+ content: {
+ top: '50%',
+ left: '50%',
+ right: 'auto',
+ bottom: 'auto',
+ marginRight: '-50%',
+ transform: 'translate(-50%, -50%)',
+ width: '620px',
+ textAlign: 'center',
+ borderRadius: '5px',
+ fontWeight: 'bold',
+ fontSize: '20px',
+ zIndex: '99999',
+ padding: '20px 0',
+ },
+};
+
+class RequestItemControls extends Component {
+ constructor() {
+ super();
+
+ this.state = {
+ modal: {
+ open: null,
+ },
+ drones: [],
+ selectedDroneId: null,
+ spinner: {
+ isOpen: false,
+ error: false,
+ content: null,
+ },
+ };
+
+ this.clickAccept = this.clickAccept.bind(this);
+ this.clickComplete = this.clickComplete.bind(this);
+ this.clickReject = this.clickReject.bind(this);
+ this.clickAssign = this.clickAssign.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.confirmAssign = this.confirmAssign.bind(this);
+ this.confirmComplete = this.confirmComplete.bind(this);
+ this.afterSelect = this.afterSelect.bind(this);
+ this.handleError = this.handleError.bind(this);
+ this.reject = this.reject.bind(this);
+ this.gotoEditData = this.gotoEditData.bind(this);
+ }
+
+ clickAccept() {
+ const {getDrones} = this.props;
+
+ this.setState({
+ spinner: {
+ isOpen: true,
+ content: 'Please Wait...',
+ error: false,
+ },
+ }, () => {
+ getDrones().then(({items}) => {
+ this.setState({
+ spinner: {
+ isOpen: false,
+ },
+ });
+ this.setState({
+ modal: {
+ open: 'assignDrone',
+ },
+ drones: items,
+ });
+ })
+ .catch(this.handleError);
+ });
+ }
+
+ clickComplete() {
+ const {completeRequest} = this.props;
+ this.setState({
+ modal: {
+ open: null,
+ },
+ });
+ this.setState({
+ spinner: {
+ isOpen: true,
+ content: 'Please Wait...',
+ error: false,
+ },
+ }, () => {
+ completeRequest(this.state.selectedDroneId)
+ .then(() => {
+ this.setState({
+ spinner: {
+ isOpen: false,
+ },
+ modal: {
+ open: null,
+ },
+ });
+ this.gotoEditData();
+ })
+ .catch(this.handleError);
+ });
+ }
+
+ handleError(error) {
+ this.setState({
+ spinner: {
+ isOpen: true,
+ content: JSON.parse(error.response.text).error,
+ error: true,
+ },
+ modal: {
+ open: null,
+ },
+ }, () => {
+ setTimeout(() => {
+ this.setState({
+ spinner: {
+ isOpen: false,
+ },
+ });
+ }, 2000);
+ });
+ }
+
+ clickReject() {
+ this.setState({
+ modal: {
+ open: 'rejectConfirm',
+ },
+ });
+ }
+
+ confirmAssign() {
+ this.setState({
+ modal: {
+ open: 'assignConfirm',
+ },
+ });
+ }
+
+ confirmComplete() {
+ this.setState({
+ modal: {
+ open: 'completeConfirm',
+ },
+ });
+ }
+
+ clickAssign() {
+ const {assignDrone} = this.props;
+ this.setState({
+ spinner: {
+ isOpen: true,
+ content: 'Please Wait...',
+ error: false,
+ },
+ }, () => {
+ assignDrone(this.state.selectedDroneId)
+ .then(() => {
+ this.setState({
+ spinner: {
+ isOpen: false,
+ },
+ modal: {
+ open: null,
+ },
+ });
+ })
+ .catch(this.handleError);
+ });
+ }
+
+ reject() {
+ const {rejectRequest} = this.props;
+
+ this.setState({
+ spinner: {
+ isOpen: true,
+ content: 'Please Wait...',
+ error: false,
+ },
+ }, () => {
+ rejectRequest(this.state.selectedDroneId)
+ .then(() => {
+ this.setState({
+ spinner: {
+ isOpen: false,
+ },
+ modal: {
+ open: null,
+ },
+ });
+ })
+ .catch(this.handleError);
+ });
+ }
+
+ closeModal() {
+ this.setState({
+ modal: {
+ open: null,
+ },
+ });
+ }
+
+ afterSelect(i) {
+ this.setState({
+ selectedDroneId: this.state.drones[i].id,
+ });
+ }
+
+ gotoEditData() {
+ browserHistory.push(`/edit-data/${this.props.requestId}`);
+ }
+
+ render() {
+ const {_toggleDetail, isOpen, index, currentStatus, requestId} = this.props;
+ return (
+
+
_toggleDetail(index)}>View Detail
+ {
+ currentStatus === 'pending' ?
+ (
+
Accept
+ ) : null
+ }
+ {
+ currentStatus === 'scheduled' || currentStatus === 'in-progress' ?
+ (
+
+
Complete Request
+
+
+ Are you sure you want to mark the request as complete
+
+
+
+
+ ) : null
+ }
+ {
+ currentStatus === 'completed' ?
+ (
+
Edit
+ ) : null
+ }
+ {
+ currentStatus === 'pending' ?
+ (
+
Reject
+ ) : null
+ }
+ {
+ currentStatus === 'pending' ?
+ (
+
+
+
+
+ {
+ this.state.modal.open === 'assignConfirm' ?
+ 'Do you really want to assign drone to this request?' :
+ 'Do you really want to reject this request?'
+ }
+
+
+
+
+ ) : null
+ }
+
+
+ );
+ }
+}
RequestItemControls.propTypes = {
_toggleDetail: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
index: PropTypes.number.isRequired,
+ currentStatus: PropTypes.string.isRequired,
+ getDrones: PropTypes.func.isRequired,
+ assignDrone: PropTypes.func.isRequired,
+ rejectRequest: PropTypes.func.isRequired,
+ completeRequest: PropTypes.func.isRequired,
+ requestId: PropTypes.string.isRequired,
};
-export default CSSModules(RequestItemControls, styles);
+export default CSSModules(RequestItemControls, styles, {allowMultiple: true});
diff --git a/src/routes/MyRequest/components/RequestItemControls/RequestItemControls.scss b/src/routes/MyRequest/components/RequestItemControls/RequestItemControls.scss
index 2be7285..f75c732 100644
--- a/src/routes/MyRequest/components/RequestItemControls/RequestItemControls.scss
+++ b/src/routes/MyRequest/components/RequestItemControls/RequestItemControls.scss
@@ -28,7 +28,14 @@
}
.accept {
background-image: url('https://github.com/styles/img/icon-accept.png');
-
+ }
+ .complete {
+ background-image: url('https://github.com/styles/img/icon-accept.png');
+ background-repeat: no-repeat;
+ }
+ .completed {
+ background-image: url('https://github.com/styles/img/icon-edit-row.png');
+ background-repeat: no-repeat;
}
.reject {
background-image: url('https://github.com/styles/img/icon-trash.png');
@@ -48,4 +55,30 @@
background-image: url('https://github.com/styles/img/icon-trash-hover.png');
color: #B90002;
}
-}
\ No newline at end of file
+}
+
+.modal-body{
+ text-align: center;
+ height: 60px;
+ padding: 10px 0;
+}
+.modal-btns{
+ display: flex;
+ height: 60px;
+ justify-content: space-around;
+ align-items:center;
+ .btn{
+ width: 160px;
+ text-align: center;
+ cursor: pointer;
+ height: 48px;
+ line-height: 48px;
+ &.cancel{
+ background-color: #E1E2E5;
+ }
+ &.confirm{
+ background-color: #224488;
+ color:#fff;
+ }
+ }
+}
diff --git a/src/routes/MyRequest/containers/MyRequestContainer.js b/src/routes/MyRequest/containers/MyRequestContainer.js
index 645c5be..4c6dfc1 100644
--- a/src/routes/MyRequest/containers/MyRequestContainer.js
+++ b/src/routes/MyRequest/containers/MyRequestContainer.js
@@ -1,12 +1,22 @@
import {asyncConnect} from 'redux-connect';
-import {actions} from '../modules/MyRequest';
+import {actions, loadRequests, loadTotals, assignDrone, rejectRequest, completeRequest, getDrones} from '../modules/MyRequest';
import MyRequestView from '../components/MyRequestView';
const resolve = [{
- promise: () => Promise.resolve(),
+ promise: ({store}) => loadTotals(store.dispatch),
}];
const mapState = (state) => state.myRequest;
-export default asyncConnect(resolve, mapState, actions)(MyRequestView);
+const mapDispatch = (dispatch) => ({
+ ...actions,
+ assignDrone,
+ rejectRequest,
+ completeRequest,
+ getDrones,
+ loadRequests: (status, limit, offset) => loadRequests(dispatch, status, limit, offset),
+ loadTotals: () => loadTotals(dispatch),
+});
+
+export default asyncConnect(resolve, mapState, mapDispatch)(MyRequestView);
diff --git a/src/routes/MyRequest/containers/MyRequestItemsContainer.js b/src/routes/MyRequest/containers/MyRequestItemsContainer.js
deleted file mode 100644
index 5f732f2..0000000
--- a/src/routes/MyRequest/containers/MyRequestItemsContainer.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import {connect} from 'react-redux';
-import MyRequestItems from '../components/MyRequestItems';
-
-const mapState = (state) => state.myRequest;
-
-export default connect(mapState, {})(MyRequestItems);
diff --git a/src/routes/MyRequest/modules/MyRequest.js b/src/routes/MyRequest/modules/MyRequest.js
index e178900..b18ca2b 100644
--- a/src/routes/MyRequest/modules/MyRequest.js
+++ b/src/routes/MyRequest/modules/MyRequest.js
@@ -1,4 +1,6 @@
-import {handleActions} from 'redux-actions';
+import {handleActions, createAction} from 'redux-actions';
+import _ from 'lodash';
+import APIService from 'services/APIService';
// ------------------------------------
// Actions
@@ -12,186 +14,90 @@ export const sendRequest = (values) => new Promise((resolve) => {
resolve();
});
+const REQUESTS_LOADED = 'MY-REQUEST/REQUESTS_LOADED';
+const TOTALS_LOADED = 'MY-REQUEST/TOTALS_LOADED';
+
+const statusArr = ['pending', 'in-progress', 'scheduled', 'completed'];
export const actions = {
+ requestLoaded: createAction(REQUESTS_LOADED),
+ totalsLoaded: createAction(TOTALS_LOADED),
+};
+
+const getLatLng = (location) => {
+ if (_.get(location, 'coordinates', []).length === 2) {
+ return {
+ lng: location.coordinates[0],
+ lat: location.coordinates[0],
+ };
+ }
+ return null;
};
+export const loadRequests = (dispatch, statuses, limit, offset) =>
+ APIService.getRequestsByProvider({
+ statuses,
+ limit,
+ offset,
+ })
+ .then((res) => dispatch(actions.requestLoaded(
+ {
+ total: res.total,
+ items: res.items.map((item) => ({
+ ...(_.pick(item, 'status', 'distance', 'payout', 'customer', 'weight', 'whatToBeDelivered', 'serviceType', 'zones', 'title')),
+ requestId: item.id,
+ deliveryLocation: item.destinationPoint ? `${item.destinationPoint.line1}, ${item.destinationPoint.city}, ${item.destinationPoint.state} ${item.destinationPoint.postalCode}` : null,
+ deliveryDate: item.launchDate,
+ requestedDeliveryTime: item.launchDate,
+ pickUpLocation: item.startingPoint ? `${item.startingPoint.line1}, ${item.startingPoint.city}, ${item.startingPoint.state} ${item.startingPoint.postalCode}` : null,
+ dropOffLocation: item.destinationPoint ? `${item.destinationPoint.line1}, ${item.destinationPoint.city}, ${item.destinationPoint.state} ${item.destinationPoint.postalCode}` : null,
+ packageType: item.serviceType,
+ startLocation: getLatLng(item.startingPoint),
+ endLocation: getLatLng(item.destinationPoint),
+ })),
+ status: statuses,
+ }
+ )
+ )
+ );
+
+export const loadTotals = (dispatch) => Promise.all(
+ _.map(
+ statusArr,
+ (statuses) => APIService.getRequestsByProvider({
+ limit: 1,
+ statuses,
+ }).then((res) => res.total)
+ )
+ ).then((res) => dispatch(actions.totalsLoaded(_.zipObject(statusArr, res))));
+
+export const assignDrone = (id, droneId) => APIService.acceptRequest(id).then(() => APIService.assignDrone(id, droneId));
+export const rejectRequest = (id) => APIService.rejectRequest(id);
+export const completeRequest = (id) => APIService.completeRequest(id);
+export const getDrones = () => APIService.getProviderDrones();
+
+
// ------------------------------------
// Reducer
// ------------------------------------
export default handleActions({
-}, {
- requestItems: [{
- status: 'new',
- requestId: '123ASDD',
- deliveryDate: '18 Oct 2016, 10:00 AM',
- distance: '99.99 miles',
- serviceType: 'Simple Delivery',
- deliveryLocation: 'Street address lorem, City, State 12355',
- payout: 999.99,
- deliveryObject: 'Delivery Object lorem ipsum',
- packageType: 'Package Lorem Ipsum',
- pickUpLocation: 'Street address lorem, City, State 12355',
- dropOffLocation: 'Street address lorem, City, State 12355',
- weight: '50 lbs',
- requestedDeliveryTime: '18 Oct 2016, 10:00 AM',
- customer: {
- name: 'James Smith',
- address: 'Street address lorem, City, State 12355',
- phone: '123 - 564 - 1231',
- email: 'email@email.com',
+ [REQUESTS_LOADED]: (state, {payload}) => ({
+ ...state,
+ requestItems: {
+ ...state.requestItems,
+ [payload.status]: payload.items,
},
- }, {
- status: 'new',
- requestId: '123ASDD',
- deliveryDate: '18 Oct 2016, 10:00 AM',
- distance: '99.99 miles',
- serviceType: 'Simple Delivery',
- deliveryLocation: 'Street address lorem, City, State 12355',
- payout: 999.99,
- packageType: 'Package Lorem Ipsum',
- pickUpLocation: 'Street address lorem, City, State 12355',
- dropOffLocation: 'Street address lorem, City, State 12355',
- weight: '50 lbs',
- requestedDeliveryTime: '18 Oct 2016, 10:00 AM',
- customer: {
- name: 'James Smith',
- address: 'Street address lorem, City, State 12355',
- phone: '123 - 564 - 1231',
- email: 'email@email.com',
+ totals: {
+ ...state.totals,
+ [payload.state]: payload.total,
},
- }, {
- status: 'new',
- requestId: '123ASDD',
- deliveryDate: '18 Oct 2016, 10:00 AM',
- distance: '99.99 miles',
- serviceType: 'Simple Delivery',
- deliveryLocation: 'Street address lorem, City, State 12355',
- payout: 999.99,
- packageType: 'Package Lorem Ipsum',
- pickUpLocation: 'Street address lorem, City, State 12355',
- dropOffLocation: 'Street address lorem, City, State 12355',
- weight: '50 lbs',
- requestedDeliveryTime: '18 Oct 2016, 10:00 AM',
- customer: {
- name: 'James Smith',
- address: 'Street address lorem, City, State 12355',
- phone: '123 - 564 - 1231',
- email: 'email@email.com',
- },
- }, {
- status: 'new',
- requestId: '123ASDD',
- deliveryDate: '18 Oct 2016, 10:00 AM',
- distance: '99.99 miles',
- serviceType: 'Simple Delivery',
- deliveryLocation: 'Street address lorem, City, State 12355',
- payout: 999.99,
- packageType: 'Package Lorem Ipsum',
- pickUpLocation: 'Street address lorem, City, State 12355',
- dropOffLocation: 'Street address lorem, City, State 12355',
- weight: '50 lbs',
- requestedDeliveryTime: '18 Oct 2016, 10:00 AM',
- customer: {
- name: 'James Smith',
- address: 'Street address lorem, City, State 12355',
- phone: '123 - 564 - 1231',
- email: 'email@email.com',
- },
- }, {
- status: 'scheduled',
- requestId: '123ASDD',
- deliveryDate: '18 Oct 2016, 10:00 AM',
- distance: '99.99 miles',
- serviceType: 'Simple Delivery',
- deliveryLocation: 'Street address lorem, City, State 12355',
- payout: 999.99,
- packageType: 'Package Lorem Ipsum',
- pickUpLocation: 'Street address lorem, City, State 12355',
- dropOffLocation: 'Street address lorem, City, State 12355',
- weight: '50 lbs',
- requestedDeliveryTime: '18 Oct 2016, 10:00 AM',
- customer: {
- name: 'James Smith',
- address: 'Street address lorem, City, State 12355',
- phone: '123 - 564 - 1231',
- email: 'email@email.com',
- },
- }, {
- status: 'scheduled',
- requestId: '123ASDD',
- deliveryDate: '18 Oct 2016, 10:00 AM',
- distance: '99.99 miles',
- serviceType: 'Simple Delivery',
- deliveryLocation: 'Street address lorem, City, State 12355',
- payout: 999.99,
- packageType: 'Package Lorem Ipsum',
- pickUpLocation: 'Street address lorem, City, State 12355',
- dropOffLocation: 'Street address lorem, City, State 12355',
- weight: '50 lbs',
- requestedDeliveryTime: '18 Oct 2016, 10:00 AM',
- customer: {
- name: 'James Smith',
- address: 'Street address lorem, City, State 12355',
- phone: '123 - 564 - 1231',
- email: 'email@email.com',
- },
- }, {
- status: 'in_progress',
- requestId: '123ASDD',
- deliveryDate: '18 Oct 2016, 10:00 AM',
- distance: '99.99 miles',
- serviceType: 'Simple Delivery',
- deliveryLocation: 'Street address lorem, City, State 12355',
- payout: 999.99,
- packageType: 'Package Lorem Ipsum',
- pickUpLocation: 'Street address lorem, City, State 12355',
- dropOffLocation: 'Street address lorem, City, State 12355',
- weight: '50 lbs',
- requestedDeliveryTime: '18 Oct 2016, 10:00 AM',
- customer: {
- name: 'James Smith',
- address: 'Street address lorem, City, State 12355',
- phone: '123 - 564 - 1231',
- email: 'email@email.com',
- },
- }, {
- status: 'in_progress',
- requestId: '123ASDD',
- deliveryDate: '18 Oct 2016, 10:00 AM',
- distance: '99.99 miles',
- serviceType: 'Simple Delivery',
- deliveryLocation: 'Street address lorem, City, State 12355',
- payout: 999.99,
- packageType: 'Package Lorem Ipsum',
- pickUpLocation: 'Street address lorem, City, State 12355',
- dropOffLocation: 'Street address lorem, City, State 12355',
- weight: '50 lbs',
- requestedDeliveryTime: '18 Oct 2016, 10:00 AM',
- customer: {
- name: 'James Smith',
- address: 'Street address lorem, City, State 12355',
- phone: '123 - 564 - 1231',
- email: 'email@email.com',
- },
- }, {
- status: 'completed',
- requestId: '123ASDD',
- deliveryDate: '18 Oct 2016, 10:00 AM',
- distance: '99.99 miles',
- serviceType: 'Simple Delivery',
- deliveryLocation: 'Street address lorem, City, State 12355',
- payout: 999.99,
- packageType: 'Package Lorem Ipsum',
- pickUpLocation: 'Street address lorem, City, State 12355',
- dropOffLocation: 'Street address lorem, City, State 12355',
- weight: '50 lbs',
- requestedDeliveryTime: '18 Oct 2016, 10:00 AM',
- customer: {
- name: 'James Smith',
- address: 'Street address lorem, City, State 12355',
- phone: '123 - 564 - 1231',
- email: 'email@email.com',
- },
- }],
+ }),
+ [TOTALS_LOADED]: (state, {payload}) => ({
+ ...state,
+ totals: payload,
+ }),
+}, {
+ statusArr,
+ totals: _.zipObject(statusArr, _.times(statusArr.length, _.constant(0))),
+ requestItems: _.zipObject(statusArr, _.times(statusArr.length, () => ([]))),
});
diff --git a/src/routes/MyRequestStatus/components/MyRequestHeader/MyRequestHeader.jsx b/src/routes/MyRequestStatus/components/MyRequestHeader/MyRequestHeader.jsx
index 6625f1c..d20f353 100644
--- a/src/routes/MyRequestStatus/components/MyRequestHeader/MyRequestHeader.jsx
+++ b/src/routes/MyRequestStatus/components/MyRequestHeader/MyRequestHeader.jsx
@@ -1,27 +1,173 @@
-import React, {PropTypes} from 'react';
+import React, {PropTypes, Component} from 'react';
import CSSModules from 'react-css-modules';
+import Modal from 'react-modal';
+import {browserHistory} from 'react-router';
import SelectDropdown from 'components/SelectDropdown';
+import Button from 'components/Button';
import styles from './MyRequestHeader.scss';
-export const MyRequestHeader = ({onStatusChange, statusValue}) => (
-
-
My Request Status
-
-
-);
+const customStyles = {
+ overlay: {
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: 'rgba(9, 9, 9, 0.58)',
+ zIndex: '9999',
+ },
+ content: {
+ top: '50%',
+ left: '50%',
+ right: 'auto',
+ bottom: 'auto',
+ marginRight: '-50%',
+ transform: 'translate(-50%, -50%)',
+ padding: '0px',
+ width: '417px',
+ borderRadius: '10px',
+ zIndex: '99999',
+ border: 'none',
+ },
+};
+
+class MyRequestHeader extends Component {
+ constructor() {
+ super();
+
+ this.state = {
+ openModal: false,
+ };
+
+ this.clickCreate = this.clickCreate.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.selectPackage = this.selectPackage.bind(this);
+ this.confirmPackage = this.confirmPackage.bind(this);
+ }
+
+ clickCreate() {
+ const {searchPackages} = this.props;
+ this.setState({
+ openModal: true,
+ }, () => {
+ searchPackages().then(() => {
+ this.setState({
+ searchError: false,
+ });
+ }).catch(() => {
+ this.setState({
+ searchError: true,
+ });
+ });
+ });
+ }
+
+ closeModal() {
+ this.setState({
+ openModal: false,
+ });
+ }
+
+ selectPackage(i) {
+ this.setState({
+ selectedPackage: i,
+ });
+ }
+
+ confirmPackage() {
+ const {availablePackages} = this.props;
+ this.setState({
+ openModal: false,
+ }, () => {
+ browserHistory.push(`/service-request/${availablePackages[this.state.selectedPackage].id}`);
+ });
+ }
+
+ render() {
+ const {onStatusChange, statusValue, availablePackages} = this.props;
+ return (
+
+
My Request Status
+
+
+ Create Request
+
+
+
+
+
+
+ Select a package
+
+
+ {
+ availablePackages.length > 0 ?
+ (
+
+ {
+ availablePackages.map((p, i) => (
+ this.selectPackage(i)}
+ styleName={this.state.selectedPackage === i ? 'selected' : null}
+ >
+ {p.name}
+
+ ))
+ }
+
+ ) :
+ (
+
+ {
+ this.state.searchError ?
+ 'An error occured when searching packages' :
+ 'No available packages for now.'
+ }
+
+ )
+ }
+
+ {
+ availablePackages.length > 0 ?
+ (
+
+
+ Confirm
+
+
+ ) : null
+ }
+
+
+
+ );
+ }
+}
MyRequestHeader.propTypes = {
onStatusChange: PropTypes.func.isRequired,
statusValue: PropTypes.string.isRequired,
+ availablePackages: PropTypes.array.isRequired,
+ searchPackages: PropTypes.func.isRequired,
};
export default CSSModules(MyRequestHeader, styles);
diff --git a/src/routes/MyRequestStatus/components/MyRequestHeader/MyRequestHeader.scss b/src/routes/MyRequestStatus/components/MyRequestHeader/MyRequestHeader.scss
index f029ca7..de2b211 100644
--- a/src/routes/MyRequestStatus/components/MyRequestHeader/MyRequestHeader.scss
+++ b/src/routes/MyRequestStatus/components/MyRequestHeader/MyRequestHeader.scss
@@ -1,3 +1,8 @@
+:global{
+ .ReactModal__Body--open{
+ overflow: hidden;
+ }
+}
.my-request-header {
align-items: center;
border-bottom: 1px solid #d5d5d5;
@@ -38,3 +43,48 @@
width: 12px;
}
}
+.right-group{
+ display: flex;
+ align-items: center;
+}
+.create-btn{
+ margin-right:20px;
+}
+.modal-wrap{
+ padding: 0;
+ ul{
+ list-style: none;
+ padding:0;
+ margin:0;
+ }
+ li{
+ padding: 6px;
+ border: 1px solid #ddd;
+ margin-bottom: 10px;
+ cursor: pointer;
+ }
+ .selected{
+ background-color: #315b95;
+ color: #fff;
+ }
+}
+.modal-title{
+ background-color: #1e526c;
+ padding: 12px;
+ color: #fff;
+ font-size: 16px;
+}
+.modal-body{
+ padding: 20px;
+ max-height: 300px;
+ overflow:auto;
+}
+
+.modal-foot{
+ padding: 8px 20px;
+ text-align: right;
+ border-top: 1px solid #ddd;
+}
+.error{
+ color:red;
+}
diff --git a/src/routes/MyRequestStatus/components/MyRequestStatusView.jsx b/src/routes/MyRequestStatus/components/MyRequestStatusView.jsx
index b88ccfd..c7e4360 100644
--- a/src/routes/MyRequestStatus/components/MyRequestStatusView.jsx
+++ b/src/routes/MyRequestStatus/components/MyRequestStatusView.jsx
@@ -5,7 +5,7 @@ import MyRequestHeader from './MyRequestHeader';
import MyRequestTable from './MyRequestTable';
import styles from './MyRequestStatusView.scss';
-export const MyRequestStatusView = ({requests, load, filterByStatus}) => (
+export const MyRequestStatusView = ({requests, load, filterByStatus, searchPackages, availablePackages}) => (
(
]}
/>
-
load(value)} statusValue={filterByStatus} />
+ load(value)}
+ statusValue={filterByStatus}
+ searchPackages={searchPackages}
+ availablePackages={availablePackages}
+ />
@@ -26,6 +31,8 @@ MyRequestStatusView.propTypes = {
requests: MyRequestTable.propTypes.requests,
load: PropTypes.func.isRequired,
filterByStatus: PropTypes.string.isRequired,
+ searchPackages: PropTypes.func.isRequired,
+ availablePackages: PropTypes.array.isRequired,
};
export default CSSModules(MyRequestStatusView, styles);
diff --git a/src/routes/MyRequestStatus/components/MyRequestTable/MyRequestTable.jsx b/src/routes/MyRequestStatus/components/MyRequestTable/MyRequestTable.jsx
index 9000887..736618f 100644
--- a/src/routes/MyRequestStatus/components/MyRequestTable/MyRequestTable.jsx
+++ b/src/routes/MyRequestStatus/components/MyRequestTable/MyRequestTable.jsx
@@ -30,9 +30,8 @@ export const MyRequestTable = ({requests}) => (
const MyRequestPropType = {
id: PropTypes.string.isRequired,
- title: PropTypes.string.isRequired,
provider: PropTypes.string.isRequired,
- timeOflaunch: PropTypes.string.isRequired,
+ timeOflaunch: PropTypes.string,
status: StatusLabel.propTypes.value,
};
diff --git a/src/routes/MyRequestStatus/containers/MyRequestStatusContainer.js b/src/routes/MyRequestStatus/containers/MyRequestStatusContainer.js
index 80b9377..0be83c6 100644
--- a/src/routes/MyRequestStatus/containers/MyRequestStatusContainer.js
+++ b/src/routes/MyRequestStatus/containers/MyRequestStatusContainer.js
@@ -9,4 +9,5 @@ const resolve = [{
const mapState = (state) => state.myRequestStatus;
+
export default asyncConnect(resolve, mapState, actions)(MyRequestStatusView);
diff --git a/src/routes/MyRequestStatus/modules/MyRequestStatus.js b/src/routes/MyRequestStatus/modules/MyRequestStatus.js
index f397bdc..357dcc5 100644
--- a/src/routes/MyRequestStatus/modules/MyRequestStatus.js
+++ b/src/routes/MyRequestStatus/modules/MyRequestStatus.js
@@ -5,18 +5,42 @@ import APIService from 'services/APIService';
// Constants
// ------------------------------------
export const LOADED = 'MyRequestStatus/LOADED';
+export const PACKAGES_LOADED = 'MyRequestStatus/PACKAGES_LOADED';
// ------------------------------------
// Actions
// ------------------------------------
export const load = (filterByStatus = 'all') => async(dispatch) => {
- const requests = await APIService.fetchMyRequestStatus(filterByStatus);
+ const res = await APIService.fetchMyRequestStatus(filterByStatus === 'all' ? undefined : filterByStatus); // eslint-disable-line no-undefined
+ const requests = res.map((r) => ({
+ id: r.id,
+ status: r.status === 'in-progress' ? 'inProgress' : r.status,
+ timeOflaunch: r.launchDate,
+ provider: r.provider.name,
+ title: r.title,
+ }));
- dispatch({type: LOADED, payload: {requests, filterByStatus}});
+ dispatch({
+ type: LOADED,
+ payload: {
+ requests,
+ filterByStatus,
+ },
+ });
};
+export const searchPackages = () => (dispatch) => APIService.searchPackages({limit: -1}).then(({items}) => {
+ dispatch({
+ type: PACKAGES_LOADED,
+ payload: {
+ availablePackages: items,
+ },
+ });
+});
+
export const actions = {
load,
+ searchPackages,
};
// ------------------------------------
@@ -24,6 +48,9 @@ export const actions = {
// ------------------------------------
export default handleActions({
[LOADED]: (state, {payload: {requests, filterByStatus}}) => ({...state, requests, filterByStatus}),
+ [PACKAGES_LOADED]: (state, {payload}) => ({...state, ...payload}),
}, {
filterByStatus: 'all',
+ requests: [],
+ availablePackages: [],
});
diff --git a/src/routes/PilotChecklist/components/PilotChecklistForm/PilotChecklistForm.jsx b/src/routes/PilotChecklist/components/PilotChecklistForm/PilotChecklistForm.jsx
new file mode 100644
index 0000000..ec821f8
--- /dev/null
+++ b/src/routes/PilotChecklist/components/PilotChecklistForm/PilotChecklistForm.jsx
@@ -0,0 +1,100 @@
+import React, {PropTypes, Component} from 'react';
+import {reduxForm} from 'redux-form';
+import Button from 'components/Button';
+import Radiobox from 'components/Radiobox';
+import TextareaField from 'components/TextareaField';
+import CSSModules from 'react-css-modules';
+import styles from './PilotChecklistForm.scss';
+import _ from 'lodash';
+
+export class PilotChecklistForm extends Component {
+ constructor(props) {
+ super(props);
+
+ this.onButtonClick = this.onButtonClick.bind(this);
+ PilotChecklistForm.pressedButton = null;
+ }
+
+ onButtonClick(name) {
+ PilotChecklistForm.pressedButton = name;
+ this.props.handleSubmit((values) => this.props.save({...values, pressedButton: name}))();
+ }
+
+ render() {
+ const {questions, fields, missionStatus} = this.props;
+ const isReadonly = _.includes(['completed', 'in-progress'], missionStatus);
+ const hasErrors = _.find(fields.answers, (answerRow) => answerRow.answer.error || answerRow.note.error);
+
+ return (
+
+ );
+ }
+}
+
+PilotChecklistForm.propTypes = {
+ questions: PropTypes.array.isRequired,
+ fields: PropTypes.object.isRequired,
+ handleSubmit: PropTypes.func.isRequired,
+ missionStatus: PropTypes.string.isRequired,
+ save: PropTypes.func.isRequired,
+};
+
+const fields = [
+ 'answers[].answer',
+ 'answers[].note',
+];
+
+/**
+ * Validate function for redux form
+ * @param {Object} values values to validate
+ * @return {Object} errors
+ */
+const validate = (values) => {
+ const errors = {};
+
+ if (PilotChecklistForm.pressedButton === 'saveload') {
+ errors.answers = _.map(values.answers, (answerRow) => {
+ let err;
+
+ if (_.isNil(answerRow.answer)) {
+ err = {answer: 'Answer is required'};
+ } else if (answerRow.answer === 'no') {
+ err = {answer: 'Answer cannot be "No"'};
+ } else if (answerRow.answer === 'note' && (!_.isString(answerRow.note) || answerRow.note.trim() === '')) {
+ err = {note: 'You have to provide a "Note", when you chose "No, but proceed with caution"'};
+ }
+
+ return err;
+ });
+ }
+
+ return errors;
+};
+
+export default reduxForm({form: 'pilotChecklist', fields, validate})(CSSModules(PilotChecklistForm, styles));
diff --git a/src/routes/PilotChecklist/components/PilotChecklistForm/PilotChecklistForm.scss b/src/routes/PilotChecklist/components/PilotChecklistForm/PilotChecklistForm.scss
new file mode 100644
index 0000000..6272302
--- /dev/null
+++ b/src/routes/PilotChecklist/components/PilotChecklistForm/PilotChecklistForm.scss
@@ -0,0 +1,62 @@
+.pilot-checklist-form {
+ margin: 0 auto;
+ max-width: 1000px;
+}
+
+.question {
+ border-bottom: 1px solid #d5d5d5;
+ margin-bottom: 30px;
+ padding-bottom: 34px;
+
+ &:last-child {
+ border-bottom: 0;
+ margin-bottom: 0;
+ }
+}
+
+.radioboxes {
+ display: flex;
+}
+
+.radiobox {
+ margin-right: 50px;
+}
+
+.note {
+ margin-top: 10px;
+}
+
+.note-label {
+ display: block;
+ margin-bottom: 8px;
+}
+
+.actions {
+ border-top: 1px solid #d5d5d5;
+ padding-bottom: 30px;
+ text-align: right;
+
+ button {
+ margin-left: 12px;
+ }
+}
+
+.error {
+ color: #f00;
+ height: 40px;
+ line-height: 40px;
+}
+
+.note-label-wrap {
+ display: flex;
+
+ .error {
+ height: auto;
+ line-height: inherit;
+ margin-left: 30px;
+ }
+}
+
+.global-error {
+ height: 40px;
+}
diff --git a/src/routes/PilotChecklist/components/PilotChecklistForm/index.js b/src/routes/PilotChecklist/components/PilotChecklistForm/index.js
new file mode 100644
index 0000000..93af7bb
--- /dev/null
+++ b/src/routes/PilotChecklist/components/PilotChecklistForm/index.js
@@ -0,0 +1,3 @@
+import PilotChecklistForm from './PilotChecklistForm';
+
+export default PilotChecklistForm;
diff --git a/src/routes/PilotChecklist/components/PilotChecklistView.jsx b/src/routes/PilotChecklist/components/PilotChecklistView.jsx
new file mode 100644
index 0000000..e977a98
--- /dev/null
+++ b/src/routes/PilotChecklist/components/PilotChecklistView.jsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import CSSModules from 'react-css-modules';
+import styles from './PilotChecklistView.scss';
+import PilotChecklistForm from '../containers/PilotChecklistFormContainer';
+
+export const PilotChecklistView = () => (
+
+
+
+
Flight Checklist
+
+
+
+
+);
+
+PilotChecklistView.propTypes = {
+};
+
+export default CSSModules(PilotChecklistView, styles);
diff --git a/src/routes/PilotChecklist/components/PilotChecklistView.scss b/src/routes/PilotChecklist/components/PilotChecklistView.scss
new file mode 100644
index 0000000..f9be2bd
--- /dev/null
+++ b/src/routes/PilotChecklist/components/PilotChecklistView.scss
@@ -0,0 +1,37 @@
+.pilot-checklist-view {
+ background-color: transparent;
+
+ :global {
+
+ }
+}
+
+.wrap {
+ padding: 0 30px 35px;
+}
+
+.header {
+ border-bottom: 1px solid #d5d5d5;
+ display: flex;
+ margin-bottom: 19px;
+ justify-content: space-between;
+ padding-bottom: 17px;
+ padding-top: 21px;
+ position: relative;
+}
+
+.title {
+ color: #333333;
+ font-size: 24px;
+ font-weight: 600;
+ line-height: 32px;
+ margin: 0;
+ padding: 0;
+}
+
+.panel {
+ background-color: #fff;
+ border: 1px solid #e0e0e0;
+ border-radius: 3px;
+ padding: 19px 24px;
+}
diff --git a/src/routes/PilotChecklist/containers/BreadcrumbItemContainer.js b/src/routes/PilotChecklist/containers/BreadcrumbItemContainer.js
new file mode 100644
index 0000000..206227f
--- /dev/null
+++ b/src/routes/PilotChecklist/containers/BreadcrumbItemContainer.js
@@ -0,0 +1,11 @@
+import {connect} from 'react-redux';
+
+// we use global BreadcrumbItem component to display breadcrumb item,
+// just pass a title property here
+import BreadcrumbItem from 'components/BreadcrumbItem';
+
+const mapState = (state) => ({
+ title: state.pilotChecklist.missionName,
+});
+
+export default connect(mapState, {})(BreadcrumbItem);
diff --git a/src/routes/PilotChecklist/containers/PilotChecklistContainer.js b/src/routes/PilotChecklist/containers/PilotChecklistContainer.js
new file mode 100644
index 0000000..7b65831
--- /dev/null
+++ b/src/routes/PilotChecklist/containers/PilotChecklistContainer.js
@@ -0,0 +1,12 @@
+import {asyncConnect} from 'redux-connect';
+import {actions} from '../modules/PilotChecklist';
+
+import PilotChecklistView from '../components/PilotChecklistView';
+
+const resolve = [{
+ promise: ({store, params}) => store.dispatch(actions.load(params.id)),
+}];
+
+const mapState = (state) => state.pilotChecklist;
+
+export default asyncConnect(resolve, mapState, actions)(PilotChecklistView);
diff --git a/src/routes/PilotChecklist/containers/PilotChecklistFormContainer.js b/src/routes/PilotChecklist/containers/PilotChecklistFormContainer.js
new file mode 100644
index 0000000..cb1bae3
--- /dev/null
+++ b/src/routes/PilotChecklist/containers/PilotChecklistFormContainer.js
@@ -0,0 +1,33 @@
+import {connect} from 'react-redux';
+import {actions} from '../modules/PilotChecklist';
+import _ from 'lodash';
+
+import PilotChecklistForm from '../components/PilotChecklistForm';
+
+/**
+ * Create initial values for the checklist form
+ * it takes into account that we can have not all answers,
+ * but form requires full quantity of elements, so we create empty answers when need
+ *
+ * @param {Array} questions list of all questions
+ * @param {Array} answers) list of answers, could be not for all questions
+ * @return {Object} initialValues for the form
+ */
+const answersToInitialValues = (questions, answers) => ({
+ answers: _.map(questions, (question) => (
+ {
+ ...{answer: undefined, note: undefined}, // eslint-disable-line no-undefined
+ ..._.find(answers, {question: question.id}),
+ }
+ )),
+});
+
+const mapState = (state) => ({
+ questions: state.pilotChecklist.questions,
+ initialValues: answersToInitialValues(state.pilotChecklist.questions, state.pilotChecklist.answers),
+ missionStatus: state.pilotChecklist.missionStatus,
+});
+
+export default connect(mapState, {
+ save: actions.save,
+})(PilotChecklistForm);
diff --git a/src/routes/PilotChecklist/index.js b/src/routes/PilotChecklist/index.js
new file mode 100644
index 0000000..602408e
--- /dev/null
+++ b/src/routes/PilotChecklist/index.js
@@ -0,0 +1,18 @@
+import {injectReducer} from '../../store/reducers';
+import React from 'react';
+import BreadcrumbItem from './containers/BreadcrumbItemContainer';
+
+export default (store) => ({
+ name: 'Flight Checklist',
+ path: 'pilot-checklist/:id',
+ getComponent(nextState, cb) {
+ require.ensure([], (require) => {
+ const PilotChecklist = require('./containers/PilotChecklistContainer').default;
+ const reducer = require('./modules/PilotChecklist').default;
+
+ injectReducer(store, {key: 'pilotChecklist', reducer});
+ cb(null, PilotChecklist);
+ }, 'PilotChecklist');
+ },
+ prettifyParam: () => React.createElement(BreadcrumbItem), // eslint-disable-line react/display-name
+});
diff --git a/src/routes/PilotChecklist/modules/PilotChecklist.js b/src/routes/PilotChecklist/modules/PilotChecklist.js
new file mode 100644
index 0000000..9991388
--- /dev/null
+++ b/src/routes/PilotChecklist/modules/PilotChecklist.js
@@ -0,0 +1,75 @@
+import {handleActions} from 'redux-actions';
+import APIService from 'services/APIService';
+import {toastr} from 'react-redux-toastr';
+import _ from 'lodash';
+
+// ------------------------------------
+// Constants
+// ------------------------------------
+export const LOADED = 'PilotChecklist/LOADED';
+export const UPDATED = 'PilotChecklist/UPDATED';
+
+// ------------------------------------
+// Actions
+// ------------------------------------
+export const load = (missionId) => async(dispatch) => {
+ const response = await APIService.getPilotChecklist(missionId);
+ const answers = response.pilotChecklist ? response.pilotChecklist.answers : [];
+
+ dispatch({type: LOADED, payload: {..._.pick(response, ['missionStatus', 'missionName', 'questions']), answers, missionId}});
+};
+
+export const save = (values) => async (dispatch, getState) => {
+ const questions = getState().pilotChecklist.questions;
+ // send to server only not empty answers and not empty answer properties
+ const notEmptyAnswers = [];
+ _.forEach(values.answers, (answerRow, index) => {
+ const notEmptyAnswer = {};
+ const hasAnswer = !!answerRow.answer;
+ const hasNote = _.isString(answerRow.note) && answerRow.note.trim() !== '';
+
+ if (hasAnswer || hasNote) {
+ hasAnswer && (notEmptyAnswer.answer = answerRow.answer);
+ hasNote && (notEmptyAnswer.note = answerRow.note);
+ // add question id to the answer
+ notEmptyAnswer.question = questions[index].id;
+ notEmptyAnswers.push(notEmptyAnswer);
+ }
+ });
+
+ const response = await APIService.updatePilotChecklist(getState().pilotChecklist.missionId, {
+ answers: notEmptyAnswers,
+ load: values.pressedButton === 'saveload',
+ });
+ dispatch({
+ type: UPDATED,
+ payload: {
+ missionStatus: response.missionStatus,
+ answers: response.pilotChecklist.answers,
+ },
+ });
+ if (values.pressedButton === 'saveload') {
+ toastr.success('Checklist saved and mission loaded');
+ } else {
+ toastr.success('Checklist saved');
+ }
+};
+
+export const actions = {
+ load,
+ save,
+};
+
+// ------------------------------------
+// Reducer
+// ------------------------------------
+export default handleActions({
+ [LOADED]: (state, {payload}) => ({...state, ...payload}),
+ [UPDATED]: (state, {payload}) => ({...state, ...payload}),
+}, {
+ missionId: '',
+ missionStatus: '',
+ missionName: '',
+ questions: [],
+ answers: [],
+});
diff --git a/src/routes/PilotMissions/components/CheckStatus/CheckStatus.jsx b/src/routes/PilotMissions/components/CheckStatus/CheckStatus.jsx
new file mode 100644
index 0000000..f8dc05f
--- /dev/null
+++ b/src/routes/PilotMissions/components/CheckStatus/CheckStatus.jsx
@@ -0,0 +1,146 @@
+import React from 'react';
+import Modal from 'react-modal';
+import CSSModules from 'react-css-modules';
+import styles from './CheckStatus.scss';
+
+/*
+* customStyles
+*/
+const customStyles = {
+ overlay: {
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: 'rgba(9, 9, 9, 0.58)',
+ zIndex: '9999',
+ },
+ content: {
+ top: '50%',
+ left: '50%',
+ right: 'auto',
+ bottom: 'auto',
+ marginRight: '-50%',
+ transform: 'translate(-50%, -50%)',
+ padding: '0px',
+ width: '900px',
+ borderRadius: '10px',
+ zIndex: '99999',
+ overflow: 'auto',
+ height: '500px',
+ },
+};
+
+const CheckStatus = ({modalOpen, droneStatus, closeModal}) => (
+
+
+
+
Mission Drone Status
+
+
+
+
+
+
Current Position
+
+
Time boot (ms)
+
: {droneStatus.currentPosition.time_boot_ms}
+
+
+
Latitude
+
: {droneStatus.currentPosition.lat}
+
+
+
Longitude
+
: {droneStatus.currentPosition.lon}
+
+
+
Altitude
+
: {droneStatus.currentPosition.alt}
+
+
+
Relative Altitude
+
: {droneStatus.currentPosition.relative_alt}
+
+
+
vx
+
: {droneStatus.currentPosition.vx}
+
+
+
vy
+
: {droneStatus.currentPosition.vy}
+
+
+
vz
+
: {droneStatus.currentPosition.vz}
+
+
+
hdg
+
: {droneStatus.currentPosition.hdg}
+
+
+
+
+
+
Mission Waypoints
+ {droneStatus.waypoints.map((point, pi) => (
+
+
+
Latitude
+
: {point.y}
+
+
+
Longitude
+
: {point.x}
+
+
+
Altitude
+
: {point.z}
+
+
+
Param1
+
: {point.param1}
+
+
+
Param2
+
: {point.param2}
+
+
+
Param3
+
: {point.param3}
+
+
+
Param4
+
: {point.param4}
+
+
+
Command
+
: {point.command}
+
+
+
Frame
+
: {point.frame}
+
+
+ ))}
+
+
+
+
+
+);
+
+CheckStatus.propTypes = {
+ modalOpen: React.PropTypes.bool.isRequired,
+ droneStatus: React.PropTypes.object.isRequired,
+ closeModal: React.PropTypes.func.isRequired,
+};
+
+export default CSSModules(CheckStatus, styles);
diff --git a/src/routes/PilotMissions/components/CheckStatus/CheckStatus.scss b/src/routes/PilotMissions/components/CheckStatus/CheckStatus.scss
new file mode 100644
index 0000000..4012be0
--- /dev/null
+++ b/src/routes/PilotMissions/components/CheckStatus/CheckStatus.scss
@@ -0,0 +1,72 @@
+.modal-header {
+ background: #fff;
+ height: 63px;
+ padding: 13px 12px;
+}
+.title {
+ font-size: 24px;
+ color: #0d0d0d;
+ text-align: center;
+ font-weight: bold;
+ font-family: 'Proxima Nova Rg';
+ padding-top: 10px;
+}
+.icon-close-modal {
+ display: block;
+ background-repeat: no-repeat;
+ background-position: 0 0;
+ width: 26px;
+ height: 26px;
+ background: url('https://github.com/icon-close-modal.png') no-repeat 0 0;
+ margin-left: auto;
+ cursor: pointer;
+}
+.modal-body {
+ padding: 20px;
+ .content-wrapper {
+ clear: both;
+ .content {
+ border: 1px solid #ccc;
+ box-shadow: 0px 0px 3px 1px rgba(204,204,204,0.7);
+ margin: 0 5px;
+ padding: 5px;
+ .point {
+ border-bottom: 1px solid #ccc;
+ margin: 5px 0;
+ height: 180px;
+ }
+ .content-heading {
+ font-weight: 600;
+ margin: 0 0 10px 0;
+ font-size: 16px;
+ }
+ .row {
+ clear: both;
+ }
+ .left {
+ float: left;
+ width: 30%;
+ }
+ .right {
+ float: right;
+ width: 70%;
+ }
+ }
+ .left-col {
+ float: left;
+ width: 50%;
+ .content {
+ height: 230px;
+ }
+ }
+ .right-col {
+ float: right;
+ width: 50%;
+ }
+ }
+}
+:global {
+ .ReactModal__Body--open {
+ overflow: hidden;
+ }
+}
\ No newline at end of file
diff --git a/src/routes/PilotMissions/components/CheckStatus/index.js b/src/routes/PilotMissions/components/CheckStatus/index.js
new file mode 100644
index 0000000..d308295
--- /dev/null
+++ b/src/routes/PilotMissions/components/CheckStatus/index.js
@@ -0,0 +1,3 @@
+import CheckStatus from './CheckStatus';
+
+export default CheckStatus;
diff --git a/src/routes/PilotMissions/components/PilotMissionsView.jsx b/src/routes/PilotMissions/components/PilotMissionsView.jsx
new file mode 100644
index 0000000..6f550ce
--- /dev/null
+++ b/src/routes/PilotMissions/components/PilotMissionsView.jsx
@@ -0,0 +1,160 @@
+import React, {PropTypes} from 'react';
+import CSSModules from 'react-css-modules';
+import {Link} from 'react-router';
+import StatusLabel from 'components/StatusLabel';
+import Table from 'components/Table';
+import styles from './PilotMissionsView.scss';
+import Button from 'components/Button';
+import _ from 'lodash';
+import CheckStatus from './CheckStatus';
+import APIService from 'services/APIService';
+import {toastr} from 'react-redux-toastr';
+
+const DEFAULT_ERROR_MESSAGE = 'something went wrong, try again later';
+
+class PilotMissionsView extends React.Component {
+
+ /**
+ * Validate that the questions checklist is completed by the pilot
+ * @param {Object} mission the mission for which to validate the checklist
+ * @return {Boolean} true if checklist is completed otherwise false
+ */
+ validatePilotChecklist(mission) {
+ const has = _.has(mission, 'pilotChecklist') && _.has(mission, 'pilotChecklist.answers');
+ if (has === false) {
+ return has;
+ }
+ let valid = true;
+ for (let i = 0; i < mission.pilotChecklist.answers.length; i += 1) {
+ const single = mission.pilotChecklist.answers[i];
+ if (single.answer === 'no' || (single.answer === 'note' && !_.has(single, 'answer.note'))) {
+ valid = false;
+ }
+ }
+ return valid;
+ }
+
+ /**
+ * Check status for a mission drone's
+ *
+ * @param {Object} event the mouse click event
+ * @param {String} missionId the mission id for which to check the drone status
+ */
+ checkStatus(event, missionId) {
+ event.preventDefault();
+ const {droneCheckStatusHandler} = this.props;
+ droneCheckStatusHandler(missionId);
+ }
+
+ /**
+ * Send the mission to the drone
+ *
+ * @param {Object} event the mouse click event
+ * @param {String} missionId the drone's mission id for which to send the mission
+ */
+ sendToDrone(event, missionId) {
+ event.preventDefault();
+ APIService.loadMission(missionId).then(() => {
+ toastr.success('', 'Mission sent to drone');
+ }).catch((reason) => {
+ const message = _.has(reason, 'respose.text') ? JSON.parse(reason.respose.text).error :
+ DEFAULT_ERROR_MESSAGE;
+ toastr.error('', message);
+ });
+ }
+
+ /**
+ * Close the check status modal popup
+ */
+ closeModal(missionId) {
+ const {droneCheckStatusHandler} = this.props;
+ droneCheckStatusHandler(missionId);
+ }
+
+ /**
+ * React Component lifecycle render method
+ */
+ render() {
+ const _self = this;
+ // only one drone status modal can be opened at a time
+ const {missions, load, offset, limit, total, sortBy, statusModalOpen, droneStatus} = this.props;
+ const columns = [{
+ header: 'Mission Name',
+ accessor: 'missionName',
+ render: (prop) => {prop.value}, // eslint-disable-line react/display-name
+ sortable: true,
+ }, {
+ header: 'Status',
+ accessor: 'status',
+ render: (prop) => , // eslint-disable-line react/display-name
+ sortable: true,
+ }, {
+ header: 'Assigned Drone',
+ accessor: 'drone.name',
+ sortable: true,
+ }, {
+ header: 'Online',
+ sortable: false,
+ render: (prop) => {prop.row.droneOnline === true ? 'Y' : 'N'} ,
+ }, {
+ header: '',
+ sortable: false,
+ render: (prop) => {
+ const disableSendToDrone = prop.row.status === 'completed' || prop.row.droneOnline === false ||
+ !_self.validatePilotChecklist(prop.row);
+
+ const disableCheckStatus = prop.row.status === 'completed' || prop.row.droneOnline === false;
+ const dStatus = droneStatus[prop.row.id] || {};
+ const isOpen = statusModalOpen[prop.row.id] || false;
+ return (
+
+ {/* only add the modal to DOM if for this mission, modal has to be opened */}
+ {isOpen && _self.closeModal(prop.row.id)} />}
+ {disableSendToDrone === false && _self.sendToDrone(event, prop.row.id)} size="medium">Send to drone }
+ {disableSendToDrone === true && _self.sendToDrone(event, prop.row.id)} size="medium">Send to drone }
+ {disableCheckStatus === false && _self.checkStatus(event, prop.row.id)} size="medium">Check Status }
+ {disableCheckStatus === true && _self.checkStatus(event, prop.row.id)} size="medium">Check Status }
+
+ );
+ },
+ }];
+ return (
+
+
+
+
Pilot Missions
+
+
+ {missions.length ? (
+
+ ) : (
+
No missions found.
+ )}
+
+
+
+ );
+ }
+}
+
+PilotMissionsView.propTypes = {
+ missions: PropTypes.array.isRequired,
+ load: PropTypes.func.isRequired,
+ offset: PropTypes.number.isRequired,
+ limit: PropTypes.number.isRequired,
+ total: PropTypes.number.isRequired,
+ sortBy: PropTypes.string.isRequired,
+ droneCheckStatusHandler: PropTypes.func.isRequired,
+ statusModalOpen: PropTypes.object.isRequired,
+ droneStatus: PropTypes.object.isRequired,
+};
+
+export default CSSModules(PilotMissionsView, styles);
diff --git a/src/routes/PilotMissions/components/PilotMissionsView.scss b/src/routes/PilotMissions/components/PilotMissionsView.scss
new file mode 100644
index 0000000..36c08f1
--- /dev/null
+++ b/src/routes/PilotMissions/components/PilotMissionsView.scss
@@ -0,0 +1,57 @@
+.pilot-missions-view {
+ background-color: transparent;
+
+ :global {
+
+ }
+}
+
+.wrap {
+ padding: 0 30px 35px;
+}
+
+.header {
+ border-bottom: 1px solid #d5d5d5;
+ display: flex;
+ margin-bottom: 19px;
+ justify-content: space-between;
+ padding-bottom: 17px;
+ padding-top: 21px;
+ position: relative;
+}
+
+.title {
+ color: #333333;
+ font-size: 24px;
+ font-weight: 600;
+ line-height: 32px;
+ margin: 0;
+ padding: 0;
+}
+
+.panel {
+ background-color: #fff;
+ border: 1px solid #e0e0e0;
+ border-radius: 3px;
+ padding: 19px 24px;
+}
+
+.create-btn {
+ background: #315b95;
+ border: none;
+ color: white;
+ display: inline-block;
+ font-weight: bold;
+ min-width: 115px;
+ margin-left: 10px;
+ padding: 13px 10px;
+
+ &:hover {
+ color: #fff;
+ }
+}
+:global {
+ .send-drone {
+ margin-right: 10px;
+ }
+}
diff --git a/src/routes/PilotMissions/containers/PilotMissionsContainer.js b/src/routes/PilotMissions/containers/PilotMissionsContainer.js
new file mode 100644
index 0000000..4fb6f8a
--- /dev/null
+++ b/src/routes/PilotMissions/containers/PilotMissionsContainer.js
@@ -0,0 +1,12 @@
+import {asyncConnect} from 'redux-connect';
+import {actions} from '../modules/PilotMissions';
+
+import PilotMissionsView from '../components/PilotMissionsView';
+
+const resolve = [{
+ promise: ({store}) => store.dispatch(actions.load()),
+}];
+
+const mapState = (state) => ({...state.pilotMissions});
+
+export default asyncConnect(resolve, mapState, actions)(PilotMissionsView);
diff --git a/src/routes/PilotMissions/index.js b/src/routes/PilotMissions/index.js
new file mode 100644
index 0000000..0da6e08
--- /dev/null
+++ b/src/routes/PilotMissions/index.js
@@ -0,0 +1,15 @@
+import {injectReducer} from '../../store/reducers';
+
+export default (store) => ({
+ name: 'Pilot Missions',
+ path: 'pilot-missions',
+ getComponent(nextState, cb) {
+ require.ensure([], (require) => {
+ const PilotMissions = require('./containers/PilotMissionsContainer').default;
+ const reducer = require('./modules/PilotMissions').default;
+
+ injectReducer(store, {key: 'pilotMissions', reducer});
+ cb(null, PilotMissions);
+ }, 'PilotMissions');
+ },
+});
diff --git a/src/routes/PilotMissions/modules/PilotMissions.js b/src/routes/PilotMissions/modules/PilotMissions.js
new file mode 100644
index 0000000..f1b5b12
--- /dev/null
+++ b/src/routes/PilotMissions/modules/PilotMissions.js
@@ -0,0 +1,62 @@
+import {handleActions} from 'redux-actions';
+import APIService from 'services/APIService';
+import _ from 'lodash';
+
+// ------------------------------------
+// Constants
+// ------------------------------------
+export const LOADED = 'PilotMissions/LOADED';
+export const DRONE_CHECK_STATUS_ACTION = 'PilotMissions/DRONE_CHECK_STATUS_ACTION';
+
+// ------------------------------------
+// Actions
+// ------------------------------------
+export const load = (params) => async(dispatch, getState) => {
+ const allParams = {..._.pick(getState().pilotMissions, ['offset', 'limit', 'sortBy']), ...params};
+ if (!allParams.sortBy) {
+ delete allParams.sortBy;
+ }
+
+ const respond = await APIService.fetchPilotMissions(allParams);
+
+ dispatch({type: LOADED, payload: {missions: respond.items, total: respond.total, ...params}});
+};
+
+export const droneCheckStatus = (missionId) => async(dispatch) => {
+ const status = await APIService.checkDroneStatusForMission(missionId);
+ const droneStatus = {};
+ droneStatus[missionId] = status;
+ dispatch({type: DRONE_CHECK_STATUS_ACTION, payload: {droneStatus, missionId}});
+};
+
+export const actions = {
+ load,
+ droneCheckStatusHandler: droneCheckStatus,
+};
+
+// ------------------------------------
+// Reducer
+// ------------------------------------
+export default handleActions({
+ [LOADED]: (state, {payload}) => ({...state, ...payload}),
+ [DRONE_CHECK_STATUS_ACTION]: (state, {payload}) => {
+ const newState = _.cloneDeep(state);
+ let isOpen = !newState.statusModalOpen[payload.missionId];
+ if (_.isUndefined(isOpen)) {
+ isOpen = true;
+ }
+
+ newState.droneStatus = payload.droneStatus;
+ newState.statusModalOpen = {};
+ newState.statusModalOpen[payload.missionId] = isOpen;
+ return newState;
+ },
+}, {
+ offset: 0,
+ limit: 10,
+ total: 0,
+ sortBy: 'missionName',
+ missions: [],
+ droneStatus: {},
+ statusModalOpen: {},
+});
diff --git a/src/routes/ResetPassword/components/ResetPasswordView.jsx b/src/routes/ResetPassword/components/ResetPasswordView.jsx
new file mode 100644
index 0000000..3fe1671
--- /dev/null
+++ b/src/routes/ResetPassword/components/ResetPasswordView.jsx
@@ -0,0 +1,80 @@
+import React, {Component} from 'react';
+import CSSModules from 'react-css-modules';
+import styles from './ResetPasswordView.scss';
+import TextField from '../../../components/TextField';
+import FormField from '../../../components/FormField';
+import Button from '../../../components/Button';
+import {reduxForm} from 'redux-form';
+import {sendRequest} from '../modules/ResetPassword';
+import {browserHistory} from 'react-router';
+import {toastr} from 'react-redux-toastr';
+
+class ResetPasswordView extends Component {
+
+ /**
+ * This function is called when the form is submitted
+ * This is triggered by handleSubmit
+ */
+ onSubmit(data) {
+ sendRequest(data).then(() => {
+ toastr.success('', 'Password reset successfuly, kindly login again');
+ browserHistory.push('/');
+ }).catch((reason) => {
+ const message = reason.response.body.error || 'something went wrong, please try again';
+ toastr.error(message);
+ });
+ }
+
+ render() {
+ const {fields, handleSubmit, location: {query: {token}}} = this.props;
+ const _self = this;
+ return (
+
+ );
+ }
+}
+
+ResetPasswordView.propTypes = {
+ fields: React.PropTypes.object.isRequired,
+ location: React.PropTypes.object.isRequired,
+ handleSubmit: React.PropTypes.func.isRequired,
+};
+
+const form = reduxForm({
+ form: 'resetPasswordForm',
+ fields: ['password', 'email'],
+ validate(values) {
+ const errors = {};
+ if (!values.password) {
+ errors.password = 'required';
+ }
+ if (!values.email) {
+ errors.email = 'required';
+ }
+
+ return errors;
+ },
+});
+
+export default form(CSSModules(ResetPasswordView, styles));
diff --git a/src/routes/ResetPassword/components/ResetPasswordView.scss b/src/routes/ResetPassword/components/ResetPasswordView.scss
new file mode 100644
index 0000000..b008991
--- /dev/null
+++ b/src/routes/ResetPassword/components/ResetPasswordView.scss
@@ -0,0 +1,48 @@
+.reset-password-form {
+ padding: 50px 0 14px;
+ margin: 0 300px;
+ height: calc(100vh - 60px - 42px - 50px); // header height - breadcrumb height - footer height
+ h4 {
+ font-weight: bold;
+ font-size: 20px;
+ color: #525051;
+ margin-top: 40px;
+ border-top: 1px solid #e7e8ea;
+ padding-top: 25px;
+ }
+ :global {
+ .form-field {
+ width: 100%;
+ &.error {
+ color: #ff3100;
+ > div:first-child {
+ border: 1px solid #ff3100;
+ }
+ }
+ }
+ }
+}
+.row {
+ display: flex;
+ margin-bottom: 22px;
+ label {
+ display: block;
+ flex: 0 0 20%;
+ align-self: center;
+ font-size: 14px;
+ color: #343434;
+ font-weight: bold;
+ }
+
+ .input-with-label {
+ flex: 0 0 20%;
+ display: flex;
+ align-items: center;
+ .input {
+ flex: 0 0 66%;
+ }
+ }
+}
+.actions {
+ text-align: right;
+}
diff --git a/src/routes/ResetPassword/containers/ResetPasswordContainer.js b/src/routes/ResetPassword/containers/ResetPasswordContainer.js
new file mode 100644
index 0000000..f6309c7
--- /dev/null
+++ b/src/routes/ResetPassword/containers/ResetPasswordContainer.js
@@ -0,0 +1,12 @@
+import {asyncConnect} from 'redux-connect';
+import {actions} from '../modules/ResetPassword';
+
+import ResetPasswordView from '../components/ResetPasswordView';
+
+const resolve = [{
+ promise: () => Promise.resolve(),
+}];
+
+const mapState = (state) => state.resetPassword;
+
+export default asyncConnect(resolve, mapState, actions)(ResetPasswordView);
diff --git a/src/routes/ResetPassword/index.js b/src/routes/ResetPassword/index.js
new file mode 100644
index 0000000..eeb7dbb
--- /dev/null
+++ b/src/routes/ResetPassword/index.js
@@ -0,0 +1,20 @@
+import {injectReducer} from '../../store/reducers';
+
+export default (store) => ({
+ path: 'reset-password',
+ name: 'Reset password', /* Breadcrumb name */
+ staticName: true,
+ getComponent(nextState, cb) {
+ require.ensure([], (require) => {
+ const Dashboard = require('./containers/ResetPasswordContainer').default;
+ const reducer = require('./modules/ResetPassword').default;
+
+ injectReducer(store, {key: 'resetPassword', reducer});
+ if (!nextState.location.query.token) {
+ cb(new Error('Invalid route invocation'));
+ } else {
+ cb(null, Dashboard);
+ }
+ }, 'ResetPassword');
+ },
+});
diff --git a/src/routes/ResetPassword/modules/ResetPassword.js b/src/routes/ResetPassword/modules/ResetPassword.js
new file mode 100644
index 0000000..a5bcb9e
--- /dev/null
+++ b/src/routes/ResetPassword/modules/ResetPassword.js
@@ -0,0 +1,23 @@
+import {handleActions} from 'redux-actions';
+import APIService from 'services/APIService';
+// ------------------------------------
+// Actions
+// ------------------------------------
+
+export const actions = {
+};
+
+export const sendRequest = (values) => new Promise((resolve, reject) => {
+ APIService.resetPassword(values).then((result) => {
+ resolve(result);
+ }).catch((reason) => {
+ reject(reason);
+ });
+});
+
+// ------------------------------------
+// Reducer
+// ------------------------------------
+export default handleActions({
+}, {
+});
diff --git a/src/routes/ServiceRequest/components/Address/Address.jsx b/src/routes/ServiceRequest/components/Address/Address.jsx
new file mode 100644
index 0000000..b2d8260
--- /dev/null
+++ b/src/routes/ServiceRequest/components/Address/Address.jsx
@@ -0,0 +1,67 @@
+import React, {PropTypes, Component} from 'react';
+import CSSModules from 'react-css-modules';
+import _ from 'lodash';
+import Accordion from 'components/Accordion';
+import FormField from 'components/FormField';
+import TextField from 'components/TextField';
+import Row from 'components/Row';
+import styles from './Address.scss';
+
+
+/*
+* Address
+*/
+
+class Address extends Component {
+
+ componentWillReceiveProps(nextProps) {
+ const {state, location, city, postalCode, line1, line2} = this.props;
+ const {location: newLocation} = nextProps;
+ if (newLocation && !_.isEqual(location, newLocation)) {
+ state.onChange(newLocation.state);
+ city.onChange(newLocation.city);
+ postalCode.onChange(newLocation.postalCode);
+ line1.onChange(newLocation.line1);
+ line2.onChange(newLocation.line2);
+ }
+ }
+
+ render() {
+ const {type, state, city, postalCode, line1, line2} = this.props;
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+Address.propTypes = {
+ type: PropTypes.string.isRequired,
+ state: PropTypes.object.isRequired,
+ city: PropTypes.object.isRequired,
+ postalCode: PropTypes.object.isRequired,
+ line1: PropTypes.object.isRequired,
+ line2: PropTypes.object.isRequired,
+ location: PropTypes.object,
+};
+
+export default CSSModules(Address, styles);
diff --git a/src/routes/ServiceRequest/components/EstimatedAmountToPay/EstimatedAmountToPay.scss b/src/routes/ServiceRequest/components/Address/Address.scss
similarity index 100%
rename from src/routes/ServiceRequest/components/EstimatedAmountToPay/EstimatedAmountToPay.scss
rename to src/routes/ServiceRequest/components/Address/Address.scss
diff --git a/src/routes/ServiceRequest/components/Address/index.js b/src/routes/ServiceRequest/components/Address/index.js
new file mode 100644
index 0000000..58ab27b
--- /dev/null
+++ b/src/routes/ServiceRequest/components/Address/index.js
@@ -0,0 +1,3 @@
+import Address from './Address';
+
+export default Address;
diff --git a/src/routes/ServiceRequest/components/ContactDetails/ContactDetails.jsx b/src/routes/ServiceRequest/components/ContactDetails/ContactDetails.jsx
index b519c8b..3cd8d86 100644
--- a/src/routes/ServiceRequest/components/ContactDetails/ContactDetails.jsx
+++ b/src/routes/ServiceRequest/components/ContactDetails/ContactDetails.jsx
@@ -3,6 +3,7 @@ import CSSModules from 'react-css-modules';
import Accordion from 'components/Accordion';
import FormField from 'components/FormField';
import TextField from 'components/TextField';
+import Row from 'components/Row';
import styles from './ContactDetails.scss';
@@ -13,9 +14,14 @@ import styles from './ContactDetails.scss';
export const ContactDetails = ({fields}) => (
);
diff --git a/src/routes/ServiceRequest/components/EstimatedAmountToPay/EstimatedAmountToPay.jsx b/src/routes/ServiceRequest/components/EstimatedAmountToPay/EstimatedAmountToPay.jsx
deleted file mode 100644
index b2b6071..0000000
--- a/src/routes/ServiceRequest/components/EstimatedAmountToPay/EstimatedAmountToPay.jsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import React, {PropTypes} from 'react';
-import CSSModules from 'react-css-modules';
-import Accordion from 'components/Accordion';
-import FormField from 'components/FormField';
-import TextField from 'components/TextField';
-import styles from './EstimatedAmountToPay.scss';
-
-
-/*
-* EstimatedAmountToPay
-*/
-
-export const EstimatedAmountToPay = ({fields}) => (
-
-);
-
-EstimatedAmountToPay.propTypes = {
- fields: PropTypes.object.isRequired,
-};
-
-export default CSSModules(EstimatedAmountToPay, styles);
diff --git a/src/routes/ServiceRequest/components/EstimatedAmountToPay/index.js b/src/routes/ServiceRequest/components/EstimatedAmountToPay/index.js
deleted file mode 100644
index a0a252e..0000000
--- a/src/routes/ServiceRequest/components/EstimatedAmountToPay/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import EstimatedAmountToPay from './EstimatedAmountToPay';
-
-export default EstimatedAmountToPay;
diff --git a/src/routes/ServiceRequest/components/ItemRequest/ItemRequest.jsx b/src/routes/ServiceRequest/components/ItemRequest/ItemRequest.jsx
index 4e0700b..1bf80b1 100644
--- a/src/routes/ServiceRequest/components/ItemRequest/ItemRequest.jsx
+++ b/src/routes/ServiceRequest/components/ItemRequest/ItemRequest.jsx
@@ -3,80 +3,48 @@ import CSSModules from 'react-css-modules';
import Accordion from 'components/Accordion';
import FormField from 'components/FormField';
import TextField from 'components/TextField';
-import Row from 'components/Row';
-import InfoIcon from 'components/InfoIcon';
-import Checkbox from 'components/Checkbox';
+import TextareaField from 'components/TextareaField';
import DatePicker from 'components/DatePicker';
-import Select from 'components/Select';
-import _ from 'lodash';
import styles from './ItemRequest.scss';
-const worthOptions = [
- {value: 1, label: '100 - 5000 $'},
- {value: 2, label: '5001 - 10000 $'},
- {value: 3, label: '> 10001 $'},
-];
-
-const weightOptions = [
- {value: 1, label: '0 - 500 gms'},
- {value: 2, label: '501 - 2500 gms'},
- {value: 3, label: '> 2500 gms'},
-];
-
-
/*
* ItemRequest
*/
-export const ItemRequest = ({fields}) => (
+export const ItemRequest = ({fields, serviceType}) => (
-
-
-
-
-
-
-
+ {
+ serviceType === 'Delivery' ?
+ (
+
+ ) : null
+ }
+
+
+
+
+
-
-
-
-
-
-
-
-
- {/* Row end */}
-
- Icon Dimension Length X Width X Height }>
-
-
-
- fields.hazardous.onChange(!fields.hazardous.value)}
- id="hazardous"
- >
- hazardous materials?
-
-
-
- {/* Row end */}
);
ItemRequest.propTypes = {
fields: PropTypes.object.isRequired,
+ serviceType: PropTypes.string.isRequired,
};
export default CSSModules(ItemRequest, styles);
diff --git a/src/routes/ServiceRequest/components/ItemRequest/ItemRequest.scss b/src/routes/ServiceRequest/components/ItemRequest/ItemRequest.scss
index 4eb5c76..6ec0c9e 100644
--- a/src/routes/ServiceRequest/components/ItemRequest/ItemRequest.scss
+++ b/src/routes/ServiceRequest/components/ItemRequest/ItemRequest.scss
@@ -8,4 +8,16 @@
.center {
display: flex;
align-items: center;
-}
\ No newline at end of file
+}
+
+.unit-group{
+ display: flex;
+ align-items:flex-start;
+ .input{
+ flex:5;
+ }
+ .unit{
+ flex: 1;
+ padding: 62px 0 0 10px;
+ }
+}
diff --git a/src/routes/ServiceRequest/components/Location/Location.jsx b/src/routes/ServiceRequest/components/Location/Location.jsx
index f763479..026a019 100644
--- a/src/routes/ServiceRequest/components/Location/Location.jsx
+++ b/src/routes/ServiceRequest/components/Location/Location.jsx
@@ -1,5 +1,6 @@
import React, {PropTypes} from 'react';
import CSSModules from 'react-css-modules';
+import cn from 'classnames';
import styles from './Location.scss';
@@ -7,19 +8,30 @@ import styles from './Location.scss';
* Location
*/
-export const Location = ({type, address}) => (
+export const Location = ({type, address, clearAddress, selectAddress, error}) => (
-
-
- {address.address},
- {address.city}, {address.state}, {address.zip}
-
+
+ {
+ address ?
+ (
+ {`lng: ${address.coor.lng()}, lat:${address.coor.lat()}`}
+
) :
+ (
+
selectAddress(type)}>Click here to select {type === 'start' ? 'starting' : 'target'} location
+ )
+ }
+ {
+ address ? (
X ) : null
+ }
);
Location.propTypes = {
type: PropTypes.string.isRequired,
- address: PropTypes.object.isRequired,
+ address: PropTypes.object,
+ clearAddress: PropTypes.func.isRequired,
+ selectAddress: PropTypes.func.isRequired,
+ error: PropTypes.bool,
};
-export default CSSModules(Location, styles);
+export default CSSModules(Location, styles, {allowMultiple: true});
diff --git a/src/routes/ServiceRequest/components/Location/Location.scss b/src/routes/ServiceRequest/components/Location/Location.scss
index fa75a84..e59164f 100644
--- a/src/routes/ServiceRequest/components/Location/Location.scss
+++ b/src/routes/ServiceRequest/components/Location/Location.scss
@@ -1,13 +1,13 @@
.location {
display: flex;
font-size: 15px;
-
+ position: relative;
i {
display: block;
margin: 0 15px;
background-repeat: no-repeat;
background-position: 0 0;
- width: 16px;
+ width: 25px;
height: 22px;
}
}
@@ -22,4 +22,17 @@
.icon-green {
background-image: url("https://github.com/icon-location-green.png");
-}
\ No newline at end of file
+}
+.clear{
+ cursor: pointer;
+ position: absolute;
+ right:10px;
+ top:22px;
+}
+.hint{
+ cursor: pointer;
+ font-style: italic;
+ &.error{
+ color:red;
+ }
+}
diff --git a/src/routes/ServiceRequest/components/MapLegends/MapLegends.jsx b/src/routes/ServiceRequest/components/MapLegends/MapLegends.jsx
index b9e6533..e1b5a97 100644
--- a/src/routes/ServiceRequest/components/MapLegends/MapLegends.jsx
+++ b/src/routes/ServiceRequest/components/MapLegends/MapLegends.jsx
@@ -24,13 +24,15 @@ export const MapLegends = ({distance}) => (
Location
- Distance: {distance}
+ {
+ distance ? `Distance: ${distance}` : null
+ }
);
MapLegends.propTypes = {
- distance: PropTypes.string.isRequired,
+ distance: PropTypes.string,
};
export default CSSModules(MapLegends, styles);
diff --git a/src/routes/ServiceRequest/components/ProviderMap/ProviderGoogleMap.jsx b/src/routes/ServiceRequest/components/ProviderMap/ProviderGoogleMap.jsx
index 0997ef0..c278fd8 100644
--- a/src/routes/ServiceRequest/components/ProviderMap/ProviderGoogleMap.jsx
+++ b/src/routes/ServiceRequest/components/ProviderMap/ProviderGoogleMap.jsx
@@ -1,40 +1,81 @@
import React, {PropTypes} from 'react';
-import {withGoogleMap, GoogleMap, Polygon, Polyline, Marker} from 'react-google-maps';
+import _ from 'lodash';
+import {withGoogleMap, GoogleMap, Polygon, Marker} from 'react-google-maps';
import DrawingManager from 'react-google-maps/lib/drawing/DrawingManager';
const getImage = (name) => `${window.location.origin}/img/${name}`;
+const defaultCenter = {
+ lat: 38.9050206,
+ lng: -77.03699279999999,
+};
+
class ProviderGoogleMap extends React.Component {
+ constructor() {
+ super();
+
+ this.geocoder = new google.maps.Geocoder();
+ }
+
+ clickMap(e) {
+ const {selectingAddress, setAddress} = this.props;
+ if (selectingAddress) {
+ const payload = {
+ type: selectingAddress,
+ coor: e.latLng,
+ };
+ setAddress(payload);
+ this.geocoder.geocode({
+ location: e.latLng,
+ }, (res) => {
+ if (res && res.length > 0) {
+ _.forEach(res[0].address_components, (c) => {
+ if (_.includes(c.types, 'locality')) {
+ payload.city = c.long_name;
+ } else if (_.includes(c.types, 'route')) {
+ payload.line1 = c.long_name;
+ } else if (_.includes(c.types, 'postal_code')) {
+ payload.postalCode = c.long_name;
+ } else if (_.includes(c.types, 'administrative_area_level_1')) {
+ payload.state = c.long_name;
+ } else if (_.includes(c.types, 'country')) {
+ // fallback
+ payload.state = payload.state || c.long_name;
+ }
+ });
+ setAddress(payload);
+ }
+ });
+ }
+ }
+
render() {
- const {doneCoords, wayPoints, addZone, zones} = this.props;
+ const {addZone, zones, startLocation, endLocation, selectingAddress} = this.props;
return (
(this.map = map)}
zoom={16}
- center={doneCoords}
+ center={defaultCenter}
+ onClick={this.clickMap.bind(this)}
+ options={{
+ draggableCursor: selectingAddress ? 'crosshair' : 'hand',
+ minZoom: 2,
+ }}
>
-
-
-
-
+ {
+ endLocation ?
+ ( ) : null
+ }
+ { startLocation ?
+ ( ) : null
+ }
{
@@ -78,10 +119,12 @@ class ProviderGoogleMap extends React.Component {
}
ProviderGoogleMap.propTypes = {
- doneCoords: PropTypes.object.isRequired,
- wayPoints: PropTypes.array.isRequired,
addZone: PropTypes.func.isRequired,
zones: PropTypes.array.isRequired,
+ selectingAddress: PropTypes.string,
+ setAddress: PropTypes.func,
+ startLocation: PropTypes.object,
+ endLocation: PropTypes.object,
};
diff --git a/src/routes/ServiceRequest/components/ProviderMap/ProviderMap.jsx b/src/routes/ServiceRequest/components/ProviderMap/ProviderMap.jsx
index de09bca..91715a8 100644
--- a/src/routes/ServiceRequest/components/ProviderMap/ProviderMap.jsx
+++ b/src/routes/ServiceRequest/components/ProviderMap/ProviderMap.jsx
@@ -15,12 +15,16 @@ const ProviderMap = (props) => (
}
{...props}
/>
-
+ {
+ props.serviceType === 'Delivery' ?
+ ( ) : null
+ }
);
ProviderMap.propTypes = {
- distance: PropTypes.string.isRequired,
+ distance: PropTypes.string,
+ serviceType: PropTypes.string,
};
export default CSSModules(ProviderMap, styles);
diff --git a/src/routes/ServiceRequest/components/ServiceDetail/ServiceDetail.jsx b/src/routes/ServiceRequest/components/ServiceDetail/ServiceDetail.jsx
index 49be3cd..6d57696 100644
--- a/src/routes/ServiceRequest/components/ServiceDetail/ServiceDetail.jsx
+++ b/src/routes/ServiceRequest/components/ServiceDetail/ServiceDetail.jsx
@@ -1,12 +1,16 @@
-import React, {PropTypes} from 'react';
+import React, {PropTypes, Component} from 'react';
import CSSModules from 'react-css-modules';
+import _ from 'lodash';
+import cn from 'classnames';
+import {browserHistory} from 'react-router';
import Button from 'components/Button';
import {reduxForm} from 'redux-form';
import Location from '../Location';
import ItemRequest from '../ItemRequest';
import Zones from '../Zones';
import ContactDetails from '../ContactDetails';
-import EstimatedAmountToPay from '../EstimatedAmountToPay';
+import Address from '../Address';
+import Spinner from 'components/Spinner';
import styles from './ServiceDetail.scss';
@@ -14,60 +18,205 @@ import styles from './ServiceDetail.scss';
* ServiceDetail
*/
-export const ServiceDetail = ({fields, handleSubmit, startLocation, endLocation, resetForm, zones, ...rest}) => (
-