Skip to content

Commit

Permalink
feat(client): improve FeedZback stats
Browse files Browse the repository at this point in the history
  • Loading branch information
avine committed Oct 8, 2024
1 parent 11a617f commit b20ab27
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 26 deletions.
65 changes: 60 additions & 5 deletions client/src/app/stats/stats.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,109 @@ <h1 class="gbl-page-title mat-headline-large">

<mat-tab-group animationDuration="0ms">
<mat-tab label="Summary">
<p class="!mt-6">
<mat-slide-toggle [checked]="showLegend()" (toggleChange)="toggleLegend()">Show legend</mat-slide-toggle>
</p>

@let summary = stats().summary;

<h2 class="mat-title-large !mt-6">Users</h2>
<ul class="gbl-list">
<li class="gbl-list__item">
<strong>{{ summary.uniqueUsers }}</strong> unique users
@if (showLegend()) {
<div class="app-stats__legend">
Number of unique users who have given, received or requested at least 1 feedback.
</div>
}
</li>

<li class="gbl-list__item">
<strong>{{ summary.uniqueGiversOrReceivers }}</strong> unique givers or receivers
@if (showLegend()) {
<div class="app-stats__legend">
Number of unique users who have given or received at least 1 feedback (spontaneous or requested).
</div>
}
</li>

<li class="gbl-list__item">
<strong>{{ summary.uniqueGivers }}</strong> unique givers
@if (showLegend()) {
<div class="app-stats__legend">
Number of unique users who have given at least 1 feedback (spontaneous or requested).
</div>
}
</li>

<li class="gbl-list__item">
<strong>{{ summary.uniqueReceivers }}</strong> unique receivers
@if (showLegend()) {
<div class="app-stats__legend">
Number of unique users who have received at least 1 feedback (spontaneous or requested).
</div>
}
</li>

<li class="gbl-list__item">
<strong>{{ summary.uniqueRequesters }}</strong> unique requesters
@if (showLegend()) {
<div class="app-stats__legend">
Number of unique users who have requested at least 1 feedback but have not yet received a reply (pending
request only).
</div>
}
</li>
</ul>

<h2 class="mat-title-large">FeedZbacks</h2>
<ul class="gbl-list">
<li class="gbl-list__item">
<strong>{{ summary.spontaneousFeedback }}</strong> spontaneous feedZbacks
@if (showLegend()) {
<div class="app-stats__legend">Number of spontaneous feedZbacks over the period.</div>
}
</li>

<li class="gbl-list__item">
<strong>{{ summary.requestedFeedbackDone }}</strong> requested feedZbacks (done)
@if (showLegend()) {
<div class="app-stats__legend">Number of feedZback requests replied over the period.</div>
}
</li>

<li class="gbl-list__item">
<strong>{{ summary.requestedFeedbackPending }}</strong> requested feedZbacks (pending)
@if (showLegend()) {
<div class="app-stats__legend">Number of feedZback requests awaiting response over the period.</div>
}
</li>
</ul>

<h2 class="mat-title-large">Period</h2>
<ul class="gbl-list">
<li class="gbl-list__item">
@let p = period();
Between <strong>{{ p.from }}</strong> and <strong>{{ p.to }}</strong>
Between <strong>{{ p.start || '...' }}</strong> and <strong>{{ p.end || '...' }}</strong>
</li>
</ul>
</mat-tab>

<mat-tab label="Details">
<h2 class="mat-title-large !mt-6 text-center">Users</h2>
<div echarts [theme]="chartTheme()" [options]="usersChartOptions()" style="height: 350px"></div>
@defer (on viewport) {
<mat-slider [min]="0" [max]="stats().details.length" step="1" class="app-stats__slider" showTickMarks>
@let end = endFactory();
<input matSliderStartThumb [(value)]="start" />
<input matSliderEndThumb [(value)]="end" />
</mat-slider>

<h2 class="mat-title-large !mt-6 text-center">Users</h2>
<div echarts [theme]="chartTheme()" [options]="usersChartOptions()" class="app-stats__chart"></div>

<h2 class="mat-title-large text-center">FeedZbacks</h2>
<div echarts [theme]="chartTheme()" [options]="feedbacksChartOptions()" style="height: 350px"></div>
<h2 class="mat-title-large text-center">FeedZbacks</h2>
<div echarts [theme]="chartTheme()" [options]="feedbacksChartOptions()" class="app-stats__chart"></div>
} @placeholder {
<div></div>
}
</mat-tab>
</mat-tab-group>
17 changes: 17 additions & 0 deletions client/src/app/stats/stats.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.app-stats {
&__legend {
margin-bottom: 0.75rem;
font: var(--sys-label-large);
color: var(--sys-tertiary);
}

&__slider {
display: block !important;
width: max(50%, 220px) !important;
margin: 1rem auto 0 auto !important;
}

&__chart {
height: max(33vh, 320px);
}
}
30 changes: 23 additions & 7 deletions client/src/app/stats/stats.component.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
import { Component, computed, inject, input, ViewEncapsulation } from '@angular/core';
import { Component, computed, inject, input, signal, ViewEncapsulation } from '@angular/core';
import { MatIconModule } from '@angular/material/icon';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSliderModule } from '@angular/material/slider';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { EChartsOption } from 'echarts';
import { NgxEchartsDirective, provideEcharts, ThemeOption } from 'ngx-echarts';
import { ThemeService } from '../shared/theme';
import { FeedbackStats } from './stats.types';
import { pluckStatsDetails } from './stats.utils';
import { pluckMonthHistoryStats } from './stats.utils';

