diff --git a/__tests__/test-utils/mock-data/manager.js b/__tests__/test-utils/mock-data/manager.js index 91856642b..ac4fb3129 100644 --- a/__tests__/test-utils/mock-data/manager.js +++ b/__tests__/test-utils/mock-data/manager.js @@ -117,6 +117,7 @@ export const mockProject = { updaters: null, walkSpeed: null }, + sharedStopsConfig: null, useCustomOsmBounds: false, user: null } diff --git a/i18n/english.yml b/i18n/english.yml index 1fa2ecc46..80e42408f 100644 --- a/i18n/english.yml +++ b/i18n/english.yml @@ -96,6 +96,8 @@ components: title: Create a new snapshot CustomCSVForm: numLines: "%numLines% lines." + csvInvalid: CSV invalid! + uploading: Uploading... DatatoolsNavbar: account: My Account alerts: Alerts @@ -972,6 +974,9 @@ components: defaultLocation: 'Default location (lat, lng)' defaultTimeZone: Default time zone title: Location + sharedStops: + title: Shared Stops + placeholder: Shared stops config CSV name: Project name title: General updates: diff --git a/i18n/german.yml b/i18n/german.yml index f92d152a7..8a3bf9ef6 100644 --- a/i18n/german.yml +++ b/i18n/german.yml @@ -1052,6 +1052,9 @@ components: localPlacesIndex: title: Adress-Index webhookUrl: Webhook URL + sharedStops: + title: Shared Stops + placeholder: Shared stops config CSV location: boundingBox: Gebiets-Grenzen (W,S,E,N) boundingBoxPlaceHolder: min_lon, min_lat, max_lon, max_lat @@ -1524,6 +1527,8 @@ components: verfügen jedoch nicht über die erforderlichen Rechte. CustomCSVForm: numLines: "%numLines% lines." + csvInvalid: CSV ungültig! + uploading: Uploading... FeedTransformationErrors: csvMissingName: Custom CSV must have a name. csvNameContainsTxt: Custom CSV name cannot contain .txt diff --git a/i18n/polish.yml b/i18n/polish.yml index d576d705d..613369242 100644 --- a/i18n/polish.yml +++ b/i18n/polish.yml @@ -95,6 +95,8 @@ components: title: Create User CustomCSVForm: numLines: "%numLines% lines." + csvInvalid: CSV ungültig! + uploading: Uploading... DatatoolsNavbar: account: Moje konto alerts: Alerty @@ -1038,6 +1040,9 @@ components: localPlacesIndex: title: Local Places Index webhookUrl: Webhook URL + sharedStops: + title: Shared Stops + placeholder: Shared stops config CSV location: boundingBox: Bounding box (W,S,E,N) boundingBoxPlaceHolder: min_lon, min_lat, max_lon, max_lat diff --git a/lib/manager/actions/__tests__/__snapshots__/projects.js.snap b/lib/manager/actions/__tests__/__snapshots__/projects.js.snap index b00ab2a30..abb117dcd 100644 --- a/lib/manager/actions/__tests__/__snapshots__/projects.js.snap +++ b/lib/manager/actions/__tests__/__snapshots__/projects.js.snap @@ -83,6 +83,7 @@ Object { "updaters": null, "walkSpeed": null, }, + "sharedStopsConfig": null, "useCustomOsmBounds": false, "user": null, }, diff --git a/lib/manager/components/ProjectSettingsForm.js b/lib/manager/components/ProjectSettingsForm.js index 3977560ff..177983a06 100644 --- a/lib/manager/components/ProjectSettingsForm.js +++ b/lib/manager/components/ProjectSettingsForm.js @@ -31,6 +31,8 @@ import { parseBounds, validationState } from '../util' import type { Bounds, Project } from '../../types' import type { ManagerUserState } from '../../types/reducers' +import CustomCSVForm from './transform/CustomCSVForm' + type ProjectModel = { autoFetchFeeds?: boolean, autoFetchHour?: number, @@ -39,7 +41,8 @@ type ProjectModel = { defaultTimeZone?: string, id?: string, name?: string, - peliasWebhookUrl?: string + peliasWebhookUrl?: string, + sharedStopsConfig?: string } type Props = { @@ -182,8 +185,12 @@ export default class ProjectSettingsForm extends Component { this.setState(update(this.state, {model: {$merge: {defaultTimeZone}}})) } - _onChangeTextInput = ({target}: {target: HTMLInputElement}) => { + // TODO: shared type + // https://github.com/ibi-group/datatools-ui/pull/986#discussion_r1362271761 + _onChangeTextInput = ({target}: {target: {name?: string, value: string}}) => { const {name, value} = target + if (!name) return + this.setState( update( this.state, @@ -223,6 +230,7 @@ export default class ProjectSettingsForm extends Component { return Object.keys(validation).every(k => validation[k]) } + // eslint-disable-next-line complexity render () { const {editDisabled, showDangerZone} = this.props const {model, validation} = this.state @@ -324,6 +332,27 @@ export default class ProjectSettingsForm extends Component { + + {this.messages('fields.sharedStops.title')} + + + + {/* TODO: on enter, textarea should NOT submit. This causes strange behavior when + editing in the textarea + + see: https://github.com/ibi-group/datatools-ui/pull/977#discussion_r1288916749 */} + {}} + placeholder={`stop_group_id,feed_id,stop_id,is_primary\n1,1,29240,1\n1,3,4705,0`} + /> + + + + {this.messages('fields.localPlacesIndex.title')} diff --git a/lib/manager/components/transform/AddCustomFile.js b/lib/manager/components/transform/AddCustomFile.js index d334d9664..ca7dad9df 100644 --- a/lib/manager/components/transform/AddCustomFile.js +++ b/lib/manager/components/transform/AddCustomFile.js @@ -42,7 +42,7 @@ export default class AddCustomFile extends Component) => { + _onChangeCsvData = (evt: {target: {name?: string, value: string}}) => { const newState = {...this.state, csvData: evt.target.value} this.setState(newState) } @@ -94,6 +94,7 @@ export default class AddCustomFile extends Component ) diff --git a/lib/manager/components/transform/CustomCSVForm.js b/lib/manager/components/transform/CustomCSVForm.js index e2144e492..b4f25aa40 100644 --- a/lib/manager/components/transform/CustomCSVForm.js +++ b/lib/manager/components/transform/CustomCSVForm.js @@ -1,24 +1,50 @@ // @flow -import React from 'react' +// $FlowFixMe Flow is outdated +import React, { useEffect, useState } from 'react' import { Button } from 'react-bootstrap' +import { parseString } from '@fast-csv/parse' import { getComponentMessages } from '../../../common/util/config' type Props = { - buttonText: string, - csvData: ?string, - headerText: string, - inputIsSame: boolean, - onChangeCsvData: (SyntheticInputEvent) => void, + buttonText?: string, + csvData?: ?string, + headerText?: string, + hideSaveButton?: boolean, + inputIsSame?: boolean, + name?: string, + onChangeCsvData: ({target: {name?: string, value: string}}) => void, onSaveCsvData: () => void, + placeholder?: string, } const CustomCSVForm = (props: Props) => { - const { buttonText, csvData, headerText, inputIsSame, onChangeCsvData, onSaveCsvData } = props + const [errorCount, setErrorCount] = useState(0) + + const { + buttonText, + csvData, + headerText, + hideSaveButton, + inputIsSame, + name, + onChangeCsvData, + onSaveCsvData, + placeholder + } = props + + useEffect(() => { + setErrorCount(0) + + parseString(csvData, { headers: true }) + .on('error', _ => setErrorCount(errorCount + 1)) + }, [csvData]) const numLines = !csvData ? 0 : csvData.split(/\r*\n/).length const messages = getComponentMessages('CustomCSVForm') + const csvIsValid = errorCount === 0 + return (