file reorganization
This commit is contained in:
@ -1,201 +0,0 @@
|
||||
// eslint-disable-next-line max-len
|
||||
// source: https://blog.flowandform.agency/create-a-custom-calendar-in-react-3df1bfd0b728
|
||||
import dateFns from 'date-fns'
|
||||
import React, { Fragment } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { getMonthActivities } from '../../actions/activities'
|
||||
import { getDateWithTZ, recordsLabels } from '../../utils'
|
||||
|
||||
const getStartAndEndMonth = date => {
|
||||
const monthStart = dateFns.startOfMonth(date)
|
||||
const monthEnd = dateFns.endOfMonth(date)
|
||||
return {
|
||||
start: dateFns.startOfWeek(monthStart),
|
||||
end: dateFns.endOfWeek(monthEnd),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Calendar extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
const calendarDate = new Date()
|
||||
this.state = {
|
||||
currentMonth: calendarDate,
|
||||
startDate: getStartAndEndMonth(calendarDate).start,
|
||||
endDate: getStartAndEndMonth(calendarDate).end,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.loadMonthActivities(this.state.startDate, this.state.endDate)
|
||||
}
|
||||
|
||||
renderHeader() {
|
||||
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>
|
||||
{dateFns.format(this.state.currentMonth, dateFormat)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="col col-end" onClick={() => this.handleNextMonth()}>
|
||||
<i
|
||||
className="fa fa-chevron-right"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderDays() {
|
||||
const dateFormat = 'ddd'
|
||||
const days = []
|
||||
const { startDate } = this.state
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
days.push(
|
||||
<div className="col col-center" key={i}>
|
||||
{dateFns.format(dateFns.addDays(startDate, i), dateFormat)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return <div className="days row">{days}</div>
|
||||
}
|
||||
|
||||
filterActivities(day) {
|
||||
const { activities, user } = this.props
|
||||
if (activities) {
|
||||
return activities
|
||||
.filter(act => dateFns.isSameDay(
|
||||
getDateWithTZ(act.activity_date, user.timezone),
|
||||
day
|
||||
))
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
renderCells() {
|
||||
const { currentMonth, startDate, endDate } = 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 = dateFns.format(day, dateFormat)
|
||||
const dayActivities = this.filterActivities(day)
|
||||
const isDisabled = dateFns.isSameMonth(day, currentMonth)
|
||||
? ''
|
||||
: 'disabled'
|
||||
days.push(
|
||||
<div
|
||||
className={`col cell img-${isDisabled}`}
|
||||
key={day}
|
||||
>
|
||||
<span className="number">{formattedDate}</span>
|
||||
{dayActivities.map(act => (
|
||||
<Link key={act.id} to={`/activities/${act.id}`}>
|
||||
<Fragment>
|
||||
<img
|
||||
alt="activity sport logo"
|
||||
className={`activity-sport ${isDisabled}`}
|
||||
src={sports
|
||||
.filter(s => s.id === act.sport_id)
|
||||
.map(s => s.img)}
|
||||
title={act.title}
|
||||
/>
|
||||
{act.records.length > 0 && (
|
||||
<sup>
|
||||
<i
|
||||
className="fa fa-trophy custom-fa-small"
|
||||
aria-hidden="true"
|
||||
title={act.records.map(rec => ` ${
|
||||
recordsLabels.filter(
|
||||
r => r.record_type === rec.record_type
|
||||
)[0].label
|
||||
}`)}
|
||||
/>
|
||||
</sup>
|
||||
)}
|
||||
</Fragment>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
day = dateFns.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.setState({
|
||||
currentMonth: calendarDate,
|
||||
startDate: start,
|
||||
endDate: end,
|
||||
})
|
||||
this.props.loadMonthActivities(start, end)
|
||||
}
|
||||
|
||||
handleNextMonth() {
|
||||
const calendarDate = dateFns.addMonths(this.state.currentMonth, 1)
|
||||
this.updateStateDate(calendarDate)
|
||||
}
|
||||
|
||||
handlePrevMonth() {
|
||||
const calendarDate = dateFns.subMonths(this.state.currentMonth, 1)
|
||||
this.updateStateDate(calendarDate)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="card activity-card">
|
||||
<div className="calendar">
|
||||
{this.renderHeader()}
|
||||
{this.renderDays()}
|
||||
{this.renderCells()}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
activities: state.calendarActivities.data,
|
||||
sports: state.sports.data,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadMonthActivities: (start, end) => {
|
||||
const dateFormat = 'YYYY-MM-DD'
|
||||
dispatch(getMonthActivities(
|
||||
dateFns.format(start, dateFormat),
|
||||
dateFns.format(end, dateFormat),
|
||||
))
|
||||
},
|
||||
})
|
||||
)(Calendar)
|
@ -1,42 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function CustomModal(props) {
|
||||
return (
|
||||
<div className="custom-modal-backdrop">
|
||||
<div className="custom-modal">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">{props.title}</h5>
|
||||
<button
|
||||
type="button"
|
||||
className="close"
|
||||
aria-label="Close"
|
||||
onClick={() => props.close}
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<p>{props.text}</p>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
onClick={() => props.confirm()}
|
||||
>
|
||||
Yes
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
onClick={() => props.close()}
|
||||
>
|
||||
No
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
import { formatDuration } from '../../utils'
|
||||
|
||||
const formatValue = (displayedData, value) => displayedData === 'duration'
|
||||
? formatDuration(value, true)
|
||||
: displayedData === 'distance'
|
||||
? value.toFixed(3)
|
||||
: value
|
||||
|
||||
|
||||
/**
|
||||
* @return {null}
|
||||
*/
|
||||
export default function CustomTooltip (props) {
|
||||
const { active } = props
|
||||
if (active) {
|
||||
const { displayedData, payload, label } = props
|
||||
let total = 0
|
||||
payload.map(p => total += p.value)
|
||||
return (
|
||||
<div className="custom-tooltip">
|
||||
<p className="custom-tooltip-label">{label}</p>
|
||||
{payload.map(p => (
|
||||
<p key={p.name} style={{ color: p.fill }}>
|
||||
{p.name}: {formatValue(displayedData, p.value)} {p.unit}
|
||||
</p>))
|
||||
}
|
||||
{payload.length > 0 && (
|
||||
<p>
|
||||
Total: {formatValue(displayedData, total)} {payload[0].unit}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis
|
||||
} from 'recharts'
|
||||
|
||||
import { activityColors, formatDuration } from '../../../utils'
|
||||
import CustomTooltip from '../CustomTooltip'
|
||||
|
||||
|
||||
export default class StatsCharts extends React.PureComponent {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {
|
||||
displayedData: 'distance'
|
||||
}
|
||||
}
|
||||
handleRadioChange (changeEvent) {
|
||||
this.setState({
|
||||
displayedData: changeEvent.target.name
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { displayedData } = this.state
|
||||
const { sports, stats } = this.props
|
||||
if (Object.keys(stats).length === 0) {
|
||||
return 'No workouts'
|
||||
}
|
||||
return (
|
||||
<div className="chart-stats">
|
||||
<div className="row chart-radio">
|
||||
<label className="radioLabel col">
|
||||
<input
|
||||
type="radio"
|
||||
name="distance"
|
||||
checked={displayedData === 'distance'}
|
||||
onChange={e => this.handleRadioChange(e)}
|
||||
/>
|
||||
distance
|
||||
</label>
|
||||
<label className="radioLabel col">
|
||||
<input
|
||||
type="radio"
|
||||
name="duration"
|
||||
checked={displayedData === 'duration'}
|
||||
onChange={e => this.handleRadioChange(e)}
|
||||
/>
|
||||
duration
|
||||
</label>
|
||||
<label className="radioLabel col">
|
||||
<input
|
||||
type="radio"
|
||||
name="activities"
|
||||
checked={displayedData === 'activities'}
|
||||
onChange={e => this.handleRadioChange(e)}
|
||||
/>
|
||||
activities
|
||||
</label>
|
||||
</div>
|
||||
<ResponsiveContainer height={300}>
|
||||
<BarChart
|
||||
data={stats[displayedData]}
|
||||
margin={{ top: 15, bottom: 0 }}
|
||||
>
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
interval={0} // to force to display all ticks
|
||||
/>
|
||||
<YAxis
|
||||
tickFormatter={value => displayedData === 'distance'
|
||||
? `${value} km`
|
||||
: displayedData === 'duration'
|
||||
? formatDuration(value)
|
||||
: value
|
||||
}
|
||||
/>
|
||||
<Tooltip content={
|
||||
<CustomTooltip
|
||||
displayedData={displayedData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{sports.map((s, i) => (
|
||||
<Bar
|
||||
key={s.id}
|
||||
dataKey={s.label}
|
||||
stackId="a"
|
||||
fill={activityColors[i]}
|
||||
unit={displayedData === 'distance' ? ' km' : ''}
|
||||
/>
|
||||
))}
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
import { format } from 'date-fns'
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import { getStats } from '../../../actions/stats'
|
||||
import { formatStats } from '../../../utils'
|
||||
import StatsChart from './StatsChart'
|
||||
|
||||
|
||||
class Statistics extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
this.props.loadMonthActivities(
|
||||
this.props.user.id,
|
||||
this.props.statsParams,
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { sports, statistics, statsParams } = this.props
|
||||
const stats = formatStats(statistics, sports, statsParams)
|
||||
return (
|
||||
<>
|
||||
{Object.keys(statistics).length === 0 ? (
|
||||
'No workouts'
|
||||
) : (
|
||||
<StatsChart sports={sports} stats={stats} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
sports: state.sports.data,
|
||||
statistics: state.statistics.data,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadMonthActivities: (userId, data) => {
|
||||
const dateFormat = 'YYYY-MM-DD'
|
||||
const params = {
|
||||
from: format(data.start, dateFormat),
|
||||
to: format(data.end, dateFormat),
|
||||
time: data.duration
|
||||
}
|
||||
dispatch(getStats(userId, data.type, params))
|
||||
},
|
||||
})
|
||||
)(Statistics)
|
Reference in New Issue
Block a user