add mapping; prevent upload before mapping office / resource

This commit is contained in:
Bilal Catic
2019-06-10 05:58:09 +02:00
parent c23077d94f
commit 04c9ee3806
7 changed files with 366 additions and 8 deletions

View File

@@ -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);

View 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);

View File

@@ -1 +1,2 @@
export * from './doorLockActions';
export * from './officeRnDActions';

View 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});
});
};

View 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;
}
};

View File

@@ -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,
});

View 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;
}
};