Client: update an activity
This commit is contained in:
parent
939746722f
commit
d90b7fed9d
@ -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}`)))
|
||||||
|
96
mpwo_client/src/components/Activities/ActivityAddOrEdit.jsx
Normal file
96
mpwo_client/src/components/Activities/ActivityAddOrEdit.jsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
|
52
mpwo_client/src/components/Activities/ActivityEdit.jsx
Normal file
52
mpwo_client/src/components/Activities/ActivityEdit.jsx
Normal 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)
|
@ -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)
|
||||||
|
@ -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" />)}
|
||||||
|
@ -91,3 +91,11 @@ input, textarea {
|
|||||||
padding: 50px;
|
padding: 50px;
|
||||||
z-index: 1040;
|
z-index: 1040;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-fa {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unlink {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
@ -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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user