diff --git a/src/actions/SuppliersActions.js b/src/actions/SuppliersActions.js index 803ceb6a..af87be7a 100644 --- a/src/actions/SuppliersActions.js +++ b/src/actions/SuppliersActions.js @@ -185,6 +185,60 @@ SuppliersActions.uploadFiles = (files, providerId) => async ( }); }; +SuppliersActions.uploadTariffZonesFiles = (files, provider) => async ( + dispatch, + getState +) => { + dispatch(sendData(0, types.UPDATED_TARIFF_ZONE_FILE_UPLOAD_PROGRESS)); + + const url = `${window.config.tariffZonesUrl}${provider.chouetteInfo.xmlns}/files`; + + var data = new FormData(); + + files.forEach(file => { + data.append('files', file); + }); + + var config = { + onUploadProgress: function(progressEvent) { + let percentCompleted = (progressEvent.loaded / progressEvent.total) * 100; + dispatch( + sendData( + percentCompleted, + types.UPDATED_TARIFF_ZONE_FILE_UPLOAD_PROGRESS + ) + ); + }, + ...(await getApiConfig(getState().UserReducer.auth)) + }; + + return axios + .post(url, data, config) + .then(function(response) { + dispatch( + SuppliersActions.addNotification( + 'Uploaded tariff zone file(s)', + 'success' + ) + ); + dispatch( + SuppliersActions.logEvent({ + title: 'Uploaded tariff zone file(s): ' + files.join(',') + }) + ); + dispatch(sendData(0, types.UPDATED_TARIFF_ZONE_FILE_UPLOAD_PROGRESS)); + }) + .catch(function(response) { + dispatch( + SuppliersActions.addNotification( + 'Unable to upload tariff zone file(s)', + 'error' + ) + ); + dispatch(sendData(0, types.UPDATED_TARIFF_ZONE_FILE_UPLOAD_PROGRESS)); + }); +}; + SuppliersActions.getAllProviderStatus = () => async (dispatch, getState) => { dispatch(sendData(null, types.REQUESTED_ALL_SUPPLIERS_STATUS)); const state = getState(); diff --git a/src/actions/actionTypes.js b/src/actions/actionTypes.js index d55ca6ee..7c2961eb 100644 --- a/src/actions/actionTypes.js +++ b/src/actions/actionTypes.js @@ -167,6 +167,8 @@ export const REQUESTED_ALL_SUPPLIERS_STATUS = 'REQUESTED_ALL_SUPPLIERS_STATUS'; export const RECEIVED_ALL_SUPPLIERS_STATUS = 'RECEIVED_ALL_SUPPLIERS_STATUS'; export const UPDATED_FILE_UPLOAD_PROGRESS = 'UPDATED_FILE_UPLOAD_PROGRESS'; +export const UPDATED_TARIFF_ZONE_FILE_UPLOAD_PROGRESS = + 'UPDATED_TARIFF_ZONE_FILE_UPLOAD_PROGRESS'; /* organization actions */ diff --git a/src/config/environments/dev.json b/src/config/environments/dev.json index 1df20cb0..7b14c840 100644 --- a/src/config/environments/dev.json +++ b/src/config/environments/dev.json @@ -8,6 +8,7 @@ "mapboxAdminBaseUrl": "https://api.dev.entur.io/mapbox-admin/v1/", "geocoderAdminBaseUrl": "https://api.dev.entur.io/timetable-admin/v1/geocoder/", "poiFilterBaseUrl": "https://api.dev.entur.io/timetable-admin/v2/poi-filter", + "tariffZonesUrl": "https://api.dev.entur.io/timetable-admin/v2/tariffzones-import/", "endpointBase": "/", "chouetteBaseUrl": "https://rutedb.dev.entur.org/", "udugBaseUrl": "/netex-validation-reports/", diff --git a/src/reducers/SuppliersReducer.js b/src/reducers/SuppliersReducer.js index 087243c6..a13d52a7 100644 --- a/src/reducers/SuppliersReducer.js +++ b/src/reducers/SuppliersReducer.js @@ -94,6 +94,11 @@ const SuppliersReducer = (state = initialState, action) => { case types.UPDATED_FILE_UPLOAD_PROGRESS: return Object.assign({}, state, { fileUploadProgress: action.payLoad }); + case types.UPDATED_TARIFF_ZONE_FILE_UPLOAD_PROGRESS: + return Object.assign({}, state, { + tariffZoneFileUploadProgress: action.payLoad + }); + case types.RECEIVED_GRAPH_STATUS: return Object.assign({}, state, { ...action.payLoad }); diff --git a/src/screens/common/components/FileUpload.js b/src/screens/common/components/FileUpload.js new file mode 100644 index 00000000..674820c2 --- /dev/null +++ b/src/screens/common/components/FileUpload.js @@ -0,0 +1,119 @@ +import React, { useEffect, useState } from 'react'; +import Dropzone from 'react-dropzone'; +import Button from 'muicss/lib/react/button'; +import { Line as Progress } from 'rc-progress'; + +export const FileUpload = ({ fileUploadProgress, handleFileUpload }) => { + const [files, setFiles] = useState([]); + const [showSuccess, setShowSuccess] = useState(false); + const [uploadProgress, setUploadProgress] = useState(0); + + useEffect(() => { + if (fileUploadProgress === 100) { + setFiles([]); + setShowSuccess(true); + } + }, [fileUploadProgress]); + + useEffect(() => { + let showSuccessTimer = setTimeout(() => resetProgress(), 3000); + return () => { + clearTimeout(showSuccessTimer); + }; + }, [showSuccess]); + + useEffect(() => { + if (!showSuccess) { + setUploadProgress(fileUploadProgress); + } + }, [fileUploadProgress]); + + const resetProgress = () => { + setShowSuccess(false); + setUploadProgress(0); + }; + + const handleOnDrop = files => { + if (files.length) { + setFiles(files); + } + }; + + const dropStyle = { + height: '150px', + borderWidth: 1, + borderColor: '#666', + borderStyle: 'dashed', + borderRadius: 2, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + cursor: 'pointer' + }; + + const filesStyle = { + marginTop: '5%', + minHeight: '300px', + maxHeight: '300px' + }; + + const uploadButtonStyle = { + width: 100, + fontSize: '0.8em', + textAlign: 'center', + marginLeft: 'auto', + marginTop: 10 + }; + + return ( +
+ { + handleOnDrop(files); + console.warn('rejected', rejected); + }} + > +
+ Try dropping some files here, or click to select files to upload. +
+
+ {files.length ? ( + + ) : ( +
No files added
+ )} + + + {showSuccess && ( +
Files successfully uploaded
+ )} +
+ ); +}; diff --git a/src/screens/common/components/SelectSupplier.js b/src/screens/common/components/SelectSupplier.js new file mode 100644 index 00000000..95fdbf2c --- /dev/null +++ b/src/screens/common/components/SelectSupplier.js @@ -0,0 +1,49 @@ +import SelectField from 'material-ui/SelectField'; +import MenuItem from 'material-ui/MenuItem'; +import React from 'react'; + +export const SelectSupplier = ({ + suppliers, + selectSupplier, + selectedSupplierId, + errorText +}) => { + return ( + <> + selectSupplier(v)} + autoWidth={true} + value={Number(selectedSupplierId) || -1} + iconStyle={{ fill: 'rgba(22, 82, 149, 0.69)' }} + > + {suppliers.map(supplier => { + const isLevel1Provider = + (supplier.chouetteInfo && + supplier.chouetteInfo.migrateDataToProvider) || + supplier.id === -1; + return ( + + {supplier.name} + + } + /> + ); + })} + + {errorText &&
{errorText}
} + + ); +}; diff --git a/src/screens/geocoder/components/tariffZonesImport/TariffZonesImport.js b/src/screens/geocoder/components/tariffZonesImport/TariffZonesImport.js new file mode 100644 index 00000000..b9dc8ede --- /dev/null +++ b/src/screens/geocoder/components/tariffZonesImport/TariffZonesImport.js @@ -0,0 +1,62 @@ +import { SelectSupplier } from '../../../common/components/SelectSupplier'; +import { useSelector, shallowEqual, useDispatch } from 'react-redux'; +import React, { useState } from 'react'; +import { FileUpload } from '../../../common/components/FileUpload'; +import SuppliersActions from '../../../../actions/SuppliersActions'; + +export const TariffZonesImport = () => { + const suppliers = useSelector( + state => state.SuppliersReducer.data, + shallowEqual + ); + + const tariffZoneFileUploadProgress = useSelector( + state => state.SuppliersReducer.tariffZoneFileUploadProgress, + shallowEqual + ); + + const [selectedSupplier, setSelectedSupplier] = useState(null); + const [noSupplierSelected, setNoSupplierSelected] = useState(null); + const dispatch = useDispatch(); + + const handleFileUpload = files => { + if (selectedSupplier === null) { + setNoSupplierSelected(true); + } else { + dispatch( + SuppliersActions.uploadTariffZonesFiles(files, selectedSupplier) + ); + } + }; + + const onSelectSupplier = selectedSupplierId => { + setNoSupplierSelected(false); + setSelectedSupplier( + suppliers.find(supplier => supplier.id === selectedSupplierId) + ); + }; + + return ( +
+ +
+ +
+
+ ); +}; diff --git a/src/screens/geocoder/index.js b/src/screens/geocoder/index.js index 7a47c9f7..46892dfe 100644 --- a/src/screens/geocoder/index.js +++ b/src/screens/geocoder/index.js @@ -1,10 +1,14 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import Tabs from '@material-ui/core/Tabs'; import Tab from '@material-ui/core/Tab'; import Typography from '@material-ui/core/Typography'; import Box from '@material-ui/core/Box'; import Pelias from './components/Pelias'; import OSMPOIFilter from './components/OSMPOIFilter'; +import { TariffZonesImport } from './components/tariffZonesImport/TariffZonesImport'; +import { getQueryVariable } from '../../utils'; +import SuppliersActions from '../../actions/SuppliersActions'; +import { useDispatch } from 'react-redux'; function TabPanel(props) { const { children, value, index, ...other } = props; @@ -32,6 +36,11 @@ function a11yProps(index) { export default () => { const [value, setValue] = React.useState(0); + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(SuppliersActions.getAllProviders()); + }, []); const handleChange = (event, newValue) => { setValue(newValue); @@ -47,6 +56,7 @@ export default () => { > + @@ -54,6 +64,9 @@ export default () => { + + + ); }; diff --git a/src/screens/providers/components/FileUpload.js b/src/screens/providers/components/FileUpload.js deleted file mode 100644 index c0858f25..00000000 --- a/src/screens/providers/components/FileUpload.js +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by - * the European Commission - subsequent versions of the EUPL (the "Licence"); - * You may not use this work except in compliance with the Licence. - * You may obtain a copy of the Licence at: - * - * https://joinup.ec.europa.eu/software/page/eupl - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the Licence is distributed on an "AS IS" basis, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Licence for the specific language governing permissions and - * limitations under the Licence. - * - */ - -import React from 'react'; -import Dropzone from 'react-dropzone'; -import Button from 'muicss/lib/react/button'; -import { Line as Progress } from 'rc-progress'; - -class FileUpload extends React.Component { - constructor(props) { - super(props); - this.state = { - files: [] - }; - } - - handleOnDrop(files) { - if (files.length) { - this.setState({ - files: files - }); - return false; - } - } - - render() { - const dropStyle = { - height: '150px', - borderWidth: 1, - borderColor: '#666', - borderStyle: 'dashed', - borderRadius: 2, - display: 'flex', - justifyContent: 'center', - alignItems: 'center' - }; - - const filesStyle = { - marginTop: '5%', - minHeight: '300px', - maxHeight: '300px' - }; - - const uploadButtonStyle = { - width: 100, - fontSize: '0.8em', - textAlign: 'center', - marginLeft: 'auto', - marginTop: 10 - }; - - const { files } = this.state; - const { fileUploadProgress, handleFileUpload } = this.props; - - return ( -
- { - this.handleOnDrop(files); - console.warn('rejected', rejected); - }} - > -
- Try dropping some files here, or click to select files to upload. -
-
- {files.length ? ( - - ) : ( -
No files added
- )} - - -
- ); - } -} - -export default FileUpload; diff --git a/src/screens/providers/components/SupplierTabWrapper.js b/src/screens/providers/components/SupplierTabWrapper.js index 48c5941e..31ca7300 100644 --- a/src/screens/providers/components/SupplierTabWrapper.js +++ b/src/screens/providers/components/SupplierTabWrapper.js @@ -24,7 +24,7 @@ import { PulseLoader as Loader } from 'halogenium'; import Tabs from 'muicss/lib/react/tabs'; import Tab from 'muicss/lib/react/tab'; import { getQueryVariable } from 'utils'; -import FileUpload from './FileUpload'; +import { FileUpload } from '../../common/components/FileUpload'; import OrganizationRegister from './OrganizationRegister'; import rolesParser from 'roles/rolesParser'; import ExportedFilesView from './ExportedFilesView'; @@ -403,10 +403,12 @@ class SupplierTabWrapper extends React.Component { )}
- +
+ +
); diff --git a/src/screens/providers/components/SuppliersContainer.js b/src/screens/providers/components/SuppliersContainer.js index 03336d93..7647413e 100644 --- a/src/screens/providers/components/SuppliersContainer.js +++ b/src/screens/providers/components/SuppliersContainer.js @@ -18,14 +18,13 @@ import { connect } from 'react-redux'; import React from 'react'; import SuppliersActions from 'actions/SuppliersActions'; import cfgreader from 'config/readConfig'; -import MenuItem from 'material-ui/MenuItem'; -import SelectField from 'material-ui/SelectField'; import MdNew from 'material-ui/svg-icons/content/add'; import { getQueryVariable } from 'utils'; import rolesParser from 'roles/rolesParser'; import MdEdit from 'material-ui/svg-icons/image/edit'; import MdDelete from 'material-ui/svg-icons/action/delete-forever'; import ConfirmDialog from 'modals/ConfirmDialog'; +import { SelectSupplier } from '../../common/components/SelectSupplier'; class SuppliersContainer extends React.Component { constructor(props) { @@ -121,39 +120,11 @@ class SuppliersContainer extends React.Component { }} >
- this.selectSupplier(v)} - autoWidth={true} - value={Number(activeProviderId) || -1} - iconStyle={{ fill: 'rgba(22, 82, 149, 0.69)' }} - > - {supplierItems.map(supplier => { - const isLevel1Provider = - (supplier.chouetteInfo && - supplier.chouetteInfo.migrateDataToProvider) || - supplier.id === -1; - return ( - - {supplier.name} - - } - /> - ); - })} - + this.selectSupplier(v)} + selectedSupplierId={activeProviderId} + /> {canEditOrganisation && (