Client: update an activity

This commit is contained in:
Sam 2018-05-10 16:56:45 +02:00
parent 939746722f
commit d90b7fed9d
8 changed files with 238 additions and 19 deletions

View File

@ -48,6 +48,7 @@ export const getActivityGpx = activityId => dispatch => {
dispatch(setGpx(null)) dispatch(setGpx(null))
} }
export const deleteActivity = id => dispatch => mpwoGenericApi export const deleteActivity = id => dispatch => mpwoGenericApi
.deleteData('activities', id) .deleteData('activities', id)
.then(ret => { .then(ret => {
@ -57,3 +58,15 @@ export const deleteActivity = id => dispatch => mpwoGenericApi
dispatch(setError(`activities: ${ret.status}`)) dispatch(setError(`activities: ${ret.status}`))
}) })
.catch(error => dispatch(setError(`activities: ${error}`))) .catch(error => dispatch(setError(`activities: ${error}`)))
export const editActivity = form => dispatch => mpwoGenericApi
.updateData('activities', form)
.then(ret => {
if (ret.status === 'success') {
history.push(`/activities/${ret.data.activities[0].id}`)
} else {
dispatch(setError(`activities: ${ret.message}`))
}
})
.catch(error => dispatch(setError(`activities: ${error}`)))

View File

@ -0,0 +1,96 @@
import React from 'react'
import { Helmet } from 'react-helmet'
import FormWithGpx from './ActivityForms/FormWithGpx'
import FormWithoutGpx from './ActivityForms/FormWithoutGpx'
export default class ActivityAddEdit extends React.Component {
constructor(props, context) {
super(props, context)
this.state = {
withGpx: true,
}
}
handleRadioChange (changeEvent) {
this.setState({
withGpx:
changeEvent.target.name === 'withGpx'
? changeEvent.target.value : !changeEvent.target.value
})
}
render() {
const { activity, sports } = this.props
const { withGpx } = this.state
return (
<div>
<Helmet>
<title>mpwo - Add an activity</title>
</Helmet>
<br /><br />
<div className="container">
<div className="row">
<div className="col-md-2" />
<div className="col-md-8">
<div className="card add-activity">
<h2 className="card-header text-center">
{activity ? 'Edit an activity' : 'Add an activity'}
</h2>
<div className="card-body">
{activity ? (
activity.with_gpx ? (
'You can\'t modify this activity.' +
' Please delete and re-import gpx file.'
) : (
<FormWithoutGpx
activity={activity}
sports={sports}
/>
)
) : (
<div>
<form>
<div className="form-group row">
<div className="col">
<label className="radioLabel">
<input
type="radio"
name="withGpx"
checked={withGpx}
onChange={event => this.handleRadioChange(event)}
/>
with gpx file
</label>
</div>
<div className="col">
<label className="radioLabel">
<input
type="radio"
name="withoutGpx"
checked={!withGpx}
onChange={event => this.handleRadioChange(event)}
/>
without gpx file
</label>
</div>
</div>
</form>
{withGpx ? (
<FormWithGpx sports={sports} />
) : (
<FormWithoutGpx sports={sports} />
)}
</div>
)}
</div>
</div>
</div>
<div className="col-md-2" />
</div>
</div>
</div>
)
}
}

View File

