This commit is contained in:
2026-05-15 17:40:15 +02:00
parent 7a7ba1ce75
commit 17b9080238
6 changed files with 78 additions and 4 deletions

View File

@@ -2,13 +2,16 @@ package com.peakup.activity.model;
import com.peakup.common.model.ReadonlyEntity;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.PostLoad;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Immutable;
import java.util.HashMap;
import java.util.Map;
@Entity
@@ -18,9 +21,28 @@ import java.util.Map;
@EqualsAndHashCode(callSuper = true)
public class ActivitySummary extends AbstractActivity implements ReadonlyEntity {
@Column(name = "training_load")
public @Nullable Double trainingLoad;
@Column(name = "ctl")
public @Nullable Double ctl;
@Column(name = "atl")
public @Nullable Double atl;
@Transient
private @Nullable Map<String, String> valuesForSummary;
@PostLoad
void populateValuesForSummary() {
final Map<String, String> map = new HashMap<>();
if (distanceM != null) map.put("distanceM", String.valueOf(distanceM));
if (trainingLoad != null) map.put("trainingLoad", String.valueOf(trainingLoad));
if (ctl != null) map.put("ctl", String.valueOf(ctl));
if (atl != null) map.put("atl", String.valueOf(atl));
valuesForSummary = map;
}
@SuppressWarnings("NullAway.Init")
public ActivitySummary() {
// hibernate

View File

@@ -49,7 +49,7 @@ public class Scheduler {
@Inject
ScheduledTaskService scheduledTaskService;
@Scheduled(every = "20m")
@Scheduled(every = "10m")
@RunOnVirtualThread
void importActivities() {
if (!running.compareAndSet(false, true)) {

View File

@@ -1,6 +1,7 @@
import { useState } from 'react'
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query'
import { Sidebar } from '@/components/Sidebar'
import { BottomNav } from '@/components/BottomNav'
import { LoginDialog } from '@/components/LoginDialog'
import { Dashboard } from '@/pages/Dashboard'
import { Activities } from '@/pages/Activities'
@@ -53,9 +54,12 @@ function AppContent() {
}
return (
<div className="flex w-full min-h-screen">
<div className="flex flex-col sm:flex-row w-full min-h-screen">
<Sidebar currentPage={page} onNavigate={setPage} athlete={athlete} onLogout={handleLogout} />
<main className="flex-1 flex overflow-hidden">{renderPage()}</main>
<div className="flex-1 flex flex-col min-h-0">
<main className="flex-1 flex overflow-hidden min-h-0">{renderPage()}</main>
<BottomNav currentPage={page} onNavigate={setPage} onLogout={handleLogout} />
</div>
</div>
)
}

View File

@@ -0,0 +1,45 @@
import { Activity, Calendar, Home, LogOut, Settings } from 'lucide-react'
const bottomNavItems = [
{ icon: <Home size={20} />, label: 'Home', page: 'dashboard' },
{ icon: <Activity size={20} />, label: 'Activities', page: 'activities' },
{ icon: <Calendar size={20} />, label: 'Plan', page: 'calendar' },
{ icon: <Settings size={20} />, label: 'Settings', page: 'settings' },
]
interface BottomNavProps {
currentPage: string
onNavigate: (page: string) => void
onLogout: () => void
}
export function BottomNav({ currentPage, onNavigate, onLogout }: BottomNavProps) {
return (
<nav className="flex sm:hidden bg-white border-t border-zinc-200 shrink-0">
{bottomNavItems.map((item) => {
const active = currentPage === item.page
return (
<button
key={item.page}
onClick={() => onNavigate(item.page)}
className={[
'flex-1 flex flex-col items-center justify-center gap-1 py-3',
'text-[10px] font-semibold tracking-wider transition-colors',
active ? 'text-red-500' : 'text-zinc-400 hover:text-zinc-700',
].join(' ')}
>
{item.icon}
<span className={active ? 'gradient-text' : ''}>{item.label}</span>
</button>
)
})}
<button
onClick={onLogout}
className="flex-1 flex flex-col items-center justify-center gap-1 py-3 text-[10px] font-semibold tracking-wider text-zinc-400 hover:text-red-500 transition-colors"
>
<LogOut size={20} />
<span>Sign out</span>
</button>
</nav>
)
}

View File

@@ -27,7 +27,7 @@ interface SidebarProps {
export function Sidebar({currentPage, onNavigate, athlete, onLogout}: SidebarProps) {
return (
<aside className="w-56 min-h-screen border-r border-zinc-200 bg-white flex flex-col shrink-0">
<aside className="hidden sm:flex flex-col w-56 min-h-screen border-r border-zinc-200 bg-white shrink-0">
{/* Logo */}
<div className="px-6 py-6 border-b border-zinc-200 flex justify-center">
<PeakupLogo className="h-24 w-auto"/>

View File

@@ -3,5 +3,8 @@
import type { AbstractActivityDo } from './AbstractActivityDo';
export interface ActivitySummaryDo extends AbstractActivityDo {
trainingLoad: number | null;
ctl: number | null;
atl: number | null;
valuesForSummary: Record<string, string> | null;
}