694 lines
49 KiB
PHP
694 lines
49 KiB
PHP
<?php
|
||
$dayDateLabel = format_display_date((string) $dayEntry['date']);
|
||
$dayWeekday = strtok($dayDateLabel, ',');
|
||
$dayBackground = is_string($dayEntry['background_image_url'] ?? null) ? (string) $dayEntry['background_image_url'] : null;
|
||
$summaryComment = trim((string) ($dayEntry['summary']['comment'] ?? $dayEntry['summary_comment'] ?? ''));
|
||
$summaryMood = normalize_signal_value($dayEntry['summary']['mood'] ?? $dayEntry['summary_mood'] ?? 0);
|
||
$summaryEnergy = normalize_signal_value($dayEntry['summary']['energy'] ?? $dayEntry['summary_energy'] ?? 0);
|
||
$summaryStress = normalize_signal_value($dayEntry['summary']['stress'] ?? $dayEntry['summary_stress'] ?? 0);
|
||
$summaryAlcohol = !empty($dayEntry['summary']['alcohol'] ?? $dayEntry['summary_alcohol'] ?? false);
|
||
$dayHealth = is_array($dayEntry['health'] ?? null) ? $dayEntry['health'] : [];
|
||
$daySteps = (int) ($dayHealth['steps'] ?? 0);
|
||
$dayStepBonus = (float) ($dayEntry['evaluation']['components']['step_bonus'] ?? 0);
|
||
$optimalSleepHours = max(1.0, min(16.0, (float) ($settings['sleep']['optimal_hours'] ?? 7.0)));
|
||
$formatBalanceValue = static function (?array $entry) use ($settings): string {
|
||
if ($entry === null) {
|
||
return '';
|
||
}
|
||
|
||
$mode = (string) ($settings['display']['score_mode'] ?? 'scale');
|
||
$balance = is_array($entry['evaluation']['balance'] ?? null) ? $entry['evaluation']['balance'] : [];
|
||
if ($mode === 'points') {
|
||
return format_points((float) ($entry['evaluation']['total'] ?? 0)) . ' Punkte';
|
||
}
|
||
|
||
if ($mode === 'percent') {
|
||
return format_points((float) ($balance['percentage'] ?? $entry['evaluation']['percentage'] ?? 0)) . ' %';
|
||
}
|
||
|
||
$level = max(-2, min(2, (int) ($balance['level'] ?? 0)));
|
||
return ($level > 0 ? '+' : '') . (string) $level;
|
||
};
|
||
?>
|
||
|
||
<section class="dashboard-shell<?= $dayBackground !== null ? ' dashboard-shell--with-image' : '' ?>" data-dashboard-root>
|
||
<?php if ($dayBackground !== null): ?>
|
||
<div class="dashboard-shell__background" aria-hidden="true">
|
||
<img src="<?= e($dayBackground) ?>" alt="">
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<header class="dashboard-topbar">
|
||
<nav class="dashboard-switcher glass-panel" aria-label="Ansicht wechseln">
|
||
<a class="<?= $dashboardView === 'day' && $dashboardDate === today() ? 'active' : '' ?>" href="/?view=day&date=<?= e(rawurlencode(today())) ?>">Heute</a>
|
||
<a class="<?= $dashboardView === 'week' ? 'active' : '' ?>" href="/?view=week&date=<?= e(rawurlencode(today())) ?>">Woche</a>
|
||
<a class="<?= $dashboardView === 'month' ? 'active' : '' ?>" href="/?view=month&date=<?= e(rawurlencode(today())) ?>">Monat</a>
|
||
</nav>
|
||
|
||
<button class="dashboard-settings glass-panel" type="button" data-settings-menu-open aria-label="Optionen öffnen">
|
||
<img src="<?= e(icon_path('options')) ?>" alt="">
|
||
</button>
|
||
</header>
|
||
|
||
<?php if ($dashboardView === 'day'): ?>
|
||
<div class="dashboard-day" data-day-swipe data-prev-date="<?= e($dashboardPrevDate) ?>" data-next-date="<?= e($dashboardNextDate) ?>">
|
||
<div class="dashboard-day__hero">
|
||
<p class="dashboard-day__eyebrow"><?= e((string) $dayWeekday) ?></p>
|
||
<h1><?= e(format_display_date((string) $dayEntry['date'], false)) ?></h1>
|
||
|
||
<nav class="dashboard-compare-strip" aria-label="Tagesvergleich">
|
||
<span class="score-scale score-scale--day" aria-hidden="true"><span>+2</span><span>+1</span><span>0</span><span>-1</span><span>-2</span></span>
|
||
<?php foreach ($dashboardCompareDays as $compareDay): ?>
|
||
<a class="compare-day offset-<?= e((string) ($compareDay['offset'] ?? 0)) ?><?= !empty($compareDay['is_current']) ? ' is-current' : '' ?><?= empty($compareDay['has_content']) ? ' is-empty' : '' ?>" href="/?view=day&date=<?= e(rawurlencode((string) $compareDay['date'])) ?>">
|
||
<span class="compare-day__line<?= empty($compareDay['has_content']) ? ' is-empty' : '' ?><?= !empty($compareDay['is_current']) ? ' is-primary' : '' ?> score-<?= e((string) ($compareDay['score_level'] ?? 'empty')) ?> compare-tone-<?= e((string) ($compareDay['line_tone'] ?? 'empty')) ?>">
|
||
<span class="compare-day__marker"></span>
|
||
</span>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</nav>
|
||
</div>
|
||
|
||
<button class="day-summary-card glass-panel<?= $dashboardHasContent ? ' is-filled' : '' ?>" type="button" data-summary-overlay-open>
|
||
<span class="day-summary-card__label">Tagesbilanz</span>
|
||
<strong class="day-summary-card__title"><?= $summaryComment !== '' ? e($summaryComment) : 'Tagesbilanz' ?></strong>
|
||
<?php if ($daySteps > 0): ?>
|
||
<span class="day-summary-card__chips">
|
||
<span class="day-chip day-chip--score">Bilanz <?= e($formatBalanceValue($dayEntry)) ?></span>
|
||
<span class="day-chip"><?= e(number_format($daySteps, 0, ',', '.')) ?> Schritte</span>
|
||
<?php if ($dayStepBonus > 0): ?>
|
||
<span class="day-chip day-chip--bonus">+<?= e(format_points($dayStepBonus)) ?> Punkt</span>
|
||
<?php endif; ?>
|
||
</span>
|
||
<?php else: ?>
|
||
<span class="day-summary-card__chips"><span class="day-chip day-chip--score">Bilanz <?= e($formatBalanceValue($dayEntry)) ?></span></span>
|
||
<?php endif; ?>
|
||
</button>
|
||
|
||
<section class="dashboard-moments-block">
|
||
<div class="section-head section-head--compact section-head--dashboard">
|
||
<div>
|
||
<p class="eyebrow">Deine Momente</p>
|
||
<h2>Momente des Tages</h2>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="timeline-list">
|
||
<?php if ($dashboardTimeline === []): ?>
|
||
<article class="timeline-card timeline-card--empty glass-panel">
|
||
<div class="timeline-card__body">
|
||
<h3>Noch keine Momente</h3>
|
||
<p>Du kannst auch vergangene Tage jederzeit nachtragen.</p>
|
||
</div>
|
||
</article>
|
||
<?php endif; ?>
|
||
|
||
<?php foreach ($dashboardTimeline as $item): ?>
|
||
<?php $eventType = (string) ($item['type'] ?? 'event'); ?>
|
||
<?php $eventComment = trim((string) ($item['comment'] ?? '')); ?>
|
||
<?php $isImportedHealth = (string) ($item['source'] ?? '') === 'health_auto_export'; ?>
|
||
<?php $sportType = $eventType === 'sport' ? find_sport_type($settings, (string) ($item['sport_type_id'] ?? '')) : null; ?>
|
||
<?php $isImportedWalkSport = $isImportedHealth && $eventType === 'sport' && str_contains(strtolower((string) ($sportType['label'] ?? $eventComment)), 'spaziergang'); ?>
|
||
<?php $eventTone = signal_value_class(normalize_signal_value($item['mood'] ?? 0)); ?>
|
||
<?php $eventValueText = (float) $item['value'] > 0 ? rtrim(rtrim(number_format((float) $item['value'], 2, ',', '.'), '0'), ',') . ' ' . (string) $item['unit'] : ''; ?>
|
||
<?php $eventTitle = match ($eventType) {
|
||
'sport' => $isImportedWalkSport ? 'Spaziergang' : (string) ($sportType['label'] ?? 'Sport'),
|
||
'walk' => 'Spaziergang',
|
||
'sleep' => 'Schlaf',
|
||
default => (string) ($item['comment'] !== '' ? $item['comment'] : day_event_type_label($eventType)),
|
||
}; ?>
|
||
<?php $eventDetail = match ($eventType) {
|
||
'sport' => trim($eventValueText),
|
||
'walk', 'sleep' => trim($eventValueText),
|
||
default => trim($eventValueText . ($sportType !== null ? ' · ' . (string) ($sportType['label'] ?? '') : '')),
|
||
}; ?>
|
||
<?php $showEventComment = in_array($eventType, ['sport', 'walk', 'sleep'], true) && $eventComment !== '' && !$isImportedHealth; ?>
|
||
<?php
|
||
$sleepPhases = ['deep' => (float) ($item['sleep_deep'] ?? 0), 'rem' => (float) ($item['sleep_rem'] ?? 0), 'core' => (float) ($item['sleep_core'] ?? 0)];
|
||
$sleepPhaseSource = trim($eventComment . ' ' . (string) ($item['duration_label'] ?? '') . ' ' . (string) ($item['distance_label'] ?? '') . ' ' . (string) ($item['energy_label'] ?? '') . ' ' . (string) ($item['heart_rate_label'] ?? ''));
|
||
if ($eventType === 'sleep' && array_sum($sleepPhases) <= 0 && $sleepPhaseSource !== '') {
|
||
if (preg_match('/(?:Tief|Tiefschlaf)\s*:?[\s]+([0-9]+(?:[,.][0-9]+)?)/u', $sleepPhaseSource, $match) === 1) {
|
||
$sleepPhases['deep'] = (float) str_replace(',', '.', $match[1]);
|
||
}
|
||
if (preg_match('/REM(?:-Schlaf)?\s*:?[\s]+([0-9]+(?:[,.][0-9]+)?)/u', $sleepPhaseSource, $match) === 1) {
|
||
$sleepPhases['rem'] = (float) str_replace(',', '.', $match[1]);
|
||
}
|
||
if (preg_match('/(?:Kern|Kernschlaf)\s*:?[\s]+([0-9]+(?:[,.][0-9]+)?)/u', $sleepPhaseSource, $match) === 1) {
|
||
$sleepPhases['core'] = (float) str_replace(',', '.', $match[1]);
|
||
}
|
||
}
|
||
$sleepPhaseTotal = max(0.0, array_sum($sleepPhases));
|
||
$sleepBarTotal = $eventType === 'sleep' ? max((float) ($item['value'] ?? 0), $sleepPhaseTotal, $optimalSleepHours / 0.75) : 0.0;
|
||
$sleepPhaseRemainder = max(0.0, $sleepBarTotal - $sleepPhaseTotal);
|
||
$sleepOptimalPercent = $sleepBarTotal > 0 ? max(0, min(100, ($optimalSleepHours / $sleepBarTotal) * 100)) : 0;
|
||
?>
|
||
<?php $eventStats = array_values(array_filter([
|
||
$eventType !== 'sleep' ? (string) ($item['duration_label'] ?? '') : '',
|
||
(string) ($item['distance_label'] ?? ''),
|
||
'',
|
||
(string) ($item['heart_rate_label'] ?? ''),
|
||
], static function (string $value): bool {
|
||
$value = trim($value);
|
||
return $value !== '' && !preg_match('/^-\s*(Distanz|Energie|Puls|Route|Tief|Tiefschlaf|REM|REM-Schlaf|Kern|Kernschlaf)(?:-?Label)?:?(?:\s*[0-9]+(?:[,.][0-9]+)?)?$/u', $value);
|
||
})); ?>
|
||
<?php $eventPayload = encode_payload([
|
||
'id' => (string) ($item['id'] ?? ''),
|
||
'type' => (string) ($item['type'] ?? 'event'),
|
||
'time' => (string) ($item['time'] ?? ''),
|
||
'comment' => (string) ($item['comment'] ?? ''),
|
||
'value' => (float) ($item['value'] ?? 0),
|
||
'unit' => (string) ($item['unit'] ?? ''),
|
||
'sport_type_id' => (string) ($item['sport_type_id'] ?? ''),
|
||
'image' => (string) ($item['image'] ?? ''),
|
||
'consumed' => !empty($item['consumed']),
|
||
'mood' => normalize_signal_value($item['mood'] ?? 0),
|
||
'energy' => normalize_signal_value($item['energy'] ?? 0),
|
||
'stress' => normalize_signal_value($item['stress'] ?? 0),
|
||
'source' => (string) ($item['source'] ?? ''),
|
||
'import_id' => (string) ($item['import_id'] ?? ''),
|
||
'duration_label' => (string) ($item['duration_label'] ?? ''),
|
||
'distance_label' => (string) ($item['distance_label'] ?? ''),
|
||
'energy_label' => (string) ($item['energy_label'] ?? ''),
|
||
'heart_rate_label' => (string) ($item['heart_rate_label'] ?? ''),
|
||
'sleep_deep' => (float) ($item['sleep_deep'] ?? 0),
|
||
'sleep_rem' => (float) ($item['sleep_rem'] ?? 0),
|
||
'sleep_core' => (float) ($item['sleep_core'] ?? 0),
|
||
]); ?>
|
||
<?php $hasEventImage = is_string($item['image_url'] ?? null); ?>
|
||
<?php $routeMap = is_array($item['route_map'] ?? null) ? $item['route_map'] : null; ?>
|
||
<article class="timeline-card timeline-card--event timeline-card--<?= e($eventTone) ?><?= $hasEventImage ? ' timeline-card--with-image' : '' ?> glass-panel" data-event-editable data-event-payload="<?= e($eventPayload) ?>">
|
||
<?php if ($hasEventImage): ?>
|
||
<button class="timeline-media-button" type="button" data-lightbox-src="<?= e((string) $item['image_url']) ?>" data-lightbox-kind="image" aria-label="Bild vergrößern">
|
||
<img class="timeline-card__image" src="<?= e((string) $item['image_url']) ?>" alt="">
|
||
</button>
|
||
<?php endif; ?>
|
||
<span class="timeline-card__time-chip"><?= e($item['time'] !== '' ? $item['time'] : '--:--') ?></span>
|
||
<div class="timeline-card__meta">
|
||
<div class="timeline-card__icon-wrap" title="<?= e(day_event_type_label((string) $item['type'])) ?>">
|
||
<img class="timeline-card__icon" src="<?= e(day_event_type_icon((string) $item['type'])) ?>" alt="">
|
||
</div>
|
||
<div>
|
||
<strong class="timeline-card__time"><?= e($item['time'] !== '' ? $item['time'] : '--:--') ?></strong>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="timeline-card__body">
|
||
<h3><?= e($eventTitle) ?></h3>
|
||
<?php if ($showEventComment): ?>
|
||
<p class="timeline-card__comment"><?= e($eventComment) ?></p>
|
||
<?php endif; ?>
|
||
<?php if ((string) ($item['type'] ?? '') === 'alcohol'): ?>
|
||
<p class="timeline-card__value">
|
||
<?= !empty($item['consumed']) ? 'Heute getrunken' : 'Heute nicht getrunken' ?>
|
||
</p>
|
||
<?php elseif ($eventDetail !== ''): ?>
|
||
<p class="timeline-card__value"><?= e($eventDetail) ?></p>
|
||
<?php endif; ?>
|
||
|
||
<?php if ((string) ($item['comment'] ?? '') !== '' && (string) ($item['type'] ?? '') === 'alcohol'): ?>
|
||
<p class="timeline-card__value"><?= e((string) $item['comment']) ?></p>
|
||
<?php endif; ?>
|
||
|
||
<?php if ($eventType === 'sleep' && $sleepPhaseTotal > 0): ?>
|
||
<div class="sleep-phase-bar" aria-label="Schlafphasen" style="--sleep-optimal-left: <?= e((string) $sleepOptimalPercent) ?>%">
|
||
<?php foreach (['deep' => ['Tief', 'deep'], 'rem' => ['REM', 'rem'], 'core' => ['Kern', 'core']] as $phase => [$label, $class]): ?>
|
||
<?php $phaseHours = max(0.0, (float) ($sleepPhases[$phase] ?? 0)); ?>
|
||
<?php if ($phaseHours <= 0) { continue; } ?>
|
||
<?php $phasePercent = $sleepBarTotal > 0 ? max(0.5, min(100, ($phaseHours / $sleepBarTotal) * 100)) : 0; ?>
|
||
<span class="sleep-phase-bar__segment sleep-phase-bar__segment--<?= e($class) ?><?= $phasePercent < 13 ? ' is-compact' : '' ?>" style="--sleep-segment-width: <?= e((string) $phasePercent) ?>%" title="<?= e($label) ?>: <?= e(format_points($phaseHours)) ?> h" data-tooltip="<?= e($label) ?>: <?= e(format_points($phaseHours)) ?> h">
|
||
<strong><?= e($label) ?></strong> <?= e(format_points($phaseHours)) ?> h
|
||
</span>
|
||
<?php endforeach; ?>
|
||
<?php if ($sleepPhaseRemainder > 0): ?>
|
||
<?php $remainderPercent = $sleepBarTotal > 0 ? max(0.5, min(100, ($sleepPhaseRemainder / $sleepBarTotal) * 100)) : 0; ?>
|
||
<span class="sleep-phase-bar__segment sleep-phase-bar__segment--rest" style="--sleep-segment-width: <?= e((string) $remainderPercent) ?>%" title="Bis Ziel-/Skalenende: <?= e(format_points($sleepPhaseRemainder)) ?> h" data-tooltip="Bis Ziel-/Skalenende: <?= e(format_points($sleepPhaseRemainder)) ?> h"></span>
|
||
<?php endif; ?>
|
||
<span class="sleep-phase-bar__target"><span><?= e(format_points($optimalSleepHours)) ?> h</span></span>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if ($eventStats !== []): ?>
|
||
<div class="timeline-card__stats" aria-label="Importdetails">
|
||
<?php foreach ($eventStats as $stat): ?>
|
||
<span><?= e($stat) ?></span>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="signal-row">
|
||
<?php foreach (['mood' => 'Stimmung', 'energy' => 'Energie', 'stress' => 'Stress'] as $metric => $label): ?>
|
||
<?php $value = normalize_signal_value($item[$metric] ?? 0); ?>
|
||
<?php $valueTone = signal_value_class($metric === 'stress' ? -$value : $value); ?>
|
||
<?php if ($value === 0) { continue; } ?>
|
||
<span class="signal-pill signal-pill--<?= e(signal_badge_tone($value, $metric)) ?> signal-pill--<?= e($valueTone) ?>">
|
||
<strong><?= e($label) ?></strong>
|
||
<img class="signal-pill__icon" src="<?= e(icon_path($metric === 'mood' ? 'signal-mood' : ($metric === 'energy' ? 'signal-energy' : 'signal-stress'))) ?>" alt="<?= e($label) ?>">
|
||
<span><?= $value >= 0 ? '+' : '' ?><?= e((string) $value) ?></span>
|
||
</span>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
|
||
<?php if ($routeMap !== null): ?>
|
||
<button class="timeline-route-map" type="button" data-lightbox-kind="html" aria-label="Route vergrößern">
|
||
<svg viewBox="0 0 <?= e((string) $routeMap['width']) ?> <?= e((string) $routeMap['height']) ?>" aria-hidden="true">
|
||
<?php foreach ($routeMap['tiles'] as $tile): ?>
|
||
<image href="<?= e((string) $tile['url']) ?>" x="<?= e((string) $tile['left']) ?>" y="<?= e((string) $tile['top']) ?>" width="256" height="256"></image>
|
||
<?php endforeach; ?>
|
||
<polyline points="<?= e((string) $routeMap['line']) ?>"></polyline>
|
||
</svg>
|
||
<span class="timeline-route-map__credit">© OpenStreetMap</span>
|
||
</button>
|
||
<?php endif; ?>
|
||
|
||
<form method="post" action="/" class="timeline-card__delete">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="form_name" value="delete_event">
|
||
<input type="hidden" name="date" value="<?= e((string) $dayEntry['date']) ?>">
|
||
<input type="hidden" name="event_id" value="<?= e((string) $item['id']) ?>">
|
||
<button class="ghost-button ghost-button--small" type="submit" data-confirm-delete aria-label="Moment löschen">×</button>
|
||
</form>
|
||
</article>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</section>
|
||
|
||
<button class="dashboard-fab" type="button" data-moment-overlay-open aria-label="Neuen Moment hinzufügen">+</button>
|
||
<div class="dashboard-fab-menu glass-panel" data-fab-menu hidden>
|
||
<?php foreach ($dashboardEventTypes as $type => $meta): ?>
|
||
<?php if ($type === 'alcohol') { continue; } ?>
|
||
<button type="button" data-fab-moment-choice="<?= e($type) ?>">
|
||
<img src="<?= e((string) $meta['icon']) ?>" alt="">
|
||
<span><?= e((string) $meta['label']) ?></span>
|
||
</button>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="dashboard-overlay" data-summary-overlay hidden>
|
||
<div class="dashboard-overlay__backdrop" data-summary-overlay-close></div>
|
||
<section class="dashboard-modal glass-panel dashboard-modal--summary" role="dialog" aria-modal="true">
|
||
<div class="dashboard-modal__controls">
|
||
<button class="dashboard-modal__round" type="button" data-summary-overlay-close>×</button>
|
||
<button class="dashboard-modal__round dashboard-modal__round--confirm" type="submit" form="day-summary-form">✓</button>
|
||
</div>
|
||
|
||
<form method="post" action="/" enctype="multipart/form-data" class="dashboard-modal__form" id="day-summary-form">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="form_name" value="save_day_summary">
|
||
<input type="hidden" name="date" value="<?= e((string) $dayEntry['date']) ?>">
|
||
|
||
<h2 class="dashboard-modal__title"><?= e(format_display_date((string) $dayEntry['date'], false)) ?></h2>
|
||
<p class="dashboard-modal__subtitle">Deine Tagesbilanz</p>
|
||
|
||
<label class="dashboard-modal__textarea">
|
||
<textarea name="summary_comment" rows="5" placeholder="Fasse deinen Tag zusammen"><?= e($summaryComment) ?></textarea>
|
||
</label>
|
||
|
||
<div class="overlay-signal-grid overlay-signal-grid--summary-row">
|
||
<?php foreach (['summary_mood' => ['Stimmung', $summaryMood], 'summary_energy' => ['Energie', $summaryEnergy], 'summary_stress' => ['Stress', $summaryStress]] as $field => [$label, $value]): ?>
|
||
<?php $metric = str_replace('summary_', '', $field); ?>
|
||
<div class="overlay-signal-card overlay-signal-card--inline" data-stepper data-stepper-metric="<?= e($metric) ?>">
|
||
<div>
|
||
<h3><?= e($label) ?></h3>
|
||
<p data-stepper-label>
|
||
<?= e(signal_labels_for_metric($metric)[$value]) ?>
|
||
</p>
|
||
</div>
|
||
<div class="overlay-signal-card__control">
|
||
<div class="overlay-signal-card__ring tone-<?= e(signal_value_class($value)) ?>">
|
||
<span data-stepper-value><?= $value >= 0 ? '+' : '' ?><?= e((string) $value) ?></span>
|
||
</div>
|
||
<div class="overlay-signal-card__buttons">
|
||
<button type="button" data-stepper-minus>-</button>
|
||
<button type="button" data-stepper-plus>+</button>
|
||
</div>
|
||
<div class="signal-scale" aria-hidden="true"><span>-2</span><span>-1</span><span>0</span><span>+1</span><span>+2</span></div>
|
||
<input type="hidden" name="<?= e($field) ?>" value="<?= e((string) $value) ?>" data-stepper-input>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
|
||
<fieldset class="moment-alcohol-field moment-alcohol-field--summary">
|
||
<legend>Alkohol</legend>
|
||
<div class="moment-choice-row">
|
||
<label class="moment-choice-pill">
|
||
<input type="radio" name="summary_alcohol" value="1" <?= $summaryAlcohol ? 'checked' : '' ?>>
|
||
<span>Ja</span>
|
||
</label>
|
||
<label class="moment-choice-pill">
|
||
<input type="radio" name="summary_alcohol" value="0" <?= !$summaryAlcohol ? 'checked' : '' ?>>
|
||
<span>Nein</span>
|
||
</label>
|
||
</div>
|
||
</fieldset>
|
||
|
||
<label>
|
||
<span>Tagesbild</span>
|
||
<input type="file" name="background_image" accept="image/jpeg,image/png,image/webp">
|
||
</label>
|
||
</form>
|
||
|
||
<?php if ($dayBackground !== null): ?>
|
||
<form method="post" action="/" class="dashboard-modal__secondary-action">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="form_name" value="remove_background">
|
||
<input type="hidden" name="date" value="<?= e((string) $dayEntry['date']) ?>">
|
||
<button class="ghost-button" type="submit">Bild entfernen</button>
|
||
</form>
|
||
<?php endif; ?>
|
||
</section>
|
||
</div>
|
||
|
||
<div class="dashboard-overlay" data-moment-overlay hidden>
|
||
<div class="dashboard-overlay__backdrop" data-moment-overlay-close></div>
|
||
<section class="dashboard-modal glass-panel dashboard-modal--moment" role="dialog" aria-modal="true" data-moment-modal>
|
||
<div class="dashboard-modal__controls">
|
||
<button class="dashboard-modal__round" type="button" data-moment-overlay-close>×</button>
|
||
<button class="dashboard-modal__round dashboard-modal__round--confirm" type="submit" form="moment-form" data-moment-submit disabled>✓</button>
|
||
</div>
|
||
|
||
<div data-moment-step="choose">
|
||
<h2 class="dashboard-modal__title">Neuer Moment</h2>
|
||
<div class="moment-type-grid">
|
||
<?php foreach ($dashboardEventTypes as $type => $meta): ?>
|
||
<?php if ($type === 'alcohol') { continue; } ?>
|
||
<button class="moment-type-card" type="button" data-moment-type-choice="<?= e($type) ?>">
|
||
<img src="<?= e((string) $meta['icon']) ?>" alt="">
|
||
<span><?= e((string) $meta['label']) ?></span>
|
||
</button>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
|
||
<form method="post" action="/" enctype="multipart/form-data" class="dashboard-modal__form" id="moment-form" data-moment-step="form" hidden>
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="form_name" value="add_event" data-moment-form-name>
|
||
<input type="hidden" name="date" value="<?= e((string) $dayEntry['date']) ?>">
|
||
<input type="hidden" name="event_id" value="" data-moment-event-id>
|
||
<input type="hidden" name="event_type" value="event" data-moment-type-input>
|
||
<input type="hidden" name="event_unit" value="" data-event-unit>
|
||
<input type="hidden" name="event_walk_mode" value="time" data-walk-mode-input>
|
||
|
||
<div class="dashboard-modal__heading-row">
|
||
<div>
|
||
<p class="dashboard-modal__subtitle" data-moment-type-label>Neuer Moment</p>
|
||
<h2 class="dashboard-modal__title">Was ist passiert?</h2>
|
||
</div>
|
||
<button class="ghost-button ghost-button--small" type="button" data-moment-back>Typ ändern</button>
|
||
</div>
|
||
|
||
<label class="dashboard-modal__textarea">
|
||
<textarea name="event_comment" rows="4" placeholder="Was hast du erlebt?" data-moment-comment></textarea>
|
||
</label>
|
||
|
||
<label>
|
||
<span>Momentbild</span>
|
||
<input type="file" name="event_image" accept="image/jpeg,image/png,image/webp">
|
||
</label>
|
||
|
||
<div class="field-grid field-grid--two">
|
||
<label>
|
||
<span>Erfasst um</span>
|
||
<input type="time" name="event_time" value="<?= e(date('H:i')) ?>" required>
|
||
</label>
|
||
<label data-moment-value-field>
|
||
<span data-moment-value-label>Wert</span>
|
||
<input type="number" name="event_value" min="0" max="50000" step="0.25" placeholder="optional" data-moment-value-input>
|
||
</label>
|
||
</div>
|
||
|
||
<fieldset data-moment-sport-field hidden>
|
||
<legend>Sportart</legend>
|
||
<input type="hidden" name="event_sport_type_id" value="">
|
||
<div class="moment-type-grid moment-type-grid--sport">
|
||
<?php foreach ($dashboardSportTypes as $sportType): ?>
|
||
<button class="moment-type-card moment-type-card--sport" type="button" data-sport-choice="<?= e((string) ($sportType['id'] ?? '')) ?>">
|
||
<img src="<?= e(sport_icon_path((string) ($sportType['icon'] ?? 'run'))) ?>" alt="">
|
||
<span><?= e((string) ($sportType['label'] ?? '')) ?></span>
|
||
</button>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</fieldset>
|
||
|
||
<fieldset class="moment-alcohol-field" data-moment-walk-field hidden>
|
||
<legend>Spaziergang als</legend>
|
||
<div class="moment-choice-row">
|
||
<label class="moment-choice-pill"><input type="radio" name="event_walk_mode" value="time" checked><span>Dauer</span></label>
|
||
<label class="moment-choice-pill"><input type="radio" name="event_walk_mode" value="steps"><span>Schritte</span></label>
|
||
</div>
|
||
</fieldset>
|
||
|
||
<fieldset class="moment-alcohol-field" data-moment-alcohol-field hidden>
|
||
<legend>Heute Alkohol getrunken?</legend>
|
||
<div class="moment-choice-row">
|
||
<label class="moment-choice-pill">
|
||
<input type="radio" name="event_consumed" value="1" checked>
|
||
<span>Ja</span>
|
||
</label>
|
||
<label class="moment-choice-pill">
|
||
<input type="radio" name="event_consumed" value="0">
|
||
<span>Nein</span>
|
||
</label>
|
||
</div>
|
||
</fieldset>
|
||
|
||
<div class="overlay-signal-grid overlay-signal-grid--summary-row overlay-signal-grid--moment">
|
||
<?php foreach (['event_mood' => ['Stimmung', 0], 'event_energy' => ['Energie', 0], 'event_stress' => ['Stress', 0]] as $field => [$label, $value]): ?>
|
||
<?php $metric = str_replace('event_', '', $field); ?>
|
||
<div class="overlay-signal-card overlay-signal-card--inline overlay-signal-card--moment" data-stepper data-stepper-metric="<?= e($metric) ?>">
|
||
<div>
|
||
<h3><?= e($label) ?></h3>
|
||
</div>
|
||
<div class="overlay-signal-card__control">
|
||
<div class="overlay-signal-card__ring tone-zero">
|
||
<span data-stepper-value><?= $value >= 0 ? '+' : '' ?><?= e((string) $value) ?></span>
|
||
</div>
|
||
<div class="overlay-signal-card__buttons">
|
||
<button type="button" data-stepper-minus>-</button>
|
||
<button type="button" data-stepper-plus>+</button>
|
||
</div>
|
||
<div class="signal-scale" aria-hidden="true"><span>-2</span><span>-1</span><span>0</span><span>+1</span><span>+2</span></div>
|
||
<input type="hidden" name="<?= e($field) ?>" value="<?= e((string) $value) ?>" data-stepper-input>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</form>
|
||
|
||
<form method="post" action="/" class="dashboard-modal__secondary-action" data-moment-delete-form hidden>
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="form_name" value="delete_event">
|
||
<input type="hidden" name="date" value="<?= e((string) $dayEntry['date']) ?>">
|
||
<input type="hidden" name="event_id" value="" data-moment-delete-id>
|
||
<button class="ghost-button" type="submit">Moment löschen</button>
|
||
</form>
|
||
</section>
|
||
</div>
|
||
<?php elseif ($dashboardView === 'week'): ?>
|
||
<section class="dashboard-range-view dashboard-range-view--week">
|
||
<header class="dashboard-range-view__hero">
|
||
<p class="eyebrow">Wochenansicht</p>
|
||
<h1><?= e($dashboardWeek['title']) ?></h1>
|
||
<h2><?= e($dashboardWeek['range']) ?></h2>
|
||
</header>
|
||
|
||
<?php $weekInsights = is_array($dashboardWeek['insights'] ?? null) ? $dashboardWeek['insights'] : []; ?>
|
||
<?php if ((int) ($weekInsights['average_steps'] ?? 0) > 0 || (int) ($weekInsights['daily_sport_minutes'] ?? 0) > 0): ?>
|
||
<section class="week-insight-card glass-panel">
|
||
<?php if ((int) ($weekInsights['average_steps'] ?? 0) > 0): ?>
|
||
<p>
|
||
Du bist in dieser Woche durchschnittlich <strong><?= e(number_format((int) $weekInsights['average_steps'], 0, ',', '.')) ?> Schritte</strong> gegangen.
|
||
<?php if (!empty($weekInsights['has_step_comparison'])): ?>
|
||
Das sind <strong><?= e(number_format(abs((int) $weekInsights['step_difference']), 0, ',', '.')) ?> Schritte <?= e((string) $weekInsights['step_direction']) ?></strong> als im vergangenen Monat.
|
||
<?php endif; ?>
|
||
</p>
|
||
<?php endif; ?>
|
||
<?php if ((int) ($weekInsights['daily_sport_minutes'] ?? 0) > 0): ?>
|
||
<p>Täglich hast du im Schnitt <strong><?= e((string) $weekInsights['daily_sport_minutes']) ?> Minuten Sport</strong> gemacht.</p>
|
||
<?php endif; ?>
|
||
</section>
|
||
<?php endif; ?>
|
||
|
||
<div class="range-period-rail range-period-rail--week">
|
||
<?php foreach (($dashboardWeek['periods'] ?? [$dashboardWeek]) as $week): ?>
|
||
<article class="range-period-panel<?= !empty($week['is_selected']) ? ' is-selected' : '' ?>">
|
||
<header class="range-period-panel__head">
|
||
<a href="/?view=week&date=<?= e(rawurlencode((string) ($week['key'] ?? $dashboardDate))) ?>">
|
||
<h3><?= e((string) $week['title']) ?></h3>
|
||
<p><?= e((string) $week['range']) ?></p>
|
||
</a>
|
||
</header>
|
||
|
||
<nav class="range-score-strip range-score-strip--week glass-panel" aria-label="Tage dieser Woche">
|
||
<span class="score-scale score-scale--range" aria-hidden="true"><span>+2</span><span>+1</span><span>0</span><span>-1</span><span>-2</span></span>
|
||
<?php foreach ($week['days'] as $day): ?>
|
||
<a class="range-score-day<?= !empty($day['is_current']) ? ' is-current' : '' ?><?= empty($day['has_content']) ? ' is-empty' : '' ?>" href="/?view=week&date=<?= e(rawurlencode((string) $day['date'])) ?>" title="<?= e((string) $day['weekday']) ?>">
|
||
<span class="compare-day__line<?= !empty($day['is_current']) ? ' is-primary' : '' ?> score-<?= e((string) ($day['score_level'] ?? 'empty')) ?> compare-tone-<?= e((string) ($day['line_tone'] ?? 'empty')) ?>">
|
||
<span class="compare-day__marker"></span>
|
||
</span>
|
||
<span class="range-score-day__label"><?= e((string) $day['day']) ?></span>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</nav>
|
||
</article>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
|
||
<?php $weekDetailDays = array_values(array_reverse(array_filter($dashboardWeek['days'], static fn (array $day): bool => !empty($day['has_content'])))); ?>
|
||
<?php if ($weekDetailDays !== []): ?>
|
||
<div class="range-day-list">
|
||
<?php foreach ($weekDetailDays as $day): ?>
|
||
<?php
|
||
$entry = is_array($day['entry'] ?? null) ? $day['entry'] : null;
|
||
$events = $entry !== null && is_array($entry['events'] ?? null) ? $entry['events'] : [];
|
||
$summaryText = $entry !== null ? trim((string) ($entry['summary']['comment'] ?? $entry['summary_comment'] ?? $entry['note'] ?? '')) : '';
|
||
$dayTone = (string) ($day['line_tone'] ?? 'empty');
|
||
$dayImage = $entry !== null && is_string($entry['background_image_url'] ?? null) ? (string) $entry['background_image_url'] : null;
|
||
?>
|
||
<a class="range-day-card range-day-card--<?= e($dayTone) ?><?= empty($day['has_content']) ? ' is-empty' : '' ?> glass-panel" href="/?view=day&date=<?= e(rawurlencode((string) $day['date'])) ?>">
|
||
<?php if ($dayImage !== null): ?>
|
||
<img class="range-day-card__image" src="<?= e($dayImage) ?>" alt="">
|
||
<?php endif; ?>
|
||
|
||
<div class="range-day-card__body">
|
||
<p class="eyebrow"><?= e((string) $day['weekday']) ?></p>
|
||
<p class="range-day-card__score">Bilanz <?= e($formatBalanceValue($entry)) ?></p>
|
||
<p class="range-day-card__summary"><?= $summaryText !== '' ? e($summaryText) : 'Noch keine Tagesbilanz.' ?></p>
|
||
|
||
<?php if ($events !== []): ?>
|
||
<ul class="range-moment-list">
|
||
<?php foreach ($events as $event): ?>
|
||
<?php if (!is_array($event)) { continue; } ?>
|
||
<?php
|
||
$eventType = (string) ($event['type'] ?? 'event');
|
||
$eventScore = signal_combo_score($event['mood'] ?? 0, $event['energy'] ?? 0, $event['stress'] ?? 0);
|
||
$eventTone = signal_value_class($eventScore);
|
||
$sportType = $eventType === 'sport' ? find_sport_type($settings, (string) ($event['sport_type_id'] ?? '')) : null;
|
||
$eventValue = (float) ($event['value'] ?? 0);
|
||
$eventValueText = $eventValue > 0 ? rtrim(rtrim(number_format($eventValue, 2, ',', '.'), '0'), ',') . ' ' . (string) ($event['unit'] ?? '') : '';
|
||
$eventTitle = trim((string) ($event['comment'] ?? '')) !== '' ? trim((string) $event['comment']) : day_event_type_label($eventType);
|
||
$eventDetail = $eventValueText;
|
||
|
||
if ($eventType === 'sport') {
|
||
$eventTitle = (string) ($sportType['label'] ?? 'Sport');
|
||
}
|
||
|
||
if ($eventType === 'sleep') {
|
||
$eventTitle = 'Schlaf';
|
||
}
|
||
?>
|
||
<li class="range-moment-list__item range-moment-list__item--<?= e($eventTone) ?>">
|
||
<span class="range-moment-list__bullet" aria-hidden="true"></span>
|
||
<span>
|
||
<strong><?= e($eventTitle) ?></strong>
|
||
<?php if ($eventDetail !== ''): ?>
|
||
<span><?= e($eventDetail) ?></span>
|
||
<?php endif; ?>
|
||
</span>
|
||
</li>
|
||
<?php endforeach; ?>
|
||
</ul>
|
||
<?php endif; ?>
|
||
</div>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
</section>
|
||
<?php else: ?>
|
||
<section class="dashboard-range-view dashboard-range-view--month">
|
||
<header class="dashboard-range-view__hero">
|
||
<p class="eyebrow">Monatsansicht</p>
|
||
<h1><?= e($dashboardMonth['title']) ?></h1>
|
||
</header>
|
||
|
||
<div class="range-period-rail range-period-rail--month">
|
||
<?php foreach (($dashboardMonth['periods'] ?? [$dashboardMonth]) as $month): ?>
|
||
<article class="range-period-panel<?= !empty($month['is_selected']) ? ' is-selected' : '' ?>">
|
||
<header class="range-period-panel__head">
|
||
<a href="/?view=month&date=<?= e(rawurlencode((string) ($month['key'] ?? $dashboardDate))) ?>">
|
||
<h3><?= e((string) $month['title']) ?></h3>
|
||
</a>
|
||
</header>
|
||
|
||
<nav class="range-score-strip range-score-strip--month glass-panel" aria-label="Tage dieses Monats">
|
||
<span class="score-scale score-scale--range score-scale--month" aria-hidden="true"><span>+2</span><span>+1</span><span>0</span><span>-1</span><span>-2</span></span>
|
||
<?php foreach ($month['days'] as $day): ?>
|
||
<a class="range-score-day<?= empty($day['has_content']) ? ' is-empty' : '' ?><?= !empty($day['is_future']) ? ' is-future' : '' ?>" href="/?view=month&date=<?= e(rawurlencode((string) $day['date'])) ?>" title="<?= e((string) $day['weekday']) ?>">
|
||
<span class="compare-day__line score-<?= e((string) ($day['score_level'] ?? 'empty')) ?> compare-tone-<?= e((string) ($day['line_tone'] ?? 'empty')) ?>">
|
||
<span class="compare-day__marker"></span>
|
||
</span>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</nav>
|
||
</article>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
|
||
<?php $monthDetailDays = array_values(array_filter($dashboardMonth['days'], static function (array $day): bool {
|
||
$entry = is_array($day['entry'] ?? null) ? $day['entry'] : null;
|
||
$summaryText = $entry !== null ? trim((string) ($entry['summary']['comment'] ?? $entry['summary_comment'] ?? $entry['note'] ?? '')) : '';
|
||
|
||
return !empty($day['has_content']) || $summaryText !== '';
|
||
})); ?>
|
||
<?php $monthDetailDays = array_reverse($monthDetailDays); ?>
|
||
<?php if ($monthDetailDays !== []): ?>
|
||
<div class="range-day-list range-day-list--month">
|
||
<?php foreach ($monthDetailDays as $day): ?>
|
||
<?php
|
||
$entry = is_array($day['entry'] ?? null) ? $day['entry'] : null;
|
||
$summaryText = $entry !== null ? trim((string) ($entry['summary']['comment'] ?? $entry['summary_comment'] ?? $entry['note'] ?? '')) : '';
|
||
$dayTone = (string) ($day['line_tone'] ?? 'empty');
|
||
?>
|
||
<a class="range-day-card range-day-card--summary-only range-day-card--<?= e($dayTone) ?> glass-panel" href="/?view=day&date=<?= e(rawurlencode((string) $day['date'])) ?>">
|
||
<div class="range-day-card__body">
|
||
<p class="eyebrow"><?= e((string) $day['weekday']) ?></p>
|
||
<p class="range-day-card__score">Bilanz <?= e($formatBalanceValue($entry)) ?></p>
|
||
<p class="range-day-card__summary"><?= $summaryText !== '' ? e($summaryText) : 'Tagesbilanz' ?></p>
|
||
</div>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
</section>
|
||
<?php endif; ?>
|
||
|
||
<div class="dashboard-overlay" data-settings-menu-overlay hidden>
|
||
<div class="dashboard-overlay__backdrop" data-settings-menu-close></div>
|
||
<section class="dashboard-modal dashboard-modal--settings glass-panel" role="dialog" aria-modal="true">
|
||
<div class="dashboard-modal__controls">
|
||
<button class="dashboard-modal__round" type="button" data-settings-menu-close>×</button>
|
||
</div>
|
||
|
||
<h2 class="dashboard-modal__title">Einstellungen und Bereiche</h2>
|
||
<div class="settings-menu-grid">
|
||
<a class="options-menu-card" href="/options?panel=sports"><strong>Sportarten anpassen</strong><span>Eigene Sportarten und Bonuspunkte</span></a>
|
||
<a class="options-menu-card" href="/options?panel=walk"><strong>Spaziergang anpassen</strong><span>Zeit oder Schritte auswerten</span></a>
|
||
<a class="options-menu-card" href="/options?panel=sleep"><strong>Schlaf anpassen</strong><span>Optimale Schlafmenge</span></a>
|
||
<a class="options-menu-card" href="/options?panel=health"><strong>Health Import</strong><span>Apple Health automatisch übernehmen</span></a>
|
||
<a class="options-menu-card" href="/options?panel=reminders"><strong>Erinnerungen setzen</strong><span>Push und tägliche Erinnerung</span></a>
|
||
<a class="options-menu-card" href="/options?panel=ratings"><strong>Bewertungsskala ändern</strong><span>Labels und Schutzregeln</span></a>
|
||
<a class="options-menu-card" href="/options?panel=stats"><strong>Statistik</strong><span>Verlauf und Aktivität</span></a>
|
||
<?php if (!empty($authUser['is_admin'])): ?>
|
||
<a class="options-menu-card" href="/options?panel=users"><strong>Neue Nutzer anlegen</strong><span>Accounts und Adminrechte</span></a>
|
||
<?php endif; ?>
|
||
<form method="post" action="/logout" class="options-logout-form">
|
||
<?= csrf_field() ?>
|
||
<button class="options-menu-card options-menu-card--danger" type="submit"><strong>Abmelden</strong><span>Sitzung sicher beenden</span></button>
|
||
</form>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<div class="media-lightbox" data-media-lightbox hidden>
|
||
<button class="media-lightbox__backdrop" type="button" data-media-lightbox-close aria-label="Ansicht schließen"></button>
|
||
<div class="media-lightbox__panel" role="dialog" aria-modal="true" aria-label="Medienansicht">
|
||
<button class="media-lightbox__close" type="button" data-media-lightbox-close aria-label="Ansicht schließen">×</button>
|
||
<div class="media-lightbox__content" data-media-lightbox-content></div>
|
||
</div>
|
||
</div>
|
||
</section>
|