419 lines
26 KiB
PHP
419 lines
26 KiB
PHP
<section class="page-grid">
|
|
<article class="glass-panel form-panel form-panel--wide">
|
|
<div class="section-head">
|
|
<div>
|
|
<p class="eyebrow">Dein Account</p>
|
|
<h3>Score und Sportarten persönlich anpassen</h3>
|
|
<p class="helper-text">Alle Einstellungen auf dieser Seite gelten nur für deinen eigenen Account und beeinflussen keine anderen Nutzer.</p>
|
|
</div>
|
|
<span class="chart-chip">Maximal <?= e(format_points((float) $maxScore)) ?> Punkte</span>
|
|
</div>
|
|
|
|
<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>
|
|
|
|
<div class="settings-section">
|
|
<div class="section-head section-head--compact">
|
|
<div>
|
|
<h4>Tracking-Felder</h4>
|
|
<p class="helper-text">Hier steuerst du, welche Zusatzfelder auf deiner Tracking-Seite sichtbar und in die Bewertung einbezogen werden.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<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>1 bedeutet keine Schmerzen, 10 starke Schmerzen. Der Wert wird wie bei Stress positiv gewichtet, wenn die Schmerzen niedrig sind.</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>
|
|
|
|
<div class="settings-section">
|
|
<h4>Sport-Bänder</h4>
|
|
<div class="band-grid">
|
|
<?php foreach ($settings['scoring']['sport_bands'] as $index => $band): ?>
|
|
<div class="band-card">
|
|
<label><span>Min</span><input type="number" name="settings[scoring][sport_bands][<?= e((string) $index) ?>][min]" value="<?= e((string) $band['min']) ?>"></label>
|
|
<label><span>Max</span><input type="number" name="settings[scoring][sport_bands][<?= e((string) $index) ?>][max]" value="<?= e((string) $band['max']) ?>"></label>
|
|
<label><span>Punkte</span><input type="number" name="settings[scoring][sport_bands][<?= e((string) $index) ?>][points]" value="<?= e((string) $band['points']) ?>"></label>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-section">
|
|
<div class="section-head section-head--compact">
|
|
<div>
|
|
<h4>Spaziergang</h4>
|
|
<p class="helper-text">Du kannst Spaziergänge pro Account entweder über Zeit oder über Schritte bewerten lassen.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<label>
|
|
<span>Spaziergang auswerten nach</span>
|
|
<select name="settings[walk][mode]">
|
|
<?php foreach ($walkModeOptions as $modeValue => $modeLabel): ?>
|
|
<option value="<?= e($modeValue) ?>" <?= ($settings['walk']['mode'] ?? 'time') === $modeValue ? 'selected' : '' ?>><?= e($modeLabel) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</label>
|
|
|
|
<?php if (($settings['walk']['mode'] ?? 'time') === 'steps'): ?>
|
|
<div class="band-card">
|
|
<h5>Schritte mit Bestwert bei 10.000</h5>
|
|
<p class="helper-text">Bei Schritten liegt der beste Wert bei 10.000. Darunter steigt die Punktzahl schrittweise an, darüber fällt sie wieder sanft ab.</p>
|
|
<p class="helper-text">Aktueller Verlauf: 0 / 3.000 / 5.000 / 7.500 / 10.000 / 12.500 / 15.000 / 20.000 Schritte</p>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="band-grid">
|
|
<?php foreach ($settings['scoring']['walk_bands'] as $index => $band): ?>
|
|
<div class="band-card">
|
|
<label><span>Min</span><input type="number" name="settings[scoring][walk_bands][<?= e((string) $index) ?>][min]" value="<?= e((string) $band['min']) ?>"></label>
|
|
<label><span>Max</span><input type="number" name="settings[scoring][walk_bands][<?= e((string) $index) ?>][max]" value="<?= e((string) $band['max']) ?>"></label>
|
|
<label><span>Punkte</span><input type="number" name="settings[scoring][walk_bands][<?= e((string) $index) ?>][points]" value="<?= e((string) $band['points']) ?>"></label>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div class="settings-section">
|
|
<div class="section-head section-head--compact">
|
|
<div>
|
|
<h4>Sportarten und Bonuspunkte</h4>
|
|
<p class="helper-text">Lege fest, welche Sportarten nur in deinem eigenen Tracking auswählbar sind. Entfernte Sportarten kannst du darunter jederzeit wieder für deinen Account hinzufügen.</p>
|
|
</div>
|
|
<button class="ghost-button" type="button" data-add-sport-type>Sportart hinzufügen</button>
|
|
</div>
|
|
|
|
<input type="hidden" name="settings[sport_types_present]" value="1">
|
|
|
|
<?php if (!empty($sportTypePresets)): ?>
|
|
<div class="preset-list">
|
|
<?php foreach ($sportTypePresets as $preset): ?>
|
|
<button
|
|
class="preset-pill"
|
|
type="button"
|
|
data-sport-preset
|
|
data-id="<?= e($preset['id']) ?>"
|
|
data-label="<?= e($preset['label']) ?>"
|
|
data-icon="<?= e($preset['icon']) ?>"
|
|
data-location="<?= e($preset['location'] ?? '') ?>"
|
|
data-recovery-group="<?= e($preset['recovery_group']) ?>"
|
|
data-bonus-points="<?= e((string) $preset['bonus_points']) ?>"
|
|
data-allow-consecutive="<?= !empty($preset['allow_consecutive']) ? '1' : '0' ?>"
|
|
>
|
|
<img src="<?= e(sport_icon_path($preset['icon'])) ?>" alt="">
|
|
<span><?= e($preset['label']) ?></span>
|
|
</button>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="sport-type-list" data-sport-type-list>
|
|
<?php foreach ($settings['sport_types'] as $index => $sportType): ?>
|
|
<div class="sport-type-card band-card" data-sport-type-row>
|
|
<input type="hidden" name="settings[sport_types][<?= e((string) $index) ?>][id]" value="<?= e($sportType['id']) ?>" data-name-template="settings[sport_types][__INDEX__][id]">
|
|
|
|
<div class="field-grid field-grid--four">
|
|
<label>
|
|
<span>Bezeichnung</span>
|
|
<input type="text" name="settings[sport_types][<?= e((string) $index) ?>][label]" value="<?= e($sportType['label']) ?>" data-name-template="settings[sport_types][__INDEX__][label]">
|
|
</label>
|
|
|
|
<label>
|
|
<span>Icon</span>
|
|
<select name="settings[sport_types][<?= e((string) $index) ?>][icon]" data-name-template="settings[sport_types][__INDEX__][icon]">
|
|
<?php foreach (sport_icon_options() as $iconValue => $iconLabel): ?>
|
|
<option value="<?= e($iconValue) ?>" <?= $sportType['icon'] === $iconValue ? 'selected' : '' ?>><?= e($iconLabel) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</label>
|
|
|
|
<label>
|
|
<span>Ort</span>
|
|
<select name="settings[sport_types][<?= e((string) $index) ?>][location]" data-name-template="settings[sport_types][__INDEX__][location]">
|
|
<?php foreach ($sportLocationOptions as $locationValue => $locationLabel): ?>
|
|
<option value="<?= e($locationValue) ?>" <?= ($sportType['location'] ?? '') === $locationValue ? 'selected' : '' ?>><?= e($locationLabel) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</label>
|
|
|
|
<label>
|
|
<span>Erholungsgruppe optional</span>
|
|
<input type="text" name="settings[sport_types][<?= e((string) $index) ?>][recovery_group]" value="<?= e($sportType['recovery_group']) ?>" data-name-template="settings[sport_types][__INDEX__][recovery_group]">
|
|
</label>
|
|
</div>
|
|
|
|
<div class="field-grid field-grid--four">
|
|
<label>
|
|
<span>Bonuspunkte</span>
|
|
<input type="number" name="settings[sport_types][<?= e((string) $index) ?>][bonus_points]" value="<?= e((string) $sportType['bonus_points']) ?>" min="0" max="20" data-name-template="settings[sport_types][__INDEX__][bonus_points]">
|
|
</label>
|
|
</div>
|
|
|
|
<label class="checkbox-row">
|
|
<input type="checkbox" name="settings[sport_types][<?= e((string) $index) ?>][allow_consecutive]" value="1" <?= !empty($sportType['allow_consecutive']) ? 'checked' : '' ?> data-name-template="settings[sport_types][__INDEX__][allow_consecutive]">
|
|
<span>Darf an aufeinanderfolgenden Tagen Bonus geben</span>
|
|
</label>
|
|
|
|
<p class="helper-text">Die Erholungsgruppe brauchst du nur, wenn mehrere Varianten dieselbe Belastung teilen sollen, zum Beispiel Krafttraining Zuhause und Krafttraining Auswärts. Wenn du nichts Besonderes einträgst, reicht die Sportart selbst.</p>
|
|
|
|
<div class="sport-type-card__actions">
|
|
<span class="sport-pill sport-pill--soft">
|
|
<img src="<?= e(sport_icon_path($sportType['icon'])) ?>" alt="">
|
|
<span><?= e($sportType['label']) ?><?= !empty($sportType['location']) ? ' · ' . e(sport_location_label((string) $sportType['location'])) : '' ?></span>
|
|
</span>
|
|
<button class="ghost-button ghost-button--small" type="button" data-remove-sport-type>Entfernen</button>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<div class="section-actions">
|
|
<button class="primary-button" type="submit">Sportarten speichern</button>
|
|
</div>
|
|
|
|
<template id="sport-type-row-template">
|
|
<div class="sport-type-card band-card" data-sport-type-row>
|
|
<input type="hidden" value="" data-name-template="settings[sport_types][__INDEX__][id]">
|
|
|
|
<div class="field-grid field-grid--four">
|
|
<label>
|
|
<span>Bezeichnung</span>
|
|
<input type="text" value="" placeholder="z. B. Krafttraining Zuhause" data-name-template="settings[sport_types][__INDEX__][label]">
|
|
</label>
|
|
|
|
<label>
|
|
<span>Icon</span>
|
|
<select data-name-template="settings[sport_types][__INDEX__][icon]">
|
|
<?php foreach (sport_icon_options() as $iconValue => $iconLabel): ?>
|
|
<option value="<?= e($iconValue) ?>"><?= e($iconLabel) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</label>
|
|
|
|
<label>
|
|
<span>Ort</span>
|
|
<select data-name-template="settings[sport_types][__INDEX__][location]">
|
|
<?php foreach ($sportLocationOptions as $locationValue => $locationLabel): ?>
|
|
<option value="<?= e($locationValue) ?>"><?= e($locationLabel) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</label>
|
|
|
|
<label>
|
|
<span>Erholungsgruppe optional</span>
|
|
<input type="text" value="" placeholder="z. B. krafttraining" data-name-template="settings[sport_types][__INDEX__][recovery_group]">
|
|
</label>
|
|
</div>
|
|
|
|
<div class="field-grid field-grid--four">
|
|
<label>
|
|
<span>Bonuspunkte</span>
|
|
<input type="number" value="2" min="0" max="20" data-name-template="settings[sport_types][__INDEX__][bonus_points]">
|
|
</label>
|
|
</div>
|
|
|
|
<label class="checkbox-row">
|
|
<input type="checkbox" value="1" data-name-template="settings[sport_types][__INDEX__][allow_consecutive]">
|
|
<span>Darf an aufeinanderfolgenden Tagen Bonus geben</span>
|
|
</label>
|
|
|
|
<p class="helper-text">Die Erholungsgruppe ist nur dann nützlich, wenn mehrere Varianten sportlich gleich belastend sind und denselben Bonusrhythmus teilen sollen.</p>
|
|
|
|
<div class="sport-type-card__actions">
|
|
<span class="sport-pill sport-pill--soft">
|
|
<img src="<?= e(sport_icon_path('run')) ?>" alt="">
|
|
<span>Neue Sportart</span>
|
|
</span>
|
|
<button class="ghost-button ghost-button--small" type="button" data-remove-sport-type>Entfernen</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<div class="settings-section">
|
|
<div class="section-head section-head--compact">
|
|
<div>
|
|
<h4>Erinnerungen</h4>
|
|
<p class="helper-text">Diese Erinnerungen gelten nur für deinen Account. Auf dem iPhone funktioniert Push nach dem Hinzufügen zum Home-Bildschirm.</p>
|
|
</div>
|
|
<span class="chart-chip"><?= e((string) $pushSubscriptionCount) ?> Gerät<?= $pushSubscriptionCount === 1 ? '' : 'e' ?></span>
|
|
</div>
|
|
|
|
<div class="field-grid field-grid--two">
|
|
<label class="checkbox-row checkbox-row--panel">
|
|
<input type="checkbox" name="settings[notifications][enabled]" value="1" <?= !empty($settings['notifications']['enabled']) ? 'checked' : '' ?>>
|
|
<span>Tägliche Push-Erinnerung aktivieren</span>
|
|
</label>
|
|
|
|
<label>
|
|
<span>Uhrzeit der Erinnerung</span>
|
|
<input type="time" name="settings[notifications][time]" value="<?= e((string) ($settings['notifications']['time'] ?? '20:30')) ?>">
|
|
</label>
|
|
</div>
|
|
|
|
<div
|
|
class="push-panel band-card"
|
|
data-push-panel
|
|
data-push-ready="<?= !empty($pushAvailable) && !empty($pushPublicKey) ? '1' : '0' ?>"
|
|
>
|
|
<div>
|
|
<h5>Push auf diesem Gerät</h5>
|
|
<p class="helper-text" data-push-status>
|
|
<?php if (!empty($pushAvailable) && !empty($pushPublicKey)): ?>
|
|
Installiere die App auf Wunsch als PWA und aktiviere dann Push direkt auf diesem Gerät.
|
|
<?php else: ?>
|
|
Push ist auf diesem Server gerade noch nicht verfügbar.
|
|
<?php endif; ?>
|
|
</p>
|
|
</div>
|
|
|
|
<div class="push-actions">
|
|
<button class="ghost-button" type="button" data-push-enable <?= empty($pushAvailable) || empty($pushPublicKey) ? 'disabled' : '' ?>>Push aktivieren</button>
|
|
<button class="ghost-button" type="button" data-push-disable <?= empty($pushAvailable) || empty($pushPublicKey) ? 'disabled' : '' ?>>Auf diesem Gerät entfernen</button>
|
|
<button class="ghost-button" type="button" data-push-test <?= empty($pushAvailable) || empty($pushPublicKey) ? 'disabled' : '' ?>>Test senden</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section-actions">
|
|
<button class="primary-button" type="submit">Erinnerungen speichern</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-section">
|
|
<h4>Bewertungsskala</h4>
|
|
<p class="helper-text">Diese Score-Grenzen und Schutzregeln gelten ebenfalls nur für deinen eigenen Account.</p>
|
|
<div class="band-grid">
|
|
<?php foreach ($settings['ratings'] as $index => $rating): ?>
|
|
<div class="band-card">
|
|
<label><span>Label</span><input type="text" name="settings[ratings][<?= e((string) $index) ?>][label]" value="<?= e($rating['label']) ?>"></label>
|
|
<label><span>Min</span><input type="number" name="settings[ratings][<?= e((string) $index) ?>][min]" value="<?= e((string) $rating['min']) ?>"></label>
|
|
<label><span>Max</span><input type="number" name="settings[ratings][<?= e((string) $index) ?>][max]" value="<?= e((string) $rating['max']) ?>"></label>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-section">
|
|
<h4>Schutzregeln</h4>
|
|
<div class="band-grid">
|
|
<?php foreach ($settings['guardrails'] as $index => $guardrail): ?>
|
|
<div class="band-card">
|
|
<label><span>Stimmung max</span><input type="number" name="settings[guardrails][<?= e((string) $index) ?>][mood_max]" value="<?= e((string) $guardrail['mood_max']) ?>" min="1" max="10"></label>
|
|
<label><span>Energie max</span><input type="number" name="settings[guardrails][<?= e((string) $index) ?>][energy_max]" value="<?= e((string) ($guardrail['energy_max'] ?? '')) ?>" min="1" max="10"></label>
|
|
<label><span>Maximales Label</span><input type="text" name="settings[guardrails][<?= e((string) $index) ?>][cap_label]" value="<?= e($guardrail['cap_label']) ?>"></label>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<label>
|
|
<span>Tagebuchpunkte bei nicht-leerer Notiz</span>
|
|
<input type="number" name="settings[scoring][journal_points]" value="<?= e((string) $settings['scoring']['journal_points']) ?>" min="0" max="20">
|
|
</label>
|
|
|
|
<button class="primary-button" type="submit">Bewertung speichern</button>
|
|
</form>
|
|
</article>
|
|
|
|
<aside class="stack-column">
|
|
<article class="glass-panel detail-card">
|
|
<p class="eyebrow">Backup</p>
|
|
<h3>Eigene Einträge sichern</h3>
|
|
<p class="helper-text">Deine Tagesdateien liegen auf dem Server verschlüsselt. Beim Download bekommst du automatisch ein lesbares Backup mit allen Tagen als `.txt`-Dateien in einer ZIP-Datei. Beim Import werden diese Dateien wieder still im Hintergrund geschützt gespeichert.</p>
|
|
|
|
<form method="post" action="/options" class="stack-form">
|
|
<?= csrf_field() ?>
|
|
<input type="hidden" name="form_name" value="export_backup">
|
|
<button class="primary-button" type="submit" <?= empty($backupAvailable) ? 'disabled' : '' ?>>Backup herunterladen</button>
|
|
<?php if (empty($backupAvailable)): ?>
|
|
<p class="helper-text">Auf diesem Server fehlt gerade die ZIP-Erweiterung für den Download.</p>
|
|
<?php endif; ?>
|
|
</form>
|
|
|
|
<form method="post" action="/options" enctype="multipart/form-data" class="stack-form">
|
|
<?= csrf_field() ?>
|
|
<input type="hidden" name="form_name" value="import_backup">
|
|
<label>
|
|
<span>Backup importieren</span>
|
|
<input type="file" name="backup_files[]" accept=".zip,.txt" multiple>
|
|
</label>
|
|
<p class="helper-text">Du kannst ein komplettes ZIP-Backup oder einzelne Tagesdateien wie `2026-04-13.txt` importieren. Vorhandene Tage mit demselben Datum werden dabei aktualisiert.</p>
|
|
<button class="ghost-button" type="submit">Backup importieren</button>
|
|
</form>
|
|
</article>
|
|
|
|
<article class="glass-panel detail-card">
|
|
<p class="eyebrow">Sicherheit</p>
|
|
<h3>Passwort ändern</h3>
|
|
<form method="post" action="/options" class="stack-form">
|
|
<?= csrf_field() ?>
|
|
<input type="hidden" name="form_name" value="password">
|
|
<label><span>Aktuelles Passwort</span><input type="password" name="current_password" required></label>
|
|
<label><span>Neues Passwort</span><input type="password" name="new_password" minlength="10" required></label>
|
|
<label><span>Neues Passwort wiederholen</span><input type="password" name="new_password_confirm" minlength="10" required></label>
|
|
<button class="primary-button" type="submit">Passwort aktualisieren</button>
|
|
</form>
|
|
</article>
|
|
|
|
<?php if (!empty($authUser['is_admin'])): ?>
|
|
<article class="glass-panel detail-card">
|
|
<p class="eyebrow">Mehrere Accounts</p>
|
|
<h3>Neuen Nutzer anlegen</h3>
|
|
<form method="post" action="/options" class="stack-form">
|
|
<?= csrf_field() ?>
|
|
<input type="hidden" name="form_name" value="create_user">
|
|
<label><span>Benutzername</span><input type="text" name="username" required></label>
|
|
<label><span>Startpasswort</span><input type="password" name="password" minlength="10" required></label>
|
|
<label class="checkbox-row"><input type="checkbox" name="is_admin" value="1"><span>Als Admin anlegen</span></label>
|
|
<button class="primary-button" type="submit">Account erstellen</button>
|
|
</form>
|
|
|
|
<?php if ($users !== []): ?>
|
|
<div class="user-list">
|
|
<?php foreach ($users as $account): ?>
|
|
<div class="user-row">
|
|
<strong><?= e($account['username']) ?></strong>
|
|
<span><?= !empty($account['is_admin']) ? 'Admin' : 'Nutzer' ?></span>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</article>
|
|
<?php endif; ?>
|
|
</aside>
|
|
</section>
|