Add multi-sport tracking with configurable recovery bonuses
This commit is contained in:
+66
-22
@@ -184,16 +184,9 @@ final class App
|
||||
private function showDashboard(): void
|
||||
{
|
||||
$user = $this->requireUser();
|
||||
$settings = $this->settings->forUser($user['username']);
|
||||
$settings = $this->hydrateSettings($this->settings->forUser($user['username']));
|
||||
$entries = $this->entries->all($user['username']);
|
||||
|
||||
$evaluatedEntries = [];
|
||||
foreach ($entries as $entry) {
|
||||
$evaluation = $this->scoring->evaluate($entry, $settings);
|
||||
$evaluatedEntries[] = array_merge($entry, ['evaluation' => $evaluation]);
|
||||
}
|
||||
|
||||
usort($evaluatedEntries, static fn (array $a, array $b): int => strcmp($a['date'], $b['date']));
|
||||
$evaluatedEntries = $this->evaluateEntriesWithContext($entries, $settings);
|
||||
|
||||
$summary = $this->buildDashboardSummary($evaluatedEntries);
|
||||
$chartData = $this->buildDashboardCharts($evaluatedEntries);
|
||||
@@ -211,7 +204,7 @@ final class App
|
||||
private function showTrack(): void
|
||||
{
|
||||
$user = $this->requireUser();
|
||||
$settings = $this->settings->forUser($user['username']);
|
||||
$settings = $this->hydrateSettings($this->settings->forUser($user['username']));
|
||||
$date = (string) ($_GET['date'] ?? today());
|
||||
if (!$this->isValidDate($date)) {
|
||||
$date = today();
|
||||
@@ -224,12 +217,15 @@ final class App
|
||||
'sleep_hours' => 7,
|
||||
'sleep_feeling' => 3,
|
||||
'sport_minutes' => 0,
|
||||
'sport_type' => '',
|
||||
'sport_types' => [],
|
||||
'walk_minutes' => 0,
|
||||
'note' => '',
|
||||
];
|
||||
|
||||
$entry = $this->scoring->normalize($entry);
|
||||
$evaluation = $this->scoring->evaluate($entry, $settings);
|
||||
$previousEntry = $this->entries->find($user['username'], shift_date($date, -1));
|
||||
$evaluation = $this->scoring->evaluate($entry, $settings, $previousEntry);
|
||||
|
||||
View::render('track', [
|
||||
'pageTitle' => 'Tag tracken',
|
||||
@@ -238,11 +234,13 @@ final class App
|
||||
'entry' => $entry,
|
||||
'evaluation' => $evaluation,
|
||||
'settings' => $settings,
|
||||
'sportTypes' => normalized_sport_types($settings),
|
||||
'trackMood' => $evaluation['sentiment'],
|
||||
'topbarDate' => $entry['date'],
|
||||
'trackPayload' => encode_payload([
|
||||
'settings' => $settings,
|
||||
'entry' => $entry,
|
||||
'previousEntry' => $previousEntry !== null ? $this->scoring->normalize($previousEntry) : null,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -252,7 +250,7 @@ final class App
|
||||
$this->enforceCsrf();
|
||||
|
||||
$user = $this->requireUser();
|
||||
$settings = $this->settings->forUser($user['username']);
|
||||
$settings = $this->hydrateSettings($this->settings->forUser($user['username']));
|
||||
|
||||
$entry = $this->scoring->normalize([
|
||||
'date' => $_POST['date'] ?? today(),
|
||||
@@ -262,6 +260,7 @@ final class App
|
||||
'sleep_hours' => $_POST['sleep_hours'] ?? 0,
|
||||
'sleep_feeling' => $_POST['sleep_feeling'] ?? 3,
|
||||
'sport_minutes' => $_POST['sport_minutes'] ?? 0,
|
||||
'sport_types' => $_POST['sport_types'] ?? [],
|
||||
'walk_minutes' => $_POST['walk_minutes'] ?? 0,
|
||||
'note' => $_POST['note'] ?? '',
|
||||
]);
|
||||
@@ -271,7 +270,8 @@ final class App
|
||||
redirect('/track');
|
||||
}
|
||||
|
||||
$evaluation = $this->scoring->evaluate($entry, $settings);
|
||||
$previousEntry = $this->entries->find($user['username'], shift_date($entry['date'], -1));
|
||||
$evaluation = $this->scoring->evaluate($entry, $settings, $previousEntry);
|
||||
$this->entries->save($user['username'], $entry['date'], $entry, $evaluation);
|
||||
|
||||
flash('success', 'Der Tag wurde gespeichert.');
|
||||
@@ -281,16 +281,10 @@ final class App
|
||||
private function showArchive(): void
|
||||
{
|
||||
$user = $this->requireUser();
|
||||
$settings = $this->settings->forUser($user['username']);
|
||||
$settings = $this->hydrateSettings($this->settings->forUser($user['username']));
|
||||
$selectedDate = isset($_GET['date']) ? (string) $_GET['date'] : null;
|
||||
$entries = $this->entries->all($user['username']);
|
||||
|
||||
$archive = [];
|
||||
foreach ($entries as $entry) {
|
||||
$archive[] = array_merge($entry, [
|
||||
'evaluation' => $this->scoring->evaluate($entry, $settings),
|
||||
]);
|
||||
}
|
||||
$archive = array_reverse($this->evaluateEntriesWithContext($entries, $settings));
|
||||
|
||||
$selectedEntry = null;
|
||||
if ($selectedDate !== null) {
|
||||
@@ -314,7 +308,7 @@ final class App
|
||||
private function showOptions(): void
|
||||
{
|
||||
$user = $this->requireUser();
|
||||
$settings = $this->settings->forUser($user['username']);
|
||||
$settings = $this->hydrateSettings($this->settings->forUser($user['username']));
|
||||
|
||||
View::render('options', [
|
||||
'pageTitle' => 'Optionen',
|
||||
@@ -329,6 +323,10 @@ final class App
|
||||
'sleep_hours' => 7,
|
||||
'sleep_feeling' => 5,
|
||||
'sport_minutes' => 999,
|
||||
'sport_types' => array_map(
|
||||
static fn (array $type): string => (string) ($type['id'] ?? ''),
|
||||
normalized_sport_types($settings)
|
||||
),
|
||||
'walk_minutes' => 999,
|
||||
'note' => 'x',
|
||||
], $settings)['max_total'],
|
||||
@@ -467,6 +465,15 @@ final class App
|
||||
'value' => $entry['sport_minutes'] + $entry['walk_minutes'],
|
||||
'sport' => $entry['sport_minutes'],
|
||||
'walk' => $entry['walk_minutes'],
|
||||
'sport_labels' => array_values(array_filter(array_map(
|
||||
static fn (array $type): string => (string) ($type['label'] ?? ''),
|
||||
$entry['sport_type_meta'] ?? []
|
||||
))),
|
||||
'sport_icons' => array_values(array_filter(array_map(
|
||||
static fn (array $type): ?string => isset($type['icon']) ? sport_icon_path((string) $type['icon']) : null,
|
||||
$entry['sport_type_meta'] ?? []
|
||||
))),
|
||||
'sport_bonus' => (float) ($entry['evaluation']['components']['sport_bonus'] ?? 0),
|
||||
];
|
||||
}, $recent),
|
||||
];
|
||||
@@ -542,9 +549,46 @@ final class App
|
||||
];
|
||||
}
|
||||
|
||||
$settings['sport_types'] = normalized_sport_types([
|
||||
'sport_types' => is_array($input['sport_types'] ?? null)
|
||||
? $input['sport_types']
|
||||
: $defaults['sport_types'],
|
||||
]);
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
private function hydrateSettings(array $settings): array
|
||||
{
|
||||
$settings['sport_types'] = normalized_sport_types($settings);
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
private function evaluateEntriesWithContext(array $entries, array $settings): array
|
||||
{
|
||||
$normalized = array_map(fn (array $entry): array => $this->scoring->normalize($entry), $entries);
|
||||
usort($normalized, static fn (array $a, array $b): int => strcmp($a['date'], $b['date']));
|
||||
|
||||
$entryMap = [];
|
||||
foreach ($normalized as $entry) {
|
||||
$entryMap[$entry['date']] = $entry;
|
||||
}
|
||||
|
||||
$evaluated = [];
|
||||
foreach ($normalized as $entry) {
|
||||
$previousEntry = $entryMap[shift_date($entry['date'], -1)] ?? null;
|
||||
$evaluation = $this->scoring->evaluate($entry, $settings, $previousEntry);
|
||||
|
||||
$evaluated[] = array_merge($entry, [
|
||||
'evaluation' => $evaluation,
|
||||
'sport_type_meta' => find_sport_types($settings, $entry['sport_types']),
|
||||
]);
|
||||
}
|
||||
|
||||
return $evaluated;
|
||||
}
|
||||
|
||||
private function sendSecurityHeaders(): void
|
||||
{
|
||||
header('Referrer-Policy: strict-origin-when-cross-origin');
|
||||
|
||||
Reference in New Issue
Block a user