@Component({
selector: 'app-stats',
host: { class: 'app-stats' },
standalone: true,
providers: [provideEcharts()],
imports: [MatIconModule, MatTableModule, MatTabsModule, NgxEchartsDirective],
imports: [MatIconModule, MatSlideToggleModule, MatSliderModule, MatTableModule, MatTabsModule, NgxEchartsDirective],
templateUrl: './stats.component.html',
styleUrl: './stats.component.scss',
encapsulation: ViewEncapsulation.None,
})
export default class StatsComponent {
stats = input.required<FeedbackStats>();

private detailsPlucked = computed(() => pluckStatsDetails(this.stats()));
protected start = signal(0);

period = computed(() => {
protected endFactory = computed(() => signal(this.stats().details.length));

private detailsPlucked = computed(() =>
pluckMonthHistoryStats(this.stats().details.slice(this.start(), this.endFactory()())),
);

protected period = computed(() => {
const { month } = this.detailsPlucked();
return {
from: month[0],
to: month[month.length - 1],
start: month[0],
end: month[month.length - 1],
};
});

Expand Down Expand Up @@ -120,4 +130,10 @@ export default class StatsComponent {
private theme = inject(ThemeService).theme;

protected chartTheme = computed((): ThemeOption | string | null => (this.theme() === 'dark' ? 'dark' : null));

protected showLegend = signal(false);

protected toggleLegend() {
this.showLegend.update((show) => !show);
}
}
24 changes: 12 additions & 12 deletions client/src/app/stats/stats.utils.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { FeedbackStats } from './stats.types';
import { FeedbackMonthHistoryStats } from './stats.types';

export const pluckStatsDetails = ({ details }: FeedbackStats) => ({
month: details.map(({ month }) => month),
export const pluckMonthHistoryStats = (data: FeedbackMonthHistoryStats[]) => ({
month: data.map(({ month }) => month),

// User stats
uniqueGivers: details.map(({ uniqueGivers }) => uniqueGivers),
uniqueReceivers: details.map(({ uniqueReceivers }) => uniqueReceivers),
uniqueGiversOrReceivers: details.map(({ uniqueGiversOrReceivers }) => uniqueGiversOrReceivers),
uniqueRequesters: details.map(({ uniqueRequesters }) => uniqueRequesters),
uniqueUsers: details.map(({ uniqueUsers }) => uniqueUsers),
uniqueGivers: data.map(({ uniqueGivers }) => uniqueGivers),
uniqueReceivers: data.map(({ uniqueReceivers }) => uniqueReceivers),
uniqueGiversOrReceivers: data.map(({ uniqueGiversOrReceivers }) => uniqueGiversOrReceivers),
uniqueRequesters: data.map(({ uniqueRequesters }) => uniqueRequesters),
uniqueUsers: data.map(({ uniqueUsers }) => uniqueUsers),

// Feedback stats
spontaneousFeedback: details.map(({ spontaneousFeedback }) => spontaneousFeedback),
requestedFeedbackDone: details.map(({ requestedFeedbackDone }) => requestedFeedbackDone),
requestedFeedbackPending: details.map(({ requestedFeedbackPending }) => requestedFeedbackPending),
sharedFeedback: details.map(({ sharedFeedback }) => sharedFeedback),
spontaneousFeedback: data.map(({ spontaneousFeedback }) => spontaneousFeedback),
requestedFeedbackDone: data.map(({ requestedFeedbackDone }) => requestedFeedbackDone),
requestedFeedbackPending: data.map(({ requestedFeedbackPending }) => requestedFeedbackPending),
sharedFeedback: data.map(({ sharedFeedback }) => sharedFeedback),
});
4 changes: 2 additions & 2 deletions client/src/styles/material/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
@include mat.chips-theme(t.$light-theme);
@include mat.slide-toggle-theme(t.$light-theme);
//@include mat.radio-theme(t.$light-theme);
//@include mat.slider-theme(t.$light-theme);
@include mat.slider-theme(t.$light-theme);
@include mat.menu-theme(t.$light-theme);
//@include mat.list-theme(t.$light-theme);
@include mat.paginator-theme(t.$light-theme);
Expand Down Expand Up @@ -71,7 +71,7 @@
@include mat.chips-color(t.$dark-theme);
@include mat.slide-toggle-color(t.$dark-theme);
//@include mat.radio-color(t.$dark-theme);
//@include mat.slider-color(t.$dark-theme);
@include mat.slider-color(t.$dark-theme);
@include mat.menu-color(t.$dark-theme);
//@include mat.list-color(t.$dark-theme);
@include mat.paginator-color(t.$dark-theme);
Expand Down

0 comments on commit b20ab27

Please sign in to comment.