Client - rename existing front
This commit is contained in:
@ -1,198 +0,0 @@
|
||||
// eslint-disable-next-line max-len
|
||||
// source: https://blog.flowandform.agency/create-a-custom-calendar-in-react-3df1bfd0b728
|
||||
import {
|
||||
addDays,
|
||||
addMonths,
|
||||
endOfMonth,
|
||||
endOfWeek,
|
||||
format,
|
||||
isSameDay,
|
||||
isSameMonth,
|
||||
isToday,
|
||||
startOfMonth,
|
||||
startOfWeek,
|
||||
subMonths,
|
||||
} from 'date-fns'
|
||||
import { enGB, fr } from 'date-fns/locale'
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import CalendarWorkouts from './CalendarWorkouts'
|
||||
import { getMonthWorkouts } from '../../actions/workouts'
|
||||
import { getDateWithTZ } from '../../utils'
|
||||
|
||||
const getStartAndEndMonth = (date, weekStartOnMonday) => {
|
||||
const monthStart = startOfMonth(date)
|
||||
const monthEnd = endOfMonth(date)
|
||||
const weekStartsOn = weekStartOnMonday ? 1 : 0
|
||||
return {
|
||||
start: startOfWeek(monthStart, { weekStartsOn }),
|
||||
end: endOfWeek(monthEnd),
|
||||
}
|
||||
}
|
||||
|
||||
class Calendar extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
const calendarDate = new Date()
|
||||
this.state = {
|
||||
currentMonth: calendarDate,
|
||||
startDate: getStartAndEndMonth(calendarDate, props.weekm).start,
|
||||
endDate: getStartAndEndMonth(calendarDate, props.weekm).end,
|
||||
weekStartOnMonday: props.weekm,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.loadMonthWorkouts(this.state.startDate, this.state.endDate)
|
||||
}
|
||||
|
||||
renderHeader(localeOptions) {
|
||||
const dateFormat = 'MMM yyyy'
|
||||
return (
|
||||
<div className="header row flex-middle">
|
||||
<div className="col col-start" onClick={() => this.handlePrevMonth()}>
|
||||
<i className="fa fa-chevron-left" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="col col-center">
|
||||
<span>
|
||||
{format(this.state.currentMonth, dateFormat, localeOptions)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="col col-end" onClick={() => this.handleNextMonth()}>
|
||||
<i className="fa fa-chevron-right" aria-hidden="true" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderDays(localeOptions) {
|
||||
const dateFormat = 'EEE'
|
||||
const days = []
|
||||
const { startDate } = this.state
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
days.push(
|
||||
<div className="col col-center" key={i}>
|
||||
{format(addDays(startDate, i), dateFormat, localeOptions)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return <div className="days row">{days}</div>
|
||||
}
|
||||
|
||||
filterWorkouts(day) {
|
||||
const { workouts, user } = this.props
|
||||
if (workouts) {
|
||||
return workouts
|
||||
.filter(act =>
|
||||
isSameDay(getDateWithTZ(act.workout_date, user.timezone), day)
|
||||
)
|
||||
.reverse()
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
renderCells() {
|
||||
const { currentMonth, startDate, endDate, weekStartOnMonday } = this.state
|
||||
const { sports } = this.props
|
||||
|
||||
const dateFormat = 'd'
|
||||
const rows = []
|
||||
|
||||
let days = []
|
||||
let day = startDate
|
||||
let formattedDate = ''
|
||||
|
||||
while (day <= endDate) {
|
||||
for (let i = 0; i < 7; i++) {
|
||||
formattedDate = format(day, dateFormat)
|
||||
const dayWorkouts = this.filterWorkouts(day)
|
||||
const isDisabled = isSameMonth(day, currentMonth) ? '' : '-disabled'
|
||||
const isWeekEnd = weekStartOnMonday
|
||||
? [5, 6].includes(i)
|
||||
: [0, 6].includes(i)
|
||||
days.push(
|
||||
<div
|
||||
className={`col cell ${isWeekEnd ? ' weekend' : ''}${
|
||||
isToday(day) ? ' today' : ''
|
||||
}`}
|
||||
key={day}
|
||||
>
|
||||
<div className={`img${isDisabled}`}>
|
||||
<span className="number">{formattedDate}</span>
|
||||
<CalendarWorkouts
|
||||
dayWorkouts={dayWorkouts}
|
||||
isDisabled={isDisabled}
|
||||
sports={sports}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
day = addDays(day, 1)
|
||||
}
|
||||
rows.push(
|
||||
<div className="row" key={day}>
|
||||
{days}
|
||||
</div>
|
||||
)
|
||||
days = []
|
||||
}
|
||||
return <div className="body">{rows}</div>
|
||||
}
|
||||
|
||||
updateStateDate(calendarDate) {
|
||||
const { start, end } = getStartAndEndMonth(
|
||||
calendarDate,
|
||||
this.state.weekStartOnMonday
|
||||
)
|
||||
this.setState({
|
||||
currentMonth: calendarDate,
|
||||
startDate: start,
|
||||
endDate: end,
|
||||
})
|
||||
this.props.loadMonthWorkouts(start, end)
|
||||
}
|
||||
|
||||
handleNextMonth() {
|
||||
const calendarDate = addMonths(this.state.currentMonth, 1)
|
||||
this.updateStateDate(calendarDate)
|
||||
}
|
||||
|
||||
handlePrevMonth() {
|
||||
const calendarDate = subMonths(this.state.currentMonth, 1)
|
||||
this.updateStateDate(calendarDate)
|
||||
}
|
||||
|
||||
render() {
|
||||
const localeOptions = {
|
||||
locale: this.props.language === 'fr' ? fr : enGB,
|
||||
}
|
||||
return (
|
||||
<div className="card workout-card">
|
||||
<div className="calendar">
|
||||
{this.renderHeader(localeOptions)}
|
||||
{this.renderDays(localeOptions)}
|
||||
{this.renderCells()}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
workouts: state.calendarWorkouts.data,
|
||||
language: state.language,
|
||||
sports: state.sports.data,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadMonthWorkouts: (start, end) => {
|
||||
const dateFormat = 'yyyy-MM-dd'
|
||||
dispatch(
|
||||
getMonthWorkouts(format(start, dateFormat), format(end, dateFormat))
|
||||
)
|
||||
},
|
||||
})
|
||||
)(Calendar)
|
@ -1,39 +0,0 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { recordsLabels } from '../../utils/workouts'
|
||||
|
||||
export default function CalendarWorkout(props) {
|
||||
const { isDisabled, isMore, sportImg, workout } = props
|
||||
return (
|
||||
<Link
|
||||
className={`calendar-workout${isMore}`}
|
||||
to={`/workouts/${workout.id}`}
|
||||
>
|
||||
<>
|
||||
<img
|
||||
alt="workout sport logo"
|
||||
className={`workout-sport ${isDisabled}`}
|
||||
src={sportImg}
|
||||
title={workout.title}
|
||||
/>
|
||||
{workout.records.length > 0 && (
|
||||
<sup>
|
||||
<i
|
||||
className="fa fa-trophy custom-fa-small"
|
||||
aria-hidden="true"
|
||||
title={workout.records.map(
|
||||
rec =>
|
||||
` ${
|
||||
recordsLabels.filter(
|
||||
r => r.record_type === rec.record_type
|
||||
)[0].label
|
||||
}`
|
||||
)}
|
||||
/>
|
||||
</sup>
|
||||
)}
|
||||
</>
|
||||
</Link>
|
||||
)
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
import CalendarWorkout from './CalendarWorkout'
|
||||
|
||||
export default class CalendarWorkouts extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {
|
||||
isHidden: true,
|
||||
}
|
||||
}
|
||||
|
||||
handleDisplayMore() {
|
||||
this.setState({
|
||||
isHidden: !this.state.isHidden,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dayWorkouts, isDisabled, sports } = this.props
|
||||
const { isHidden } = this.state
|
||||
return (
|
||||
<div>
|
||||
{dayWorkouts.map(act => (
|
||||
<CalendarWorkout
|
||||
key={act.id}
|
||||
workout={act}
|
||||
isDisabled={isDisabled}
|
||||
isMore=""
|
||||
sportImg={sports.filter(s => s.id === act.sport_id).map(s => s.img)}
|
||||
/>
|
||||
))}
|
||||
{dayWorkouts.length > 2 && (
|
||||
<i
|
||||
className={`fa fa-${isHidden ? 'plus' : 'times'} calendar-more`}
|
||||
aria-hidden="true"
|
||||
onClick={() => this.handleDisplayMore()}
|
||||
title="show more workouts"
|
||||
/>
|
||||
)}
|
||||
{!isHidden && (
|
||||
<div className="calendar-display-more">
|
||||
{dayWorkouts.map(act => (
|
||||
<CalendarWorkout
|
||||
key={act.id}
|
||||
workout={act}
|
||||
isDisabled={isDisabled}
|
||||
isMore="-more"
|
||||
sportImg={sports
|
||||
.filter(s => s.id === act.sport_id)
|
||||
.map(s => s.img)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { formatRecord, translateSports } from '../../utils/workouts'
|
||||
|
||||
export default function RecordsCard(props) {
|
||||
const { records, sports, t, user } = props
|
||||
const translatedSports = translateSports(sports, t)
|
||||
const recordsBySport = records.reduce((sportList, record) => {
|
||||
const sport = translatedSports.find(s => s.id === record.sport_id)
|
||||
if (sportList[sport.label] === void 0) {
|
||||
sportList[sport.label] = {
|
||||
img: sport.img,
|
||||
records: [],
|
||||
}
|
||||
}
|
||||
sportList[sport.label].records.push(formatRecord(record, user.timezone))
|
||||
return sportList
|
||||
}, {})
|
||||
|
||||
return (
|
||||
<div className="card workout-card">
|
||||
<div className="card-header">{t('workouts:Personal records')}</div>
|
||||
<div className="card-body">
|
||||
{Object.keys(recordsBySport).length === 0
|
||||
? t('common:No records.')
|
||||
: Object.keys(recordsBySport)
|
||||
.sort()
|
||||
.map(sportLabel => (
|
||||
<div key={sportLabel}>
|
||||
<span className="heading-span">
|
||||
<img
|
||||
alt={`${sportLabel} logo`}
|
||||
className="record-logo"
|
||||
src={recordsBySport[sportLabel].img}
|
||||
/>
|
||||
{sportLabel}
|
||||
</span>
|
||||
{/* eslint-disable-next-line max-len */}
|
||||
<table className="table table-borderless table-sm record-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan="3">
|
||||
<img
|
||||
alt={`${sportLabel} logo`}
|
||||
className="record-logo"
|
||||
src={recordsBySport[sportLabel].img}
|
||||
/>
|
||||
{sportLabel}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{recordsBySport[sportLabel].records.map(rec => (
|
||||
<tr className="record-tr" key={rec.id}>
|
||||
<td className="record-td">
|
||||
{t(`workouts:${rec.record_type}`)}
|
||||
</td>
|
||||
<td className="record-td text-right">{rec.value}</td>
|
||||
<td className="record-td text-right">
|
||||
<Link to={`/workouts/${rec.workout_id}`}>
|
||||
{rec.workout_date}
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
import { endOfMonth, startOfMonth } from 'date-fns'
|
||||
import React from 'react'
|
||||
|
||||
import Stats from '../Common/Stats'
|
||||
|
||||
export default class Statistics extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
const date = new Date()
|
||||
this.state = {
|
||||
start: startOfMonth(date),
|
||||
end: endOfMonth(date),
|
||||
duration: 'week',
|
||||
type: 'by_time',
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { t } = this.props
|
||||
return (
|
||||
<div className="card workout-card">
|
||||
<div className="card-header">{t('dashboard:This month')}</div>
|
||||
<div className="card-body">
|
||||
<Stats
|
||||
displayEmpty={false}
|
||||
statsParams={this.state}
|
||||
t={t}
|
||||
withElevation={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function UserStatistics(props) {
|
||||
const { t, user } = props
|
||||
const days = user.total_duration.match(/day/g)
|
||||
? `${user.total_duration.split(' ')[0]} ${
|
||||
user.total_duration.match(/days/g) ? t('common:days') : t('common:day')
|
||||
}`
|
||||
: `0 ${t('common:days')},`
|
||||
let duration = user.total_duration.match(/day/g)
|
||||
? user.total_duration.split(', ')[1]
|
||||
: user.total_duration
|
||||
duration = `${duration.split(':')[0]}h ${duration.split(':')[1]}min`
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||
<div className="card workout-card">
|
||||
<div className="card-body row">
|
||||
<div className="col-3">
|
||||
<i className="fa fa-calendar fa-3x fa-color" />
|
||||
</div>
|
||||
<div className="col-9 text-right">
|
||||
<div className="huge">{user.nb_workouts}</div>
|
||||
<div>{`${
|
||||
user.nb_workouts === 1
|
||||
? t('common:workout')
|
||||
: t('common:workouts')
|
||||
}`}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||
<div className="card workout-card">
|
||||
<div className="card-body row">
|
||||
<div className="col-3">
|
||||
<i className="fa fa-road fa-3x fa-color" />
|
||||
</div>
|
||||
<div className="col-9 text-right">
|
||||
<div className="huge">
|
||||
{Number(user.total_distance).toFixed(2)}
|
||||
</div>
|
||||
<div>km</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||
<div className="card workout-card">
|
||||
<div className="card-body row">
|
||||
<div className="col-3">
|
||||
<i className="fa fa-clock-o fa-3x fa-color" />
|
||||
</div>
|
||||
<div className="col-9 text-right">
|
||||
<div className="huge">{days}</div>
|
||||
<div>{duration}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||
<div className="card workout-card">
|
||||
<div className="card-body row">
|
||||
<div className="col-3">
|
||||
<i className="fa fa-tags fa-3x fa-color" />
|
||||
</div>
|
||||
<div className="col-9 text-right">
|
||||
<div className="huge">{user.nb_sports}</div>
|
||||
<div>{`${
|
||||
user.nb_sports === 1 ? t('common:sport') : t('common:sports')
|
||||
}`}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
import { format } from 'date-fns'
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import StaticMap from '../Common/StaticMap'
|
||||
import { getDateWithTZ } from '../../utils'
|
||||
|
||||
export default function WorkoutCard(props) {
|
||||
const { sports, t, user, workout } = props
|
||||
|
||||
return (
|
||||
<div className="card workout-card text-center">
|
||||
<div className="card-header">
|
||||
<Link to={`/workouts/${workout.id}`}>
|
||||
{sports
|
||||
.filter(sport => sport.id === workout.sport_id)
|
||||
.map(sport => t(`sports:${sport.label}`))}{' '}
|
||||
-{' '}
|
||||
{format(
|
||||
getDateWithTZ(workout.workout_date, user.timezone),
|
||||
'dd/MM/yyyy HH:mm'
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="row">
|
||||
{workout.map && (
|
||||
<div className="col">
|
||||
<StaticMap workout={workout} />
|
||||
</div>
|
||||
)}
|
||||
<div className={`col${workout.map ? ' col-with-map' : ''}`}>
|
||||
<p>
|
||||
<i className="fa fa-clock-o" aria-hidden="true" />{' '}
|
||||
{t('workouts:Duration')}: {workout.moving}
|
||||
{workout.map ? (
|
||||
<span>
|
||||
<br />
|
||||
<br />
|
||||
</span>
|
||||
) : (
|
||||
' - '
|
||||
)}
|
||||
<i className="fa fa-road" aria-hidden="true" />{' '}
|
||||
{t('workouts:Distance')}: {workout.distance} km
|
||||
<br />
|
||||
</p>
|
||||
{workout.min_alt && workout.max_alt && (
|
||||
<p>
|
||||
<i className="fi-mountains custom-fa" />
|
||||
{t('workouts:Min. altitude')}: {workout.min_alt}m
|
||||
<br />
|
||||
{t('workouts:Max. altitude')}: {workout.max_alt}m
|
||||
<br />
|
||||
</p>
|
||||
)}
|
||||
{workout.ascent && workout.descent && (
|
||||
<p>
|
||||
<i className="fa fa-location-arrow custom-fa" />
|
||||
{t('workouts:Ascent')}: {workout.ascent}m
|
||||
<br />
|
||||
{t('workouts:Descent')}: {workout.descent}m
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import Calendar from './Calendar'
|
||||
import Message from '../Common/Message'
|
||||
import NoWorkouts from '../Common/NoWorkouts'
|
||||
import Records from './Records'
|
||||
import Statistics from './Statistics'
|
||||
import UserStatistics from './UserStatistics'
|
||||
import WorkoutCard from './WorkoutCard'
|
||||
import { getOrUpdateData } from '../../actions'
|
||||
import { getMoreWorkouts } from '../../actions/workouts'
|
||||
|
||||
class DashBoard extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {
|
||||
page: 1,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.loadWorkouts()
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loadMoreWorkouts, message, records, sports, t, user, workouts } =
|
||||
this.props
|
||||
const paginationEnd =
|
||||
workouts.length > 0
|
||||
? workouts[workouts.length - 1].previous_workout === null
|
||||
: true
|
||||
const { page } = this.state
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>FitTrackee - {t('common:Dashboard')}</title>
|
||||
</Helmet>
|
||||
{message ? (
|
||||
<Message message={message} t={t} />
|
||||
) : (
|
||||
workouts &&
|
||||
user.total_duration &&
|
||||
sports.length > 0 && (
|
||||
<div className="container dashboard">
|
||||
<UserStatistics user={user} t={t} />
|
||||
<div className="row">
|
||||
<div className="col-md-4">
|
||||
<Statistics t={t} />
|
||||
<Records
|
||||
t={t}
|
||||
records={records}
|
||||
sports={sports}
|
||||
user={user}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-8">
|
||||
<Calendar weekm={user.weekm} />
|
||||
{workouts.length > 0 ? (
|
||||
workouts.map(workout => (
|
||||
<WorkoutCard
|
||||
workout={workout}
|
||||
key={workout.id}
|
||||
sports={sports}
|
||||
t={t}
|
||||
user={user}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<NoWorkouts t={t} />
|
||||
)}
|
||||
{!paginationEnd && (
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-default btn-md btn-block"
|
||||
value="Load more workouts"
|
||||
onClick={() => {
|
||||
loadMoreWorkouts(page + 1)
|
||||
this.setState({ page: page + 1 })
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withTranslation()(
|
||||
connect(
|
||||
state => ({
|
||||
workouts: state.workouts.data,
|
||||
message: state.message,
|
||||
records: state.records.data,
|
||||
sports: state.sports.data,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadWorkouts: () => {
|
||||
dispatch(getOrUpdateData('getData', 'workouts', { page: 1 }))
|
||||
dispatch(getOrUpdateData('getData', 'records'))
|
||||
},
|
||||
loadMoreWorkouts: page => {
|
||||
dispatch(getMoreWorkouts({ page }))
|
||||
},
|
||||
})
|
||||
)(DashBoard)
|
||||
)
|
Reference in New Issue
Block a user