add mapping; prevent upload before mapping office / resource
This commit is contained in:
@@ -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 (
|
||||
<div>
|
||||
<Form.Input
|
||||
@@ -43,18 +102,30 @@ class FileUpload extends Component {
|
||||
accept=".csv"
|
||||
onChange={this.onFileChange}
|
||||
/>
|
||||
<Form.Button onClick={this.onUploadClick} disabled={pending} >Upload</Form.Button>
|
||||
{
|
||||
unknownMappings.map((mapping, index) =>
|
||||
<UnknownMapping
|
||||
key={`unknown-mapping-${index}`}
|
||||
mapping={mapping}
|
||||
/>)
|
||||
}
|
||||
<br/>
|
||||
<Form.Button onClick={this.onUploadClick} disabled={uploadDisabled} >Upload</Form.Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
172
client/src/scenes/UploadDLockData/components/UnknownMapping.js
Normal file
172
client/src/scenes/UploadDLockData/components/UnknownMapping.js
Normal file
@@ -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 (
|
||||
<div>
|
||||
<br/>
|
||||
<Message>
|
||||
<Message.Header>{mapping.file}</Message.Header>
|
||||
<br/>
|
||||
<span>
|
||||
This file contains the unknown location. Based on ORD data, it seems that this file is related to {' '}
|
||||
<Dropdown
|
||||
inline
|
||||
options={officeDropdownOptions}
|
||||
onChange={this.onOfficeChange.bind(this)}
|
||||
value={selectedOfficeId}
|
||||
/>
|
||||
{' '}
|
||||
/
|
||||
{' '}
|
||||
<Dropdown
|
||||
inline
|
||||
options={resourceDropdownOptions}
|
||||
onChange={this.onResourceChange.bind(this)}
|
||||
value={selectedResourceId}
|
||||
/>
|
||||
</span>
|
||||
<br/>
|
||||
<Button
|
||||
disabled={saveButtonDisabled}
|
||||
onClick={this.onSave.bind(this)}
|
||||
>Save</Button>
|
||||
</Message>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
mappings: state.mappingsData.result,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
addNewMapping: (mapping) => addNewMapping(dispatch, mapping),
|
||||
});
|
||||
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UnknownMapping);
|
||||
@@ -1 +1,2 @@
|
||||
export * from './doorLockActions';
|
||||
export * from './officeRnDActions';
|
||||
|
||||
34
client/src/store/actions/officeRnDActions.js
Normal file
34
client/src/store/actions/officeRnDActions.js
Normal file
@@ -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});
|
||||
});
|
||||
};
|
||||
38
client/src/store/reducers/addMappingReducer.js
Normal file
38
client/src/store/reducers/addMappingReducer.js
Normal file
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
38
client/src/store/reducers/mappingsReducer.js
Normal file
38
client/src/store/reducers/mappingsReducer.js
Normal file
@@ -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;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user