Browse Source

StopForm works with draggable marker for location

master
sipp11 6 years ago
parent
commit
251786acd3
  1. 1
      package.json
  2. 25
      src/actions/index.js
  3. 64
      src/actions/trip.js
  4. 6
      src/components/RouteDetail.js
  5. 149
      src/components/StopForm.js
  6. 14
      src/components/StopList.js
  7. 186
      src/components/StopTimeForm.js
  8. 69
      src/components/StopTimeOne.js
  9. 251
      src/components/TripForm.js
  10. 24
      src/components/parts/Date.js
  11. 36
      src/components/parts/HorizontalSelect.js
  12. 15
      src/components/parts/Input.js
  13. 29
      src/components/parts/Select.js
  14. 13
      src/constants/ActionTypes.js
  15. 34
      src/container/Geo.js
  16. 2
      src/container/Main.js
  17. 19
      src/reducers/geo.js
  18. 2
      src/reducers/index.js
  19. 2
      src/reducers/route.js
  20. 3
      src/reducers/stoptime.js
  21. 76
      src/reducers/trip.js

1
package.json

@ -17,6 +17,7 @@
"react-redux": "^5.0.7",
"react-router-dom": "^4.3.1",
"react-scripts": "1.1.4",
"react-select": "^1.2.1",
"redux": "^4.0.0",
"redux-api-middleware": "^2.3.0",
"redux-logger": "^3.0.6",

25
src/actions/index.js

