Browse Source
* This Geo container needs to be publicly available to comply with Mapbox policymaster
sipp11
7 years ago
22 changed files with 980 additions and 120 deletions
@ -0,0 +1,67 @@
|
||||
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 getStopTime = (query) => ({ |
||||
[RSAA]: { |
||||
endpoint: `${API_URL}/stoptime/?${query || ''}`, |
||||
method: 'GET', |
||||
headers: RSAAHeaders, |
||||
bailout: (state) => state.stoptime.fetching, |
||||
types: [ |
||||
{ |
||||
type: types.STOPTIME_REQUEST, |
||||
meta: { query: query }, |
||||
}, |
||||
types.STOPTIME_SUCCESS, |
||||
types.STOPTIME_FAILURE, |
||||
] |
||||
} |
||||
}) |
||||
|
||||
export const updateStopTime = (id, body) => ({ |
||||
[RSAA]: { |
||||
endpoint: `${API_URL}/stoptime/${id}/`, |
||||
body: JSON.stringify(body), |
||||
method: 'PATCH', |
||||
headers: RSAAHeaders, |
||||
types: [ |
||||
types.STOPTIME_REQUEST, |
||||
types.STOPTIME_UPDATE, |
||||
types.STOPTIME_FAILURE, |
||||
] |
||||
} |
||||
}) |
||||
|
||||
export const createStopTime = (body) => ({ |
||||
[RSAA]: { |
||||
endpoint: `${API_URL}/stoptime/`, |
||||
body: JSON.stringify(body), |
||||
method: 'POST', |
||||
headers: RSAAHeaders, |
||||
types: [ |
||||
types.STOPTIME_REQUEST, |
||||
types.STOPTIME_CREATE, |
||||
types.STOPTIME_FAILURE, |
||||
] |
||||
} |
||||
}) |
||||
|
||||
export const deleteStopTime = (id) => ({ |
||||
[RSAA]: { |
||||
endpoint: `${API_URL}/stoptime/${id}/`, |
||||
method: 'DELETE', |
||||
headers: RSAAHeaders, |
||||
types: [ |
||||
types.STOPTIME_REQUEST, |
||||
{ |
||||
type: types.STOPTIME_DELETE, |
||||
meta: { id } |
||||
}, |
||||
types.STOPTIME_FAILURE, |
||||
] |
||||
} |
||||
}) |
@ -0,0 +1,26 @@
|
||||
import React from 'react' |
||||
import styled from 'styled-components' |
||||
import { Link } from 'react-router-dom' |
||||
|
||||
const StyledBreadcrumb = styled.nav` |
||||
padding: 0 5px; |
||||
font-size: 0.7rem; |
||||
margin-bottom: 5px !important; |
||||
` |
||||
|
||||
|
||||
const Breadcrumb = (props) => ( |
||||
<StyledBreadcrumb className="breadcrumb has-succeeds-separator" aria-label="breadcrumbs"> |
||||
<ul> |
||||
<li><Link to='/map'>ALL</Link></li> |
||||
{props.agencyId && <li className={`${!props.routeId && 'is-active'}`}> |
||||
<Link to={`/map/${props.agencyId}`} aria-current="page"> |
||||
{props.agencyId}</Link></li>} |
||||
{props.routeId && <li className={`${props.routeId && 'is-active'}`}> |
||||
<Link to={`/map/${props.agencyId}/route/${props.routeId}`} aria-current="page"> |
||||
{props.routeId}</Link></li>} |
||||
</ul> |
||||
</StyledBreadcrumb> |
||||
) |
||||
|
||||
export default Breadcrumb |
@ -0,0 +1,157 @@
|
||||
import React, { Component } from 'react' |
||||
import { Link, Route } from 'react-router-dom' |
||||
import { connect } from 'react-redux' |
||||
import styled from 'styled-components' |
||||
|
||||
import { logout } from '../utils/Auth' |
||||
import RouteList from './RouteList' |
||||
import RouteDetail from './RouteDetail' |
||||
import Breadcrumb from './Breadcrumb' |
||||
import { polygonReset } from '../actions' |
||||
import store from '../store' |
||||
|
||||
const StyledLink = styled(Link)` |
||||
&[data-active] { |
||||
color: red; |
||||
} |
||||
` |
||||
|
||||
const StyledLRPaddingNav = styled.nav` |
||||
padding-right: 10px; |
||||
padding-left: 0; |
||||
margin-bottom: 5px !important; |
||||
` |
||||
|
||||
const StyledFloatPane = styled.div` |
||||
min-width: 300px; |
||||
height: 100%; |
||||
width: 25vw; |
||||
z-index: 30; |
||||
background: #fefefefe; |
||||
border-radius: 0 5px 5px 0; |
||||
position: fixed; |
||||
left: ${props => props.hidePane ? '-500px' : 0}; |
||||
top: 0px; |
||||
` |
||||
|
||||
const StyledPaneToggler = styled.div` |
||||
width: 20px |
||||
height: 30px; |
||||
background: #fefefefe; |
||||
color: #209cee; |
||||
position: fixed; |
||||
text-align: center; |
||||
left: ${props => props.hidePane ? 0 : '25vw'}; |
||||
top: 30px; |
||||
border-radius: 0 5px 5px 0; |
||||
|
||||
@media (max-width: 1200px) { |
||||
left: ${props => props.hidePane ? 0 : '300px'}; |
||||
} |
||||
` |
||||
|
||||
const StyledScrollY = styled.div` |
||||
height: 100vh; |
||||
overflow: auto; |
||||
padding-bottom: 5rem; |
||||
` |
||||
|
||||
const SimpleAgencyList = (props) => ( |
||||
<nav className="panel"> |
||||
<p className="panel-heading"> |
||||
Agency |
||||
</p> |
||||
{props.agencies.map(ele => ( |
||||
<Link key={ele.agency_id} className="panel-block" to={`/map/${ele.agency_id}`}> |
||||
{ele.name} |
||||
</Link>))} |
||||
</nav> |
||||
) |
||||
|
||||
|
||||
|
||||
class FloatPane extends Component { |
||||
|
||||
state = { hidePane: false, breadcrumb: {} } |
||||
|
||||
renderTopLevel(loggedIn) { |
||||
return ( |
||||
<StyledLRPaddingNav className="level is-mobile"> |
||||
<div className="level-left"> |
||||
<StyledLink to="/" className="navbar-item"> |
||||
<img src="https://static.10ninox.com/goth-rect-640x160.svg" alt="GoTH" width="112" height="28" /> |
||||
</StyledLink> |
||||
</div> |
||||
<div className="level-right"> |
||||
<a className="button is-small is-primary" |
||||
onClick={() => store.dispatch(polygonReset())}> |
||||
<span className="icon"> |
||||
<i className="fas fa-sign-out-alt"></i> |
||||
</span> |
||||
<span>Clear PG</span> |
||||
</a> |
||||
{loggedIn && |
||||
<a className="button is-small is-outlined" |
||||
onClick={() => logout()}> |
||||
<span className="icon"> |
||||
<i className="fas fa-sign-out-alt"></i> |
||||
</span> |
||||
<span>Logout</span> |
||||
</a> |
||||
} |
||||
{!loggedIn && |
||||
<Link className="button is-small is-outlined is-info" to='/login'> |
||||
<span className="icon"> |
||||
<i className="fas fa-sign-in-alt"></i> |
||||
</span> |
||||
<span>Sign in</span> |
||||
</Link> |
||||
} |
||||
</div> |
||||
</StyledLRPaddingNav> |
||||
) |
||||
} |
||||
|
||||
togglePane() { |
||||
this.setState({ hidePane: !this.state.hidePane }) |
||||
} |
||||
|
||||
handleChildMatchParams(params) { |
||||
this.setState({ breadcrumb: params }) |
||||
} |
||||
|
||||
render () { |
||||
const { loggedIn, agency } = this.props |
||||
const { hidePane } = this.state |
||||
const { results } = agency |
||||
|
||||
return ( |
||||
<StyledFloatPane hidePane={hidePane}> |
||||
<StyledPaneToggler hidePane={hidePane} onClick={this.togglePane.bind(this)}> |
||||
<i className='fas fa-align-justify' /> |
||||
</StyledPaneToggler> |
||||
{this.renderTopLevel(loggedIn)} |
||||
<StyledScrollY> |
||||
<Breadcrumb {...this.state.breadcrumb} /> |
||||
<Route exact path={`/map/:agencyId/route/:routeId/:routeParams?`} render={(props) => ( |
||||
<RouteDetail {...props} |
||||
updateBreadcrumb={this.handleChildMatchParams.bind(this)} />)} /> |
||||
<Route exact path={`/map`} render={(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> |
||||
|
||||
</StyledFloatPane> |
||||
) |
||||
} |
||||
} |
||||
|
||||
const mapStateToProps = state => ({ |
||||
agency: state.agency, |
||||
}) |
||||
export default connect( |
||||
mapStateToProps |
||||
)(FloatPane) |
@ -0,0 +1,167 @@
|
||||
import React, { Component } from 'react' |
||||
import styled from 'styled-components' |
||||
import { Link, Route } from 'react-router-dom' |
||||
import { connect } from 'react-redux' |
||||
|
||||
import Spinner from './Spinner' |
||||
import { getRoute, polygonUpdate } from '../actions' |
||||
import { getStopTime } from '../actions/stoptime' |
||||
import store from '../store' |
||||
|
||||
/* |
||||
TODO: add shape |
||||
TODO: add Route detail |
||||
|
||||
*/ |
||||
|
||||
const StyledTripDesc = styled.div` |
||||
font-size: 0.87rem; |
||||
line-height: 0.85rem; |
||||
margin-left: 10px; |
||||
` |
||||
|
||||
|
||||
const RouteDesc = (props) => ( |
||||
<div> |
||||
<span className="panel-block"> |
||||
ID: {props.route.id} |
||||
</span> |
||||
<span className="panel-block"> |
||||
long_name: {props.route.long_name} |
||||
</span> |
||||
<span className="panel-block"> |
||||
short_name: {props.route.short_name} |
||||
</span> |
||||
<span className="panel-block"> |
||||
type: {props.route.route_type} |
||||
</span> |
||||
<span className="panel-block"> |
||||
color: <br/>Text #{props.route.text_color || '-'} <br />BG #{props.route.route_text_color} |
||||
</span> |
||||
<span className="panel-block"> |
||||
Sort order: {props.route.route_sort_order} |
||||
</span> |
||||
<span className="panel-block"> |
||||
URL: {props.route.route_url || '-'} |
||||
</span> |
||||
<span className="panel-block"> |
||||
desc: {props.route.desc || '-'} |
||||
</span> |
||||
<span className="panel-block"> |
||||
shapes: {props.route.geosjson !== null ? 'yes' : 'n/a'} |
||||
</span> |
||||
</div> |
||||
) |
||||
|
||||
|
||||
const TripList = (props) => ( |
||||
<div> |
||||
{props.trips.map(ele => ( |
||||
<span key={ele.id} className="panel-block" |
||||
onClick={() => { store.dispatch(getStopTime(`trip=${ele.id}`))}}> |
||||
{ele.trip_id} |
||||
<StyledTripDesc> |
||||
(<b>ID</b> {ele.id}) |
||||
<b>service</b> <Link to={`/calendar/${ele.service.service_id}`}>{ele.service.service_id}</Link> |
||||
<br /> |
||||
{ele.stoptime.count > 0 && <div> |
||||
<b>Stop</b> #{ele.stoptime.count} |
||||
<br /> |
||||
<b>Time period</b> {ele.stoptime.period[0]} - {ele.stoptime.period[1]} |
||||
</div>} |
||||
</StyledTripDesc> |
||||
</span> |
||||
))} |
||||
{props.trips.length === 0 && <span key="empty-trips" className="panel-block"> |
||||
No trip set |
||||
</span>} |
||||
</div> |
||||
) |
||||
|
||||
const FareRuleList = (props) => ( |
||||
<div> |
||||
{props.farerules.map(ele => ( |
||||
<span key={ele.id} className="panel-block"> |
||||
{ele.fare.fare_id} - {ele.fare.price} |
||||
</span> |
||||
))} |
||||
{props.farerules.length === 0 && <span key="empty-farerule" className="panel-block"> |
||||
No fare rule set |
||||
</span>} |
||||
</div> |
||||
) |
||||
|
||||
class RouteDetail extends Component { |
||||
|
||||
state = { |
||||
tab: 'detail', |
||||
} |
||||
|
||||
componentDidMount() { |
||||
const { updateBreadcrumb, match } = this.props |
||||
updateBreadcrumb(match.params) |
||||
} |
||||
|
||||
componentWillMount() { |
||||
const { route, match } = this.props |
||||
if (route.count === 0) { |
||||
store.dispatch(getRoute(`agency=${match.params.agencyId}`)) |
||||
} else { |
||||
this.pushShapeToStore(match, route) |
||||
} |
||||
} |
||||
|
||||
componentWillReceiveProps(newProps) { |
||||
if (this.props.route.count < newProps.route.count) { |
||||
this.pushShapeToStore(this.props.match, newProps.route) |
||||
} |
||||
} |
||||
|
||||
pushShapeToStore(match, routeStore) { |
||||
const { routeId } = match.params |
||||
const tRoute = routeStore.results.filter(ele => ele.route_id === routeId) |
||||
if (tRoute.length === 0) |
||||
return |
||||
const one = tRoute[0] |
||||
const oneStyle = {} |
||||
if (one.route_color) |
||||
oneStyle['color'] = `#${one.route_color}` |
||||
store.dispatch(polygonUpdate(one.route_id, one.geojson, oneStyle)) |
||||
} |
||||
|
||||
render() { |
||||
const { route, match } = this.props |
||||
const { routeId, agencyId, routeParams } = match.params |
||||
const tRoute = route.results.filter(ele => ele.route_id === routeId) |
||||
if (tRoute.length === 0) { |
||||
return <Spinner show={true} /> |
||||
} |
||||
const item = tRoute[0] |
||||
const baseUrl = `/map/${agencyId}/route/${routeId}` |
||||
return ( |
||||
<nav className="panel"> |
||||
<p className="panel-heading"> |
||||
{item.long_name} <small>#{item.id}</small> |
||||
</p> |
||||
<p className="panel-tabs"> |
||||
<Link to={`${baseUrl}`} className={`${!routeParams && 'is-active'}`}>detail</Link> |
||||
<Link to={`${baseUrl}/trip`} className={`${routeParams === 'trip' && 'is-active'}`}>trip</Link> |
||||
<Link to={`${baseUrl}/fare`} className={`${routeParams === 'fare' && 'is-active'}`}>fare</Link> |
||||
</p> |
||||
<Route exact path={`${baseUrl}`} render={(props) => ( |
||||
<RouteDesc route={item} {...props} />)} /> |
||||
<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} />)} /> |
||||
</nav> |
||||
) |
||||
} |
||||
} |
||||
|
||||
const mapStateToProps = state => ({ |
||||
route: state.route, |
||||
}) |
||||
export default connect( |
||||
mapStateToProps |
||||
)(RouteDetail) |
@ -0,0 +1,134 @@
|
||||
import React, { Component } from 'react' |
||||
import { connect } from 'react-redux' |
||||
import styled from 'styled-components' |
||||
// import { Redirect, Route, Switch } from 'react-router-dom'
|
||||
import { |
||||
Map, TileLayer, CircleMarker, ZoomControl, |
||||
FeatureGroup, GeoJSON } from 'react-leaflet' |
||||
// import { EditControl } from 'react-leaflet-draw'
|
||||
// import L from 'leaflet'
|
||||
|
||||
import { loggedIn } from '../reducers/auth' |
||||
import { geoLocationFailed, geoLocationUpdate, getAgency } from '../actions' |
||||
import FloatPane from '../components/FloatPane' |
||||
import store from '../store' |
||||
|
||||
|
||||
const FullPageBox = styled.div` |
||||
height: 100%; |
||||
min-height: 100vh; |
||||
z-index: 1; |
||||
flex: 1; |
||||
display: flex; |
||||
flex-direction: column; |
||||
` |
||||
|
||||
class Geo extends Component { |
||||
|
||||
constructor(props) { |
||||
super(props) |
||||
this.renderGeoJSON = this.renderGeoJSON.bind(this) |
||||
} |
||||
|
||||
componentWillMount() { |
||||
const { count } = this.props.agency |
||||
if (count === 0) |
||||
store.dispatch(getAgency()) |
||||
} |
||||
|
||||
componentWillUnmount() { |
||||
navigator.geolocation.clearWatch(this.watchID) |
||||
} |
||||
|
||||
componentDidMount() { |
||||
/* if (!navigator.geolocation) { |
||||
getCurrentPosition: (success, failure) => { |
||||
const failureMsg = "Your browser doesn't support geolocation." |
||||
console.log(success, failure) |
||||
// failure(dispatch(locationError(failureMsg)))
|
||||
} |
||||
} */ |
||||
navigator.geolocation.getCurrentPosition( |
||||
(position) => store.dispatch(geoLocationUpdate(position)), |
||||
(error) => store.dispatch(geoLocationFailed(error)), |
||||
{ enableHighAccuracy: true, timeout: 10000, maximumAge: 1000 }, |
||||
) |
||||
this.watchID = navigator.geolocation.watchPosition( |
||||
(position) => store.dispatch(geoLocationUpdate(position)), |
||||
(error) => store.dispatch(geoLocationFailed(error)), |
||||
) |
||||
} |
||||
|
||||
renderGeoJSON() { |
||||
const { polygons } = this.props.geo |
||||
const style = { |
||||
color: '#a63eff', |
||||
weight: 5, |
||||
opacity: 0.65 |
||||
} |
||||
return ( |
||||
<FeatureGroup> |
||||
{polygons && polygons.map(ele => ( |
||||
<GeoJSON |
||||
key={`geojson-${ele.id}`} |
||||
data={ele.geojson} |
||||
style={{...style, ...ele.style}} /> |
||||
))} |
||||
</FeatureGroup> |
||||
) |
||||
} |
||||
|
||||
render() { |
||||
const { loggedIn, geo } = this.props |
||||
const myLocationMarker = geo.coords ? ( |
||||
<CircleMarker |
||||
center={[geo.coords.latitude, geo.coords.longitude]} |
||||
radius={8} |
||||
fillColor={'rgb(33, 150, 243)'} |
||||
fillOpacity={0.9} |
||||
color={'white'} |
||||
stroke |
||||
weight={2} |
||||
opacity={1} |
||||
className='my-location-marker' /> |
||||
) : null |
||||
// const mapCenter = geo.coords
|
||||
// ? [geo.coords.latitude, geo.coords.longitude]
|
||||
// : [13.84626739, 100.538]
|
||||
const mapCenter = [13.84626739, 100.538] |
||||
return ( |
||||
<FullPageBox> |
||||
{/* <Nav loggedIn={loggedIn} /> */} |
||||
<FloatPane loggedIn={loggedIn} {...this.props} /> |
||||
<Map |
||||
center={mapCenter} |
||||
zoom={13} |
||||
length={4} |
||||
zoomControl={false} |
||||
animate |
||||
style={{flex: 1}} |
||||
ref='map'> |
||||
<TileLayer |
||||
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' |
||||
// url="http://{s}.tile.osm.org/{z}/{x}/{y}.png"
|
||||
url='https://api.tiles.mapbox.com/v4/sipp11.p4efho4p/{z}/{x}/{y}.png?access_token=pk.eyJ1Ijoic2lwcDExIiwiYSI6ImNpa2hzbGpzcDAyYWl0eWo3azhkaHR3aWIifQ.cc4CGgGKkpP_8XVa2BUwtQ' |
||||
/> |
||||
<ZoomControl position="topright" /> |
||||
{myLocationMarker} |
||||
{this.renderGeoJSON()} |
||||
</Map> |
||||
</FullPageBox> |
||||
) |
||||
} |
||||
} |
||||
|
||||
|
||||
const mapStateToProps = state => ({ |
||||
loggedIn: loggedIn(state.auth), |
||||
agency: state.agency, |
||||
geo: state.geo, |
||||
}) |
||||
export default connect( |
||||
mapStateToProps, |
||||
{} |
||||
)(Geo) |
@ -1,31 +0,0 @@
|
||||
import { |
||||
FIRST_INCREMENT_COUNTER, FIRST_ASSIGN_ID |
||||
} from '../constants/ActionTypes' |
||||
|
||||
const initialState = { |
||||
counter: 1, |
||||
obj: {} |
||||
} |
||||
|
||||
const first = (state = initialState, action) => { |
||||
switch (action.type) { |
||||
case FIRST_INCREMENT_COUNTER: |
||||
return { |
||||
...state, |
||||
counter: state.counter +1 |
||||
} |
||||
case FIRST_ASSIGN_ID: |
||||
return { |
||||
...state, |
||||
obj: action.newId |
||||
} |
||||
default: |
||||
return state |
||||
} |
||||
} |
||||
|
||||
export default first |
||||
|
||||
|
||||
export const getCounter = state => state.counter |
||||
export const getId = state => state.id |
@ -0,0 +1,131 @@
|
||||
import { |
||||
GEO_LOCATION_SUCCESS, GEO_LOCATION_FAILURE, |
||||
GEO_MARKER_ADD, GEO_MARKER_RESET, GEO_MARKER_UPDATE, |
||||
GEO_POLYGON_ADD, GEO_POLYGON_RESET, GEO_POLYGON_UPDATE, |
||||
STOPTIME_SUCCESS, |
||||
} from '../constants/ActionTypes' |
||||
|
||||
const initialState = { |
||||
timestamp: 0, |
||||
coords: null, |
||||
message: '', |
||||
polygons: [], |
||||
markers: [], |
||||
} |
||||
|
||||
const geo = (state = initialState, action) => { |
||||
switch (action.type) { |
||||
case GEO_LOCATION_SUCCESS: |
||||
/* |
||||
action = { |
||||
coords: { |
||||
latitude: 13.8462448, |
||||
longitude: 100.53825479999999, |
||||
altitude: null, |
||||
altitudeAccuracy: null, |
||||
accuracy: 20, |
||||
speed: 20, |
||||
}, |
||||
timestamp: 1530214120419 |
||||
} |
||||
*/ |
||||
return { |
||||
...state, |
||||
message: '', |
||||
timestamp: action.timestamp, |
||||
coords: {...action.coords}, |
||||
} |
||||
case GEO_LOCATION_FAILURE: |
||||
// action = {code: 3, message: "Timeout expired"}
|
||||
return { |
||||
...state, |
||||
timestamp: 0, |
||||
coords: null, |
||||
message: action.message, |
||||
} |
||||
case GEO_POLYGON_ADD: |
||||
return { |
||||
...state, |
||||
polygons: [ |
||||
...state.polygons, |
||||
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: |
||||
const polyInd = state.polygons.findIndex(ele => ele.id === action.payload.id) |
||||
const oldPolygons = state.polygons |
||||
let newPolys = [...oldPolygons] |
||||
if (polyInd > -1) { |
||||
newPolys[polyInd] = action.payload |
||||
} else { |
||||
newPolys.push(action.payload) |
||||
} |
||||
return { |
||||
...state, |
||||
polygons: newPolys, |
||||
} |
||||
case GEO_POLYGON_RESET: |
||||
return { |
||||
...state, |
||||
polygons: [], |
||||
} |
||||
case GEO_MARKER_ADD: |
||||
return { |
||||
...state, |
||||
markers: [ |
||||
...state.markers, |
||||
action.polygon, |
||||
], |
||||
} |
||||
case GEO_MARKER_UPDATE: |
||||
const markerInd = state.markers.findIndex(ele => ele.id === action.marker.id) |
||||
const oldMarkers = state.markers |
||||
return { |
||||
...state, |
||||
markers: [ |
||||
...oldMarkers.slice(0, markerInd), |
||||
action.polygon, |
||||
...oldMarkers.slice(markerInd + 1), |
||||
], |
||||
} |
||||
case GEO_MARKER_RESET: |
||||
return { |
||||
...state, |
||||
markers: [], |
||||
} |
||||
default: |
||||
return state |
||||
} |
||||
} |
||||
|
||||
export default geo |
@ -1,16 +1,18 @@
|
||||
import { combineReducers } from 'redux' |
||||
import first from './first' |
||||
import geo from './geo' |
||||
import auth from './auth' |
||||
import agency from './agency' |
||||
import route from './route' |
||||
import fareattr from './fareattr' |
||||
import calendar from './calendar' |
||||
import stoptime from './stoptime' |
||||
|
||||
export default combineReducers({ |
||||
auth, |
||||
first, |
||||
geo, |
||||
agency, |
||||
route, |
||||
fareattr, |
||||
calendar, |
||||
stoptime, |
||||
}) |
||||
|
@ -0,0 +1,79 @@
|
||||
import { |
||||
STOPTIME_CREATE, STOPTIME_DELETE, STOPTIME_UPDATE, |
||||
STOPTIME_REQUEST, STOPTIME_SUCCESS, STOPTIME_FAILURE, |
||||
} from '../constants/ActionTypes' |
||||
|
||||
|
||||
const stoptimeInitState = { |
||||
results: [], |
||||
next: null, |
||||
count: 0, |
||||
fetching: false, |
||||
query: '', |
||||
} |
||||
const stoptime = (state = stoptimeInitState, action) => { |
||||
switch (action.type) { |
||||
case STOPTIME_REQUEST: |
||||
const { query } = action.meta |
||||
return { |
||||
...state, |
||||
fetching: true, |
||||
query, |
||||
} |
||||
case STOPTIME_SUCCESS: |
||||
const { count, next, prev, results } = action.payload |
||||
return { |
||||
...state, |
||||
fetching: false, |
||||
count, |
||||
next, |
||||
results: [ |
||||
...((prev) ? state.results : []), |
||||
...results, |
||||
] |
||||
} |
||||
case STOPTIME_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 STOPTIME_CREATE: |
||||
return { |
||||
...state, |
||||
fetching: false, |
||||
count: state.count + 1, |
||||
results: [ |
||||
...state.results, |
||||
action.payload, |
||||
] |
||||
} |
||||
case STOPTIME_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 STOPTIME_FAILURE: |
||||
return { |
||||
...state, |
||||
fetching: false, |
||||
} |
||||
default: |
||||
return state; |
||||
} |
||||
} |
||||
|
||||
export default stoptime |
Loading…
Reference in new issue