add health import

This commit is contained in:
2026-05-19 14:50:19 +02:00
parent bc6e850afb
commit e36f27da4a
12 changed files with 1843 additions and 14 deletions
+293 -2
View File
@@ -625,9 +625,35 @@ body.page-dashboard .content {
.day-summary-card__title {
display: block;
font-size: clamp(1.05rem, 2.5vw, 1.5rem);
font-weight: 400;
font-size: clamp(1.35rem, 4vw, 2.35rem);
font-weight: 650;
line-height: 1.2;
color: #fff;
text-shadow: 0 8px 24px rgba(0, 0, 0, 0.38);
}
.day-summary-card__chips {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
margin-top: 0.85rem;
}
.day-chip {
display: inline-flex;
align-items: center;
min-height: 2rem;
padding: 0.35rem 0.72rem;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.16);
color: rgba(255, 255, 255, 0.92);
font-size: 0.88rem;
}
.day-chip--bonus {
background: rgba(139, 255, 207, 0.16);
border-color: rgba(139, 255, 207, 0.32);
}
.day-summary-card__head {
@@ -778,6 +804,72 @@ body.page-dashboard .content {
color: var(--muted);
}
.timeline-card__comment {
margin: 0.28rem 0 0;
color: rgba(239, 247, 255, 0.9);
font-size: 0.98rem;
line-height: 1.35;
}
.timeline-card__stats {
display: flex;
flex-wrap: wrap;
gap: 0.42rem;
margin-top: 0.72rem;
}
.timeline-card__stats span {
display: inline-flex;
align-items: center;
min-height: 1.9rem;
padding: 0.32rem 0.66rem;
border-radius: 999px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.14);
color: rgba(239, 247, 255, 0.88);
font-size: 0.82rem;
}
.timeline-route-map {
position: relative;
width: 100%;
max-width: 24rem;
height: 10.5rem;
margin-top: 0.8rem;
overflow: hidden;
border-radius: 1.1rem;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.08);
}
.timeline-route-map svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.timeline-route-map polyline {
fill: none;
stroke: #1494de;
stroke-width: 5;
stroke-linecap: round;
stroke-linejoin: round;
filter: drop-shadow(0 1px 2px rgba(255, 255, 255, 0.9));
}
.timeline-route-map a {
position: absolute;
right: 0.35rem;
bottom: 0.28rem;
padding: 0.12rem 0.35rem;
border-radius: 0.45rem;
background: rgba(255, 255, 255, 0.82);
color: rgba(10, 22, 35, 0.82);
font-size: 0.66rem;
text-decoration: none;
}
.timeline-card__meta strong {
font-size: 0.92rem;
color: rgba(239, 247, 255, 0.68);
@@ -1620,6 +1712,13 @@ body.page-dashboard .content {
border-color: rgba(69, 201, 141, 0.34);
}
.week-insight-card {
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.78), rgba(248, 253, 255, 0.62)),
radial-gradient(circle at top left, rgba(90, 188, 242, 0.14), transparent 42%);
border-color: rgba(92, 129, 160, 0.2);
}
.range-moment-list__item,
.timeline-card__body h3,
.day-summary-card__title,
@@ -1655,6 +1754,27 @@ body.page-dashboard .content {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.week-insight-card {
display: grid;
gap: 0.55rem;
margin-bottom: 1rem;
padding: 1rem 1.1rem;
border-radius: 1.55rem;
background:
linear-gradient(180deg, rgba(25, 36, 56, 0.72), rgba(16, 25, 40, 0.58)),
radial-gradient(circle at top left, rgba(139, 228, 255, 0.16), transparent 44%);
}
.week-insight-card p {
margin: 0;
color: var(--muted);
line-height: 1.45;
}
.week-insight-card strong {
color: var(--text);
}
.range-card {
padding: 1rem 1.1rem;
border-radius: 1.45rem;
@@ -1746,6 +1866,38 @@ body.page-dashboard .content {
border-color: rgba(255, 143, 143, 0.18);
}
.health-import-progress {
display: grid;
gap: 0.45rem;
margin-bottom: 1rem;
}
.health-import-progress__bar {
display: block;
width: 100%;
height: 0.72rem;
overflow: hidden;
border-radius: 999px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.1);
appearance: none;
-webkit-appearance: none;
}
.health-import-progress__bar::-webkit-progress-bar {
background: transparent;
}
.health-import-progress__bar::-webkit-progress-value {
border-radius: inherit;
background: linear-gradient(90deg, var(--primary), var(--accent));
}
.health-import-progress__bar::-moz-progress-bar {
border-radius: inherit;
background: linear-gradient(90deg, var(--primary), var(--accent));
}
.options-logout-form {
margin: 0;
}
@@ -2116,6 +2268,145 @@ body.page-dashboard .content {
}
}
@media (prefers-color-scheme: light) {
.dashboard-overlay__backdrop,
.options-overlay__backdrop {
background: rgba(224, 235, 245, 0.64);
}
.dashboard-modal,
.dashboard-modal--settings,
.options-modal {
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.97), rgba(242, 248, 253, 0.93)),
radial-gradient(circle at 50% 0%, rgba(90, 188, 242, 0.16), transparent 46%);
border-color: rgba(92, 129, 160, 0.2);
color: var(--text);
}
.dashboard-modal__controls,
.options-modal__controls {
background: linear-gradient(180deg, rgba(247, 252, 255, 0.96), rgba(247, 252, 255, 0.76), transparent);
}
.dashboard-modal__round {
background: rgba(255, 255, 255, 0.74);
border-color: rgba(92, 129, 160, 0.22);
color: var(--text);
}
.dashboard-modal__round--confirm {
background: linear-gradient(180deg, rgba(90, 188, 242, 0.24), rgba(99, 217, 180, 0.2));
color: #0e2b45;
}
.dashboard-modal__subtitle,
.overlay-signal-card p {
color: rgba(18, 48, 75, 0.62);
}
.dashboard-modal__textarea textarea,
.options-modal input[type="text"],
.options-modal input[type="password"],
.options-modal input[type="number"],
.options-modal input[type="date"],
.options-modal select,
.options-modal textarea {
background: rgba(255, 255, 255, 0.72);
border-color: rgba(92, 129, 160, 0.22);
color: var(--text);
}
.overlay-signal-card {
border-top-color: rgba(92, 129, 160, 0.16);
}
.overlay-signal-card__buttons {
background: rgba(18, 48, 75, 0.08);
}
.overlay-signal-card__buttons button + button {
border-left-color: rgba(18, 48, 75, 0.12);
}
.dashboard-fab-menu {
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.88), rgba(245, 251, 255, 0.76)),
radial-gradient(circle at top left, rgba(90, 188, 242, 0.16), transparent 48%);
border-color: rgba(92, 129, 160, 0.2);
}
.dashboard-fab-menu button,
.moment-type-card,
.moment-choice-pill span,
.options-menu-card,
.options-modal .settings-section,
.options-modal .band-card,
.options-modal .sport-type-card,
.options-modal .checkbox-row--panel,
.options-modal .push-panel,
.options-modal .detail-card--overlay {
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.78), rgba(247, 252, 255, 0.62)),
radial-gradient(circle at top left, rgba(90, 188, 242, 0.12), transparent 48%);
border-color: rgba(92, 129, 160, 0.2);
color: var(--text);
}
.options-menu-card--danger {
background: rgba(219, 107, 107, 0.1);
border-color: rgba(219, 107, 107, 0.22);
}
.health-import-progress__bar {
background: rgba(18, 48, 75, 0.08);
border-color: rgba(92, 129, 160, 0.16);
}
.week-insight-card {
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.78), rgba(248, 253, 255, 0.62)),
radial-gradient(circle at top left, rgba(90, 188, 242, 0.14), transparent 42%);
border-color: rgba(92, 129, 160, 0.2);
}
.moment-type-card.is-selected,
.moment-choice-pill input:checked + span {
background: rgba(90, 188, 242, 0.18);
border-color: rgba(20, 148, 222, 0.34);
}
.day-summary-card__title {
color: #fff;
}
.day-chip {
background: rgba(255, 255, 255, 0.62);
border-color: rgba(92, 129, 160, 0.2);
color: rgba(18, 48, 75, 0.9);
}
.day-chip--bonus {
background: rgba(99, 217, 180, 0.18);
border-color: rgba(69, 201, 141, 0.3);
}
.timeline-card__comment {
color: rgba(18, 48, 75, 0.82);
}
.timeline-card__stats span {
background: rgba(255, 255, 255, 0.58);
border-color: rgba(92, 129, 160, 0.18);
color: rgba(18, 48, 75, 0.82);
}
.timeline-route-map {
border-color: rgba(92, 129, 160, 0.2);
background: rgba(255, 255, 255, 0.5);
}
}
.site-footer {
display: flex;
justify-content: space-between;
+95
View File
@@ -1557,6 +1557,100 @@
}
}
function initHealthImportStatus() {
const panel = document.querySelector("[data-health-import-status]");
if (!panel) {
return;
}
const progressWrap = panel.querySelector("[data-health-progress-wrap]");
const progress = panel.querySelector("[data-health-progress-bar]");
const progressText = panel.querySelector("[data-health-progress-text]");
const lastImport = panel.querySelector("[data-health-last-import]");
const lastMessage = panel.querySelector("[data-health-last-message]");
let timer = null;
const formatDuration = seconds => {
const rounded = Math.max(0, Math.round(Number(seconds) || 0));
if (rounded < 60) {
return `${rounded} s`;
}
return `${Math.ceil(rounded / 60)} min`;
};
const render = status => {
const done = Math.max(0, Number(status.progress_done || 0));
const total = Math.max(0, Number(status.progress_total || 0));
const percent = total > 0 ? Math.min(100, Math.round((done / total) * 100)) : 0;
if (progress) {
progress.value = String(percent);
progress.textContent = `${percent}%`;
}
if (progressWrap) {
progressWrap.dataset.progressDone = String(done);
progressWrap.dataset.progressTotal = String(total);
}
if (lastImport) {
lastImport.textContent = status.last_import_at ? new Date(status.last_import_at).toLocaleString("de-DE") : "-";
}
if (lastMessage) {
lastMessage.textContent = status.last_message || "-";
}
if (progressText) {
if (status.last_status === "running") {
let eta = "wird berechnet";
const started = Date.parse(status.started_at || "");
if (started && done > 0 && total > done) {
const elapsed = (Date.now() - started) / 1000;
eta = formatDuration((elapsed / done) * (total - done));
}
progressText.textContent = `Import läuft: ${done} von ${total} verarbeitet. Restzeit ca. ${eta}.`;
return;
}
if (status.last_status === "error") {
progressText.textContent = `${status.last_message || "Import fehlgeschlagen."} Derselbe Export kann erneut gesendet werden und wird idempotent übernommen.`;
return;
}
progressText.textContent = status.last_message || "Noch kein Import gelaufen.";
}
};
const initialDone = progressWrap ? Number(progressWrap.dataset.progressDone || 0) : 0;
const initialTotal = progressWrap ? Number(progressWrap.dataset.progressTotal || 0) : 0;
render({ progress_done: initialDone, progress_total: initialTotal });
const refresh = async () => {
try {
const response = await fetch("/api/health/status", { credentials: "same-origin" });
if (!response.ok) {
return;
}
const data = await response.json();
if (!data || !data.ok || !data.status) {
return;
}
render(data.status);
if (data.status.last_status !== "running" && timer !== null) {
window.clearInterval(timer);
timer = null;
}
} catch (error) {
// Status polling is best-effort; the import itself is server-side.
}
};
refresh();
timer = window.setInterval(refresh, 3500);
}
function csrfToken() {
return document.querySelector('meta[name="csrf-token"]')?.getAttribute("content") || "";
}
@@ -1888,6 +1982,7 @@
initDashboardCharts();
initDashboardExperience();
initOptionsPanels();
initHealthImportStatus();
initSportTypeManager();
initPwaShell();
initPullToRefresh();