diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index a958c1f..d023cac 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -3790,6 +3790,52 @@ export function App(): ReactElement { : 0; const liveSessionRuntimeMs = Math.max(0, (snapshot.stats.sessionRuntimeMs || 0) + runtimeOffsetMs); const liveTotalRuntimeMs = Math.max(0, (snapshot.stats.totalRuntimeMs || 0) + runtimeOffsetMs); + const etaSeparatorIndex = snapshot.etaText.includes(": ") ? snapshot.etaText.indexOf(": ") : -1; + const etaValue = etaSeparatorIndex >= 0 + ? snapshot.etaText.slice(etaSeparatorIndex + 2) + : "--"; + const resetFailedDownloads = (): void => { + if (itemStatusCounts.failed === 0) return; + const failedIds = Object.values(snapshot.session.items) + .filter((it) => it.status === "failed") + .map((it) => it.id); + void window.rd.resetItems(failedIds).catch(() => {}); + }; + const statsSections = [ + { + key: "live", + title: "Aktuell", + items: [ + { key: "speed", eyebrow: "Live", label: "Geschwindigkeit", value: snapshot.speedText.replace("Geschwindigkeit: ", "") }, + { key: "eta", eyebrow: "Live", label: "Restzeit", value: etaValue, compactValue: true }, + { key: "active", eyebrow: "Queue", label: "Aktive Downloads", value: String(itemStatusCounts.downloading) }, + { key: "queued", eyebrow: "Queue", label: "In Warteschlange", value: String(itemStatusCounts.queued) }, + { + key: "failed", + eyebrow: "Status", + label: "Fehlerhaft", + value: String(itemStatusCounts.failed), + danger: itemStatusCounts.failed > 0, + clickable: itemStatusCounts.failed > 0, + title: itemStatusCounts.failed > 0 ? "Klicken zum Zurücksetzen aller fehlerhaften Downloads" : undefined, + onClick: resetFailedDownloads + }, + { key: "packages", eyebrow: "Queue", label: "Pakete", value: String(snapshot.stats.totalPackages) } + ] + }, + { + key: "summary", + title: "Bilanz", + items: [ + { key: "downloaded-session", eyebrow: "Session", label: "Heruntergeladen", value: humanSize(snapshot.stats.totalDownloaded) }, + { key: "downloaded-total", eyebrow: "Gesamt", label: "Heruntergeladen", value: humanSize(snapshot.stats.totalDownloadedAllTime) }, + { key: "runtime-session", eyebrow: "Session", label: "Laufzeit", value: formatRuntimeDuration(liveSessionRuntimeMs), compactValue: true }, + { key: "runtime-total", eyebrow: "Gesamt", label: "Laufzeit", value: formatRuntimeDuration(liveTotalRuntimeMs), compactValue: true }, + { key: "files-session", eyebrow: "Session", label: "Fertige Dateien", value: String(snapshot.stats.totalFilesSession) }, + { key: "files-total", eyebrow: "Gesamt", label: "Fertige Dateien", value: String(snapshot.stats.totalFilesAllTime) } + ] + } + ] as const; return (
Heruntergeladen zurücksetzen
-
-
- Aktuelle Geschwindigkeit - {snapshot.speedText.replace("Geschwindigkeit: ", "")} -
-
- Heruntergeladen (Session) - {humanSize(snapshot.stats.totalDownloaded)} -
-
- Heruntergeladen (Gesamt) - {humanSize(snapshot.stats.totalDownloadedAllTime)} -
+
+ {statsSections.map((section) => ( +
+
{section.title}
+
+ {section.items.map((item) => item.onClick ? ( + + ) : ( +
+ + {item.eyebrow} + {item.label} + + {item.value} +
+ ))} +
+
+ ))} + {false && (<>
Laufzeit (Session) {formatRuntimeDuration(liveSessionRuntimeMs)} @@ -4493,6 +4558,7 @@ export function App(): ReactElement { {snapshot.etaText.includes(": ") ? snapshot.etaText.slice(0, snapshot.etaText.indexOf(": ")) : snapshot.etaText} {snapshot.etaText.includes(": ") ? snapshot.etaText.slice(snapshot.etaText.indexOf(": ") + 2) : "--"}
+ )}
@@ -6120,4 +6186,3 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, stripe } return true; }); - diff --git a/src/renderer/styles.css b/src/renderer/styles.css index 8c1f3d0..34c916e 100644 --- a/src/renderer/styles.css +++ b/src/renderer/styles.css @@ -2501,39 +2501,97 @@ td { margin-top: 10px; } +.stats-sections { + display: flex; + flex-direction: column; + gap: 14px; + margin-top: 14px; +} + +.stats-section { + display: flex; + flex-direction: column; + gap: 10px; +} + +.stats-section-title { + color: var(--muted); + font-size: 11px; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; +} + .stats-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 12px; - margin-top: 8px; } .stat-item { display: flex; flex-direction: column; - gap: 4px; - padding: 10px 12px; + justify-content: space-between; + gap: 12px; + min-height: 96px; + min-width: 0; + padding: 12px 14px; background: var(--field); border-radius: 10px; border: 1px solid var(--border); + text-align: left; } + +.stat-button { + width: 100%; + font: inherit; + color: inherit; + appearance: none; + cursor: default; +} + +.stat-top { + display: flex; + flex-direction: column; + gap: 3px; + min-width: 0; +} + +.stat-eyebrow { + color: var(--muted); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; +} + .stat-item.stat-item-clickable { cursor: pointer; - transition: border-color 0.15s; + transition: border-color 0.15s, transform 0.15s ease; } .stat-item.stat-item-clickable:hover { border-color: var(--danger, #e05555); + transform: translateY(-1px); } .stat-label { - color: var(--muted); - font-size: 12px; + color: var(--text); + font-size: 14px; + font-weight: 600; + line-height: 1.25; } .stat-value { - font-size: 18px; + font-size: clamp(22px, 1.35vw, 30px); font-weight: 700; font-variant-numeric: tabular-nums; + line-height: 1.1; + overflow-wrap: anywhere; +} + +.stat-value.stat-value-compact { + font-size: clamp(18px, 1.05vw, 24px); + line-height: 1.2; } .stat-value.danger { @@ -2629,6 +2687,12 @@ td { font-size: 13px; } +@media (max-width: 1680px) { + .stats-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } +} + @media (max-width: 1100px) { .statistics-view { grid-template-columns: 1fr; @@ -2640,7 +2704,23 @@ td { } .stats-grid { - grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +@media (max-width: 760px) { + .stats-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .stat-item { + min-height: 88px; + } +} + +@media (max-width: 520px) { + .stats-grid { + grid-template-columns: 1fr; } }