@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import ActivityMap from './ActivityMap' import ActivityMap from './ActivityMap'
import CustomModal from './../Others/CustomModal' import CustomModal from './../Others/CustomModal'
@ -17,7 +18,7 @@ class ActivityDisplay extends React.Component {
componentDidMount() { componentDidMount() {
this.props.loadActivity( this.props.loadActivity(
this.props.location.pathname.replace('/activities/', '') this.props.match.params.activityId
) )
} }
@ -55,20 +56,30 @@ class ActivityDisplay extends React.Component {
{sports.filter(sport => sport.id === activity.sport_id) {sports.filter(sport => sport.id === activity.sport_id)
.map(sport => sport.label)} -{' '} .map(sport => sport.label)} -{' '}
{activity.activity_date}{' '} {activity.activity_date}{' '}
<i className="fa fa-edit" aria-hidden="true" />{' '} {!activity.with_gpx && (
<Link
className="unlink"
to={`/activities/${activity.id}/edit`}
>
<i className="fa fa-edit custom-fa" aria-hidden="true" />
</Link>
)}
<i <i
className="fa fa-trash" className="fa fa-trash custom-fa"
aria-hidden="true" aria-hidden="true"
onClick={() => this.setState({ displayModal: true })} onClick={() => this.setState({ displayModal: true })}
/>{' '} />
</div> </div>
<div className="card-body"> <div className="card-body">
<p> <p>
<i className="fa fa-calendar" aria-hidden="true" />{' '} <i
className="fa fa-calendar custom-fa"
aria-hidden="true"
/>
Start at {activity.activity_date} Start at {activity.activity_date}
</p> </p>
<p> <p>
<i className="fa fa-clock-o" aria-hidden="true" />{' '} <i className="fa fa-clock-o custom-fa" aria-hidden="true" />
Duration: {activity.duration} {' '} Duration: {activity.duration} {' '}
{activity.pauses !== '0:00:00' && {activity.pauses !== '0:00:00' &&
activity.pauses !== null && ( activity.pauses !== null && (
@ -76,22 +87,24 @@ class ActivityDisplay extends React.Component {
)} )}
</p> </p>
<p> <p>
<i className="fa fa-road" aria-hidden="true" />{' '} <i className="fa fa-road custom-fa" aria-hidden="true" />
Distance: {activity.distance} km</p> Distance: {activity.distance} km</p>
<p> <p>
<i className="fa fa-tachometer" aria-hidden="true" /> <i
{' '} className="fa fa-tachometer custom-fa"
aria-hidden="true"
/>
Average speed: {activity.ave_speed} km/h -{' '} Average speed: {activity.ave_speed} km/h -{' '}
Max speed : {activity.max_speed} km/h Max speed : {activity.max_speed} km/h
</p> </p>
{activity.min_alt && activity.max_alt && ( {activity.min_alt && activity.max_alt && (
<p><i className="fi-mountains" />{' '} <p><i className="fi-mountains custom-fa" />
Min altitude: {activity.min_alt}m -{' '} Min altitude: {activity.min_alt}m -{' '}
Max altitude: {activity.max_alt}m Max altitude: {activity.max_alt}m
</p> </p>
)} )}
{activity.ascent && activity.descent && ( {activity.ascent && activity.descent && (
<p><i className="fa fa-location-arrow" />{' '} <p><i className="fa fa-location-arrow custom-fa" />
Ascent: {activity.ascent}m -{' '} Ascent: {activity.ascent}m -{' '}
Descent: {activity.descent}m Descent: {activity.descent}m
</p> </p>

View File

@ -0,0 +1,52 @@
import React from 'react'
import { Helmet } from 'react-helmet'
import { connect } from 'react-redux'
import ActivityAddOrEdit from './ActivityAddOrEdit'
import { getData } from '../../actions/index'
class ActivityEdit extends React.Component {
componentDidMount() {
this.props.loadActivity(
this.props.match.params.activityId
)
}
render() {
const { activities, message, sports } = this.props
const [activity] = activities
return (
<div>
<Helmet>
<title>mpwo - Edit activity</title>
</Helmet>
<br /><br />
{message && (
<code>{message}</code>
)}
{sports.length > 0 && (
<ActivityAddOrEdit
activity={activity}
sports={sports}
/>
)}
</div>
)
}
}
export default connect(
state => ({
activities: state.activities.data,
message: state.message,
sports: state.sports.data,
user: state.user,
}),
dispatch => ({
loadActivity: activityId => {
dispatch(getData('sports'))
dispatch(getData('activities', activityId))
},
})
)(ActivityEdit)

View File

@ -1,12 +1,22 @@
import React from 'react' import React from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { addActivityWithoutGpx } from '../../../actions/activities' import {
addActivityWithoutGpx, editActivity
} from '../../../actions/activities'
import { history } from '../../../index' import { history } from '../../../index'
import { formatActivityDate } from '../../../utils'
function FormWithoutGpx (props) { function FormWithoutGpx (props) {
const { onAddSport, sports } = props const { activity, onAddOrEdit, sports } = props
let activityDate, activityTime, sportId = ''
if (activity) {
const activityDateTime = formatActivityDate(activity.activity_date)
activityDate = activityDateTime.activity_date
activityTime = activityDateTime.activity_time
sportId = activity.sport_id
}
return ( return (
<form <form
onSubmit={event => event.preventDefault()} onSubmit={event => event.preventDefault()}
@ -16,6 +26,7 @@ function FormWithoutGpx (props) {
Sport: Sport:
<select <select
className="form-control input-lg" className="form-control input-lg"
defaultValue={sportId}
name="sport_id" name="sport_id"
required required
> >
@ -35,12 +46,14 @@ function FormWithoutGpx (props) {
<div className="row"> <div className="row">
<input <input
name="activity_date" name="activity_date"
defaultValue={activityDate}
className="form-control col-md" className="form-control col-md"
required required
type="date" type="date"
/> />
<input <input
name="activity_time" name="activity_time"
defaultValue={activityTime}
className="form-control col-md" className="form-control col-md"
required required
type="time" type="time"
@ -54,8 +67,9 @@ function FormWithoutGpx (props) {
Duration: Duration:
<input <input
name="duration" name="duration"
defaultValue={activity ? activity.duration : ''}
className="form-control col-xs-4" className="form-control col-xs-4"
pattern="([0-2][0-3]):([0-5][0-9]):([0-5][0-9])" pattern="^([0-9]*[0-9]):([0-5][0-9]):([0-5][0-9])$"
placeholder="hh:mm:ss" placeholder="hh:mm:ss"
required required
type="text" type="text"
@ -67,6 +81,7 @@ function FormWithoutGpx (props) {
Distance (km): Distance (km):
<input <input
name="distance" name="distance"
defaultValue={activity ? activity.distance : ''}
className="form-control input-lg" className="form-control input-lg"
min={0} min={0}
required required
@ -77,7 +92,7 @@ function FormWithoutGpx (props) {
<input <input
type="submit" type="submit"
className="btn btn-primary btn-lg btn-block" className="btn btn-primary btn-lg btn-block"
onClick={event => onAddSport(event)} onClick={event => onAddOrEdit(event, activity)}
value="Submit" value="Submit"
/> />
<input <input
@ -93,7 +108,7 @@ function FormWithoutGpx (props) {
export default connect( export default connect(
() => ({ }), () => ({ }),
dispatch => ({ dispatch => ({
onAddSport: e => { onAddOrEdit: (e, activity) => {
const d = e.target.form.duration.value.split(':') const d = e.target.form.duration.value.split(':')
const duration = +d[0] * 60 * 60 + +d[1] * 60 + +d[2] const duration = +d[0] * 60 * 60 + +d[1] * 60 + +d[2]
@ -106,7 +121,12 @@ export default connect(
duration, duration,
sport_id: +e.target.form.sport_id.value, sport_id: +e.target.form.sport_id.value,
} }
dispatch(addActivityWithoutGpx(data)) if (activity) {
data.id = activity.id
dispatch(editActivity(data))
} else {
dispatch(addActivityWithoutGpx(data))
}
}, },
}) })
)(FormWithoutGpx) )(FormWithoutGpx)

View File

@ -5,6 +5,7 @@ import { Redirect, Route, Switch } from 'react-router-dom'
import ActivityAdd from './ActivityAdd' import ActivityAdd from './ActivityAdd'
import ActivityDisplay from './ActivityDisplay' import ActivityDisplay from './ActivityDisplay'
import ActivityEdit from './ActivityEdit'
import NotFound from './../Others/NotFound' import NotFound from './../Others/NotFound'
import { isLoggedIn } from '../../utils' import { isLoggedIn } from '../../utils'
@ -19,7 +20,14 @@ class Activity extends React.Component {
{isLoggedIn() ? ( {isLoggedIn() ? (
<Switch> <Switch>
<Route exact path="/activities/add" component={ActivityAdd} /> <Route exact path="/activities/add" component={ActivityAdd} />
<Route path="/activities" component={ActivityDisplay} /> <Route
exact path="/activities/:activityId"
component={ActivityDisplay}
/>
<Route
exact path="/activities/:activityId/edit"
component={ActivityEdit}
/>
<Route component={NotFound} /> <Route component={NotFound} />
</Switch> </Switch>
) : (<Redirect to="/login" />)} ) : (<Redirect to="/login" />)}

View File

@ -91,3 +91,11 @@ input, textarea {
padding: 50px; padding: 50px;
z-index: 1040; z-index: 1040;
} }
.custom-fa {
margin-right: 5px;
}
.unlink {
color: black;
}

View File

@ -1,4 +1,5 @@
import togeojson from '@mapbox/togeojson' import togeojson from '@mapbox/togeojson'
import { format, parse } from 'date-fns'
import bbox from 'geojson-bbox' import bbox from 'geojson-bbox'
export const apiUrl = `${process.env.REACT_APP_API_URL}/api/` export const apiUrl = `${process.env.REACT_APP_API_URL}/api/`
@ -31,3 +32,11 @@ export const getGeoJson = gpxContent => {
} }
return { jsonData, bounds } return { jsonData, bounds }
} }
export const formatActivityDate = activityDateTime => {
const dateTime = parse(activityDateTime)
return {
activity_date: format(dateTime, 'YYYY-MM-DD'),
activity_time: activityDateTime.match(/[0-2][0-9]:[0-5][0-9]/)[0]
}
}