@ -94,7 +94,30 @@ export const polygonReset = () => {
export const mapCenterUpdate = (lat, lon) => {
return {
type: types.GEO_MAPCENTER_UPDATE,
payload: [lat, lon]
payload: [lat, lon],
}
}
export const draggableMarkerUpdate = (lat, lon) => {
return {
type: types.GEO_DRAGMARKER_CHANGE,
payload: [lat, lon],
}
}
export const draggableMarkerEnable = (lat, lon) => {
let payload = (lat !== undefined && lon !== undefined) ? [lat, lon] : []
return {
type: types.GEO_DRAGMARKER_ENABLE,
payload,
}
}
export const draggableMarkerDisable = () => {
return {
type: types.GEO_DRAGMARKER_DISABLE
}
}

64
src/actions/trip.js

@ -0,0 +1,64 @@
import { RSAA } from 'redux-api-middleware'
import * as types from '../constants/ActionTypes'
import { RSAAHeaders } from '../utils/ApiClient'
import { API_URL } from '../constants/Api'
export const getTrip = (query) => ({
[RSAA]: {
endpoint: `${API_URL}/trip/?${query || ''}`,
method: 'GET',
headers: RSAAHeaders,
bailout: (state) => state.trip.fetching,
types: [
types.TRIP_REQUEST,
types.TRIP_SUCCESS,
types.TRIP_FAILURE,
]
}
})
export const updateTrip = (id, body) => ({
[RSAA]: {
endpoint: `${API_URL}/trip/${id}/`,
body: JSON.stringify(body),
method: 'PATCH',
headers: RSAAHeaders,
types: [
types.TRIP_REQUEST,
types.TRIP_UPDATE,
types.TRIP_FAILURE,
]
}
})
export const createTrip = (body) => ({
[RSAA]: {
endpoint: `${API_URL}/trip/`,
body: JSON.stringify(body),
method: 'POST',
headers: RSAAHeaders,
types: [
types.TRIP_REQUEST,
types.TRIP_CREATE,
types.TRIP_FAILURE,
]
}
})
export const deleteTrip = (id) => ({
[RSAA]: {
endpoint: `${API_URL}/trip/${id}/`,
method: 'DELETE',
headers: RSAAHeaders,
types: [
types.TRIP_REQUEST,
{
type: types.TRIP_DELETE,
meta: { id }
},
types.TRIP_FAILURE,
]
}
})

6
src/components/RouteDetail.js

@ -55,7 +55,7 @@ const TripList = (props) => (
onClick={() => { store.dispatch(getStopTime(`trip=${ele.id}&limit=100`))}}>
{ele.trip_id}
<StyledTripDesc>
(<b>ID</b> {ele.id})&nbsp;
(<b>ID</b> <Link to={`/trip/${props.agencyId}/${props.routeId}/${ele.trip_id}`}>{ele.id}</Link>)&nbsp;
<b>service</b> <Link to={`/calendar/${ele.service.service_id}`}>{ele.service.service_id}</Link>
<br />
{ele.stoptime.count > 0 && <div>
@ -108,6 +108,7 @@ class RouteDetail extends Component {
componentWillReceiveProps(newProps) {
if (this.props.route.count < newProps.route.count) {
this.pushShapeToStore(this.props.match, newProps.route)
}
}
@ -135,6 +136,7 @@ class RouteDetail extends Component {
}
const item = tRoute[0]
const baseUrl = `/map/${agencyId}/route/${routeId}`
// TODO: change TripList to use from store.trip (when update will change automatically)
return (
<nav className="panel">
<p className="panel-heading">
@ -151,7 +153,7 @@ class RouteDetail extends Component {
<Route exact path={`${baseUrl}/fare`} render={(props) => (
<FareRuleList farerules={item.farerule_set} {...props} />)} />
<Route exact path={`${baseUrl}/trip`} render={(props) => (
<TripList trips={item.trip_set} {...props} />)} />
<TripList trips={item.trip_set} routeId={routeId} agencyId={agencyId} {...props} />)} />
</nav>
)
}

149
src/components/StopForm.js

@ -4,8 +4,10 @@ import { connect } from 'react-redux'
import { Redirect, Link } from 'react-router-dom'
import HorizontalInput from './parts/HorizontalInput'
import { mapCenterUpdate } from '../actions'
import { updateStop, createStop, deleteStop } from '../actions/stop'
import {
mapCenterUpdate, draggableMarkerEnable, draggableMarkerDisable
} from '../actions'
import { updateStop, createStop, deleteStop, getStop } from '../actions/stop'
import store from '../store'
const StyledStopForm = styled.div`
@ -27,6 +29,7 @@ class StopForm extends Component {
location_type: '0',
stop_timezone: 'Asia/Bangkok',
wheelchair_boarding: '0',
latlon: [],
// parent_station: null,
}
@ -36,6 +39,7 @@ class StopForm extends Component {
this.handleSubmit = this.handleSubmit.bind(this)
this.handleDelete = this.handleDelete.bind(this)
this.renderForm = this.renderForm.bind(this)
this.setToCurrentLocation = this.setToCurrentLocation.bind(this)
}
handleChange(evt) {
@ -45,10 +49,14 @@ class StopForm extends Component {
}
handleSubmit() {
const { id } = this.state
let body = { ...this.state }
const { id, latlon } = this.state
let body = {
...this.state,
location: { latitude: latlon[0], longitude: latlon[1] },
}
delete body.id
delete body.geojson
delete body.latlon
if (id !== null) {
store.dispatch(updateStop(id, body))
} else {
@ -63,33 +71,81 @@ class StopForm extends Component {
this.setState({justSubmit: true})
}
componentWillUnmount() {
store.dispatch(draggableMarkerDisable())
}
componentWillMount() {
const { props } = this
const { stopId } = props.match.params
const { results } = props.stop
const ones = results.filter(ele => ele.stop_id === stopId)
if (ones.length > 0) {
this.setState(ones[0])
props.updateBreadcrumb({ stopId })
const { coordinates } = ones[0].geojson
store.dispatch(mapCenterUpdate(coordinates[1], coordinates[0]))
this.updateStopFromStore(ones[0])
} else {
store.dispatch(getStop(`search=${stopId}`))
}
}
updateStopFromStore(one) {
this.props.updateBreadcrumb({ stopId: one.stop_id })
const { coordinates } = one.geojson
this.setState({
...one,
latlon: [coordinates[1], coordinates[0]],
})
store.dispatch(mapCenterUpdate(coordinates[1], coordinates[0]))
store.dispatch(draggableMarkerEnable(coordinates[1], coordinates[0]))
}
componentWillReceiveProps(newProps) {
// if stop changes, we need to refetch
const { stopId } = this.props.match.params
const newStopId = newProps.match.params.stopId
if (stopId !== newStopId) {
store.dispatch(getStop(`search=${newStopId}`))
// form setup this way won't change value in textinput, so
// we get back to StopList (with getStop invoked with target stop)
this.setState({back2list: true})
}
if (stopId !== undefined && this.state.stop_id === null) {
const { results } = newProps.stop
const ones = results.filter(ele => ele.stop_id === stopId)
if (ones.length === 0) {
this.setState({ back2list: true })
} else {
this.updateStopFromStore(ones[0])
}
return
}
const oLatlon = this.props.draggableMarkerLatlon
const newLatlon = newProps.draggableMarkerLatlon
if (oLatlon[0] !== newLatlon[0] || oLatlon[1] !== newLatlon[1]) {
this.setState({latlon: newLatlon})
}
}
setToCurrentLocation() {
const { coords } = this.props
store.dispatch(draggableMarkerEnable(coords.latitude, coords.longitude))
}
renderForm() {
const one = this.state
const { stopId } = this.props.match.params
const { coords } = this.props
const { results, fetching } = this.props.stop
const stops = results.filter(ele => ele.stop_id === stopId)
const { latlon } = one
return (
<StyledStopForm>
<h1 className="title">{one.stop_id || 'New Stop'}&nbsp;&nbsp;</h1>
<div className="content">
<HorizontalInput
label="Stop ID"
type="text"
fieldName="stop_id"
value={one.stop_id || ''}
label="Stop ID"
type="text"
fieldName="stop_id"
value={one.stop_id || ''}
handleChange={this.handleChange} />
<HorizontalInput
@ -99,6 +155,12 @@ class StopForm extends Component {
value={one.name || ''}
handleChange={this.handleChange} />
<p><b>Lat, Lon</b>
{coords && <a onClick={this.setToCurrentLocation.bind(this)}> -> current location</a>}
<br />
{latlon[0] && latlon[0].toFixed(4)}, {latlon[1] && latlon[1].toFixed(4)}
</p>
<HorizontalInput
label="Stop code"
type="text"
@ -107,45 +169,45 @@ class StopForm extends Component {
handleChange={this.handleChange} />
<HorizontalInput
label="Description"
type="text"
fieldName="stop_desc"
value={one.stop_desc || ''}
handleChange={this.handleChange} />
label="Description"
type="text"
fieldName="stop_desc"
value={one.stop_desc || ''}
handleChange={this.handleChange} />
<HorizontalInput
label="Zone ID"
type="text"
fieldName="zone_id"
value={one.zone_id || ''}
handleChange={this.handleChange} />
label="Zone ID"
type="text"
fieldName="zone_id"
value={one.zone_id || ''}
handleChange={this.handleChange} />
<HorizontalInput
label="Location Type"
type="text"
fieldName="location_type"
value={one.location_type || ''}
handleChange={this.handleChange} />
label="Location Type"
type="text"
fieldName="location_type"
value={one.location_type || ''}
handleChange={this.handleChange} />
<HorizontalInput
label="Timezone"
type="text"
fieldName="stop_timezone"
value={one.stop_timezone || ''}
handleChange={this.handleChange} />
label="Timezone"
type="text"
fieldName="stop_timezone"
value={one.stop_timezone || ''}
handleChange={this.handleChange} />
<HorizontalInput
label="Wheelchair"
type="text"
fieldName="wheelchair_boarding"
value={one.wheelchair_boarding || ''}
handleChange={this.handleChange} />
label="Wheelchair"
type="text"
fieldName="wheelchair_boarding"
value={one.wheelchair_boarding || ''}
handleChange={this.handleChange} />
{/* <HorizontalInput
label="Parent Station"
type="text"
fieldName="parent_station"
defaultValue="0"
value="0"
value={one.parent_station || ''}
handleChange={this.handleChange} /> */}
</div>
@ -176,12 +238,13 @@ class StopForm extends Component {
render () {
const one = this.state
const { fetching } = this.props.stop
// redirect to view page if no data
const { stopId } = this.props.match.params
// redirect to agency list if submitted
if (one.justSubmit === true && !fetching) {
return <Redirect to={`/map/stop`} />
}
if (this.state.back2list === true) {
return <Redirect to={`/map/stop`} />
}
return this.renderForm()
}
@ -190,6 +253,8 @@ class StopForm extends Component {
const mapStateToProps = state => ({
stop: state.stop,
draggableMarkerLatlon: state.geo.draggableMarkerLatlon,
coords: state.geo.coords,
})
const connectStopForm = connect(

14
src/components/StopList.js

@ -19,6 +19,10 @@ class StopList extends Component {
store.dispatch(getStop())
}
handleStopSearch(evt) {
store.dispatch(getStop(`search=${evt.target.value}`))
}
render() {
const { results } = this.props.stop
return (
@ -34,7 +38,11 @@ class StopList extends Component {
</p>
<div className="panel-block">
<p className="control has-icons-left">
<input className="input is-small" type="text" placeholder="search" />
<input
className="input is-small"
type="text"
placeholder="search"
onKeyPress={(e) => { (e.key === 'Enter') && this.handleStopSearch(e) }} />
<span className="icon is-small is-left">
<i className="fas fa-search" aria-hidden="true"></i>
</span>
@ -45,8 +53,8 @@ class StopList extends Component {
<Link to={`/map/stop/${ele.stop_id}`}>{ele.stop_id} - {ele.name}</Link>
</span>
))}
{this.props.stop.length === 0 && <span key="empty-farerule" className="panel-block">
No stop yet</span>}
{results.length === 0 && <span key="empty-farerule" className="panel-block">
No stop found</span>}
</nav>
</StyledBox>
)

186
src/components/StopTimeForm.js

@ -0,0 +1,186 @@
import React, { Component } from 'react'
import styled from 'styled-components'
import Select from 'react-select'
import Input from './parts/Input'
import OurSelect from './parts/Select'
import {
updateStopTime, createStopTime, deleteStopTime
} from '../actions/stoptime'
import store from '../store'
import { API_URL } from '../constants/Api'
const StyleBox = styled.div`
padding: 5px;
background: white;
margin-bottom: 1rem;
`
class StopTimeForm extends Component {
state = {
editMode: false,
trip: null,
id: null,
}
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.handleDelete = this.handleDelete.bind(this)
}
handleChange(evt) {
let updated = {}
updated[evt.target.name] = evt.target.value
this.setState(updated)
}
handleSubmit() {
const { id } = this.state
let body = {...this.state }
delete body.id
if (id !== null) {
store.dispatch(updateStopTime(id, body))
} else {
store.dispatch(createStopTime(body))
}
this.props.toggleEditMode()
}
handleDelete() {
const { id } = this.state
store.dispatch(deleteStopTime(id))
this.props.toggleEditMode()
}
static getDerivedStateFromProps(props, state) {
if (props.item && props.item.id !== null && state.id === null) {
return props.item
} else if (props.trip !== undefined) {
return { trip: props.trip }
}
return null
}
getStops = (inputValue) => {
if (!inputValue) {
return Promise.resolve({ options: [] });
}
return fetch(`${API_URL}/stop/?search=${inputValue}`)
.then((response) => response.json())
.then((json) => ({ options: json.results }))
}
render() {
const item = this.state
return (
<StyleBox>
<Input
label="Sequence"
type="text"
fieldName="sequence"
value={item.sequence || ''}
handleChange={this.handleChange} />
<div className="field">
<label className="label">Stop</label>
<Select.Async
value={this.state.stop}
labelKey="stop_id"
valueKey="id"
filterOptionunion={false}
loadOptions={this.getStops}
onChange={(data) => {
let evt = {
target: {
name: 'stop',
value: data,
}}
return this.handleChange(evt)
}}
/>
</div>
<Input
label="Arrival"
type="text"
fieldName="arrival"
value={item.arrival || ''}
handleChange={this.handleChange} />
<Input
label="Departure"
type="text"
fieldName="departure"
value={item.departure || ''}
handleChange={this.handleChange} />
<Input
label="Stop Headsign"
type="text"
fieldName="stop_headsign"
value={item.stop_headsign || ''}
handleChange={this.handleChange} />
<OurSelect
label="Pickup Type"
type="text"
fieldName="pickup_type"
value={item.pickup_type || '0'}
handleChange={this.handleChange}
choices={[
{ value: '0', label: 'Regularly scheduled pickup' },
{ value: '1', label: 'No pickup' },
{ value: '2', label: 'Arrange pickup' },
{ value: '3', label: 'Contact driver' },
]} />
<OurSelect
label="Drop off Type"
type="text"
fieldName="pickup_type"
value={item.pickup_type || '0'}
handleChange={this.handleChange}
choices={[
{ value: '0', label: 'Regularly scheduled dropoff' },
{ value: '1', label: 'No drop off' },
{ value: '2', label: 'Arrange drop off' },
{ value: '3', label: 'Contact driver' },
]} />
<OurSelect
label="Timepoint"
type="text"
fieldName="timepoint"
value={item.timepoint || '1'}
handleChange={this.handleChange}
choices={[
{ value: '0', label: 'times are considered approximate.' },
{ value: '1', label: 'times are considered exact.' },
]} />
<div className="field is-grouped">
<div className="control">
<button className="button is-link"
onClick={this.handleSubmit}
disabled={false}>
Save</button>
</div>
{item.id !== null && <div className="control">
<button className="button is-danger"
onClick={this.handleDelete}
disabled={false}>
DELETE</button>
</div>}
{this.props.toggleEditMode &&
<div className="control">
<a onClick={() => this.props.toggleEditMode()} className="button is-text">Cancel</a>
</div>}
</div>
</StyleBox>
)
}
}
export default StopTimeForm

69
src/components/StopTimeOne.js

@ -0,0 +1,69 @@
import React, { Component } from 'react'
import styled from 'styled-components'
import StopTimeForm from './StopTimeForm'
const StyledRow = styled.div`
padding-top: 5px;
padding-bottom: 5px;
background: white;
margin-bottom: 1rem;
`
class StopTimeOne extends Component {
state = {
editMode: false
}
constructor(props) {
super(props)
this.toggleEditMode = this.toggleEditMode.bind(this)
}
toggleEditMode() {
this.setState({editMode: !this.state.editMode})
}
renderReadOnly = (item) => (
<StyledRow className="level panel" key={`st-item-${item.id}`}>
<div className="level-item has-text-centered">
<div>
<p className="heading">
Stop <a onClick={() => this.toggleEditMode()}>EDIT</a>
</p>
<p className="title">{item.sequence}. {item.stop.stop_id}</p>
</div>
</div>
<div className="level-item has-text-centered">
<div>
<p className="heading">Arrival</p>
<p className="title">{item.arrival}</p>
</div>
</div>
<div className="level-item has-text-centered">
<div>
<p className="heading">Departure</p>
<p className="title">{item.departure}</p>
</div>
</div>
<div className="level-item has-text-centered">
<div>
<p className="heading">Stop headsign</p>
<p className="title">{item.stop_headsign || '-'}</p>
</div>
</div>
</StyledRow>
)
render() {
const { item } = this.props
if (this.state.editMode)
return <StopTimeForm item={item}
toggleEditMode={this.toggleEditMode} />
return this.renderReadOnly(item)
}
}
export default StopTimeOne

251
src/components/TripForm.js

@ -0,0 +1,251 @@
import React, { Component } from 'react'
import styled from 'styled-components'
import { connect } from 'react-redux'
import { Redirect, Link } from 'react-router-dom'
import StopTimeOne from './StopTimeOne'
import StopTimeForm from './StopTimeForm'
import HorizontalInput from './parts/HorizontalInput'
import HorizontalSelect from './parts/HorizontalSelect'
import { updateTrip, createTrip, deleteTrip } from '../actions/trip'
import { getCalendar } from '../actions/calendar'
import { getRoute } from '../actions'
import { getStopTime } from '../actions/stoptime'
import store from '../store'
const StyledTripForm = styled.div`
padding: 1rem;
background: #fafafa;
`
const StyleBox = styled.div`
padding: 5px;
background: white;
margin-bottom: 1rem;
`
class TripForm extends Component {
state = {
id: null,
trip_id: "",
email: "",
fare_url: "",
lang: "",
name: "",
phone: "",
timezone: "",
url: "",
newStopTime: false,
}
constructor() {
super()
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.handleDelete = this.handleDelete.bind(this)
this.renderForm = this.renderForm.bind(this)
}
toggleNewStopTime = () => {
this.setState({ newStopTime: !this.state.newStopTime })
}
handleChange(evt) {
let updated = {}
updated[evt.target.name] = evt.target.value
this.setState(updated)
}
handleSubmit() {
const { id } = this.state
let body = {...this.state }
delete body.id
delete body.newStopTime
if (id !== null) {
store.dispatch(updateTrip(id, body))
} else {
store.dispatch(createTrip(body))
}
this.setState({justSubmit: true})
}
handleDelete() {
const { id } = this.state
store.dispatch(deleteTrip(id))
this.setState({justSubmit: true})
}
static getDerivedStateFromProps(props, state) {
const { agencyId, routeId, tripId } = props.match.params
if (!props.calendar.fetching && props.calendar.count === 0)
store.dispatch(getCalendar())
if (tripId !== undefined && state.id === null) {
const { route } = props
const tRoute = route.results.filter(ele => ele.route_id === routeId)
if (tRoute.length > 0) {
const trips = tRoute[0].trip_set.filter(ele => ele.trip_id === tripId)
if (trips.length > 0) {
store.dispatch(getStopTime(`trip=${trips[0].id}&limit=100`))
return trips[0]
}
} else {
store.dispatch(getRoute(`agency=${agencyId}`))
}
}
return null
}
renderForm() {
const one = this.state
const { agencyId, routeId } = this.props.match.params
const { fetching } = this.props.route
const { calendar } = this.props
return (
<StyledTripForm>
<h1 className="title">{one.name}&nbsp;&nbsp;</h1>
<div className="content">
<HorizontalInput
label="Trip ID"
type="text"
fieldName="trip_id"
value={one.trip_id || ''}
handleChange={this.handleChange} />
<HorizontalInput
label="Headsign"
type="text"
fieldName="trip_headsign"
value={one.trip_headsign || ''}
handleChange={this.handleChange} />
<HorizontalInput
label="Name"
type="text"
fieldName="short_name"
value={one.short_name || ''}
handleChange={this.handleChange} />
{!calendar.fetching && <HorizontalSelect
label="Service"
type="text"
fieldName="service"
value={one.service ? one.service.id : ''}
handleChange={this.handleChange}
choices={calendar.results.map(ele => ({ value: ele.id, label: ele.service_id }))} />}
<HorizontalSelect
label="Direction ID"
type="text"
fieldName="direction_id"
value={one.direction_id || ''}
handleChange={this.handleChange}
choices={[
{ value: '0', label: 'outbound' },
{ value: '1', label: 'inbound' },
]} />
<HorizontalInput
label="Block ID"
type="text"
fieldName="block_id"
value={one.block_id || ''}
handleChange={this.handleChange} />
<HorizontalSelect
label="Wheelchair Accessible"
type="text"
fieldName="wheelchair_accessible"
value={one.wheelchair_accessible || '0'}
handleChange={this.handleChange}
choices={[
{ value: '0', label: 'No information' },
{ value: '1', label: 'Yes' },
{ value: '2', label: 'Not possible' },
]} />
<HorizontalSelect
label="Bike Allowance"
type="text"
fieldName="bike_allowed"
value={one.bike_allowed || '0'}
handleChange={this.handleChange}
choices={[
{ value: '0', label: 'No information' },
{ value: '1', label: 'Yes' },
{ value: '2', label: 'Not possible' },
]} />
</div>
<div className="field is-grouped">
<div className="control">
<button className="button is-link"
onClick={this.handleSubmit}
disabled={fetching}>
Save</button>
</div>
{one.id !== null && <div className="control">
<button className="button is-danger"
onClick={this.handleDelete}
disabled={fetching}>
DELETE</button>
</div>}
<div className="control">
<Link to={`/map/${agencyId}/route/${routeId}/trip`} className="button is-text">Cancel</Link>
</div>
</div>
</StyledTripForm>
)
}
render () {
const one = this.state
const { fetching } = this.props.trip
// redirect to view page if no data
const { agencyId, routeId, tripId } = this.props.match.params
// this is a create form
// if (tripId === undefined) {
// if (one.justSubmit === true && !fetching) {
// return <Redirect to={`/trip/`} />
// }
// return this.renderForm()
// }
// if (one.id === null && tripId.length > 0)
// return <Redirect to={`/trip/`} />
// redirect to trip list if submitted
if (one.justSubmit === true && !fetching) {
return <Redirect to={`/map/${agencyId}/route/${routeId}/trip`} />
}
return (
<div className="columns">
<div className="column is-half">
{this.renderForm()}
</div>
<div className="column is-half">
<StyleBox>
{!this.state.newStopTime && <button className="button is-outlined" onClick={this.toggleNewStopTime}>new StopTime</button>}
{this.state.newStopTime && <StopTimeForm {...this.props}
trip={this.state.id}
toggleEditMode={this.toggleNewStopTime} /> }
</StyleBox>
{this.props.stoptime.results.map(
ele => <StopTimeOne key={`stb-${ele.id}`} item={ele} />)}
</div>
</div>
)
}
}
const mapStateToProps = state => ({
route: state.route,
trip: state.trip,
calendar: state.calendar,
stoptime: state.stoptime,
})
const connectTripForm = connect(
mapStateToProps,
)(TripForm)
export default connectTripForm

24
src/components/parts/Date.js

@ -0,0 +1,24 @@
import React from 'react'
import Flatpickr from 'react-flatpickr'
import 'flatpickr/dist/themes/airbnb.css'
const Date = (props) => (
<div className="field">
<label className="label">{props.label}</label>
<p className="control">
<Flatpickr
className="input"
defaultValue={props.value}
onChange={(date) => {
let evt = {
target: {
name: props.fieldName,
value: date[0].toISOString('YYYY-MM-DD').slice(0, 10)
}}
return props.handleChange(evt)}} />
</p>
</div>
)
export default Date

36
src/components/parts/HorizontalSelect.js

@ -0,0 +1,36 @@
import React from 'react'
import Select from 'react-select'
import 'react-select/dist/react-select.css'
const HorizontalInput = (props) => (
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">{props.label}</label>
</div>
<div className="field-body">
<div className="field">
<Select
className="control"
name={props.fieldName}
value={props.value}
onChange={(data) => {
console.log('change', data)
let evt = {
target: {
name: props.fieldName,
value: data ? data.value : '',
}}
return props.handleChange(evt)
}}
options={props.choices || [
{ value: 'one', label: 'change choices with' },
{ value: 'two', label: 'props.choices' },
]}
/>
</div>
</div>
</div>
)
export default HorizontalInput

15
src/components/parts/Input.js

@ -0,0 +1,15 @@
import React from 'react'
const Input = (props) => (
<div className="field">
<label className="label">{props.label}</label>
<p className="control">
<input className="input" type={props.type || 'text'}
name={props.fieldName}
onChange={props.handleChange}
defaultValue={props.value} />
</p>
</div>
)
export default Input

29
src/components/parts/Select.js

@ -0,0 +1,29 @@
import React from 'react'
import Select from 'react-select'
import 'react-select/dist/react-select.css'
const NormalSelect = (props) => (
<div className="field">
<label className="label">{props.label}</label>
<Select
className="control"
name={props.fieldName}
value={props.value}
onChange={(data) => {
let evt = {
target: {
name: props.fieldName,
value: data ? data.value : '',
}}
return props.handleChange(evt)
}}
options={props.choices || [
{ value: 'one', label: 'change choices with' },
{ value: 'two', label: 'props.choices' },
]}
/>
</div>
)
export default NormalSelect

13
src/constants/ActionTypes.js

@ -10,6 +10,11 @@ export const GEO_MARKER_UPDATE = 'GEO_MARKER_UPDATE'
export const GEO_MAPCENTER_UPDATE = 'GEO_MAPCENTER_UPDATE'
export const GEO_DRAGMARKER_ENABLE = 'GEO_DRAGMARKER_ENABLE'
export const GEO_DRAGMARKER_DISABLE = 'GEO_DRAGMARKER_DISABLE'
export const GEO_DRAGMARKER_CHANGE = 'GEO_DRAGMARKER_CHANGE'
export const REQUEST_LOGIN = 'REQUEST_LOGIN'
export const SUCCESS_LOGIN = 'SUCCESS_LOGIN'
export const FAILED_LOGIN = 'FAILED_LOGIN'
@ -48,6 +53,14 @@ export const STOP_CREATE = 'STOP_CREATE'
export const STOP_DELETE = 'STOP_DELETE'
export const STOP_UPDATE = 'STOP_UPDATE'
export const TRIP_REQUEST = 'TRIP_REQUEST'
export const TRIP_FAILURE = 'TRIP_FAILURE'
export const TRIP_SUCCESS = 'TRIP_SUCCESS'
// below items are SUCCESS for other tasks
export const TRIP_CREATE = 'TRIP_CREATE'
export const TRIP_DELETE = 'TRIP_DELETE'
export const TRIP_UPDATE = 'TRIP_UPDATE'
export const CALENDAR_REQUEST = 'CALENDAR_REQUEST'
export const CALENDAR_FAILURE = 'CALENDAR_FAILURE'
export const CALENDAR_SUCCESS = 'CALENDAR_SUCCESS'

34
src/container/Geo.js

@ -9,7 +9,10 @@ import {
import L from 'leaflet'
import { loggedIn } from '../reducers/auth'
import { geoLocationFailed, geoLocationUpdate, getAgency } from '../actions'
import {
geoLocationFailed, geoLocationUpdate, getAgency,
draggableMarkerUpdate
} from '../actions'
import FloatPane from '../components/FloatPane'
import store from '../store'
@ -31,7 +34,15 @@ const stopIcon = L.divIcon({
className: 'divIcon',
html: '<div><img src="//static.traffy.xyz/icon/bus-stop-48.png" width="24" height="24"></div>',
popupAnchor: [2, -22],
iconAnchor: [12, 22]
iconAnchor: [12, 23]
})
const dragableIcon = L.icon({
iconUrl: '//static.10ninox.com/map/pink-marker-icon.png',
iconRetinaUrl: '//static.10ninox.com/map/pink-marker-icon-2x.png',
iconSize: [25, 41],
iconAnchor: [12, 40],
popupAnchor: [2, -22],
})
@ -113,6 +124,7 @@ class Geo extends Component {
"type": "Feature",
"properties": {
"popupContent": `${ele.stop.name} - ${ele.arrival}`,
"stop_id": `${ele.stop.stop_id}`,
"icon": stopIcon,
},
"geometry": ele.stop.geojson,
@ -125,7 +137,7 @@ class Geo extends Component {
return (
L.marker(latlon, {
icon: stopIcon
}).bindPopup(`${feat.properties.popupContent}`)
}).bindPopup(`<a href="/#/map/stop/${feat.properties.stop_id}">${feat.properties.popupContent}</a>`)
)
}}/>
}
@ -145,9 +157,9 @@ class Geo extends Component {
className='my-location-marker' />
) : null
/*
draggable={true}
onDragend={(e) => { console.log(e)}}
*/
draggable={true}
onDragend={(e) => { console.log(e)}}
*/
const centerMarker = geo.coords ? (
<CircleMarker
center={this.state.mapCenter}
@ -160,6 +172,15 @@ class Geo extends Component {
opacity={0.75}
className='my-location-marker' />
) : null
const draggableMarker = geo.draggableMarker ? (
<Marker
icon={dragableIcon}
draggable={true}
onDragend={(e) => { store.dispatch(draggableMarkerUpdate(e.target._latlng.lat, e.target._latlng.lng)) }}
position={geo.draggableMarkerLatlon}
/>
) : null
// const mapCenter = geo.coords
// ? [geo.coords.latitude, geo.coords.longitude]
// : [13.84626739, 100.538]
@ -185,6 +206,7 @@ class Geo extends Component {
<ZoomControl position="topright" />
{myLocationMarker}
{centerMarker}
{draggableMarker}
{this.renderGeoJSON()}
{this.renderStopTime()}
</Map>

2
src/container/Main.js

@ -11,6 +11,7 @@ import CalendarList from '../components/CalendarList'
import AgencyList from '../components/AgencyList'
import AgencyItem from '../components/AgencyItem'
import AgencyForm from '../components/AgencyForm'
import TripForm from '../components/TripForm'
import { LOGIN_PATH } from '../constants/path'
import store from '../store'
@ -38,6 +39,7 @@ class Main extends Component {
<Nav loggedIn={loggedIn} />
<div className="container is-widescreen">
<Switch>
<Route exact path={`${match.url}trip/:agencyId/:routeId/:tripId`} component={TripForm} />
<Route exact path={`${match.url}agency/new`} component={AgencyForm} />
<Route exact path={`${match.url}agency/:agencyId`} component={AgencyItem} />
<Route path={`${match.url}agency/:agencyId/edit`} component={AgencyForm} />

19
src/reducers/geo.js

@ -3,6 +3,7 @@ import {
GEO_MARKER_ADD, GEO_MARKER_RESET, GEO_MARKER_UPDATE,
GEO_POLYGON_ADD, GEO_POLYGON_RESET, GEO_POLYGON_UPDATE,
GEO_MAPCENTER_UPDATE,
GEO_DRAGMARKER_CHANGE, GEO_DRAGMARKER_DISABLE, GEO_DRAGMARKER_ENABLE,
} from '../constants/ActionTypes'
const initialState = {
@ -12,10 +13,28 @@ const initialState = {
polygons: [],
markers: [],
mapCenter: [13.84626739, 100.538],
draggableMarker: false,
draggableMarkerLatlon: [13.8462745, 100.5382592],
}
const geo = (state = initialState, action) => {
switch (action.type) {
case GEO_DRAGMARKER_ENABLE:
return {
...state,
draggableMarker: true,
draggableMarkerLatlon: action.payload,
}
case GEO_DRAGMARKER_DISABLE:
return {
...state,
draggableMarker: false,
}
case GEO_DRAGMARKER_CHANGE:
return {
...state,
draggableMarkerLatlon: action.payload,
}
case GEO_MAPCENTER_UPDATE:
return {
...state,

2
src/reducers/index.js

@ -7,6 +7,7 @@ import fareattr from './fareattr'
import calendar from './calendar'
import stoptime from './stoptime'
import stop from './stop'
import trip from './trip'
export default combineReducers({
auth,
@ -17,4 +18,5 @@ export default combineReducers({
fareattr,
calendar,
stoptime,
trip,
})

2
src/reducers/route.js

@ -17,7 +17,7 @@ const route = (state = routeInitState, action) => {
return {
...state,
fetching: true,
query: action.meta !== undefined ? action.meta.query : '',
query: action.meta !== undefined ? action.meta.query : state.query,
}
case ROUTE_SUCCESS:
const { count, next, prev, results } = action.payload

3
src/reducers/stoptime.js

@ -14,11 +14,10 @@ const stoptimeInitState = {
const stoptime = (state = stoptimeInitState, action) => {
switch (action.type) {
case STOPTIME_REQUEST:
const { query } = action.meta
return {
...state,
fetching: true,
query,
query: action.meta !== undefined ? action.meta.query : state.query,
}
case STOPTIME_SUCCESS:
const { count, next, prev, results } = action.payload

76
src/reducers/trip.js

@ -0,0 +1,76 @@
import {
TRIP_CREATE, TRIP_DELETE, TRIP_UPDATE,
TRIP_REQUEST, TRIP_SUCCESS, TRIP_FAILURE,
} from '../constants/ActionTypes'
const tripInitState = {
results: [],
next: null,
count: 0,
fetching: false,
}
const trip = (state = tripInitState, action) => {
switch(action.type) {
case TRIP_REQUEST:
return {
...state,
fetching: true,
}
case TRIP_SUCCESS:
const { count, next, prev, results } = action.payload
return {
...state,
fetching: false,
count,
next,
results: [
...(prev ? state.results : []),
...results,
]
}
case TRIP_UPDATE:
const { id } = action.payload
const oldResults = state.results
const targetInd = oldResults.findIndex(ele => ele.id === id)
return {
...state,
fetching: false,
results: [
...oldResults.slice(0, targetInd),
action.payload,
...oldResults.slice(targetInd + 1)
]
}
case TRIP_CREATE:
return {
...state,
fetching: false,
count: state.count + 1,
results: [
...state.results,
action.payload,
]
}
case TRIP_DELETE:
const deleteInd = state.results.findIndex(ele => ele.id === action.meta.id)
return {
...state,
count: state.count - 1,
fetching: false,
results: [
...state.results.slice(0, deleteInd),
...state.results.slice(deleteInd + 1)
]
}
case TRIP_FAILURE:
return {
...state,
fetching: false,
}
default:
return state;
}
}
export default trip
Loading…
Cancel
Save