Browse Source

Show linestring & marker from shapes & stops

Route.shapes & StopTime.stop
master
sipp11 7 years ago
parent
commit
5cff8428ea
  1. 5
      .vscode/settings.json
  2. 7
      src/actions/index.js
  3. 64
      src/actions/route.js
  4. 64
      src/actions/stop.js
  5. 2
      src/actions/stoptime.js
  6. 4
      src/components/AgencyForm.js
  7. 14
      src/components/AgencyItem.js
  8. 8
      src/components/Breadcrumb.js
  9. 38
      src/components/FloatPane.js
  10. 16
      src/components/RouteDetail.js
  11. 208
      src/components/RouteForm.js
  12. 2
      src/components/RouteList.js
  13. 198
      src/components/StopForm.js
  14. 67
      src/components/StopList.js
  15. 9
      src/constants/ActionTypes.js
  16. 82
      src/container/Geo.js
  17. 38
      src/reducers/geo.js
  18. 2
      src/reducers/index.js
  19. 3
      src/reducers/route.js
  20. 76
      src/reducers/stop.js

5
.vscode/settings.json vendored

@ -0,0 +1,5 @@
{
"todo-tree.flat": true,
"todo-tree.grouped": false,
"todo-tree.expanded": true
}

7
src/actions/index.js

