feat(dashboard): refine moment media experience

This commit is contained in:
2026-05-18 23:49:15 +02:00
parent b8a96e96ef
commit bc6e850afb
12 changed files with 624 additions and 158 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ $brandSubtitle = match ($page) {
'setup' => 'Erstkonfiguration',
default => 'Stimmungstracker',
};
$immersiveDashboard = $page === 'dashboard';
$immersiveDashboard = in_array($page, ['dashboard', 'options'], true);
$cssVersion = is_file(base_path('assets/css/app.css')) ? (string) filemtime(base_path('assets/css/app.css')) : '1';
$jsVersion = is_file(base_path('assets/js/app.js')) ? (string) filemtime(base_path('assets/js/app.js')) : '1';
?>
+48 -24
View File
@@ -69,8 +69,21 @@ $summaryAlcohol = !empty($dayEntry['summary']['alcohol'] ?? $dayEntry['summary_a
<?php endif; ?>
<?php foreach ($dashboardTimeline as $item): ?>
<?php $sportType = ($item['type'] ?? '') === 'sport' ? find_sport_type($settings, (string) ($item['sport_type_id'] ?? '')) : null; ?>
<?php $eventType = (string) ($item['type'] ?? 'event'); ?>
<?php $sportType = $eventType === 'sport' ? find_sport_type($settings, (string) ($item['sport_type_id'] ?? '')) : null; ?>
<?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' => (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 $eventPayload = encode_payload([
'id' => (string) ($item['id'] ?? ''),
'type' => (string) ($item['type'] ?? 'event'),
@@ -79,39 +92,35 @@ $summaryAlcohol = !empty($dayEntry['summary']['alcohol'] ?? $dayEntry['summary_a
'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),
]); ?>
<article class="timeline-card timeline-card--event timeline-card--<?= e($eventTone) ?> glass-panel" data-event-editable data-event-payload="<?= e($eventPayload) ?>">
<?php $hasEventImage = is_string($item['image_url'] ?? 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): ?>
<img class="timeline-card__image" src="<?= e((string) $item['image_url']) ?>" alt="">
<?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><?= e($item['time'] !== '' ? $item['time'] : '--:--') ?></strong>
<strong class="timeline-card__time"><?= e($item['time'] !== '' ? $item['time'] : '--:--') ?></strong>
</div>
</div>
<div class="timeline-card__body">
<h3>
<?php if ((string) ($item['type'] ?? '') === 'alcohol'): ?>
<?= !empty($item['consumed']) ? 'Heute getrunken' : 'Heute nicht getrunken' ?>
<?php else: ?>
<?= e($item['comment'] !== '' ? $item['comment'] : day_event_type_label((string) $item['type'])) ?>
<?php endif; ?>
</h3>
<?php if ((float) $item['value'] > 0): ?>
<h3><?= e($eventTitle) ?></h3>
<?php if ((string) ($item['type'] ?? '') === 'alcohol'): ?>
<p class="timeline-card__value">
<?= e(rtrim(rtrim(number_format((float) $item['value'], 2, ',', '.'), '0'), ',')) ?>
<?= e((string) $item['unit']) ?>
<?php if ($sportType !== null): ?>
· <?= e((string) ($sportType['label'] ?? '')) ?>
<?php endif; ?>
<?= !empty($item['consumed']) ? 'Heute getrunken' : 'Heute nicht getrunken' ?>
</p>
<?php elseif ($sportType !== null): ?>
<p class="timeline-card__value"><?= e((string) ($sportType['label'] ?? '')) ?></p>
<?php elseif ($eventDetail !== ''): ?>
<p class="timeline-card__value"><?= e($eventDetail) ?></p>
<?php endif; ?>
<?php if ((string) ($item['comment'] ?? '') !== '' && (string) ($item['type'] ?? '') === 'alcohol'): ?>
@@ -121,8 +130,10 @@ $summaryAlcohol = !empty($dayEntry['summary']['alcohol'] ?? $dayEntry['summary_a
<div class="signal-row">
<?php foreach (['mood' => 'Stimmung', 'energy' => 'Energie', 'stress' => 'Stress'] as $metric => $label): ?>
<?php $value = normalize_signal_value($item[$metric] ?? 0); ?>
<span class="signal-pill signal-pill--<?= e(signal_badge_tone($value, $metric)) ?>">
<?php $valueTone = signal_value_class($metric === 'stress' ? -$value : $value); ?>
<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; ?>
@@ -142,6 +153,15 @@ $summaryAlcohol = !empty($dayEntry['summary']['alcohol'] ?? $dayEntry['summary_a
</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>
@@ -240,7 +260,7 @@ $summaryAlcohol = !empty($dayEntry['summary']['alcohol'] ?? $dayEntry['summary_a
</div>
</div>
<form method="post" action="/" class="dashboard-modal__form" id="moment-form" data-moment-step="form" hidden>
<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']) ?>">
@@ -261,6 +281,11 @@ $summaryAlcohol = !empty($dayEntry['summary']['alcohol'] ?? $dayEntry['summary_a
<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>
@@ -377,7 +402,7 @@ $summaryAlcohol = !empty($dayEntry['summary']['alcohol'] ?? $dayEntry['summary_a
<?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'] ?? '')) : '';
$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;
?>
@@ -461,7 +486,7 @@ $summaryAlcohol = !empty($dayEntry['summary']['alcohol'] ?? $dayEntry['summary_a
<?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'] ?? '')) : '';
$summaryText = $entry !== null ? trim((string) ($entry['summary']['comment'] ?? $entry['summary_comment'] ?? $entry['note'] ?? '')) : '';
return !empty($day['has_content']) || $summaryText !== '';
})); ?>
@@ -470,7 +495,7 @@ $summaryAlcohol = !empty($dayEntry['summary']['alcohol'] ?? $dayEntry['summary_a
<?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'] ?? '')) : '';
$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&amp;date=<?= e(rawurlencode((string) $day['date'])) ?>">
@@ -495,7 +520,6 @@ $summaryAlcohol = !empty($dayEntry['summary']['alcohol'] ?? $dayEntry['summary_a
<h2 class="dashboard-modal__title">Einstellungen und Bereiche</h2>
<div class="settings-menu-grid">
<a class="options-menu-card" href="/options?panel=score"><strong>Score anpassen</strong><span>Multiplikatoren und Tageslogik</span></a>
<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=reminders"><strong>Erinnerungen setzen</strong><span>Push und tägliche Erinnerung</span></a>
+26 -93
View File
@@ -1,64 +1,5 @@
<section class="options-shell">
<article class="glass-panel options-menu-panel">
<div class="section-head">
<div>
<p class="eyebrow">Optionen</p>
<h3>Einstellungen und Bereiche</h3>
</div>
</div>
<div class="options-menu-grid">
<button class="options-menu-card" type="button" data-options-open="score">
<strong>Score anpassen</strong>
<span>Multiplikatoren und Tageslogik</span>
</button>
<button class="options-menu-card" type="button" data-options-open="sports">
<strong>Sportarten anpassen</strong>
<span>Eigene Sportarten und Bonuspunkte</span>
</button>
<button class="options-menu-card" type="button" data-options-open="walk">
<strong>Spaziergang anpassen</strong>
<span>Zeit oder Schritte auswerten</span>
</button>
<button class="options-menu-card" type="button" data-options-open="reminders">
<strong>Erinnerungen setzen</strong>
<span>Push und tägliche Erinnerung</span>
</button>
<button class="options-menu-card" type="button" data-options-open="ratings">
<strong>Bewertungsskala ändern</strong>
<span>Labels und Schutzregeln</span>
</button>
<button class="options-menu-card" type="button" data-options-open="stats">
<strong>Statistik</strong>
<span>Verlauf und Aktivität</span>
</button>
<?php if (!empty($authUser['is_admin'])): ?>
<button class="options-menu-card" type="button" data-options-open="users">
<strong>Neue Nutzer anlegen</strong>
<span>Accounts und Adminrechte</span>
</button>
<?php endif; ?>
<button class="options-menu-card" type="button" data-options-open="security">
<strong>Sicherheit</strong>
<span>Passwort und Backup</span>
</button>
<?php if (!empty($authUser['is_admin'])): ?>
<button class="options-menu-card" type="button" data-options-open="ai">
<strong>KI</strong>
<span>OpenAI und Zusammenfassungen</span>
</button>
<?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>
</article>
<div class="options-overlay" data-options-overlay<?= !empty($optionsOpenPanel) ? '' : ' hidden' ?> data-open-panel="<?= e((string) ($optionsOpenPanel ?? '')) ?>">
<div class="options-overlay" data-options-overlay data-options-standalone="1" data-open-panel="<?= e((string) ($optionsOpenPanel ?? '')) ?>">
<div class="options-overlay__backdrop" data-options-close></div>
<section class="options-modal glass-panel" role="dialog" aria-modal="true">
<div class="options-modal__controls">
@@ -66,40 +7,32 @@
<button class="dashboard-modal__round" type="button" data-options-close>×</button>
</div>
<div class="options-panel" data-options-panel="score" hidden>
<h2>Score anpassen</h2>
<form method="post" action="/options" class="stack-form stack-form--spacious">
<?= csrf_field() ?>
<input type="hidden" name="form_name" value="settings">
<div class="settings-section">
<h4>Multiplikatoren</h4>
<div class="field-grid field-grid--four">
<label><span>Stimmung</span><input type="number" name="settings[scoring][mood_multiplier]" value="<?= e((string) $settings['scoring']['mood_multiplier']) ?>" min="0" max="10"></label>
<label><span>Energie</span><input type="number" name="settings[scoring][energy_multiplier]" value="<?= e((string) $settings['scoring']['energy_multiplier']) ?>" min="0" max="10"></label>
<label><span>Stress</span><input type="number" name="settings[scoring][stress_multiplier]" value="<?= e((string) $settings['scoring']['stress_multiplier']) ?>" min="0" max="10"></label>
<label><span>Schlafgefühl</span><input type="number" name="settings[scoring][sleep_feeling_multiplier]" value="<?= e((string) $settings['scoring']['sleep_feeling_multiplier']) ?>" min="0" max="10"></label>
</div>
<div class="options-menu-panel" data-options-menu>
<div class="section-head">
<div>
<p class="eyebrow">Optionen</p>
<h3>Einstellungen und Bereiche</h3>
</div>
<div class="settings-section">
<h4>Tracking-Felder</h4>
<div class="field-grid field-grid--two">
<label class="checkbox-row checkbox-row--panel">
<input type="checkbox" name="settings[tracking][pain_enabled]" value="1" <?= !empty($settings['tracking']['pain_enabled']) ? 'checked' : '' ?>>
<span><strong>Schmerzen aktivieren</strong><small>Schmerzen werden weiter in den Score einbezogen.</small></span>
</label>
<label><span>Schmerzfaktor</span><input type="number" name="settings[scoring][pain_multiplier]" value="<?= e((string) $settings['scoring']['pain_multiplier']) ?>" min="0" max="10"></label>
</div>
</div>
<div class="settings-section">
<h4>Schlafdauerpunkte</h4>
<div class="field-grid field-grid--four">
<?php foreach ($settings['scoring']['sleep_duration_points'] as $key => $value): ?>
<label><span><?= e($key) ?></span><input type="number" name="settings[scoring][sleep_duration_points][<?= e($key) ?>]" value="<?= e((string) $value) ?>" min="0" max="20"></label>
<?php endforeach; ?>
</div>
</div>
<button class="primary-button" type="submit">Score speichern</button>
</form>
</div>
<div class="options-menu-grid">
<button class="options-menu-card" type="button" data-options-open="sports"><strong>Sportarten anpassen</strong><span>Eigene Sportarten und Bonuspunkte</span></button>
<button class="options-menu-card" type="button" data-options-open="walk"><strong>Spaziergang anpassen</strong><span>Zeit oder Schritte auswerten</span></button>
<button class="options-menu-card" type="button" data-options-open="reminders"><strong>Erinnerungen setzen</strong><span>Push und tägliche Erinnerung</span></button>
<button class="options-menu-card" type="button" data-options-open="ratings"><strong>Bewertungsskala ändern</strong><span>Labels und Schutzregeln</span></button>
<button class="options-menu-card" type="button" data-options-open="stats"><strong>Statistik</strong><span>Verlauf und Aktivität</span></button>
<?php if (!empty($authUser['is_admin'])): ?>
<button class="options-menu-card" type="button" data-options-open="users"><strong>Neue Nutzer anlegen</strong><span>Accounts und Adminrechte</span></button>
<?php endif; ?>
<button class="options-menu-card" type="button" data-options-open="security"><strong>Sicherheit</strong><span>Passwort und Backup</span></button>
<?php if (!empty($authUser['is_admin'])): ?>
<button class="options-menu-card" type="button" data-options-open="ai"><strong>KI</strong><span>OpenAI und Zusammenfassungen</span></button>
<?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>
</div>
<div class="options-panel" data-options-panel="sports" hidden>