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))
|
||||
}
|
||||
|
||||
|
||||
export const deleteActivity = id => dispatch => mpwoGenericApi
|
||||
.deleteData('activities', id)
|
||||
.then(ret => {
|
||||
@ -57,3 +58,15 @@ export const deleteActivity = id => dispatch => mpwoGenericApi
|
||||
dispatch(setError(`activities: ${ret.status}`))
|
||||
})
|
||||
.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 { Helmet } from 'react-helmet'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import ActivityMap from './ActivityMap'
|
||||
import CustomModal from './../Others/CustomModal'
|
||||
@ -17,7 +18,7 @@ class ActivityDisplay extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
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)
|
||||
.map(sport => sport.label)} -{' '}
|
||||
{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
|
||||
className="fa fa-trash"
|
||||
className="fa fa-trash custom-fa"
|
||||
aria-hidden="true"
|
||||
onClick={() => this.setState({ displayModal: true })}
|
||||
/>{' '}
|
||||
/>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<p>
|
||||
<i className="fa fa-calendar" aria-hidden="true" />{' '}
|
||||
<i
|
||||
className="fa fa-calendar custom-fa"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Start at {activity.activity_date}
|
||||
</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} {' '}
|
||||
{activity.pauses !== '0:00:00' &&
|
||||
activity.pauses !== null && (
|
||||
@ -76,22 +87,24 @@ class ActivityDisplay extends React.Component {
|
||||
)}
|
||||
</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>
|
||||
<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 -{' '}
|
||||
Max speed : {activity.max_speed} km/h
|
||||
</p>
|
||||
{activity.min_alt && activity.max_alt && (
|
||||
<p><i className="fi-mountains" />{' '}
|
||||
<p><i className="fi-mountains custom-fa" />
|
||||
Min altitude: {activity.min_alt}m -{' '}
|
||||
Max altitude: {activity.max_alt}m
|
||||
</p>
|
||||
)}
|
||||
{activity.ascent && activity.descent && (
|
||||
<p><i className="fa fa-location-arrow" />{' '}
|
||||
<p><i className="fa fa-location-arrow custom-fa" />
|
||||
Ascent: {activity.ascent}m -{' '}
|
||||
Descent: {activity.descent}m
|
||||
</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 { connect } from 'react-redux'
|
||||
|
||||
import { addActivityWithoutGpx } from '../../../actions/activities'
|
||||
import {
|
||||
addActivityWithoutGpx, editActivity
|
||||
} from '../../../actions/activities'
|
||||
import { history } from '../../../index'
|
||||
|
||||
import { formatActivityDate } from '../../../utils'
|
||||
|
||||
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 (
|
||||
<form
|
||||
onSubmit={event => event.preventDefault()}
|
||||
@ -16,6 +26,7 @@ function FormWithoutGpx (props) {
|
||||
Sport:
|
||||
<select
|
||||
className="form-control input-lg"
|
||||
defaultValue={sportId}
|
||||
name="sport_id"
|
||||
required
|
||||
>
|
||||
@ -35,12 +46,14 @@ function FormWithoutGpx (props) {
|
||||
<div className="row">
|
||||
<input
|
||||
name="activity_date"
|
||||
defaultValue={activityDate}
|
||||
className="form-control col-md"
|
||||
required
|
||||
type="date"
|
||||
/>
|
||||
<input
|
||||
name="activity_time"
|
||||
defaultValue={activityTime}
|
||||
className="form-control col-md"
|
||||
required
|
||||
type="time"
|
||||
@ -54,8 +67,9 @@ function FormWithoutGpx (props) {
|
||||
Duration:
|
||||
<input
|
||||
name="duration"
|
||||
defaultValue={activity ? activity.duration : ''}
|
||||
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"
|
||||
required
|
||||
type="text"
|
||||
@ -67,6 +81,7 @@ function FormWithoutGpx (props) {
|
||||
Distance (km):
|
||||
<input
|
||||
name="distance"
|
||||
defaultValue={activity ? activity.distance : ''}
|
||||
className="form-control input-lg"
|
||||
min={0}
|
||||
required
|
||||
@ -77,7 +92,7 @@ function FormWithoutGpx (props) {
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-primary btn-lg btn-block"
|
||||
onClick={event => onAddSport(event)}
|
||||
onClick={event => onAddOrEdit(event, activity)}
|
||||
value="Submit"
|
||||
/>
|
||||
<input
|
||||
@ -93,7 +108,7 @@ function FormWithoutGpx (props) {
|
||||
export default connect(
|
||||
() => ({ }),
|
||||
dispatch => ({
|
||||
onAddSport: e => {
|
||||
onAddOrEdit: (e, activity) => {
|
||||
const d = e.target.form.duration.value.split(':')
|
||||
const duration = +d[0] * 60 * 60 + +d[1] * 60 + +d[2]
|
||||
|
||||
@ -106,7 +121,12 @@ export default connect(
|
||||
duration,
|
||||
sport_id: +e.target.form.sport_id.value,
|
||||
}
|
||||
if (activity) {
|
||||
data.id = activity.id
|
||||
dispatch(editActivity(data))
|
||||
} else {
|
||||
dispatch(addActivityWithoutGpx(data))
|
||||
}
|
||||
},
|
||||
})
|
||||
)(FormWithoutGpx)
|
||||
|
@ -5,6 +5,7 @@ import { Redirect, Route, Switch } from 'react-router-dom'
|
||||
|
||||
import ActivityAdd from './ActivityAdd'
|
||||
import ActivityDisplay from './ActivityDisplay'
|
||||
import ActivityEdit from './ActivityEdit'
|
||||
import NotFound from './../Others/NotFound'
|
||||
import { isLoggedIn } from '../../utils'
|
||||
|
||||
@ -19,7 +20,14 @@ class Activity extends React.Component {
|
||||
{isLoggedIn() ? (
|
||||
<Switch>
|
||||
<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} />
|
||||
</Switch>
|
||||
) : (<Redirect to="/login" />)}
|
||||
|
@ -91,3 +91,11 @@ input, textarea {
|
||||
padding: 50px;
|
||||
z-index: 1040;
|
||||
}
|
||||
|
||||
.custom-fa {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.unlink {
|
||||
color: black;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import togeojson from '@mapbox/togeojson'
|
||||
import { format, parse } from 'date-fns'
|
||||
import bbox from 'geojson-bbox'
|
||||
|
||||
export const apiUrl = `${process.env.REACT_APP_API_URL}/api/`
|
||||
@ -31,3 +32,11 @@ export const getGeoJson = gpxContent => {
|
||||
}
|
||||
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