diff --git a/booklore-ui/src/app/features/stats/component/user-stats/charts/completion-race-chart/completion-race-chart.component.html b/booklore-ui/src/app/features/stats/component/user-stats/charts/completion-race-chart/completion-race-chart.component.html
index 1a87944aa..496e6d707 100644
--- a/booklore-ui/src/app/features/stats/component/user-stats/charts/completion-race-chart/completion-race-chart.component.html
+++ b/booklore-ui/src/app/features/stats/component/user-stats/charts/completion-race-chart/completion-race-chart.component.html
@@ -35,16 +35,26 @@
{{ totalBooks }}
Books
+
+ {{ totalSessions }}
+ Sessions
+
{{ avgDaysToFinish }}d
Avg Days
+
+ {{ medianDaysToFinish }}d
+ Median Days
+
- {{ fastestCompletion }}
+ {{ fastestDays }}
+ {{ fastestTitle }}
Fastest
- {{ slowestCompletion }}
+ {{ slowestDays }}
+ {{ slowestTitle }}
Slowest
diff --git a/booklore-ui/src/app/features/stats/component/user-stats/charts/completion-race-chart/completion-race-chart.component.scss b/booklore-ui/src/app/features/stats/component/user-stats/charts/completion-race-chart/completion-race-chart.component.scss
index 0a56e891e..41bd8e5de 100644
--- a/booklore-ui/src/app/features/stats/component/user-stats/charts/completion-race-chart/completion-race-chart.component.scss
+++ b/booklore-ui/src/app/features/stats/component/user-stats/charts/completion-race-chart/completion-race-chart.component.scss
@@ -97,12 +97,15 @@
color: #4caf50;
}
- .stat-value-sm {
- font-size: 0.85rem;
- font-weight: 500;
- color: var(--text-color, #ffffff);
+ .stat-subtitle {
+ font-size: 0.75rem;
+ color: var(--text-secondary-color);
text-align: center;
line-height: 1.3;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
.stat-label {
@@ -113,11 +116,11 @@
margin-top: 0.25rem;
}
- &.fastest .stat-value-sm {
+ &.fastest .stat-value {
color: #4caf50;
}
- &.slowest .stat-value-sm {
+ &.slowest .stat-value {
color: #ff9800;
}
}
diff --git a/booklore-ui/src/app/features/stats/component/user-stats/charts/completion-race-chart/completion-race-chart.component.ts b/booklore-ui/src/app/features/stats/component/user-stats/charts/completion-race-chart/completion-race-chart.component.ts
index 22c9dd646..21786eb82 100644
--- a/booklore-ui/src/app/features/stats/component/user-stats/charts/completion-race-chart/completion-race-chart.component.ts
+++ b/booklore-ui/src/app/features/stats/component/user-stats/charts/completion-race-chart/completion-race-chart.component.ts
@@ -38,9 +38,13 @@ export class CompletionRaceChartComponent implements OnInit, OnDestroy {
public chartOptions: ChartConfiguration<'line'>['options'];
public totalBooks = 0;
- public fastestCompletion = '';
- public slowestCompletion = '';
public avgDaysToFinish = 0;
+ public medianDaysToFinish = 0;
+ public totalSessions = 0;
+ public fastestDays = '';
+ public fastestTitle = '';
+ public slowestDays = '';
+ public slowestTitle = '';
private readonly userStatsService = inject(UserStatsService);
private readonly destroy$ = new Subject();
@@ -189,16 +193,26 @@ export class CompletionRaceChartComponent implements OnInit, OnDestroy {
this.totalBooks = races.length;
if (races.length > 0) {
- const days = races.map(r => r.totalDays);
+ const days = races.map(r => r.totalDays).sort((a, b) => a - b);
const fastest = races.reduce((a, b) => a.totalDays <= b.totalDays ? a : b);
const slowest = races.reduce((a, b) => a.totalDays >= b.totalDays ? a : b);
- this.fastestCompletion = `${fastest.bookTitle.substring(0, 25)}${fastest.bookTitle.length > 25 ? '...' : ''} (${fastest.totalDays}d)`;
- this.slowestCompletion = `${slowest.bookTitle.substring(0, 25)}${slowest.bookTitle.length > 25 ? '...' : ''} (${slowest.totalDays}d)`;
this.avgDaysToFinish = Math.round(days.reduce((a, b) => a + b, 0) / days.length);
+ this.medianDaysToFinish = days.length % 2 === 0
+ ? Math.round((days[days.length / 2 - 1] + days[days.length / 2]) / 2)
+ : days[Math.floor(days.length / 2)];
+ this.totalSessions = races.reduce((sum, r) => sum + r.sessions.length, 0);
+ this.fastestDays = `${fastest.totalDays}d`;
+ this.fastestTitle = fastest.bookTitle.length > 25 ? fastest.bookTitle.substring(0, 25) + '...' : fastest.bookTitle;
+ this.slowestDays = `${slowest.totalDays}d`;
+ this.slowestTitle = slowest.bookTitle.length > 25 ? slowest.bookTitle.substring(0, 25) + '...' : slowest.bookTitle;
} else {
- this.fastestCompletion = '-';
- this.slowestCompletion = '-';
this.avgDaysToFinish = 0;
+ this.medianDaysToFinish = 0;
+ this.totalSessions = 0;
+ this.fastestDays = '-';
+ this.fastestTitle = '';
+ this.slowestDays = '-';
+ this.slowestTitle = '';
}
const datasets = races.map((race, i) => {
diff --git a/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-clock-chart/reading-clock-chart.component.html b/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-clock-chart/reading-clock-chart.component.html
index 83f61dfff..cdf8c69f6 100644
--- a/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-clock-chart/reading-clock-chart.component.html
+++ b/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-clock-chart/reading-clock-chart.component.html
@@ -25,7 +25,7 @@
Total Read
- {{ readerType }}
+ {{ readerType }}
Reader Type
diff --git a/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-clock-chart/reading-clock-chart.component.scss b/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-clock-chart/reading-clock-chart.component.scss
index 29864612f..c402b20a4 100644
--- a/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-clock-chart/reading-clock-chart.component.scss
+++ b/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-clock-chart/reading-clock-chart.component.scss
@@ -58,12 +58,6 @@
color: var(--text-color, #ffffff);
}
- .stat-value-sm {
- font-size: 1rem;
- font-weight: 500;
- color: var(--text-color, #ffffff);
- }
-
.stat-label {
font-size: 0.75rem;
color: var(--text-secondary-color);
@@ -80,7 +74,7 @@
color: #42a5f5;
}
- &.type .stat-value-sm {
+ &.type .stat-value {
color: #9c27b0;
}
}
@@ -89,7 +83,8 @@
.chart-wrapper {
position: relative;
height: 350px;
- width: 100%;
+ aspect-ratio: 1;
+ margin: 0 auto;
}
.no-data-message {
diff --git a/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-survival-chart/reading-survival-chart.component.html b/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-survival-chart/reading-survival-chart.component.html
index ed2628325..b69502add 100644
--- a/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-survival-chart/reading-survival-chart.component.html
+++ b/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-survival-chart/reading-survival-chart.component.html
@@ -25,11 +25,12 @@
Completion Rate
- {{ medianDropout }}
+ {{ medianDropout }}
Median Dropout
- {{ dangerZone }}
+ {{ dangerZoneRange }}
+ {{ dangerZoneDrop }}
Danger Zone
diff --git a/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-survival-chart/reading-survival-chart.component.scss b/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-survival-chart/reading-survival-chart.component.scss
index 46b91230d..bfd8c09ee 100644
--- a/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-survival-chart/reading-survival-chart.component.scss
+++ b/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-survival-chart/reading-survival-chart.component.scss
@@ -58,10 +58,9 @@
color: #e91e63;
}
- .stat-value-sm {
- font-size: 0.85rem;
- font-weight: 500;
- color: var(--text-color, #ffffff);
+ .stat-subtitle {
+ font-size: 0.75rem;
+ color: var(--text-secondary-color);
text-align: center;
}
@@ -73,7 +72,11 @@
margin-top: 0.25rem;
}
- &.danger .stat-value-sm {
+ &.danger .stat-value {
+ color: #ff5722;
+ }
+
+ &.danger .stat-subtitle {
color: #ff5722;
}
}
diff --git a/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-survival-chart/reading-survival-chart.component.ts b/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-survival-chart/reading-survival-chart.component.ts
index a640a83c2..29afad393 100644
--- a/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-survival-chart/reading-survival-chart.component.ts
+++ b/booklore-ui/src/app/features/stats/component/user-stats/charts/reading-survival-chart/reading-survival-chart.component.ts
@@ -28,7 +28,8 @@ export class ReadingSurvivalChartComponent implements OnInit, OnDestroy {
public totalStarted = 0;
public completionRate = 0;
public medianDropout = '';
- public dangerZone = '';
+ public dangerZoneRange = '';
+ public dangerZoneDrop = '';
public readonly chartOptions: ChartConfiguration<'line'>['options'] = {
responsive: true,
@@ -156,7 +157,8 @@ export class ReadingSurvivalChartComponent implements OnInit, OnDestroy {
dangerIdx = i;
}
}
- this.dangerZone = `${THRESHOLDS[dangerIdx - 1]}-${THRESHOLDS[dangerIdx]}% (-${maxDrop.toFixed(0)}%)`;
+ this.dangerZoneRange = `${THRESHOLDS[dangerIdx - 1]}-${THRESHOLDS[dangerIdx]}%`;
+ this.dangerZoneDrop = `-${maxDrop.toFixed(0)}%`;
const labels = THRESHOLDS.map(t => `${t}%`);
this.chartDataSubject.next({