@ -91,6 +91,13 @@ export const polygonReset = () => {
} }
} }
export const mapCenterUpdate = (lat, lon) => {
return {
type: types.GEO_MAPCENTER_UPDATE,
payload: [lat, lon]
}
}
// AGENCY // AGENCY
export const getAgency = () => ({ export const getAgency = () => ({

64
src/actions/route.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 getRoute = (query) => ({
[RSAA]: {
endpoint: `${API_URL}/route/?${query || ''}`,
method: 'GET',
headers: RSAAHeaders,
bailout: (state) => state.route.fetching,
types: [
types.ROUTE_REQUEST,
types.ROUTE_SUCCESS,
types.ROUTE_FAILURE,
]
}
})
export const updateRoute = (id, body) => ({
[RSAA]: {
endpoint: `${API_URL}/route/${id}/`,
body: JSON.stringify(body),
method: 'PATCH',
headers: RSAAHeaders,
types: [
types.ROUTE_REQUEST,
types.ROUTE_UPDATE,
types.ROUTE_FAILURE,
]
}
})
export const createRoute = (body) => ({
[RSAA]: {
endpoint: `${API_URL}/route/`,
body: JSON.stringify(body),
method: 'POST',
headers: RSAAHeaders,
types: [
types.ROUTE_REQUEST,
types.ROUTE_CREATE,
types.ROUTE_FAILURE,
]
}
})
export const deleteRoute = (id) => ({
[RSAA]: {
endpoint: `${API_URL}/route/${id}/`,
method: 'DELETE',
headers: RSAAHeaders,
types: [
types.ROUTE_REQUEST,
{
type: types.ROUTE_DELETE,
meta: { id }
},
types.ROUTE_FAILURE,
]
}
})

64
src/actions/stop.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 getStop = (query) => ({
[RSAA]: {
endpoint: `${API_URL}/stop/?${query || ''}`,
method: 'GET',
headers: RSAAHeaders,
bailout: (state) => state.stop.fetching,
types: [
types.STOP_REQUEST,
types.STOP_SUCCESS,
types.STOP_FAILURE,
]
}
})
export const updateStop = (id, body) => ({
[RSAA]: {
endpoint: `${API_URL}/stop/${id}/`,
body: JSON.stringify(body),
method: 'PATCH',
headers: RSAAHeaders,
types: [
types.STOP_REQUEST,
types.STOP_UPDATE,
types.STOP_FAILURE,
]
}
})
export const createStop = (body) => ({
[RSAA]: {
endpoint: `${API_URL}/stop/`,
body: JSON.stringify(body),
method: 'POST',
headers: RSAAHeaders,
types: [
types.STOP_REQUEST,
types.STOP_CREATE,
types.STOP_FAILURE,
]
}
})
export const deleteStop = (id) => ({
[RSAA]: {
endpoint: `${API_URL}/stop/${id}/`,
method: 'DELETE',
headers: RSAAHeaders,
types: [
types.STOP_REQUEST,
{
type: types.STOP_DELETE,
meta: { id }
},
types.STOP_FAILURE,
]
}
})

2
src/actions/stoptime.js

@ -10,7 +10,7 @@ export const getStopTime = (query) => ({
endpoint: `${API_URL}/stoptime/?${query || ''}`, endpoint: `${API_URL}/stoptime/?${query || ''}`,
method: 'GET', method: 'GET',
headers: RSAAHeaders, headers: RSAAHeaders,
bailout: (state) => state.stoptime.fetching, bailout: (state) => state.stoptime.fetching || state.stoptime.query === query,
types: [ types: [
{ {
type: types.STOPTIME_REQUEST, type: types.STOPTIME_REQUEST,

4
src/components/AgencyForm.js

@ -184,7 +184,7 @@ const mapStateToProps = state => ({
agency: state.agency agency: state.agency
}) })
const connectAgencyList = connect( const connectAgencyForm = connect(
mapStateToProps, mapStateToProps,
)(AgencyForm) )(AgencyForm)
export default connectAgencyList export default connectAgencyForm

14
src/components/AgencyItem.js

@ -1,10 +1,11 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { Link, Redirect, Route } from 'react-router-dom' import { Link, Redirect, Route, Switch} from 'react-router-dom'
import Spinner from './Spinner' import Spinner from './Spinner'
import RouteList from './RouteList' import RouteList from './RouteList'
import RouteForm from './RouteForm'
import FareAttrList from './FareAttrList' import FareAttrList from './FareAttrList'
const StyledAgencyItem = styled.div` const StyledAgencyItem = styled.div`
@ -62,10 +63,13 @@ class AgencyItem extends Component {
<li><Link to={`/agency/${one.agency_id}/edit`}>Edit</Link></li> <li><Link to={`/agency/${one.agency_id}/edit`}>Edit</Link></li>
</ul> </ul>
</div> </div>
<Route path={`/agency/:agencyId/route/:routeId`} component={RouteList} /> <Switch>
<Route path={`/agency/:agencyId/route`} component={RouteList} /> <Route exact path={`/agency/:agencyId/route/new`} component={RouteForm} />
<Route path={`/agency/:agencyId/fare-attr/:fareAttrId`} component={FareAttrList} /> <Route path={`/agency/:agencyId/route/:routeId`} component={RouteList} />
<Route path={`/agency/:agencyId/fare-attr`} component={FareAttrList} /> <Route path={`/agency/:agencyId/route`} component={RouteList} />
<Route path={`/agency/:agencyId/fare-attr/:fareAttrId`} component={FareAttrList} />
<Route path={`/agency/:agencyId/fare-attr`} component={FareAttrList} />
</Switch>
{agencyChild === undefined && <p>Pick options on the tab</p>} {agencyChild === undefined && <p>Pick options on the tab</p>}
</div> </div>
</div> </div>

8
src/components/Breadcrumb.js

@ -10,15 +10,19 @@ margin-bottom: 5px !important;
const Breadcrumb = (props) => ( const Breadcrumb = (props) => (
<StyledBreadcrumb className="breadcrumb has-succeeds-separator" aria-label="breadcrumbs"> <StyledBreadcrumb className="breadcrumb" aria-label="breadcrumbs">
<ul> <ul>
<li><Link to='/map'>ALL</Link></li> <li><Link to='/map/stop'>Stop</Link></li>
<li><Link to='/map'>Agency</Link></li>
{props.agencyId && <li className={`${!props.routeId && 'is-active'}`}> {props.agencyId && <li className={`${!props.routeId && 'is-active'}`}>
<Link to={`/map/${props.agencyId}`} aria-current="page"> <Link to={`/map/${props.agencyId}`} aria-current="page">
{props.agencyId}</Link></li>} {props.agencyId}</Link></li>}
{props.routeId && <li className={`${props.routeId && 'is-active'}`}> {props.routeId && <li className={`${props.routeId && 'is-active'}`}>
<Link to={`/map/${props.agencyId}/route/${props.routeId}`} aria-current="page"> <Link to={`/map/${props.agencyId}/route/${props.routeId}`} aria-current="page">
{props.routeId}</Link></li>} {props.routeId}</Link></li>}
{props.stopId && <li className={`${props.stopId && 'is-active'}`}>
<Link to={`/map/stop/${props.stopId}`} aria-current="page">
{props.stopId}</Link></li>}
</ul> </ul>
</StyledBreadcrumb> </StyledBreadcrumb>
) )

38
src/components/FloatPane.js

@ -1,11 +1,14 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom' import { Link, Route, Switch } from 'react-router-dom'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import styled from 'styled-components' import styled from 'styled-components'
import { logout } from '../utils/Auth' import { logout } from '../utils/Auth'
import RouteList from './RouteList' import RouteList from './RouteList'
import RouteDetail from './RouteDetail' import RouteDetail from './RouteDetail'
import StopList from './StopList'
import StopForm from './StopForm'
import RouteForm from './RouteForm'
import Breadcrumb from './Breadcrumb' import Breadcrumb from './Breadcrumb'
import { polygonReset } from '../actions' import { polygonReset } from '../actions'
import store from '../store' import store from '../store'
@ -61,6 +64,11 @@ const SimpleAgencyList = (props) => (
<p className="panel-heading"> <p className="panel-heading">
Agency Agency
</p> </p>
<p className="panel-tabs">
<Link className="link is-info" to={`/agency/new`}>
<i className="fas fa-plus" /> New agency
</Link>
</p>
{props.agencies.map(ele => ( {props.agencies.map(ele => (
<Link key={ele.agency_id} className="panel-block" to={`/map/${ele.agency_id}`}> <Link key={ele.agency_id} className="panel-block" to={`/map/${ele.agency_id}`}>
{ele.name} {ele.name}
@ -133,15 +141,29 @@ class FloatPane extends Component {
{this.renderTopLevel(loggedIn)} {this.renderTopLevel(loggedIn)}
<StyledScrollY> <StyledScrollY>
<Breadcrumb {...this.state.breadcrumb} /> <Breadcrumb {...this.state.breadcrumb} />
<Route exact path={`/map/:agencyId/route/:routeId/:routeParams?`} render={(props) => ( <Switch>
<RouteDetail {...props} <Route exact path={`/map/stop/new`} render={(props) => (
updateBreadcrumb={this.handleChildMatchParams.bind(this)} />)} /> <StopForm {...props}
updateBreadcrumb={this.handleChildMatchParams.bind(this)} />)} />
<Route exact path={`/map/stop/:stopId`} render={(props) => (
<StopForm {...props}
updateBreadcrumb={this.handleChildMatchParams.bind(this)} />)} />
<Route exact path={`/map/stop`} component={StopList} />
<Route exact path={`/map/:agencyId/route/new`} render={(props) => (
<RouteForm {...props}
updateBreadcrumb={this.handleChildMatchParams.bind(this)} />)} />
<Route exact path={`/map/:agencyId/route/:routeId/edit`} render={(props) => (
<RouteForm {...props}
updateBreadcrumb={this.handleChildMatchParams.bind(this)}/>)} />
<Route exact path={`/map/:agencyId/route/:routeId/:routeParams?`} render={(props) => (
<RouteDetail {...props}
updateBreadcrumb={this.handleChildMatchParams.bind(this)} />)} />
<Route exact path={`/map/:agencyId`} render={(props) => (
<RouteList {...props}
updateBreadcrumb={this.handleChildMatchParams.bind(this)} />)} />
</Switch>
<Route exact path={`/map`} render={(props) => ( <Route exact path={`/map`} render={(props) => (
<SimpleAgencyList agencies={results} {...props} />)} /> <SimpleAgencyList agencies={results} {...props} />)} />
<Route exact path={`/map/stop`} component={RouteList} />
<Route exact path={`/map/:agencyId`} render={(props) => (
<RouteList {...props}
updateBreadcrumb={this.handleChildMatchParams.bind(this)} />)} />
</StyledScrollY> </StyledScrollY>
</StyledFloatPane> </StyledFloatPane>

16
src/components/RouteDetail.js

@ -1,6 +1,6 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { Link, Route } from 'react-router-dom' import { Link, Route, Redirect } from 'react-router-dom'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import Spinner from './Spinner' import Spinner from './Spinner'
@ -8,12 +8,6 @@ import { getRoute, polygonUpdate } from '../actions'
import { getStopTime } from '../actions/stoptime' import { getStopTime } from '../actions/stoptime'
import store from '../store' import store from '../store'
/*
TODO: add shape
TODO: add Route detail
*/
const StyledTripDesc = styled.div` const StyledTripDesc = styled.div`
font-size: 0.87rem; font-size: 0.87rem;
line-height: 0.85rem; line-height: 0.85rem;
@ -58,7 +52,7 @@ const TripList = (props) => (
<div> <div>
{props.trips.map(ele => ( {props.trips.map(ele => (
<span key={ele.id} className="panel-block" <span key={ele.id} className="panel-block"
onClick={() => { store.dispatch(getStopTime(`trip=${ele.id}`))}}> onClick={() => { store.dispatch(getStopTime(`trip=${ele.id}&limit=100`))}}>
{ele.trip_id} {ele.trip_id}
<StyledTripDesc> <StyledTripDesc>
(<b>ID</b> {ele.id})&nbsp; (<b>ID</b> {ele.id})&nbsp;
@ -134,7 +128,10 @@ class RouteDetail extends Component {
const { routeId, agencyId, routeParams } = match.params const { routeId, agencyId, routeParams } = match.params
const tRoute = route.results.filter(ele => ele.route_id === routeId) const tRoute = route.results.filter(ele => ele.route_id === routeId)
if (tRoute.length === 0) { if (tRoute.length === 0) {
return <Spinner show={true} /> if (route.fetching)
return <Spinner show={true} />
return <Redirect to={`/map/${agencyId}/`} />
} }
const item = tRoute[0] const item = tRoute[0]
const baseUrl = `/map/${agencyId}/route/${routeId}` const baseUrl = `/map/${agencyId}/route/${routeId}`
@ -147,6 +144,7 @@ class RouteDetail extends Component {
<Link to={`${baseUrl}`} className={`${!routeParams && 'is-active'}`}>detail</Link> <Link to={`${baseUrl}`} className={`${!routeParams && 'is-active'}`}>detail</Link>
<Link to={`${baseUrl}/trip`} className={`${routeParams === 'trip' && 'is-active'}`}>trip</Link> <Link to={`${baseUrl}/trip`} className={`${routeParams === 'trip' && 'is-active'}`}>trip</Link>
<Link to={`${baseUrl}/fare`} className={`${routeParams === 'fare' && 'is-active'}`}>fare</Link> <Link to={`${baseUrl}/fare`} className={`${routeParams === 'fare' && 'is-active'}`}>fare</Link>
<Link to={`${baseUrl}/edit`}>edit</Link>
</p> </p>
<Route exact path={`${baseUrl}`} render={(props) => ( <Route exact path={`${baseUrl}`} render={(props) => (
<RouteDesc route={item} {...props} />)} /> <RouteDesc route={item} {...props} />)} />

208
src/components/RouteForm.js

@ -0,0 +1,208 @@
import React, { Component } from 'react'
import styled from 'styled-components'
import { connect } from 'react-redux'
import { Redirect, Link } from 'react-router-dom'
import HorizontalInput from './parts/HorizontalInput'
import { updateRoute, createRoute, deleteRoute } from '../actions/route';
import store from '../store'
const StyledRouteForm = styled.div`
padding: 1rem;
background: #fafafa;
`
// TODO: need to deal with shapes
class RouteForm extends Component {
state = {
id: null,
agency: null,
route_id: "",
short_name: "",
long_name: "",
desc: "",
route_type: "2",
route_url: "",
route_color: "",
route_text_color: "",
route_sort_order: "0",
}
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)
}
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.farerule_set
delete body.trip_set
delete body.geojson
if (id !== null) {
store.dispatch(updateRoute(id, body))
} else {
store.dispatch(createRoute(body))
}
this.setState({justSubmit: true})
}
handleDelete() {
const { id } = this.state
store.dispatch(deleteRoute(id))
this.setState({justSubmit: true})
}
componentWillMount() {
const { props } = this
const { agencyId, routeId } = props.match.params
const { results } = props.route
const ones = results.filter(ele => ele.route_id === routeId)
if (ones.length > 0) {
this.setState(ones[0])
props.updateBreadcrumb({ agencyId, routeId })
} else {
const agencies = props.agency.results.filter(ele => ele.agency_id === agencyId)
if (agencies.length > 0) {
let d = {}
d["agency_id"] = agencies[0].id
this.setState(d)
props.updateBreadcrumb({ agencyId, routeId: 'new' })
}
}
}
renderForm() {
const one = this.state
const { agencyId, routeId } = this.props.match.params
const { agency } = this.props
const agencies = agency.results.filter(ele => ele.agency_id === agencyId)
const { fetching } = this.props.route
return (
<StyledRouteForm>
<h1 className="title">{one.route_id || 'New Route'}&nbsp;&nbsp;</h1>
<div className="content">
<HorizontalInput
label="Route ID"
type="text"
fieldName="route_id"
value={one.route_id || ''}
handleChange={this.handleChange} />
<HorizontalInput
label="Short Name"
type="text"
fieldName="short_name"
value={one.short_name || ''}
handleChange={this.handleChange} />
<HorizontalInput
label="Long Name"
type="text"
fieldName="long_name"
value={one.long_name || ''}
handleChange={this.handleChange} />
<HorizontalInput
label="Description"
type="text"
fieldName="desc"
value={one.desc || ''}
handleChange={this.handleChange} />
<HorizontalInput
label="Route Type"
type="text"
fieldName="route_type"
value={one.route_type || ''}
handleChange={this.handleChange} />
<HorizontalInput
label="Route URL"
type="text"
fieldName="route_url"
value={one.route_url || ''}
handleChange={this.handleChange} />
<HorizontalInput
label="Route Color"
type="text"
fieldName="route_color"
value={one.route_color || ''}
handleChange={this.handleChange} />
<HorizontalInput
label="Route Text Color"
type="text"
fieldName="route_text_color"
value={one.route_text_color || ''}
handleChange={this.handleChange} />
<HorizontalInput
label="Route Sort Order"
type="text"
fieldName="route_sort_order"
defaultValue="0"
value={one.route_sort_order || ''}
handleChange={this.handleChange} />
</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">
{agencies[0] &&
<Link to={`/map/${agencies[0].agency_id}/${routeId ? `route/${routeId}` : ''}`}
className="button is-text">Cancel</Link>}
</div>
</div>
</StyledRouteForm>
)
}
render () {
const one = this.state
const { fetching } = this.props.agency
// redirect to view page if no data
const { agencyId } = this.props.match.params
// redirect to agency list if submitted
if (one.justSubmit === true && !fetching) {
return <Redirect to={`/map/${agencyId}/route/${one.route_id || ''}`} />
}
return this.renderForm()
}
}
const mapStateToProps = state => ({
agency: state.agency,
route: state.route,
})
const connectRouteForm = connect(
mapStateToProps,
)(RouteForm)
export default connectRouteForm

2
src/components/RouteList.js

@ -37,7 +37,7 @@ class RouteList extends Component {
return ( return (
<nav className="panel"> <nav className="panel">
<p className="panel-heading"> <p className="panel-heading">
Route Route <Link to={`/map/${agencyId}/route/new`}> new </Link>
</p> </p>
{/*<p className="panel-tabs"> {/*<p className="panel-tabs">
<a className="is-active">all</a> <a className="is-active">all</a>

198
src/components/StopForm.js

@ -0,0 +1,198 @@
import React, { Component } from 'react'
import styled from 'styled-components'
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 store from '../store'
const StyledStopForm = styled.div`
padding: 1rem;
background: #fafafa;
`
// TODO: need to deal with shapes
class StopForm extends Component {
state = {
id: null,
stop_id: null,
name: '',
stop_code: '',
stop_desc: '',
zone_id: '',
location_type: '0',
stop_timezone: 'Asia/Bangkok',
wheelchair_boarding: '0',
// parent_station: null,
}
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)
}
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.geojson
if (id !== null) {
store.dispatch(updateStop(id, body))
} else {
store.dispatch(createStop(body))
}
this.setState({justSubmit: true})
}
handleDelete() {
const { id } = this.state
store.dispatch(deleteStop(id))
this.setState({justSubmit: true})
}
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]))
}
}
renderForm() {
const one = this.state
const { stopId } = this.props.match.params
const { results, fetching } = this.props.stop
const stops = results.filter(ele => ele.stop_id === stopId)
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 || ''}
handleChange={this.handleChange} />
<HorizontalInput
label="Name"
type="text"
fieldName="name"
value={one.name || ''}
handleChange={this.handleChange} />
<HorizontalInput
label="Stop code"
type="text"
fieldName="stop_code"
value={one.stop_code || ''}
handleChange={this.handleChange} />
<HorizontalInput
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} />
<HorizontalInput
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} />
<HorizontalInput
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={one.parent_station || ''}
handleChange={this.handleChange} /> */}
</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">
{results &&
<Link to={`/map/stop`}
className="button is-text">Cancel</Link>}
</div>
</div>
</StyledStopForm>
)
}
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`} />
}
return this.renderForm()
}
}
const mapStateToProps = state => ({
stop: state.stop,
})
const connectStopForm = connect(
mapStateToProps,
)(StopForm)
export default connectStopForm

67
src/components/StopList.js

@ -0,0 +1,67 @@
import React, { Component } from 'react'
import styled from 'styled-components'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import { getStop } from '../actions/stop'
import store from '../store'
const StyledBox = styled.div`
background: #fafafa;
`
class StopList extends Component {
componentWillMount() {
const { count } = this.props.stop
if (count === 0)
store.dispatch(getStop())
}
render() {
const { results } = this.props.stop
return (
<StyledBox>
<nav className="panel">
<p className="panel-heading">
Stop
</p>
<p className="panel-tabs">
<Link className="link is-info" to={`/map/stop/new`}>
<i className="fas fa-plus" /> New stop
</Link>
</p>
<div className="panel-block">
<p className="control has-icons-left">
<input className="input is-small" type="text" placeholder="search" />
<span className="icon is-small is-left">
<i className="fas fa-search" aria-hidden="true"></i>
</span>
</p>
</div>
{results.map(ele => (
<span key={ele.id} className="panel-block">
<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>}
</nav>
</StyledBox>
)
}
}
const mapStateToProps = state => ({
stop: state.stop
})
const connectStopList = connect(
mapStateToProps,
{},
)(StopList)
export default styled(connectStopList)`
color: palevioletred;
font-weight: bold;
`

9
src/constants/ActionTypes.js

@ -8,6 +8,8 @@ export const GEO_MARKER_ADD = 'GEO_MARKER_ADD'
export const GEO_MARKER_RESET = 'GEO_MARKER_RESET' export const GEO_MARKER_RESET = 'GEO_MARKER_RESET'
export const GEO_MARKER_UPDATE = 'GEO_MARKER_UPDATE' export const GEO_MARKER_UPDATE = 'GEO_MARKER_UPDATE'
export const GEO_MAPCENTER_UPDATE = 'GEO_MAPCENTER_UPDATE'
export const REQUEST_LOGIN = 'REQUEST_LOGIN' export const REQUEST_LOGIN = 'REQUEST_LOGIN'
export const SUCCESS_LOGIN = 'SUCCESS_LOGIN' export const SUCCESS_LOGIN = 'SUCCESS_LOGIN'
export const FAILED_LOGIN = 'FAILED_LOGIN' export const FAILED_LOGIN = 'FAILED_LOGIN'
@ -38,6 +40,13 @@ export const ROUTE_CREATE = 'ROUTE_CREATE'
export const ROUTE_DELETE = 'ROUTE_DELETE' export const ROUTE_DELETE = 'ROUTE_DELETE'
export const ROUTE_UPDATE = 'ROUTE_UPDATE' export const ROUTE_UPDATE = 'ROUTE_UPDATE'
export const STOP_REQUEST = 'STOP_REQUEST'
export const STOP_FAILURE = 'STOP_FAILURE'
export const STOP_SUCCESS = 'STOP_SUCCESS'
// below items are SUCCESS for other tasks
export const STOP_CREATE = 'STOP_CREATE'
export const STOP_DELETE = 'STOP_DELETE'
export const STOP_UPDATE = 'STOP_UPDATE'
export const CALENDAR_REQUEST = 'CALENDAR_REQUEST' export const CALENDAR_REQUEST = 'CALENDAR_REQUEST'
export const CALENDAR_FAILURE = 'CALENDAR_FAILURE' export const CALENDAR_FAILURE = 'CALENDAR_FAILURE'

82
src/container/Geo.js

@ -4,9 +4,9 @@ import styled from 'styled-components'
// import { Redirect, Route, Switch } from 'react-router-dom' // import { Redirect, Route, Switch } from 'react-router-dom'
import { import {
Map, TileLayer, CircleMarker, ZoomControl, Map, TileLayer, CircleMarker, ZoomControl,
FeatureGroup, GeoJSON } from 'react-leaflet' FeatureGroup, GeoJSON, Marker } from 'react-leaflet'
// import { EditControl } from 'react-leaflet-draw' // import { EditControl } from 'react-leaflet-draw'
// import L from 'leaflet' import L from 'leaflet'
import { loggedIn } from '../reducers/auth' import { loggedIn } from '../reducers/auth'
import { geoLocationFailed, geoLocationUpdate, getAgency } from '../actions' import { geoLocationFailed, geoLocationUpdate, getAgency } from '../actions'
@ -23,17 +23,31 @@ display: flex;
flex-direction: column; flex-direction: column;
` `
// TODO: filter for existing polygons
// TODO: update mapCenter to recent active route/trip or current location
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]
})
class Geo extends Component { class Geo extends Component {
constructor(props) { constructor(props) {
super(props) super(props)
this.renderGeoJSON = this.renderGeoJSON.bind(this) this.renderGeoJSON = this.renderGeoJSON.bind(this)
this.renderStopTime = this.renderStopTime.bind(this)
} }
componentWillMount() { componentWillMount() {
const { count } = this.props.agency const { count } = this.props.agency
if (count === 0) if (count === 0)
store.dispatch(getAgency()) store.dispatch(getAgency())
this.setState({ mapCenter: this.props.geo.mapCenter })
} }
componentWillUnmount() { componentWillUnmount() {
@ -59,6 +73,14 @@ class Geo extends Component {
) )
} }
componentWillReceiveProps(newProps) {
const omCenter = this.props.geo.mapCenter
const newCenter = newProps.geo.mapCenter
if (omCenter[0] !== newCenter[0] || omCenter[1] !== newCenter[1]) {
this.setState({mapCenter: newCenter})
}
}
renderGeoJSON() { renderGeoJSON() {
const { polygons } = this.props.geo const { polygons } = this.props.geo
const style = { const style = {
@ -68,7 +90,7 @@ class Geo extends Component {
} }
return ( return (
<FeatureGroup> <FeatureGroup>
{polygons && polygons.map(ele => ( {polygons && polygons.filter(ele => ele.geojson).map(ele => (
<GeoJSON <GeoJSON
key={`geojson-${ele.id}`} key={`geojson-${ele.id}`}
data={ele.geojson} data={ele.geojson}
@ -78,6 +100,36 @@ class Geo extends Component {
) )
} }
renderStopTime() {
const { count, results } = this.props.stoptime
if (count === 0)
return
let stopFeatures = {
"type": "FeatureCollection",
"features": []
}
results.forEach(ele => {
stopFeatures.features.push({
"type": "Feature",
"properties": {
"popupContent": `${ele.stop.name} - ${ele.arrival}`,
"icon": stopIcon,
},
"geometry": ele.stop.geojson,
})
})
return <GeoJSON
key={`stoptime-${results[0].id}-${results[count-1].id}`}
data={stopFeatures}
pointToLayer={(feat, latlon) => {
return (
L.marker(latlon, {
icon: stopIcon
}).bindPopup(`${feat.properties.popupContent}`)
)
}}/>
}
render() { render() {
const { loggedIn, geo } = this.props const { loggedIn, geo } = this.props
const myLocationMarker = geo.coords ? ( const myLocationMarker = geo.coords ? (
@ -92,16 +144,33 @@ class Geo extends Component {
opacity={1} opacity={1}
className='my-location-marker' /> className='my-location-marker' />
) : null ) : null
/*
draggable={true}
onDragend={(e) => { console.log(e)}}
*/
const centerMarker = geo.coords ? (
<CircleMarker
center={this.state.mapCenter}
radius={7}
fillColor={'rgb(255, 25, 90)'}
fillOpacity={0.75}
color={'black'}
stroke
weight={2}
opacity={0.75}
className='my-location-marker' />
) : null
// const mapCenter = geo.coords // const mapCenter = geo.coords
// ? [geo.coords.latitude, geo.coords.longitude] // ? [geo.coords.latitude, geo.coords.longitude]
// : [13.84626739, 100.538] // : [13.84626739, 100.538]
const mapCenter = [13.84626739, 100.538]
return ( return (
<FullPageBox> <FullPageBox>
{/* <Nav loggedIn={loggedIn} /> */} {/* <Nav loggedIn={loggedIn} /> */}
<FloatPane loggedIn={loggedIn} {...this.props} /> <FloatPane loggedIn={loggedIn} {...this.props} />
<Map <Map
center={mapCenter} center={this.state.mapCenter}
zoom={13} zoom={13}
length={4} length={4}
zoomControl={false} zoomControl={false}
@ -115,7 +184,9 @@ class Geo extends Component {
/> />
<ZoomControl position="topright" /> <ZoomControl position="topright" />
{myLocationMarker} {myLocationMarker}
{centerMarker}
{this.renderGeoJSON()} {this.renderGeoJSON()}
{this.renderStopTime()}
</Map> </Map>
</FullPageBox> </FullPageBox>
) )
@ -126,6 +197,7 @@ class Geo extends Component {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
loggedIn: loggedIn(state.auth), loggedIn: loggedIn(state.auth),
agency: state.agency, agency: state.agency,
stoptime: state.stoptime,
geo: state.geo, geo: state.geo,
}) })
export default connect( export default connect(

38
src/reducers/geo.js

@ -2,7 +2,7 @@ import {
GEO_LOCATION_SUCCESS, GEO_LOCATION_FAILURE, GEO_LOCATION_SUCCESS, GEO_LOCATION_FAILURE,
GEO_MARKER_ADD, GEO_MARKER_RESET, GEO_MARKER_UPDATE, GEO_MARKER_ADD, GEO_MARKER_RESET, GEO_MARKER_UPDATE,
GEO_POLYGON_ADD, GEO_POLYGON_RESET, GEO_POLYGON_UPDATE, GEO_POLYGON_ADD, GEO_POLYGON_RESET, GEO_POLYGON_UPDATE,
STOPTIME_SUCCESS, GEO_MAPCENTER_UPDATE,
} from '../constants/ActionTypes' } from '../constants/ActionTypes'
const initialState = { const initialState = {
@ -11,10 +11,16 @@ const initialState = {
message: '', message: '',
polygons: [], polygons: [],
markers: [], markers: [],
mapCenter: [13.84626739, 100.538],
} }
const geo = (state = initialState, action) => { const geo = (state = initialState, action) => {
switch (action.type) { switch (action.type) {
case GEO_MAPCENTER_UPDATE:
return {
...state,
mapCenter: action.payload,
}
case GEO_LOCATION_SUCCESS: case GEO_LOCATION_SUCCESS:
/* /*
action = { action = {
@ -51,36 +57,6 @@ const geo = (state = initialState, action) => {
action.payload, action.payload,
], ],
} }
case STOPTIME_SUCCESS:
/* add all stop into another polygon // we will assign unique id as
`stoptime` so it won't get in other ways
*/
const stInd = state.polygons.findIndex(ele => ele.id === 'stoptime')
let polyWstoptime = [...state.polygons]
let stopFeatures = {
"type": "FeatureCollection",
"features": []
}
action.payload.results.map(ele => {
stopFeatures.features.push({
"type": "Feature",
"properties": {
"name": `${ele.stop.name} - ${ele.arrival}`,
},
"geometry": ele.stop.geojson,
})
return true
})
const n = { id: 'stoptime', geojson: stopFeatures }
if (stInd > -1) {
polyWstoptime[stInd] = n
} else {
polyWstoptime.push(n)
}
return {
...state,
polygons: polyWstoptime,
}
case GEO_POLYGON_UPDATE: case GEO_POLYGON_UPDATE:
const polyInd = state.polygons.findIndex(ele => ele.id === action.payload.id) const polyInd = state.polygons.findIndex(ele => ele.id === action.payload.id)
const oldPolygons = state.polygons const oldPolygons = state.polygons

2
src/reducers/index.js

@ -6,12 +6,14 @@ import route from './route'
import fareattr from './fareattr' import fareattr from './fareattr'
import calendar from './calendar' import calendar from './calendar'
import stoptime from './stoptime' import stoptime from './stoptime'
import stop from './stop'
export default combineReducers({ export default combineReducers({
auth, auth,
geo, geo,
agency, agency,
route, route,
stop,
fareattr, fareattr,
calendar, calendar,
stoptime, stoptime,

3
src/reducers/route.js

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

76
src/reducers/stop.js

@ -0,0 +1,76 @@
import {
STOP_CREATE, STOP_DELETE, STOP_UPDATE,
STOP_REQUEST, STOP_SUCCESS, STOP_FAILURE,
} from '../constants/ActionTypes'
const stopInitState = {
results: [],
next: null,
count: 0,
fetching: false,
}
const stop = (state = stopInitState, action) => {
switch(action.type) {
case STOP_REQUEST:
return {
...state,
fetching: true,
}
case STOP_SUCCESS:
const { count, next, prev, results } = action.payload
return {
...state,
fetching: false,
count,
next,
results: [
...(prev ? state.results : []),
...results,
]
}
case STOP_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 STOP_CREATE:
return {
...state,
fetching: false,
count: state.count + 1,
results: [
...state.results,
action.payload,
]
}
case STOP_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 STOP_FAILURE:
return {
...state,
fetching: false,
}
default:
return state;
}
}
export default stop
Loading…
Cancel
Save