@@ -477,13 +497,15 @@ class SensewareNode extends Node {
}
SensewareNode.propTypes = {
+ offline: PropTypes.bool,
form: PropTypes.shape({
node_id: PropTypes.string,
+ space_id: PropTypes.number,
repeater: PropTypes.bool,
- temperature_probe_1: PropTypes.number,
- temperature_probe_2: PropTypes.number,
- temperature_probe_3: PropTypes.number,
- temperature_probe_4: PropTypes.number,
+ temperature_probe_1: PropTypes.oneOfType(PropTypes.number, PropTypes.string),
+ temperature_probe_2: PropTypes.oneOfType(PropTypes.number, PropTypes.string),
+ temperature_probe_3: PropTypes.oneOfType(PropTypes.number, PropTypes.string),
+ temperature_probe_4: PropTypes.oneOfType(PropTypes.number, PropTypes.string),
notes: PropTypes.string,
}),
buildingId: PropTypes.string,
@@ -492,13 +514,19 @@ SensewareNode.propTypes = {
uploadDocument: PropTypes.func,
documents: documentsPropType,
user: userPropType,
+ createApartment: PropTypes.func,
+ createCommonArea: PropTypes.func,
+ createServiceArea: PropTypes.func,
+ createSpace: PropTypes.func,
+ updateApartment: PropTypes.func,
+ updateSpace: PropTypes.func,
+ buildingArea: PropTypes.object, // eslint-disable-line
};
SensewareNode.defaultProps = {
form: {
node_id: '',
- // Unfortunately wtforms only interprets the string 'false' as false,
- // normal javascript booleans don't work
+ space_id: null,
repeater: false,
temperature_probe_1: '',
temperature_probe_2: '',
diff --git a/src/components/SpacePicker/SpacePicker.js b/src/components/SpacePicker/SpacePicker.js
new file mode 100644
index 0000000000000000000000000000000000000000..eaa8aa3fb3180b4014543e5e4416f72ab1139db2
--- /dev/null
+++ b/src/components/SpacePicker/SpacePicker.js
@@ -0,0 +1,656 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { Icon } from 'react-fa';
+import ErrorAlert from '../../components/ErrorAlert';
+import userPropType from '../../containers/User/propTypes';
+
+class SpacePicker extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ offlineSaving: false,
+ saved: true,
+ error: false,
+ lastEdited: Date.now(),
+
+ area: null,
+ areaType: null,
+ areaForm: {},
+
+ space: null,
+ spaceForm: {},
+
+ creatingApartment: false,
+ creatingCommonArea: false,
+ creatingServiceArea: false,
+ creatingAreaType: null,
+ creatingSpace: false,
+
+ receivedParentSpaceId: false,
+ parentSpaceId: null,
+
+ };
+ }
+
+ componentWillMount() {
+ if (this.props.spaceId) {
+ this.setState({
+ receivedParentSpaceId: true,
+ parentSpaceId: this.props.spaceId,
+ });
+ // If somehow all areas/spaces have been loaded
+ // when this is mounted, let's set the space here
+ this.setSpaceFromParent(this.props, this.props.spaceId);
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (
+ this.state.receivedParentSpaceId &&
+ !nextProps.buildingArea.apartmentsLoading &&
+ !nextProps.buildingArea.commonAreasLoading &&
+ !nextProps.buildingArea.seviceAreasLoading
+ ) {
+ this.setState({
+ receivedParentSpaceId: false,
+ });
+ this.setSpaceFromParent(nextProps, this.state.parentSpaceId);
+ }
+
+ if (this.state.creatingApartment) {
+ if (
+ !nextProps.buildingArea.apartmentCreateLoading &&
+ this.props.buildingArea.apartmentCreateLoading
+ ) {
+ if (nextProps.buildingArea.apartmentCreateError) {
+ // TODO handle error
+ this.setState({ creatingApartment: false });
+ } else {
+ this.findNewArea(
+ this.props.buildingArea.apartments,
+ nextProps.buildingArea.apartments,
+ 'apartments',
+ 'creatingApartment',
+ );
+ }
+ }
+ }
+ if (this.state.creatingCommonArea) {
+ if (
+ !nextProps.buildingArea.commonAreaCreateLoading &&
+ this.props.buildingArea.commonAreaCreateLoading
+ ) {
+ if (nextProps.buildingArea.commonAreaCreateError) {
+ // TODO handle error
+ this.setState({ creatingCommonArea: false });
+ } else {
+ this.findNewArea(
+ this.props.buildingArea.commonAreas,
+ nextProps.buildingArea.commonAreas,
+ 'commonAreas',
+ 'creatingCommonArea',
+ );
+ }
+ }
+ }
+ if (this.state.creatingServiceArea) {
+ if (
+ !nextProps.buildingArea.serviceAreaCreateLoading &&
+ this.props.buildingArea.serviceAreaCreateLoading
+ ) {
+ if (nextProps.buildingArea.serviceAreaCreateError) {
+ // TODO handle error
+ this.setState({ creatingServiceArea: false });
+ } else {
+ this.findNewArea(
+ this.props.buildingArea.serviceAreas,
+ nextProps.buildingArea.serviceAreas,
+ 'serviceAreas',
+ 'creatingServiceArea',
+ );
+ }
+ }
+ }
+ if (this.state.creatingSpace) {
+ if (
+ !nextProps.buildingArea.spaceCreateLoading &&
+ this.props.buildingArea.spaceCreateLoading
+ ) {
+ if (nextProps.buildingArea.spaceCreateError) {
+ // TODO handle error
+ this.setState({ creatingSpace: false });
+ } else {
+ const oldSpaceIdList = this.state.area.spaces.map(val => (
+ val.id
+ ));
+ const updatedArea = nextProps.buildingArea[this.state.areaType].reduce(
+ (acc, val) => {
+ if (!acc && val.id === this.state.area.id) {
+ return val;
+ }
+ return acc;
+ },
+ null,
+ );
+ if (updatedArea) {
+ const newSpace = updatedArea.spaces.reduce((acc, val) => {
+ if (!acc && oldSpaceIdList.indexOf(val.id) === -1) {
+ return val;
+ }
+ return acc;
+ }, null);
+ if (newSpace) {
+ const form = this.createFormFromObject(newSpace);
+ this.setState({
+ area: updatedArea,
+ space: newSpace,
+ spaceForm: form,
+ creatingSpace: false,
+ });
+ this.props.updateSpaceId(newSpace.id);
+ }
+ }
+ this.setState({ creatingSpace: false });
+ }
+ }
+ }
+ }
+
+ componentWillUnmount() {
+ clearInterval(this.trySave);
+ clearTimeout(this.saveSoon);
+ }
+
+ setSpaceFromParent = (props, spaceId) => {
+ let areaSpaceDict = this.findSpaceInAreaList(props.buildingArea.apartments, spaceId);
+ let areaType = 'apartments';
+ if (!areaSpaceDict) {
+ areaSpaceDict = this.findSpaceInAreaList(props.buildingArea.commonAreas, spaceId);
+ areaType = 'commonAreas';
+ }
+ if (!areaSpaceDict) {
+ areaSpaceDict = this.findSpaceInAreaList(props.buildingArea.serviceAreas, spaceId);
+ areaType = 'serviceAreas';
+ }
+ if (areaSpaceDict) {
+ const areaForm = this.createFormFromObject(areaSpaceDict.area);
+ const spaceForm = this.createFormFromObject(areaSpaceDict.space);
+ this.setState({
+ area: areaSpaceDict.area,
+ areaType,
+ areaForm,
+ space: areaSpaceDict.space,
+ spaceForm,
+ });
+ }
+ }
+
+ getError = () => {
+ const area = this.props.buildingArea;
+ return (
+ area.apartmentsError ||
+ area.apartmentCreateError ||
+ area.apartmentUpdateError ||
+ area.commonAreasError ||
+ area.commonAreaCreateError ||
+ area.serviceAreasError ||
+ area.serviceAreaCreateError ||
+ area.spaceCreateError ||
+ area.spaceUpdateError
+ );
+ }
+
+ findSpaceInAreaList = (areaList, spaceId) => {
+ return areaList.reduce((acc, val) => {
+ if (!acc) {
+ const space = val.spaces.reduce((acc2, val2) => {
+ if (!acc2 && val2.id === spaceId) {
+ return val2;
+ }
+ return acc2;
+ }, null);
+ if (space) {
+ return {
+ area: val,
+ space,
+ };
+ }
+ }
+ return acc;
+ }, null);
+ }
+
+ createFormFromObject = (obj) => {
+ const form = JSON.parse(JSON.stringify(obj));
+ // Delete data we dont' want the front end to send back to backend on PUT
+ delete form.time_created;
+ delete form.time_modified;
+ delete form.user_created;
+ delete form.user_modified;
+ return form;
+
+ }
+
+ findNewArea = (oldList, newList, areaType, createAreaKey) => {
+ /*
+ * Find a single new entity in newList that doesn't exist in oldList
+ * and add it to the state
+ */
+ const idList = oldList.map(val => (
+ val.id
+ ));
+ const newArea = newList.reduce((acc, val) => {
+ if (!acc && idList.indexOf(val.id) === -1) {
+ return val;
+ }
+ return acc;
+ }, null);
+ if (newArea) {
+ const form = this.createFormFromObject(newArea);
+ this.setState({
+ area: {
+ ...newArea,
+ spaces: [],
+ },
+ areaForm: form,
+ areaType,
+ space: null,
+ [createAreaKey]: false,
+ });
+ } else {
+ this.setState({ [createAreaKey]: false });
+ }
+ }
+
+ /* Currently only works for apartment areas */
+ handleAreaInputChange = (event) => {
+ const eventName = event.target.name;
+ const eventValue = event.target.value;
+ // Make a request to the backend to update the area
+ this.setState({
+ areaForm: {
+ ...this.state.areaForm,
+ [eventName]: eventValue,
+ },
+ });
+ clearTimeout(this.saveArea);
+ // A switch case for when we add metadata to areas other than apartments
+ switch (this.state.areaType) {
+ case 'apartments':
+ this.saveArea = setTimeout(
+ () => {
+ this.props.updateApartment(
+ this.state.area.id,
+ {
+ // We can't use a variable here because there is a race condition
+ // on the setState if different fields are updated quickly
+ ...this.state.areaForm,
+ [eventName]: eventValue,
+ }
+ );
+ },
+ 500,
+ );
+ break;
+ default:
+ break;
+ }
+ }
+
+ handleSpaceInputChange = (event) => {
+ const eventName = event.target.name;
+ const eventValue = event.target.value;
+ this.setState({
+ spaceForm: {
+ ...this.state.spaceForm,
+ [eventName]: eventValue,
+ },
+ });
+ // Make a request to the backend to update the space
+ clearTimeout(this.saveSpace);
+ this.saveSpace = setTimeout(
+ () => {
+ this.props.updateSpace(
+ this.state.space.id,
+ {
+ // We can't use a variable here because there is a race condition
+ // on the setState if different fields are updated quickly
+ ...{
+ ...this.state.spaceForm,
+ [eventName]: eventValue,
+ },
+ area: {
+ area_type: this.state.areaType,
+ area_id: this.state.area.id,
+ },
+ },
+ );
+ },
+ 500,
+ );
+ }
+
+ handleAreaDropdownChange = (event) => {
+ // Handle a new chosen area
+ if (event.target.value === 'new apartment') {
+ this.setState({ creatingApartment: true });
+ this.props.createApartment({
+ number: '',
+ building_id: this.props.buildingId,
+ });
+ } else if (event.target.value === 'new common area') {
+ this.setState({ creatingCommonArea: true });
+ this.props.createCommonArea({
+ building_id: this.props.buildingId,
+ });
+ } else if (event.target.value === 'new service area') {
+ this.setState({ creatingServiceArea: true });
+ this.props.createServiceArea({
+ building_id: this.props.buildingId,
+ });
+ } else {
+ const res = event.target.value.split('-');
+ const type = res[0];
+ const id = parseInt(res[1], 10);
+ const areas = this.props.buildingArea[type];
+ const area = areas.reduce((acc, val) => {
+ if (!acc && val.id === id) {
+ return val;
+ }
+ return acc;
+ }, null);
+ // Make a deep copy of form
+ const form = this.createFormFromObject(area);
+ this.setState({
+ areaType: type,
+ areaForm: form,
+ area,
+ space: null,
+ });
+ }
+ }
+
+ handleSpaceDropdownChange = (event) => {
+ // Handle a new chosen space
+ if (event.target.value === 'new space') {
+ this.setState({ creatingSpace: true });
+ let areaType = '';
+ switch (this.state.areaType) {
+ case 'apartments':
+ areaType = 'apartment';
+ break;
+ case 'commonAreas':
+ areaType = 'common_area';
+ break;
+ case 'serviceAreas':
+ areaType = 'service_area';
+ break;
+ default:
+ break;
+ }
+ this.props.createSpace({
+ building_id: this.props.buildingId,
+ description: 'new room',
+ area: {
+ area_type: areaType,
+ area_id: this.state.area.id,
+ },
+ });
+ } else {
+ const id = parseInt(event.target.value, 10);
+ // If an area's space is empty, return empty list
+ const space = this.state.area.spaces ? this.state.area.spaces.reduce(
+ (acc, val) => {
+ if (!acc && val.id === id) {
+ return val;
+ }
+ return acc;
+ }, null
+ ) : [];
+ // Make a deep copy of form
+ const form = this.createFormFromObject(space);
+ this.setState({
+ space,
+ spaceForm: form,
+ });
+ this.props.updateSpaceId(id);
+ }
+ }
+
+ renderLoading = () => {
+ return (
+
+
+
+ );
+ }
+
+ render() {
+ if (
+ this.props.buildingArea.apartmentsLoading ||
+ this.props.buildingArea.commonAreasLoading ||
+ this.props.buildingArea.serviceAreasLoading
+ ) {
+ return this.renderLoading();
+ }
+ const areaDropdown = (
+
+
+
+
+
+
+
+
+ );
+ let areaMetadata = null;
+ // Only show metadata for apartment nodes
+ if (this.state.area && this.state.areaType === 'apartments') {
+ if (this.state.areaForm) {
+ areaMetadata = (
+
+
+
+
+
+
+ );
+ }
+ }
+
+ let spaceDropdown = null;
+ if (this.state.area) {
+ spaceDropdown = (
+
+
+
+
+
+
+
+
+ );
+ }
+ let spaceMetadata = null;
+ if (this.state.space && this.state.spaceForm) {
+ spaceMetadata = (
+
+
+
+
+
+
+
+
+
+ );
+ }
+ let loading = null;
+ if (
+ (this.state.creatingApartment && this.props.buildingArea.apartmentCreateLoading) ||
+ (this.state.creatingCommonArea && this.props.buildingArea.commonAreaCreateLoading) ||
+ (this.state.creatingServiceArea && this.props.buildingArea.serviceAreaCreateLoading) ||
+ (this.state.creatingSpace && this.props.buildingArea.spaceCreateLoading) ||
+ // TODO handle the state so that multiple of these componenets
+ // on the same page won't all show loading
+ this.props.buildingArea.apartmentUpdateLoading ||
+ this.props.buildingArea.spaceUpdateLoading
+ ) {
+ loading = this.renderLoading();
+ }
+
+ return (
+
+ );
+ }
+}
+
+SpacePicker.propTypes = {
+ updateSpaceId: PropTypes.func,
+ uniqueId: PropTypes.string,
+ spaceId: PropTypes.number,
+ buildingId: PropTypes.string,
+ /* eslint-disable react/no-unused-prop-types */
+ offline: PropTypes.bool,
+ user: userPropType,
+ createApartment: PropTypes.func,
+ createCommonArea: PropTypes.func,
+ createServiceArea: PropTypes.func,
+ createSpace: PropTypes.func,
+ updateApartment: PropTypes.func,
+ updateSpace: PropTypes.func,
+ buildingArea: PropTypes.object, // eslint-disable-line
+};
+
+SpacePicker.defaultProps = {
+ uniqueId: '',
+ spaceId: null,
+ offline: false,
+};
+
+export default SpacePicker;
diff --git a/src/containers/BuildingArea/BuildingAreaTable.js b/src/containers/BuildingArea/BuildingAreaTable.js
new file mode 100644
index 0000000000000000000000000000000000000000..909c814e59bca6684796f96d030c31c63004ce7d
--- /dev/null
+++ b/src/containers/BuildingArea/BuildingAreaTable.js
@@ -0,0 +1,88 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import { loadApartments, loadCommonAreas, loadServiceAreas } from './actions';
+
+
+class BuildingAreaTable extends Component {
+ state = { }
+
+ componentDidMount() {
+ this.props.loadApartments({ verbose: '', building_id: this.props.buildingId });
+ this.props.loadCommonAreas({ verbose: '', building_id: this.props.buildingId });
+ this.props.loadServiceAreas({ verbose: '', building_id: this.props.buildingId });
+ }
+
+ renderHeading = (heading) => {
+ return (
+
+ );
+ }
+
+ renderApartments = () => {
+ return this.props.buildingArea.apartments.map(apt => {
+ return (
+
+
+ Apartment {apt.number}
+
+
+
+ );
+ });
+ }
+
+ render() {
+ return (
+
+ {this.renderHeading('Apartments')}
+
+
+
+ {this.renderApartments()}
+
+
+
+ {this.renderHeading('Common Area')}
+
+ {this.renderHeading('Service Area')}
+
+ );
+ }
+}
+
+BuildingAreaTable.propTypes = {
+ loadApartments: PropTypes.func,
+ loadCommonAreas: PropTypes.func,
+ loadServiceAreas: PropTypes.func,
+ buildingArea: PropTypes.object, // eslint-disable-line
+ buildingId: PropTypes.string,
+};
+
+const mapStateToProps = state => ({
+ buildingArea: state.buildingArea,
+});
+
+const mapDispatchToProps = dispatch => (
+ bindActionCreators({
+ loadApartments,
+ loadCommonAreas,
+ loadServiceAreas,
+ }, dispatch)
+);
+
+export default connect(mapStateToProps, mapDispatchToProps)(BuildingAreaTable);
diff --git a/src/containers/BuildingArea/actions.js b/src/containers/BuildingArea/actions.js
new file mode 100644
index 0000000000000000000000000000000000000000..1f0e5fa5b3dc66fa4595d3a1eb738e12ea27c360
--- /dev/null
+++ b/src/containers/BuildingArea/actions.js
@@ -0,0 +1,71 @@
+import { makeActionCreator } from '../../utils/reduxHelpers';
+import {
+ FETCH_APARTMENTS_REQUESTED,
+ FETCH_APARTMENTS_SUCCEEDED,
+ FETCH_APARTMENTS_FAILED,
+ FETCH_COMMON_AREAS_REQUESTED,
+ FETCH_COMMON_AREAS_SUCCEEDED,
+ FETCH_COMMON_AREAS_FAILED,
+ FETCH_SERVICE_AREAS_REQUESTED,
+ FETCH_SERVICE_AREAS_SUCCEEDED,
+ FETCH_SERVICE_AREAS_FAILED,
+
+ CREATE_APARTMENT_REQUESTED,
+ CREATE_APARTMENT_SUCCEEDED,
+ CREATE_APARTMENT_FAILED,
+ CREATE_COMMON_AREA_REQUESTED,
+ CREATE_COMMON_AREA_SUCCEEDED,
+ CREATE_COMMON_AREA_FAILED,
+ CREATE_SERVICE_AREA_REQUESTED,
+ CREATE_SERVICE_AREA_SUCCEEDED,
+ CREATE_SERVICE_AREA_FAILED,
+ CREATE_SPACE_REQUESTED,
+ CREATE_SPACE_SUCCEEDED,
+ CREATE_SPACE_FAILED,
+
+ UPDATE_APARTMENT_REQUESTED,
+ UPDATE_APARTMENT_SUCCEEDED,
+ UPDATE_APARTMENT_FAILED,
+ UPDATE_SPACE_REQUESTED,
+ UPDATE_SPACE_SUCCEEDED,
+ UPDATE_SPACE_FAILED,
+} from './constants';
+
+// Load area actions
+export const loadApartments = makeActionCreator(FETCH_APARTMENTS_REQUESTED, 'filters');
+export const apartmentsLoaded = makeActionCreator(FETCH_APARTMENTS_SUCCEEDED, 'payload');
+export const apartmentsFailed = makeActionCreator(FETCH_APARTMENTS_FAILED, 'error');
+
+export const loadCommonAreas = makeActionCreator(FETCH_COMMON_AREAS_REQUESTED, 'filters');
+export const commonAreasLoaded = makeActionCreator(FETCH_COMMON_AREAS_SUCCEEDED, 'payload');
+export const commonAreasFailed = makeActionCreator(FETCH_COMMON_AREAS_FAILED, 'error');
+
+export const loadServiceAreas = makeActionCreator(FETCH_SERVICE_AREAS_REQUESTED, 'filters');
+export const serviceAreasLoaded = makeActionCreator(FETCH_SERVICE_AREAS_SUCCEEDED, 'payload');
+export const serviceAreasFailed = makeActionCreator(FETCH_SERVICE_AREAS_FAILED, 'error');
+
+// Create area actions
+export const createApartment = makeActionCreator(CREATE_APARTMENT_REQUESTED, 'payload');
+export const apartmentCreated = makeActionCreator(CREATE_APARTMENT_SUCCEEDED, 'payload');
+export const apartmentCreateFailed = makeActionCreator(CREATE_APARTMENT_FAILED, 'error');
+
+export const createCommonArea = makeActionCreator(CREATE_COMMON_AREA_REQUESTED, 'payload');
+export const commonAreaCreated = makeActionCreator(CREATE_COMMON_AREA_SUCCEEDED, 'payload');
+export const commonAreaCreateFailed = makeActionCreator(CREATE_COMMON_AREA_FAILED, 'error');
+
+export const createServiceArea = makeActionCreator(CREATE_SERVICE_AREA_REQUESTED, 'payload');
+export const serviceAreaCreated = makeActionCreator(CREATE_SERVICE_AREA_SUCCEEDED, 'payload');
+export const serviceAreaCreateFailed = makeActionCreator(CREATE_SERVICE_AREA_FAILED, 'error');
+
+export const createSpace = makeActionCreator(CREATE_SPACE_REQUESTED, 'payload');
+export const spaceCreated = makeActionCreator(CREATE_SPACE_SUCCEEDED, 'payload', 'areaId', 'areaType');
+export const spaceCreateFailed = makeActionCreator(CREATE_SPACE_FAILED, 'error');
+
+// Update area actions
+export const updateApartment = makeActionCreator(UPDATE_APARTMENT_REQUESTED, 'id', 'payload');
+export const apartmentUpdated = makeActionCreator(UPDATE_APARTMENT_SUCCEEDED, 'payload');
+export const apartmentUpdateFailed = makeActionCreator(UPDATE_APARTMENT_FAILED, 'error');
+
+export const updateSpace = makeActionCreator(UPDATE_SPACE_REQUESTED, 'id', 'payload');
+export const spaceUpdated = makeActionCreator(UPDATE_SPACE_SUCCEEDED, 'payload', 'areaId', 'areaType');
+export const spaceUpdateFailed = makeActionCreator(UPDATE_SPACE_FAILED, 'error');
diff --git a/src/containers/BuildingArea/constants.js b/src/containers/BuildingArea/constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..2541c06966dd62df3124150dba134dad577bac50
--- /dev/null
+++ b/src/containers/BuildingArea/constants.js
@@ -0,0 +1,29 @@
+export const FETCH_APARTMENTS_REQUESTED = 'FETCH_APARTMENTS_REQUESTED';
+export const FETCH_APARTMENTS_SUCCEEDED = 'FETCH_APARTMENTS_SUCCEEDED';
+export const FETCH_APARTMENTS_FAILED = 'FETCH_APARTMENTS_FAILED';
+export const FETCH_COMMON_AREAS_REQUESTED = 'FETCH_COMMON_AREAS_REQUESTED';
+export const FETCH_COMMON_AREAS_SUCCEEDED = 'FETCH_COMMON_AREAS_SUCCEEDED';
+export const FETCH_COMMON_AREAS_FAILED = 'FETCH_COMMON_AREAS_FAILED';
+export const FETCH_SERVICE_AREAS_REQUESTED = 'FETCH_SERVICE_AREAS_REQUESTED';
+export const FETCH_SERVICE_AREAS_SUCCEEDED = 'FETCH_SERVICE_AREAS_SUCCEEDED';
+export const FETCH_SERVICE_AREAS_FAILED = 'FETCH_SERVICE_AREAS_FAILED';
+
+export const CREATE_APARTMENT_REQUESTED = 'CREATE_APARTMENT_REQUESTED';
+export const CREATE_APARTMENT_SUCCEEDED = 'CREATE_APARTMENT_SUCCEEDED';
+export const CREATE_APARTMENT_FAILED = 'CREATE_APARTMENT_FAILED';
+export const CREATE_COMMON_AREA_REQUESTED = 'CREATE_COMMON_AREA_REQUESTED';
+export const CREATE_COMMON_AREA_SUCCEEDED = 'CREATE_COMMON_AREA_SUCCEEDED';
+export const CREATE_COMMON_AREA_FAILED = 'CREATE_COMMON_AREA_FAILED';
+export const CREATE_SERVICE_AREA_REQUESTED = 'CREATE_SERVICE_AREA_REQUESTED';
+export const CREATE_SERVICE_AREA_SUCCEEDED = 'CREATE_SERVICE_AREA_SUCCEEDED';
+export const CREATE_SERVICE_AREA_FAILED = 'CREATE_SERVICE_AREA_FAILED';
+export const CREATE_SPACE_REQUESTED = 'CREATE_SPACE_REQUESTED';
+export const CREATE_SPACE_SUCCEEDED = 'CREATE_SPACE_SUCCEEDED';
+export const CREATE_SPACE_FAILED = 'CREATE_SPACE_FAILED';
+
+export const UPDATE_APARTMENT_REQUESTED = 'UPDATE_APARTMENT_REQUESTED';
+export const UPDATE_APARTMENT_SUCCEEDED = 'UPDATE_APARTMENT_SUCCEEDED';
+export const UPDATE_APARTMENT_FAILED = 'UPDATE_APARTMENT_FAILED';
+export const UPDATE_SPACE_REQUESTED = 'UPDATE_SPACE_REQUESTED';
+export const UPDATE_SPACE_SUCCEEDED = 'UPDATE_SPACE_SUCCEEDED';
+export const UPDATE_SPACE_FAILED = 'UPDATE_SPACE_FAILED';
diff --git a/src/containers/BuildingArea/reducer.js b/src/containers/BuildingArea/reducer.js
new file mode 100644
index 0000000000000000000000000000000000000000..932aa9eaa126a3def2f343999c3389480bb8b28c
--- /dev/null
+++ b/src/containers/BuildingArea/reducer.js
@@ -0,0 +1,432 @@
+import {
+ FETCH_APARTMENTS_REQUESTED,
+ FETCH_APARTMENTS_SUCCEEDED,
+ FETCH_APARTMENTS_FAILED,
+ FETCH_COMMON_AREAS_REQUESTED,
+ FETCH_COMMON_AREAS_SUCCEEDED,
+ FETCH_COMMON_AREAS_FAILED,
+ FETCH_SERVICE_AREAS_REQUESTED,
+ FETCH_SERVICE_AREAS_SUCCEEDED,
+ FETCH_SERVICE_AREAS_FAILED,
+
+ CREATE_APARTMENT_REQUESTED,
+ CREATE_APARTMENT_SUCCEEDED,
+ CREATE_APARTMENT_FAILED,
+ CREATE_COMMON_AREA_REQUESTED,
+ CREATE_COMMON_AREA_SUCCEEDED,
+ CREATE_COMMON_AREA_FAILED,
+ CREATE_SERVICE_AREA_REQUESTED,
+ CREATE_SERVICE_AREA_SUCCEEDED,
+ CREATE_SERVICE_AREA_FAILED,
+
+ UPDATE_APARTMENT_REQUESTED,
+ UPDATE_APARTMENT_SUCCEEDED,
+ UPDATE_APARTMENT_FAILED,
+
+ CREATE_SPACE_REQUESTED,
+ CREATE_SPACE_SUCCEEDED,
+ CREATE_SPACE_FAILED,
+ UPDATE_SPACE_REQUESTED,
+ UPDATE_SPACE_SUCCEEDED,
+ UPDATE_SPACE_FAILED,
+} from './constants';
+import {
+ LOAD_BUILDING_DETAIL,
+} from '../Building/constants';
+
+
+const initState = {
+ apartments: [],
+ apartmentsLoading: false,
+ apartmentsError: false,
+ apartmentCreateLoading: false,
+ apartmentCreateError: false,
+ apartmentUpdateLoading: false,
+ apartmentUpdateError: false,
+
+ commonAreas: [],
+ commonAreasLoading: false,
+ commonAreasError: false,
+ commonAreaCreateLoading: false,
+ commonAreaCreateError: false,
+
+ serviceAreas: [],
+ serviceAreasLoading: false,
+ serviceAreasError: false,
+ serviceAreaCreateLoading: false,
+ serviceAreaCreateError: false,
+
+ spaceCreateLoading: false,
+ spaceCreateError: false,
+ spaceUpdateLoading: false,
+ spaceUpdateError: false,
+};
+
+const initErrorState = {
+ apartmentsError: false,
+ apartmentCreateError: false,
+ apartmentUpdateError: false,
+ commonAreasError: false,
+ commonAreaCreateError: false,
+ serviceAreasError: false,
+ serviceAreaCreateError: false,
+ spaceCreateError: false,
+ spaceUpdateError: false,
+};
+
+export default (state = initState, action) => {
+ let apartments = state.apartments;
+ let commonAreas = state.commonAreas;
+ let serviceAreas = state.serviceAreas;
+ switch (action.type) {
+ case LOAD_BUILDING_DETAIL:
+ return {
+ ...initState,
+ };
+
+ case FETCH_APARTMENTS_REQUESTED:
+ return {
+ ...state,
+ ...initErrorState,
+ apartmentsLoading: true,
+ apartmentsError: false,
+ apartments: [],
+ };
+
+ case FETCH_APARTMENTS_SUCCEEDED:
+ return {
+ ...state,
+ ...initErrorState,
+ apartmentsLoading: false,
+ apartmentsError: false,
+ apartments: action.payload.data,
+ };
+
+ case FETCH_APARTMENTS_FAILED:
+ return {
+ ...state,
+ ...initErrorState,
+ apartmentsLoading: false,
+ apartmentsError: action.error,
+ apartments: [],
+ };
+
+ case CREATE_APARTMENT_REQUESTED:
+ return {
+ ...state,
+ ...initErrorState,
+ apartmentCreateLoading: true,
+ apartmentCreateError: false,
+ };
+
+ case CREATE_APARTMENT_SUCCEEDED:
+ return {
+ ...state,
+ ...initErrorState,
+ apartmentCreateLoading: false,
+ apartmentCreateError: false,
+ apartments: [
+ ...state.apartments,
+ { ...action.payload.data, spaces: [] },
+ ],
+ };
+
+ case CREATE_APARTMENT_FAILED:
+ return {
+ ...state,
+ ...initErrorState,
+ apartmentCreateLoading: false,
+ apartmentCreateError: action.error,
+ };
+
+ case UPDATE_APARTMENT_REQUESTED:
+ return {
+ ...state,
+ ...initErrorState,
+ apartmentUpdateLoading: true,
+ apartmentUpdateError: false,
+ };
+
+ case UPDATE_APARTMENT_SUCCEEDED:
+ return {
+ ...state,
+ ...initErrorState,
+ apartmentUpdateLoading: false,
+ apartmentUpdateError: false,
+ apartments: state.apartments.map((val) => {
+ if (val.id === action.payload.data.id) {
+ return {
+ ...action.payload.data,
+ spaces: val.spaces,
+ };
+ }
+ return val;
+ }),
+ };
+
+ case UPDATE_APARTMENT_FAILED:
+ return {
+ ...state,
+ ...initErrorState,
+ apartmentUpdateLoading: false,
+ apartmentUpdateError: action.error,
+ };
+
+ case FETCH_COMMON_AREAS_REQUESTED:
+ return {
+ ...state,
+ ...initErrorState,
+ commonAreasLoading: true,
+ commonAreasError: false,
+ commonAreas: [],
+ };
+
+ case FETCH_COMMON_AREAS_SUCCEEDED:
+ return {
+ ...state,
+ ...initErrorState,
+ commonAreasLoading: false,
+ commonAreasError: false,
+ commonAreas: action.payload.data,
+ };
+
+ case FETCH_COMMON_AREAS_FAILED:
+ return {
+ ...state,
+ ...initErrorState,
+ commonAreasLoading: false,
+ commonAreasError: action.error,
+ commonAreas: [],
+ };
+
+ case CREATE_COMMON_AREA_REQUESTED:
+ return {
+ ...state,
+ ...initErrorState,
+ commonAreaCreateLoading: true,
+ commonAreaCreateError: false,
+ };
+
+ case CREATE_COMMON_AREA_SUCCEEDED:
+ return {
+ ...state,
+ ...initErrorState,
+ commonAreaCreateLoading: false,
+ commonAreaCreateError: false,
+ commonAreas: [
+ ...state.commonAreas,
+ { ...action.payload.data, spaces: [] },
+ ],
+ };
+
+ case CREATE_COMMON_AREA_FAILED:
+ return {
+ ...state,
+ ...initErrorState,
+ commonAreaCreateLoading: false,
+ commonAreaCreateError: action.error,
+ };
+
+ case FETCH_SERVICE_AREAS_REQUESTED:
+ return {
+ ...state,
+ ...initErrorState,
+ serviceAreasLoading: true,
+ serviceAreasError: false,
+ serviceAreas: [],
+ };
+
+ case FETCH_SERVICE_AREAS_SUCCEEDED:
+ return {
+ ...state,
+ ...initErrorState,
+ serviceAreasLoading: false,
+ serviceAreasError: false,
+ serviceAreas: action.payload.data,
+ };
+
+ case FETCH_SERVICE_AREAS_FAILED:
+ return {
+ ...state,
+ ...initErrorState,
+ serviceAreasLoading: false,
+ serviceAreasError: action.error,
+ serviceAreas: [],
+ };
+
+ case CREATE_SERVICE_AREA_REQUESTED:
+ return {
+ ...state,
+ ...initErrorState,
+ serviceAreaCreateLoading: true,
+ serviceAreaCreateError: false,
+ };
+
+ case CREATE_SERVICE_AREA_SUCCEEDED:
+ return {
+ ...state,
+ ...initErrorState,
+ serviceAreaCreateLoading: false,
+ serviceAreaCreateError: false,
+ serviceAreas: [
+ ...state.serviceAreas,
+ { ...action.payload.data, spaces: [] },
+ ],
+ };
+
+ case CREATE_SERVICE_AREA_FAILED:
+ return {
+ ...state,
+ ...initErrorState,
+ serviceAreaCreateLoading: false,
+ serviceAreaCreateError: action.error,
+ };
+
+ case CREATE_SPACE_REQUESTED:
+ return {
+ ...state,
+ ...initErrorState,
+ spaceCreateLoading: true,
+ spaceCreateError: false,
+ };
+
+ case CREATE_SPACE_SUCCEEDED:
+ switch (action.areaType) {
+ case 'apartment':
+ apartments = state.apartments.map((val) => {
+ if (val.id === action.areaId) {
+ return {
+ ...val,
+ spaces: [...val.spaces, action.payload.data],
+ };
+ }
+ return val;
+ });
+ break;
+ case 'common_area':
+ commonAreas = state.commonAreas.map((val) => {
+ if (val.id === action.areaId) {
+ return {
+ ...val,
+ spaces: [...val.spaces, action.payload.data],
+ };
+ }
+ return val;
+ });
+ break;
+ case 'service_area':
+ serviceAreas = state.serviceAreas.map((val) => {
+ if (val.id === action.areaId) {
+ return {
+ ...val,
+ spaces: [...val.spaces, action.payload.data],
+ };
+ }
+ return val;
+ });
+ break;
+ default:
+ break;
+ }
+ return {
+ ...state,
+ ...initErrorState,
+ spaceCreateLoading: false,
+ spaceCreateError: false,
+ apartments,
+ commonAreas,
+ serviceAreas,
+ };
+
+ case CREATE_SPACE_FAILED:
+ return {
+ ...state,
+ ...initErrorState,
+ spaceCreateLoading: false,
+ spaceCreateError: action.error,
+ };
+
+ case UPDATE_SPACE_REQUESTED:
+ return {
+ ...state,
+ ...initErrorState,
+ spaceUpdateLoading: true,
+ spaceUpdateError: false,
+ };
+
+ case UPDATE_SPACE_SUCCEEDED:
+ switch (action.areaType) {
+ case 'apartment':
+ apartments = state.apartments.map((val) => {
+ if (val.id === action.areaId) {
+ return {
+ ...val,
+ spaces: val.spaces.map((space) => {
+ if (space.id === action.payload.data.id) {
+ return action.payload.data;
+ }
+ return space;
+ }),
+ };
+ }
+ return val;
+ });
+ break;
+ case 'common_area':
+ commonAreas = state.commonAreas.map((val) => {
+ if (val.id === action.areaId) {
+ return {
+ ...val,
+ spaces: val.spaces.map((space) => {
+ if (space.id === action.payload.data.id) {
+ return action.payload.data;
+ }
+ return space;
+ }),
+ };
+ }
+ return val;
+ });
+ break;
+ case 'service_area':
+ serviceAreas = state.serviceAreas.map((val) => {
+ if (val.id === action.areaId) {
+ return {
+ ...val,
+ spaces: val.spaces.map((space) => {
+ if (space.id === action.payload.data.id) {
+ return action.payload.data;
+ }
+ return space;
+ }),
+ };
+ }
+ return val;
+ });
+ break;
+ default:
+ break;
+ }
+ return {
+ ...state,
+ ...initErrorState,
+ spaceUpdateLoading: false,
+ spaceUpdateError: false,
+ apartments,
+ commonAreas,
+ serviceAreas,
+ };
+
+ case UPDATE_SPACE_FAILED:
+ return {
+ ...state,
+ ...initErrorState,
+ spaceUpdateLoading: false,
+ spaceUpdateError: action.error,
+ };
+
+
+ default:
+ return state;
+ }
+};
diff --git a/src/containers/BuildingArea/sagas.js b/src/containers/BuildingArea/sagas.js
new file mode 100644
index 0000000000000000000000000000000000000000..2c3ae7799e4a185be30c4f871bd9538a17642a0c
--- /dev/null
+++ b/src/containers/BuildingArea/sagas.js
@@ -0,0 +1,120 @@
+import { takeEvery, call, put } from 'redux-saga/effects';
+import request from '../../utils/request';
+import { SagaRequests } from '../../utils';
+import {
+ getHeaders,
+ apartmentsURL,
+ commonAreasURL,
+ serviceAreasURL,
+ spacesURL,
+} from '../../utils/restServices';
+
+import {
+ FETCH_APARTMENTS_REQUESTED,
+ FETCH_COMMON_AREAS_REQUESTED,
+ FETCH_SERVICE_AREAS_REQUESTED,
+ CREATE_APARTMENT_REQUESTED,
+ CREATE_COMMON_AREA_REQUESTED,
+ CREATE_SERVICE_AREA_REQUESTED,
+ CREATE_SPACE_REQUESTED,
+ UPDATE_APARTMENT_REQUESTED,
+ UPDATE_SPACE_REQUESTED,
+} from './constants';
+
+import {
+ apartmentsLoaded,
+ apartmentsFailed,
+ commonAreasLoaded,
+ commonAreasFailed,
+ serviceAreasLoaded,
+ serviceAreasFailed,
+ apartmentCreated,
+ apartmentCreateFailed,
+ commonAreaCreated,
+ commonAreaCreateFailed,
+ serviceAreaCreated,
+ serviceAreaCreateFailed,
+ spaceCreated,
+ spaceCreateFailed,
+ apartmentUpdated,
+ apartmentUpdateFailed,
+ spaceUpdated,
+ spaceUpdateFailed,
+
+} from './actions';
+
+function* fetchApartments(action) {
+ yield SagaRequests.get(action, apartmentsURL, apartmentsLoaded, apartmentsFailed);
+}
+
+function* fetchCommonAreas(action) {
+ yield SagaRequests.get(action, commonAreasURL, commonAreasLoaded, commonAreasFailed);
+}
+
+function* fetchServiceAreas(action) {
+ yield SagaRequests.get(action, serviceAreasURL, serviceAreasLoaded, serviceAreasFailed);
+}
+
+function* createApartment(action) {
+ yield SagaRequests.post(action, apartmentsURL, apartmentCreated, apartmentCreateFailed);
+}
+
+function* createCommonArea(action) {
+ yield SagaRequests.post(action, commonAreasURL, commonAreaCreated, commonAreaCreateFailed);
+}
+
+function* createServiceArea(action) {
+ yield SagaRequests.post(action, serviceAreasURL, serviceAreaCreated, serviceAreaCreateFailed);
+}
+
+function* createSpace(action) {
+ const res = yield call(
+ request,
+ SagaRequests.generateURL(spacesURL, action),
+ {
+ method: 'POST',
+ headers: getHeaders(),
+ body: JSON.stringify(action.payload),
+ }
+ );
+ if (!res.err) {
+ yield put(spaceCreated(res, action.payload.area.area_id, action.payload.area.area_type));
+ } else {
+ yield put(spaceCreateFailed(res.err));
+ }
+}
+
+function* updateApartment(action) {
+ yield SagaRequests.put(action, apartmentsURL, apartmentUpdated, apartmentUpdateFailed);
+}
+
+function* updateSpace(action) {
+ const res = yield call(
+ request,
+ SagaRequests.generateURL(spacesURL, action),
+ {
+ method: 'PUT',
+ headers: getHeaders(),
+ body: JSON.stringify(action.payload),
+ }
+ );
+ if (!res.err) {
+ yield put(spaceUpdated(res, action.payload.area.area_id, action.payload.area.area_type));
+ } else {
+ yield put(spaceUpdateFailed(res.err));
+ }
+}
+
+function* sensorWatcher() {
+ yield takeEvery(FETCH_APARTMENTS_REQUESTED, fetchApartments);
+ yield takeEvery(FETCH_COMMON_AREAS_REQUESTED, fetchCommonAreas);
+ yield takeEvery(FETCH_SERVICE_AREAS_REQUESTED, fetchServiceAreas);
+ yield takeEvery(CREATE_APARTMENT_REQUESTED, createApartment);
+ yield takeEvery(CREATE_COMMON_AREA_REQUESTED, createCommonArea);
+ yield takeEvery(CREATE_SERVICE_AREA_REQUESTED, createServiceArea);
+ yield takeEvery(CREATE_SPACE_REQUESTED, createSpace);
+ yield takeEvery(UPDATE_APARTMENT_REQUESTED, updateApartment);
+ yield takeEvery(UPDATE_SPACE_REQUESTED, updateSpace);
+}
+
+export default sensorWatcher;
diff --git a/src/containers/Sensors/SensorInstall.js b/src/containers/Sensors/SensorInstall.js
new file mode 100644
index 0000000000000000000000000000000000000000..f009c4fe1d21cffe0e6087e9bc3c3f3a88dc5b69
--- /dev/null
+++ b/src/containers/Sensors/SensorInstall.js
@@ -0,0 +1,91 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+
+import completeOverviewPropTypes from '../Building/propTypes';
+
+import documentsPropType from '../Documents/propTypes';
+import {
+ loadApartments,
+ loadCommonAreas,
+ loadServiceAreas,
+ createApartment,
+ createCommonArea,
+ createServiceArea,
+ createSpace,
+ updateApartment,
+ updateSpace,
+} from '../BuildingArea/actions';
+import userPropType from '../../containers/User/propTypes';
+
+
+class SensorInstall extends Component {
+
+ componentDidMount() {
+ this.props.loadApartments({ verbose: '', building_id: this.props.buildingId });
+ this.props.loadCommonAreas({ verbose: '', building_id: this.props.buildingId });
+ this.props.loadServiceAreas({ verbose: '', building_id: this.props.buildingId });
+ }
+
+ render() {
+ let mainContent = null;
+ if (this.props.children) {
+ mainContent = React.cloneElement(this.props.children, {
+ buildingId: this.props.buildingId,
+ building: this.props.building,
+ documents: this.props.documents,
+ uploadDocument: this.props.uploadDocument,
+ user: this.props.user,
+ buildingArea: this.props.buildingArea,
+ createApartment: this.props.createApartment,
+ createCommonArea: this.props.createCommonArea,
+ createServiceArea: this.props.createServiceArea,
+ createSpace: this.props.createSpace,
+ updateApartment: this.props.updateApartment,
+ updateSpace: this.props.updateSpace,
+ });
+ }
+ return mainContent;
+ }
+}
+
+SensorInstall.propTypes = {
+ children: PropTypes.element,
+ building: completeOverviewPropTypes,
+ buildingId: PropTypes.string,
+ uploadDocument: PropTypes.func,
+ documents: documentsPropType,
+ user: userPropType,
+ loadApartments: PropTypes.func,
+ loadCommonAreas: PropTypes.func,
+ loadServiceAreas: PropTypes.func,
+ createApartment: PropTypes.func,
+ createCommonArea: PropTypes.func,
+ createServiceArea: PropTypes.func,
+ createSpace: PropTypes.func,
+ updateApartment: PropTypes.func,
+ updateSpace: PropTypes.func,
+ buildingArea: PropTypes.object, // eslint-disable-line
+};
+
+const mapStateToProps = state => ({
+ buildingArea: state.buildingArea,
+});
+
+
+const mapDispatchToProps = dispatch => (
+ bindActionCreators({
+ loadApartments,
+ loadCommonAreas,
+ loadServiceAreas,
+ createApartment,
+ createCommonArea,
+ createServiceArea,
+ createSpace,
+ updateApartment,
+ updateSpace,
+ }, dispatch)
+);
+
+export default connect(mapStateToProps, mapDispatchToProps)(SensorInstall);
diff --git a/src/containers/Sensors/Sensors.js b/src/containers/Sensors/Sensors.js
index 054decfe6d1eec00c95a77307d90cfbe2969468f..00e470f8ca8f07b1d956715306a3f74c469f013b 100644
--- a/src/containers/Sensors/Sensors.js
+++ b/src/containers/Sensors/Sensors.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import LinkBarDetail from '../../components/LinkBarDetail';
import SensorGraph from './SensorGraph';
+import BuildingAreaTable from '../BuildingArea/BuildingAreaTable';
class Sensors extends Component {
@@ -28,6 +29,7 @@ class Sensors extends Component {
className="mt-2"
buildingId={this.props.buildingId}
/>
+
);
}
diff --git a/src/reducers.js b/src/reducers.js
index b259a6f089e441aadb81403612194375b17e6c5f..01c73d6785a31e40ac72990248102dfb7419d8b5 100644
--- a/src/reducers.js
+++ b/src/reducers.js
@@ -13,6 +13,7 @@ import report from './containers/Reports/reducer';
import bGroup from './containers/BGroup/reducer';
import user from './containers/User/reducer';
import sensors from './containers/Sensors/reducer';
+import buildingArea from './containers/BuildingArea/reducer';
export default combineReducers({
@@ -29,4 +30,5 @@ export default combineReducers({
bGroup,
user,
sensors,
+ buildingArea,
});
diff --git a/src/routes.js b/src/routes.js
index 6a10f1ba9f126f413debc70beba86b2263422706..381b8699d32f795b53981ae64b23c82a131b7e24 100644
--- a/src/routes.js
+++ b/src/routes.js
@@ -20,7 +20,8 @@ import ReportsHome from './screens/ReportsHome';
import Utilities from './components/Utilities';
import Envelope from './containers/Envelope';
import Sensors from './containers/Sensors/Sensors';
-import SensorInstall from './components/SensorInstall/SensorInstall';
+import SensorInstall from './containers/Sensors/SensorInstall';
+import GatewayList from './components/SensorInstall/GatewayList';
import BuildingReports from './containers/BuildingReports';
import Wrapper from './containers/Wrapper';
import { BGroupOverview, BGroup } from './containers/BGroup';
@@ -51,7 +52,9 @@ export default (