Stats charts minor fix
This commit is contained in:
@@ -5,132 +5,132 @@ import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContai
|
|||||||
import { t } from '../services/i18n';
|
import { t } from '../services/i18n';
|
||||||
|
|
||||||
interface StatsProps {
|
interface StatsProps {
|
||||||
sessions: WorkoutSession[];
|
sessions: WorkoutSession[];
|
||||||
lang: Language;
|
lang: Language;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Stats: React.FC<StatsProps> = ({ sessions, lang }) => {
|
const Stats: React.FC<StatsProps> = ({ sessions, lang }) => {
|
||||||
|
|
||||||
const volumeData = useMemo(() => {
|
const volumeData = useMemo(() => {
|
||||||
const data = [...sessions].reverse().map(session => {
|
const data = [...sessions].reverse().map(session => {
|
||||||
const sessionWeight = session.userBodyWeight || 70;
|
const sessionWeight = session.userBodyWeight || 70;
|
||||||
const work = session.sets.reduce((acc, set) => {
|
const work = session.sets.reduce((acc, set) => {
|
||||||
let setWork = 0;
|
let setWork = 0;
|
||||||
const reps = set.reps || 0;
|
const reps = set.reps || 0;
|
||||||
const weight = set.weight || 0;
|
const weight = set.weight || 0;
|
||||||
if (set.type === ExerciseType.STRENGTH) {
|
if (set.type === ExerciseType.STRENGTH) {
|
||||||
setWork = weight * reps;
|
setWork = weight * reps;
|
||||||
} else if (set.type === ExerciseType.BODYWEIGHT) {
|
} else if (set.type === ExerciseType.BODYWEIGHT) {
|
||||||
const percentage = set.bodyWeightPercentage || 100;
|
const percentage = set.bodyWeightPercentage || 100;
|
||||||
const effectiveBw = sessionWeight * (percentage / 100);
|
const effectiveBw = sessionWeight * (percentage / 100);
|
||||||
setWork = (effectiveBw + weight) * reps;
|
setWork = (effectiveBw + weight) * reps;
|
||||||
} else if (set.type === ExerciseType.STATIC) {
|
} else if (set.type === ExerciseType.STATIC) {
|
||||||
setWork = 0;
|
setWork = 0;
|
||||||
}
|
}
|
||||||
return acc + Math.max(0, setWork);
|
return acc + Math.max(0, setWork);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
date: new Date(session.startTime).toLocaleDateString(lang === 'ru' ? 'ru-RU' : 'en-US', { day: 'numeric', month: 'short' }),
|
date: new Date(session.startTime).toLocaleDateString(lang === 'ru' ? 'ru-RU' : 'en-US', { day: 'numeric', month: 'short' }),
|
||||||
work: Math.round(work)
|
work: Math.round(work)
|
||||||
};
|
};
|
||||||
}).filter(d => d.work > 0);
|
}).filter(d => d.work > 0);
|
||||||
return data;
|
return data;
|
||||||
}, [sessions, lang]);
|
}, [sessions, lang]);
|
||||||
|
|
||||||
const setsData = useMemo(() => {
|
const setsData = useMemo(() => {
|
||||||
return [...sessions].reverse().map(session => ({
|
return [...sessions].reverse().map(session => ({
|
||||||
date: new Date(session.startTime).toLocaleDateString(lang === 'ru' ? 'ru-RU' : 'en-US', { day: 'numeric', month: 'short' }),
|
|
||||||
sets: session.sets.length
|
|
||||||
}));
|
|
||||||
}, [sessions, lang]);
|
|
||||||
|
|
||||||
const weightData = useMemo(() => {
|
|
||||||
return [...sessions].reverse()
|
|
||||||
.filter(s => s.userBodyWeight)
|
|
||||||
.map(session => ({
|
|
||||||
date: new Date(session.startTime).toLocaleDateString(lang === 'ru' ? 'ru-RU' : 'en-US', { day: 'numeric', month: 'short' }),
|
date: new Date(session.startTime).toLocaleDateString(lang === 'ru' ? 'ru-RU' : 'en-US', { day: 'numeric', month: 'short' }),
|
||||||
weight: session.userBodyWeight
|
sets: session.sets.length
|
||||||
}));
|
}));
|
||||||
}, [sessions, lang]);
|
}, [sessions, lang]);
|
||||||
|
|
||||||
if (sessions.length < 2) {
|
const weightData = useMemo(() => {
|
||||||
return (
|
return [...sessions].reverse()
|
||||||
<div className="p-8 text-center text-on-surface-variant flex flex-col items-center justify-center h-full">
|
.filter(s => s.userBodyWeight)
|
||||||
<p>{t('not_enough_data', lang)}</p>
|
.map(session => ({
|
||||||
</div>
|
date: new Date(session.startTime).toLocaleDateString(lang === 'ru' ? 'ru-RU' : 'en-US', { day: 'numeric', month: 'short' }),
|
||||||
)
|
weight: session.userBodyWeight
|
||||||
}
|
}));
|
||||||
|
}, [sessions, lang]);
|
||||||
|
|
||||||
return (
|
if (sessions.length < 2) {
|
||||||
<div className="h-full overflow-y-auto p-4 space-y-6 pb-24 bg-surface">
|
return (
|
||||||
<h2 className="text-3xl font-normal text-on-surface mb-2 pl-2">{t('progress', lang)}</h2>
|
<div className="p-8 text-center text-on-surface-variant flex flex-col items-center justify-center h-full">
|
||||||
|
<p>{t('not_enough_data', lang)}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
{/* Volume Chart */}
|
return (
|
||||||
<div className="bg-surface-container p-5 rounded-[24px] shadow-elevation-1 border border-outline-variant/20">
|
<div className="h-full overflow-y-auto p-4 space-y-6 pb-24 bg-surface">
|
||||||
<div className="flex justify-between items-end mb-6">
|
<h2 className="text-3xl font-normal text-on-surface mb-2 pl-2">{t('progress', lang)}</h2>
|
||||||
<div>
|
|
||||||
<h3 className="text-title-medium font-medium text-on-surface">{t('volume_title', lang)}</h3>
|
{/* Volume Chart */}
|
||||||
<p className="text-xs text-on-surface-variant mt-1">{t('volume_subtitle', lang)}</p>
|
<div className="bg-surface-container p-5 rounded-[24px] shadow-elevation-1 border border-outline-variant/20">
|
||||||
</div>
|
<div className="flex justify-between items-end mb-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-title-medium font-medium text-on-surface">{t('volume_title', lang)}</h3>
|
||||||
|
<p className="text-xs text-on-surface-variant mt-1">{t('volume_subtitle', lang)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="h-64 min-h-64 w-full">
|
||||||
|
<ResponsiveContainer width="100%" height={256}>
|
||||||
|
<LineChart data={volumeData}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" stroke="#49454F" vertical={false} opacity={0.5} />
|
||||||
|
<XAxis dataKey="date" stroke="#CAC4D0" fontSize={12} tickLine={false} axisLine={false} dy={10} />
|
||||||
|
<YAxis stroke="#CAC4D0" fontSize={12} tickLine={false} axisLine={false} tickFormatter={(val) => `${(val / 1000).toFixed(1)}k`} />
|
||||||
|
<Tooltip
|
||||||
|
contentStyle={{ backgroundColor: '#2B2930', borderColor: '#49454F', color: '#E6E0E9', borderRadius: '12px' }}
|
||||||
|
itemStyle={{ color: '#D0BCFF' }}
|
||||||
|
formatter={(val: number) => [`${val.toLocaleString()} kg`, t('volume_title', lang)]}
|
||||||
|
/>
|
||||||
|
<Line type="monotone" dataKey="work" stroke="#D0BCFF" strokeWidth={3} dot={{ r: 4, fill: '#D0BCFF' }} activeDot={{ r: 6 }} />
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sets Chart */}
|
||||||
|
<div className="bg-surface-container p-5 rounded-[24px] shadow-elevation-1 border border-outline-variant/20">
|
||||||
|
<h3 className="text-title-medium font-medium text-on-surface mb-6">{t('sets_title', lang)}</h3>
|
||||||
|
<div className="h-64 min-h-64 w-full">
|
||||||
|
<ResponsiveContainer width="100%" height={256}>
|
||||||
|
<BarChart data={setsData}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" stroke="#49454F" vertical={false} opacity={0.5} />
|
||||||
|
<XAxis dataKey="date" stroke="#CAC4D0" fontSize={12} tickLine={false} axisLine={false} dy={10} />
|
||||||
|
<YAxis stroke="#CAC4D0" fontSize={12} tickLine={false} axisLine={false} />
|
||||||
|
<Tooltip
|
||||||
|
contentStyle={{ backgroundColor: '#2B2930', borderColor: '#49454F', color: '#E6E0E9', borderRadius: '12px' }}
|
||||||
|
cursor={{ fill: 'rgba(255,255,255,0.05)' }}
|
||||||
|
/>
|
||||||
|
<Bar dataKey="sets" fill="#CCC2DC" radius={[4, 4, 0, 0]} />
|
||||||
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Body Weight Chart */}
|
||||||
|
<div className="bg-surface-container p-5 rounded-[24px] shadow-elevation-1 border border-outline-variant/20">
|
||||||
|
<h3 className="text-title-medium font-medium text-on-surface mb-6">{t('weight_title', lang)}</h3>
|
||||||
|
<div className="h-64 min-h-64 w-full">
|
||||||
|
<ResponsiveContainer width="100%" height={256}>
|
||||||
|
<LineChart data={weightData}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" stroke="#49454F" vertical={false} opacity={0.5} />
|
||||||
|
<XAxis dataKey="date" stroke="#CAC4D0" fontSize={12} tickLine={false} axisLine={false} dy={10} />
|
||||||
|
<YAxis domain={['auto', 'auto']} stroke="#CAC4D0" fontSize={12} tickLine={false} axisLine={false} />
|
||||||
|
<Tooltip
|
||||||
|
contentStyle={{ backgroundColor: '#2B2930', borderColor: '#49454F', color: '#E6E0E9', borderRadius: '12px' }}
|
||||||
|
itemStyle={{ color: '#6EE7B7' }}
|
||||||
|
formatter={(val: number) => [`${val} kg`, t('weight_kg', lang)]}
|
||||||
|
/>
|
||||||
|
<Line type="monotone" dataKey="weight" stroke="#6EE7B7" strokeWidth={3} dot={{ r: 4, fill: '#6EE7B7' }} activeDot={{ r: 6 }} />
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-64 w-full">
|
);
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
|
||||||
<LineChart data={volumeData}>
|
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="#49454F" vertical={false} opacity={0.5} />
|
|
||||||
<XAxis dataKey="date" stroke="#CAC4D0" fontSize={12} tickLine={false} axisLine={false} dy={10} />
|
|
||||||
<YAxis stroke="#CAC4D0" fontSize={12} tickLine={false} axisLine={false} tickFormatter={(val) => `${(val/1000).toFixed(1)}k`} />
|
|
||||||
<Tooltip
|
|
||||||
contentStyle={{ backgroundColor: '#2B2930', borderColor: '#49454F', color: '#E6E0E9', borderRadius: '12px' }}
|
|
||||||
itemStyle={{ color: '#D0BCFF' }}
|
|
||||||
formatter={(val: number) => [`${val.toLocaleString()} kg`, t('volume_title', lang)]}
|
|
||||||
/>
|
|
||||||
<Line type="monotone" dataKey="work" stroke="#D0BCFF" strokeWidth={3} dot={{r: 4, fill: '#D0BCFF'}} activeDot={{r: 6}} />
|
|
||||||
</LineChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sets Chart */}
|
|
||||||
<div className="bg-surface-container p-5 rounded-[24px] shadow-elevation-1 border border-outline-variant/20">
|
|
||||||
<h3 className="text-title-medium font-medium text-on-surface mb-6">{t('sets_title', lang)}</h3>
|
|
||||||
<div className="h-64 w-full">
|
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
|
||||||
<BarChart data={setsData}>
|
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="#49454F" vertical={false} opacity={0.5} />
|
|
||||||
<XAxis dataKey="date" stroke="#CAC4D0" fontSize={12} tickLine={false} axisLine={false} dy={10} />
|
|
||||||
<YAxis stroke="#CAC4D0" fontSize={12} tickLine={false} axisLine={false} />
|
|
||||||
<Tooltip
|
|
||||||
contentStyle={{ backgroundColor: '#2B2930', borderColor: '#49454F', color: '#E6E0E9', borderRadius: '12px' }}
|
|
||||||
cursor={{fill: 'rgba(255,255,255,0.05)'}}
|
|
||||||
/>
|
|
||||||
<Bar dataKey="sets" fill="#CCC2DC" radius={[4, 4, 0, 0]} />
|
|
||||||
</BarChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Body Weight Chart */}
|
|
||||||
<div className="bg-surface-container p-5 rounded-[24px] shadow-elevation-1 border border-outline-variant/20">
|
|
||||||
<h3 className="text-title-medium font-medium text-on-surface mb-6">{t('weight_title', lang)}</h3>
|
|
||||||
<div className="h-64 w-full">
|
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
|
||||||
<LineChart data={weightData}>
|
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="#49454F" vertical={false} opacity={0.5} />
|
|
||||||
<XAxis dataKey="date" stroke="#CAC4D0" fontSize={12} tickLine={false} axisLine={false} dy={10} />
|
|
||||||
<YAxis domain={['auto', 'auto']} stroke="#CAC4D0" fontSize={12} tickLine={false} axisLine={false} />
|
|
||||||
<Tooltip
|
|
||||||
contentStyle={{ backgroundColor: '#2B2930', borderColor: '#49454F', color: '#E6E0E9', borderRadius: '12px' }}
|
|
||||||
itemStyle={{ color: '#6EE7B7' }}
|
|
||||||
formatter={(val: number) => [`${val} kg`, t('weight_kg', lang)]}
|
|
||||||
/>
|
|
||||||
<Line type="monotone" dataKey="weight" stroke="#6EE7B7" strokeWidth={3} dot={{r: 4, fill: '#6EE7B7'}} activeDot={{r: 6}} />
|
|
||||||
</LineChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Stats;
|
export default Stats;
|
||||||
|
|||||||
Reference in New Issue
Block a user