Skip to content

feat(#9231): enable place filter aggregate targets for users with multiple facility_ids #9232

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 40 additions & 10 deletions webapp/src/css/inbox.less
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,27 @@ body {
left: 0;
right: 0;
display: flex;
.right-side-filter {
margin-left: auto;
.open-filter-label {
color: @filter-text-color;
}
.btn.open-filter {
background-color: transparent;
margin: 0;
&:focus,
&:active {
outline: none;
border: none;
box-shadow: none;
}
.fa {
font-size: @font-extra-large;
color: @filter-icon-color;
padding: 2px 2px 0 0;
}
}
}
}

.freetext-filter {
Expand Down Expand Up @@ -1151,13 +1172,12 @@ a.fa:hover {
.analytics {
.content {
.inner {
/* allow the scrollable content to grow to full width */
max-width: inherit;
max-width: 100%;
.page {
max-width: 100%;
}
.page:not(.target-aggregates) {
max-width: inherit;
/* set the max with and center all the children again */
> div {
max-width: @content-width;
margin: 0 auto;
}
}
Expand All @@ -1167,7 +1187,14 @@ a.fa:hover {
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;

&.sidebar-closed {
margin-right: auto;
}

&.sidebar-open {
margin-right: 0;
}
}
}
}
Expand Down Expand Up @@ -1712,6 +1739,10 @@ mm-search-bar {
display: none;
}

.open-filter-label {
display: none;
}

mm-search-bar {
padding: 5px;
.mm-search-bar-container {
Expand All @@ -1730,10 +1761,6 @@ mm-search-bar {
.btn.open-filter {
margin: 0;

.open-filter-label {
display: none;
}

.filter-counter {
position: absolute;
top: 3px;
Expand Down Expand Up @@ -1779,6 +1806,9 @@ mm-search-bar {
.filters .filter {
width: 17%;
}
.right-side-filter {
padding-right: 6px;
}
.header {
.tabs {
padding-left: 2px;
Expand Down
62 changes: 62 additions & 0 deletions webapp/src/css/sidebar-filter.less
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,68 @@
overflow-y: auto;
overflow-x: hidden;

.facility-options-container {
padding-top: 25px;
padding-left: 18px;
padding-right: 18px;
width: 100%;
height: 100%;

.facility-options-title {
display: block;
height: 20px;
font-weight: bold;
font-size: @font-medium;
color: @gray-ultra-dark;
}

.facility-option-place {
padding-top: 20px;

.facility-option-wrapper {
display: flex;
align-items: center;
margin: 0;

.facility-option-radio {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 20px !important;
height: 20px !important;
border: 3px solid @gray-medium-dark;
border-radius: 50%;
outline: none;
margin: 0;
position: relative;
}

.facility-option-radio:checked {
border-color: @blue-dark;
}

.facility-option-radio:checked::after {
content: '';
width: 7px !important;
height: 7px !important;
background: @blue-dark;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
}
}

.facility-option-label {
font-size: @font-medium;
font-weight: normal;
color: @gray-ultra-dark;
padding-left: 10px;
}
}
}

mat-expansion-panel {
margin: 0;
box-shadow: none;
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/css/targets.less
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@
}

.target-aggregates {
max-width: @content-width;
max-width: @content-width !important;
left: 0;
right: 0;
}
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/css/variables.less
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
@chip-border-color: @white;
@sidebar-background-color: @white;
@filter-icon-color: @gray-ultra-dark;
@filter-text-color: @gray-medium;
@filter-text-color: @black;
@progress-bar-background-color: @white;
@targets-progress-bar-unmet-goal: @gray-medium-dark;
@backdrop-color: @black;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,10 @@
</ul>
</span>

<div class="right-side-filter" *ngIf="showFilterButton">
<button class="btn open-filter" (click)="openSidebar()">
<span class="fa fa-sliders"></span>
<span class="open-filter-label">{{ 'search_bar.filter.label' | translate }}</span>
</button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,19 +1,54 @@
import { AfterContentChecked, AfterContentInit, Component, Input, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import {
AfterContentChecked,
AfterContentInit,
Component,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output
} from '@angular/core';
import { Store } from '@ngrx/store';

import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { Subscription, filter } from 'rxjs';

import { GlobalActions } from '@mm-actions/global';
import { Selectors } from '@mm-selectors/index';
import { AuthService } from '@mm-services/auth.service';
import { SessionService } from '@mm-services/session.service';
import { UserSettingsService } from '@mm-services/user-settings.service';

@Component({
selector: 'mm-analytics-filters',
templateUrl: './analytics-filter.component.html'
})
export class AnalyticsFilterComponent implements AfterContentInit, AfterContentChecked, OnDestroy {
export class AnalyticsFilterComponent implements AfterContentInit, AfterContentChecked, OnInit, OnDestroy {
@Input() analyticsModules: any[] = [];
@Output() toggleFilter: EventEmitter<any> = new EventEmitter();

private globalActions;
activeModule;
subscriptions: Subscription = new Subscription();
showFilterButton;
isOpen = false;

constructor(
private store: Store,
private route: ActivatedRoute,
) { }
private router: Router,
private authService: AuthService,
private sessionService: SessionService,
private userSettingsService: UserSettingsService
) {
this.globalActions = new GlobalActions(store);
}

ngOnInit() {
this.canDisplayFilterButton();
this.subscribeToRouteChanges();
this.subscribeToStore();
}

ngAfterContentInit() {
const subscription = this.route.url.subscribe(() => this.setActiveModule());
Expand All @@ -28,11 +63,59 @@ export class AnalyticsFilterComponent implements AfterContentInit, AfterContentC

ngOnDestroy(): void {
this.subscriptions.unsubscribe();
this.globalActions.clearSidebarFilter();
}

private subscribeToStore() {
const subscription = this.store
.select(Selectors.getSidebarFilter)
.subscribe((filterState) => this.isOpen = filterState?.isOpen ?? false);
this.subscriptions.add(subscription);
}

private getCurrentModuleId() {
return this.route.snapshot?.firstChild?.data?.moduleId;
}

private setActiveModule() {
this.activeModule = this.analyticsModules?.find(module => {
return module.id === this.route.snapshot?.firstChild?.data?.moduleId;
});
const currentModuleId = this.getCurrentModuleId();
this.activeModule = this.analyticsModules?.find(module => module.id === currentModuleId);
}

private subscribeToRouteChanges() {
const routeSubscription = this.router.events
.pipe(filter(event => event instanceof NavigationEnd))
.subscribe(() => this.canDisplayFilterButton());
this.subscriptions.add(routeSubscription);
}

private checkPermissions() {
const permissions = [
'!can_view_old_filter_and_search',
'!can_view_old_action_bar'
];

return this.authService
.has(permissions)
.then((permissions) => permissions === true);
}

private isTargetAggregates() {
const AGGREGATE_TARGET_ID = 'target-aggregates';
return this.getCurrentModuleId() === AGGREGATE_TARGET_ID;
}

private async canDisplayFilterButton() {
const isAdmin = this.sessionService.isAdmin();
const [hasMultipleFacilities, checkPermissions] = await Promise.all([
this.userSettingsService.hasMultipleFacilities(),
this.checkPermissions(),
]);

this.showFilterButton = !isAdmin && hasMultipleFacilities && checkPermissions && this.isTargetAggregates();
}

openSidebar() {
this.globalActions.setSidebarFilter({ isOpen: !this.isOpen });
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<div class="content-pane right-pane">
<div class="col-sm-8 item-content empty-selection" *ngIf="!selected && !error">
<div>
<div class="item-content empty-selection" *ngIf="!selected && !error">
<div>{{'analytics.target.aggregates.no.target.selected' | translate}}</div>
</div>

<div class="col-sm-8 item-content empty-selection selection-error" *ngIf="error || selected?.error">
<div class="item-content empty-selection selection-error" *ngIf="error || selected?.error">
<div>{{ (error?.translationKey || selected?.error?.translationKey || 'analytics.target.aggregates.select.error') | translate}}</div>
</div>

<div class="col-sm-8 item-content" *ngIf="selected && !selected.error && !error">
<div class="item-content" *ngIf="selected && !selected.error && !error">
<div class="material">
<div class="body meta">
<div class="target-detail card" [ngClass]="{ 'has-goal': selected.goal >= 0, 'goal-met': (selected.value?.pass >= selected.goal) || (selected.value?.percent >= selected.goal) }">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<section class="sidebar-filter">
<div class="sidebar-backdrop" [ngClass]="{ hidden: !isOpen }" (click)="toggleSidebarFilter()"></div> <!-- //NOSONAR -->

<div class="sidebar-main" [ngClass]="{ hidden: !isOpen }">
<div class="sidebar-header">
<p class="sidebar-title">Filter</p>
<a class="sidebar-close fa fa-times" (click)="toggleSidebarFilter()"></a>
</div>

<div class="sidebar-body">
<mat-accordion multi>
<div class="facility-options-container">
<div class="facility-filter-title">
<span class="facility-options-title"> {{ facilityFilterLabel | translate }} </span>
</div>
<ng-container *ngFor="let facility of userFacilities">
<div class="facility-option-place">
<label class="facility-option-wrapper">
<input type="radio" name="facility" [value]="facility._id"
class="facility-option-radio" [(ngModel)]="selectedFacilityId"
(change)="fetchAggregateTargets(facility._id)">
<span class="facility-option-label">{{ facility.name }}</span>
</label>
</div>
</ng-container>
</div>
</mat-accordion>
</div>

<div class="sidebar-footer">
<button type="button" class="btn btn-primary" (click)="toggleSidebarFilter()">Apply</button>
</div>
</div>
</section>

<ng-template #headerTemplate let-label="label" let-filters="filters">
<p class="title">{{ label | translate }}</p>
</ng-template>
Loading
Loading