diff --git a/client/src/scenes/UploadDLockData/components/FileUpload.js b/client/src/scenes/UploadDLockData/components/FileUpload.js
index c217294..87b1038 100644
--- a/client/src/scenes/UploadDLockData/components/FileUpload.js
+++ b/client/src/scenes/UploadDLockData/components/FileUpload.js
@@ -1,8 +1,10 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
-import {Form} from "semantic-ui-react";
+import { Form } from "semantic-ui-react";
-import { uploadDoorLockData } from "../../../store/actions";
+import UnknownMapping from './UnknownMapping';
+
+import { uploadDoorLockData, fetchMappings } from "../../../store/actions";
class FileUpload extends Component {
constructor(props) {
@@ -10,15 +12,68 @@ class FileUpload extends Component {
this.state = {
files: null,
+ unknownMappings: [],
};
this.onFileChange = this.onFileChange.bind(this);
this.onUploadClick = this.onUploadClick.bind(this);
}
+ componentDidMount() {
+ const { fetchMappings } = this.props;
+ fetchMappings();
+ }
+
+ componentWillReceiveProps(nextProps, nextContext) {
+ const { addedMapping } = nextProps;
+ const { unknownMappings } = this.state;
+
+ const filteredUnknownMappings = unknownMappings.filter(mapping => {
+ return mapping.officeSlug !== addedMapping.officeSlug
+ || mapping.resourceSlug !== addedMapping.resourceSlug
+ });
+
+ this.setState({unknownMappings: filteredUnknownMappings});
+ }
+
+ extractMappingFromFileName(fileName) {
+ const contentBetweenBracketsRegex = /\[(.*?)\]/;
+ const rawContent = fileName.match(contentBetweenBracketsRegex)[1];
+ const mappingContent = rawContent.split('-').map(word => word.trim());
+ return {
+ officeSlug: mappingContent[0],
+ resourceSlug: mappingContent[1],
+ }
+ }
+
+ checkIfMappingExsists(mappingFromFileName) {
+ const { mappings } = this.props;
+ const { officeSlug, resourceSlug } = mappingFromFileName;
+
+ const { existingMappings } = mappings;
+
+ return existingMappings.find(mapping => (mapping.officeSlug === officeSlug) && (mapping.resourceSlug === resourceSlug));
+ }
+
onFileChange(event) {
const files = event.target.files;
- this.setState({files});
+ const unknownMappings = [];
+
+
+ Array.from(files).forEach(file => {
+ const mappingFromFileName = this.extractMappingFromFileName(file.name);
+ if (!this.checkIfMappingExsists(mappingFromFileName)){
+ unknownMappings.push({
+ file: file.name,
+ officeId: null,
+ resourceId: null,
+ officeSlug: mappingFromFileName.officeSlug,
+ resourceSlug: mappingFromFileName.resourceSlug,
+ })
+ }
+ });
+
+ this.setState({files, unknownMappings});
};
onUploadClick() {
@@ -31,7 +86,11 @@ class FileUpload extends Component {
};
render() {
- const { pending } = this.props;
+ const { pendingUpload } = this.props;
+ const { unknownMappings, files } = this.state;
+
+ const uploadDisabled = pendingUpload || unknownMappings.length > 0 || !files;
+
return (
- Upload
+ {
+ unknownMappings.map((mapping, index) =>
+ )
+ }
+
+ Upload
);
}
}
const mapStateToProps = (state) => ({
- pending: state.doorLockData.pending,
+ pendingUpload: state.doorLockData.pending,
+ pendingMappings: state.mappingsData.pending,
+ mappings: state.mappingsData.result,
+ addedMapping: state.addMapping.result,
});
const mapDispatchToProps = (dispatch) => ({
- uploadDoorLockData: (doorLockDataFiles) => uploadDoorLockData(dispatch, doorLockDataFiles)
+ uploadDoorLockData: (doorLockDataFiles) => uploadDoorLockData(dispatch, doorLockDataFiles),
+ fetchMappings: () => fetchMappings(dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(FileUpload);
diff --git a/client/src/scenes/UploadDLockData/components/UnknownMapping.js b/client/src/scenes/UploadDLockData/components/UnknownMapping.js
new file mode 100644
index 0000000..3bee93e
--- /dev/null
+++ b/client/src/scenes/UploadDLockData/components/UnknownMapping.js
@@ -0,0 +1,172 @@
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import {Button, Dropdown, Message} from "semantic-ui-react";
+import Fuse from 'fuse.js';
+import { addNewMapping } from "../../../store/actions";
+
+class UnknownMapping extends Component {
+ constructor(props) {
+ super(props);
+
+ const guessedValues = this.guessDropdownValues(this.props);
+
+ this.state = {
+ selectedOfficeId: guessedValues.officeValue,
+ selectedResourceId: guessedValues.resourceValue,
+ }
+ }
+
+ componentWillReceiveProps(nextProps, nextContext) {
+ const guessedValues = this.guessDropdownValues(nextProps);
+ this.setState({selectedOfficeId: guessedValues.officeValue, selectedResourceId: guessedValues.resourceValue});
+ }
+
+ guessDropdownValues(props){
+ const { mappings, mapping } = props;
+
+ const offices = mappings && mappings.offices ? mappings.offices : [];
+ const resources = mappings && mappings.resources ? mappings.resources : [];
+
+ const fuzzySearchOptions = {
+ shouldSort: true,
+ threshold: 0.5,
+ location: 0,
+ distance: 100,
+ maxPatternLength: 32,
+ minMatchCharLength: 1,
+ keys: [
+ "officeName",
+ "resourceName",
+ ]
+ };
+
+ const officesFuse = new Fuse(offices, fuzzySearchOptions);
+ const fuzzyOfficeSearchResults = officesFuse.search(mapping.officeSlug);
+
+ let officeValue = null;
+ if (fuzzyOfficeSearchResults.length > 0){
+ officeValue = fuzzyOfficeSearchResults[0].officeId;
+ }else if (offices.length > 0){
+ officeValue = offices[0].officeId;
+ }
+
+ const filteredResources = resources.filter(resource => resource.officeId === officeValue);
+
+ const resourcesFuse = new Fuse(filteredResources, fuzzySearchOptions);
+ const fuzzyResourcesSearchResults = resourcesFuse.search(mapping.resourceSlug);
+
+ let resourceValue = null;
+ if (fuzzyResourcesSearchResults.length > 0){
+ resourceValue = fuzzyResourcesSearchResults[0].resourceId;
+ }else if (filteredResources.length > 0){
+ resourceValue = filteredResources[0].resourceId;
+ }
+
+ return {
+ officeValue,
+ resourceValue,
+ }
+ }
+
+ onOfficeChange(event, data) {
+ const { mappings } = this.props;
+
+ const selectedOfficeId = data.value || null;
+ const resources = mappings && mappings.resources ? mappings.resources : [];
+ const filteredResources = resources.filter(resource => resource.officeId === selectedOfficeId);
+ const selectedResourceId = filteredResources.length > 0 ? filteredResources[0].resourceId : null;
+ this.setState({selectedOfficeId, selectedResourceId});
+ }
+
+ onResourceChange(event, data) {
+ const selectedResourceId = data.value || null;
+ this.setState({selectedResourceId});
+ }
+
+ onSave(){
+ const { addNewMapping, mapping } = this.props;
+ const { selectedOfficeId, selectedResourceId } = this.state;
+ const officeSlug = mapping.officeSlug;
+ const resourceSlug = mapping.resourceSlug;
+
+ const newMapping = {
+ officeSlug,
+ resourceSlug,
+ officeId: selectedOfficeId,
+ resourceId: selectedResourceId,
+ };
+
+ addNewMapping(newMapping);
+ }
+
+ render() {
+ const { mapping, mappings } = this.props;
+ const { selectedOfficeId, selectedResourceId } = this.state;
+
+ const offices = mappings && mappings.offices ? mappings.offices : [];
+ const resources = mappings && mappings.resources ? mappings.resources : [];
+
+ const officeDropdownOptions = offices.map(office => {
+ return {
+ key: office.officeId,
+ value: office.officeId,
+ text: office.officeName,
+ }
+ });
+
+ const filteredResources = resources.filter(resource => resource.officeId === selectedOfficeId);
+
+ const resourceDropdownOptions = filteredResources.map(resource => {
+ return {
+ key: resource.resourceId,
+ value: resource.resourceId,
+ text: resource.resourceName,
+ }
+ });
+
+ const saveButtonDisabled = !selectedOfficeId || !selectedResourceId;
+
+ return (
+
+
+
+ {mapping.file}
+
+
+ This file contains the unknown location. Based on ORD data, it seems that this file is related to {' '}
+
+ {' '}
+ /
+ {' '}
+
+
+
+ Save
+
+
);
+ }
+}
+
+const mapStateToProps = (state) => ({
+ mappings: state.mappingsData.result,
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ addNewMapping: (mapping) => addNewMapping(dispatch, mapping),
+});
+
+
+export default connect(mapStateToProps, mapDispatchToProps)(UnknownMapping);
diff --git a/client/src/store/actions/index.js b/client/src/store/actions/index.js
index d21e20e..4415670 100644
--- a/client/src/store/actions/index.js
+++ b/client/src/store/actions/index.js
@@ -1 +1,2 @@
export * from './doorLockActions';
+export * from './officeRnDActions';
diff --git a/client/src/store/actions/officeRnDActions.js b/client/src/store/actions/officeRnDActions.js
new file mode 100644
index 0000000..f858967
--- /dev/null
+++ b/client/src/store/actions/officeRnDActions.js
@@ -0,0 +1,34 @@
+import {
+ FETCH_MAPPINGS_PENDING,
+ FETCH_MAPPINGS_SUCCESS,
+ FETCH_MAPPINGS_FAILED,
+ ADD_NEW_MAPPING_PENDING,
+ ADD_NEW_MAPPING_SUCCESS,
+ ADD_NEW_MAPPING_FAILED,
+} from "../constants";
+
+import API from '../../utilities/api';
+
+export const fetchMappings = (dispatch) => {
+ dispatch({type: FETCH_MAPPINGS_PENDING});
+ API.get('doorLock/mappings')
+ .then(response => {
+ dispatch({type: FETCH_MAPPINGS_SUCCESS, payload: response.data});
+ })
+ .catch(error => {
+ dispatch({type: FETCH_MAPPINGS_FAILED, payload: error.response});
+ });
+};
+
+export const addNewMapping = (dispatch, mapping) => {
+ dispatch({type: ADD_NEW_MAPPING_PENDING});
+ API.post('doorLock/mappings', {
+ mapping
+ })
+ .then(response => {
+ dispatch({type: ADD_NEW_MAPPING_SUCCESS, payload: response.data});
+ })
+ .catch(error => {
+ dispatch({type: ADD_NEW_MAPPING_FAILED, payload: error.response});
+ });
+};
diff --git a/client/src/store/reducers/addMappingReducer.js b/client/src/store/reducers/addMappingReducer.js
new file mode 100644
index 0000000..c360882
--- /dev/null
+++ b/client/src/store/reducers/addMappingReducer.js
@@ -0,0 +1,38 @@
+import {
+ ADD_NEW_MAPPING_PENDING,
+ ADD_NEW_MAPPING_SUCCESS,
+ ADD_NEW_MAPPING_FAILED,
+} from "../constants";
+
+const initialState = {
+ pending: false,
+ result: null,
+ error: null,
+};
+
+export const addMapping = (state, action) => {
+ state = state || initialState;
+ action = action || {};
+
+ switch(action.type){
+ case ADD_NEW_MAPPING_PENDING:
+ return Object.assign({}, state, {
+ pending: true,
+ error: null,
+ });
+ case ADD_NEW_MAPPING_SUCCESS:
+ return Object.assign({}, state, {
+ pending: false,
+ result: action.payload,
+ error: null,
+ });
+ case ADD_NEW_MAPPING_FAILED:
+ return Object.assign({}, state, {
+ pending: false,
+ result: {},
+ error: action.payload,
+ });
+ default:
+ return state;
+ }
+};
diff --git a/client/src/store/reducers/index.js b/client/src/store/reducers/index.js
index 274b7bd..ed2a547 100644
--- a/client/src/store/reducers/index.js
+++ b/client/src/store/reducers/index.js
@@ -1,8 +1,12 @@
import { combineReducers } from "redux";
import { doorLockData} from "./doorLockReducers";
+import { mappingsData } from "./mappingsReducer";
+import { addMapping } from './addMappingReducer';
export const rootReducer = combineReducers({
- doorLockData
+ doorLockData,
+ mappingsData,
+ addMapping,
});
diff --git a/client/src/store/reducers/mappingsReducer.js b/client/src/store/reducers/mappingsReducer.js
new file mode 100644
index 0000000..95e0d9d
--- /dev/null
+++ b/client/src/store/reducers/mappingsReducer.js
@@ -0,0 +1,38 @@
+import {
+ FETCH_MAPPINGS_PENDING,
+ FETCH_MAPPINGS_SUCCESS,
+ FETCH_MAPPINGS_FAILED,
+} from "../constants";
+
+const initialState = {
+ pending: false,
+ result: null,
+ error: null,
+};
+
+export const mappingsData = (state, action) => {
+ state = state || initialState;
+ action = action || {};
+
+ switch(action.type){
+ case FETCH_MAPPINGS_PENDING:
+ return Object.assign({}, state, {
+ pending: true,
+ error: null,
+ });
+ case FETCH_MAPPINGS_SUCCESS:
+ return Object.assign({}, state, {
+ pending: false,
+ result: action.payload,
+ error: null,
+ });
+ case FETCH_MAPPINGS_FAILED:
+ return Object.assign({}, state, {
+ pending: false,
+ result: {},
+ error: action.payload,
+ });
+ default:
+ return state;
+ }
+};