Dashboard
This commit is contained in:
@@ -57,18 +57,35 @@ public class GoalMetricPeriodTotalDAO extends AbstractDAO<GoalMetricPeriodTotal>
|
||||
return em.createQuery(cq).setMaxResults(1).getResultStream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public List<GoalMetricPeriodTotal> findByAthleteAndWeek(
|
||||
public List<GoalMetricPeriodTotal> findByAthleteForDashboard(
|
||||
final long athleteId,
|
||||
final int year,
|
||||
final int calendarWeek) {
|
||||
final int weekYear,
|
||||
final int weekIndex,
|
||||
final int monthYear,
|
||||
final int monthIndex,
|
||||
final int calendarYear) {
|
||||
final CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
final CriteriaQuery<GoalMetricPeriodTotal> cq = cb.createQuery(GoalMetricPeriodTotal.class);
|
||||
final Root<GoalMetricPeriodTotal> root = cq.from(GoalMetricPeriodTotal.class);
|
||||
cq.select(root).where(
|
||||
cb.equal(root.get(GoalMetricPeriodTotal_.athleteId), athleteId),
|
||||
cb.equal(root.get(GoalMetricPeriodTotal_.period), GoalPeriodEnum.WEEKLY),
|
||||
cb.equal(root.get(GoalMetricPeriodTotal_.periodYear), year),
|
||||
cb.equal(root.get(GoalMetricPeriodTotal_.periodIndex), calendarWeek)
|
||||
cb.or(
|
||||
cb.and(
|
||||
cb.equal(root.get(GoalMetricPeriodTotal_.period), GoalPeriodEnum.WEEKLY),
|
||||
cb.equal(root.get(GoalMetricPeriodTotal_.periodYear), weekYear),
|
||||
cb.equal(root.get(GoalMetricPeriodTotal_.periodIndex), weekIndex)
|
||||
),
|
||||
cb.and(
|
||||
cb.equal(root.get(GoalMetricPeriodTotal_.period), GoalPeriodEnum.MONTHLY),
|
||||
cb.equal(root.get(GoalMetricPeriodTotal_.periodYear), monthYear),
|
||||
cb.equal(root.get(GoalMetricPeriodTotal_.periodIndex), monthIndex)
|
||||
),
|
||||
cb.and(
|
||||
cb.equal(root.get(GoalMetricPeriodTotal_.period), GoalPeriodEnum.YEARLY),
|
||||
cb.equal(root.get(GoalMetricPeriodTotal_.periodYear), calendarYear),
|
||||
cb.equal(root.get(GoalMetricPeriodTotal_.periodIndex), 1)
|
||||
)
|
||||
)
|
||||
);
|
||||
return em.createQuery(cq).getResultList();
|
||||
}
|
||||
|
||||
@@ -69,7 +69,15 @@ public class GoalMetricPeriodTotalService extends AbstractService<GoalMetricPeri
|
||||
|
||||
@Transactional
|
||||
public List<GoalMetricPeriodTotal> getByAthleteAndWeek(final long athleteId, final int year, final int calendarWeek) {
|
||||
return goalMetricAggregateDAO.findByAthleteAndWeek(athleteId, year, calendarWeek);
|
||||
final LocalDate monday = LocalDate.of(year, 1, 4)
|
||||
.with(WeekFields.ISO.weekOfWeekBasedYear(), calendarWeek)
|
||||
.with(WeekFields.ISO.dayOfWeek(), 1);
|
||||
return goalMetricAggregateDAO.findByAthleteForDashboard(
|
||||
athleteId,
|
||||
year, calendarWeek,
|
||||
monday.getYear(), monday.getMonthValue(),
|
||||
monday.getYear()
|
||||
);
|
||||
}
|
||||
|
||||
private List<IntervalsActivity> fetchWeekActivities(final long athleteId, final int year, final int calendarWeek) {
|
||||
|
||||
@@ -2,14 +2,12 @@ import {
|
||||
addWeeks,
|
||||
eachDayOfInterval,
|
||||
endOfISOWeek,
|
||||
endOfMonth,
|
||||
format,
|
||||
getISOWeek,
|
||||
getISOWeekYear,
|
||||
isSameDay,
|
||||
isToday,
|
||||
startOfISOWeek,
|
||||
startOfMonth,
|
||||
} from 'date-fns'
|
||||
import {ChevronLeft, ChevronRight} from 'lucide-react'
|
||||
import {useSearchParams} from 'react-router-dom'
|
||||
@@ -19,6 +17,8 @@ import {useDashboardGetDashboard} from '@/api/hooks'
|
||||
import type {IntervalsActivityDo} from '@/types/IntervalsActivityDo'
|
||||
import type {GoalDo} from '@/types/GoalDo'
|
||||
import type {GoalMetricEnum} from '@/types/GoalMetricEnum'
|
||||
import type {GoalMetricPeriodTotalDo} from '@/types/GoalMetricPeriodTotalDo'
|
||||
import type {GoalPeriodEnum} from '@/types/GoalPeriodEnum'
|
||||
import {formatDistance, formatDuration, sportIcon} from '@/utils/activity'
|
||||
|
||||
// ── Week helpers ──────────────────────────────────────────────────────────────
|
||||
@@ -102,13 +102,20 @@ function formatGoalValue(metric: Metric, value: number): string {
|
||||
}
|
||||
}
|
||||
|
||||
function computeActuals(acts: IntervalsActivityDo[]): Actuals {
|
||||
return {
|
||||
DISTANCE: acts.reduce((s, a) => s + a.distanceM, 0) / 1000,
|
||||
TSS: acts.reduce((s, a) => s + (a.trainingLoad ?? 0), 0),
|
||||
ELEVATION: acts.reduce((s, a) => s + (a.totalElevationGainM ?? 0), 0),
|
||||
TIME: acts.reduce((s, a) => s + a.movingTimeS, 0),
|
||||
function buildActualsFromTotals(
|
||||
totals: GoalMetricPeriodTotalDo[],
|
||||
period: GoalPeriodEnum,
|
||||
periodYear: number,
|
||||
periodIndex: number,
|
||||
): Actuals {
|
||||
const relevant = totals.filter(
|
||||
(t) => t.period === period && t.periodYear === periodYear && t.periodIndex === periodIndex,
|
||||
)
|
||||
const result: Actuals = {DISTANCE: 0, TSS: 0, ELEVATION: 0, TIME: 0}
|
||||
for (const t of relevant) {
|
||||
result[t.metric] = t.metric === 'DISTANCE' ? t.aggregatedValue / 1000 : t.aggregatedValue
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ── GoalRow ───────────────────────────────────────────────────────────────────
|
||||
@@ -334,6 +341,7 @@ export function Dashboard({athleteId}: { athleteId: number }) {
|
||||
const {data: dashboardData, isLoading} = useDashboardGetDashboard(athleteId, week, year)
|
||||
const activities = dashboardData?.activities ?? []
|
||||
const goals = dashboardData?.goals ?? []
|
||||
const goalMetricPeriodTotals = dashboardData?.goalMetricPeriodTotals ?? []
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -351,23 +359,9 @@ export function Dashboard({athleteId}: { athleteId: number }) {
|
||||
|
||||
// ── Actuals ─────────────────────────────────────────────────────────────────
|
||||
|
||||
const mStart = startOfMonth(wStart)
|
||||
const mEnd = endOfMonth(wStart)
|
||||
const yStart = new Date(wStart.getFullYear(), 0, 1)
|
||||
const yEnd = new Date(wStart.getFullYear(), 11, 31, 23, 59, 59)
|
||||
|
||||
const weekActuals = computeActuals(activities.filter((a) => {
|
||||
const t = new Date(a.startTime);
|
||||
return t >= wStart && t <= wEnd
|
||||
}))
|
||||
const monthActuals = computeActuals(activities.filter((a) => {
|
||||
const t = new Date(a.startTime);
|
||||
return t >= mStart && t <= mEnd
|
||||
}))
|
||||
const yearActuals = computeActuals(activities.filter((a) => {
|
||||
const t = new Date(a.startTime);
|
||||
return t >= yStart && t <= yEnd
|
||||
}))
|
||||
const weekActuals = buildActualsFromTotals(goalMetricPeriodTotals, 'WEEKLY', year, week)
|
||||
const monthActuals = buildActualsFromTotals(goalMetricPeriodTotals, 'MONTHLY', wStart.getFullYear(), wStart.getMonth() + 1)
|
||||
const yearActuals = buildActualsFromTotals(goalMetricPeriodTotals, 'YEARLY', wStart.getFullYear(), 1)
|
||||
|
||||
const weeklyGoals = goals.filter((g) => g.period === 'WEEKLY')
|
||||
const monthlyGoals = goals.filter((g) => g.period === 'MONTHLY')
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// GENERATED — do not edit manually.
|
||||
|
||||
import type { AbstractTimestampedEntityDo } from './AbstractTimestampedEntityDo';
|
||||
import type { GoalPeriodEnum } from './GoalPeriodEnum';
|
||||
import type { GoalMetricEnum } from './GoalMetricEnum';
|
||||
|
||||
export interface GoalDo extends AbstractTimestampedEntityDo {
|
||||
athleteId: number;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// GENERATED — do not edit manually.
|
||||
|
||||
import type { AbstractTimestampedEntityDo } from './AbstractTimestampedEntityDo';
|
||||
import type { GoalMetricEnum } from './GoalMetricEnum';
|
||||
import type { GoalPeriodEnum } from './GoalPeriodEnum';
|
||||
|
||||
export interface GoalMetricPeriodTotalDo extends AbstractTimestampedEntityDo {
|
||||
athleteId: number;
|
||||
|
||||
@@ -210,6 +210,7 @@ public class TypeScriptClientGeneratorMojo extends AbstractMojo {
|
||||
if (!cls.getName().startsWith(BACKEND_JAVA_PREFIX)) return false;
|
||||
if (cls.isAnnotation() || cls.isSynthetic() || cls.isInterface()) return false;
|
||||
if (cls.getEnclosingClass() != null) return false;
|
||||
if (cls.isEnum()) return true; // enums with abstract methods are still domain types
|
||||
return !Modifier.isAbstract(cls.getModifiers());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
com/peakup/maven/TypeScriptClientGeneratorMojo.class
|
||||
com/peakup/maven/TypeScriptClientGeneratorMojo$ResourceInfo.class
|
||||
com/peakup/maven/PackageInfoGeneratorMojo.class
|
||||
com/peakup/maven/TypeScriptClientGeneratorMojo$ParamInfo.class
|
||||
com/peakup/maven/TypeScriptClientGeneratorMojo$ResourceInfo.class
|
||||
com/peakup/maven/TypeScriptClientGeneratorMojo$EndpointInfo.class
|
||||
|
||||
Reference in New Issue
Block a user