Skip to content
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

Feature/350 Add links to sidebar report menu #401

Merged
merged 7 commits into from
Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 5 additions & 1 deletion front-end/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { LoginEffects } from './store/login.effects';
import { AppState } from './store/app-state.model';
import { labelLookupReducer } from './store/label-lookup.reducer';
import { LabelLookupEffects } from './store/label-lookup.effects';
import { activeReportReducer } from './store/active-report.reducer';

// PrimeNG
import { ConfirmationService, MessageService } from 'primeng/api';
Expand Down Expand Up @@ -44,11 +45,12 @@ import { ConfirmTwoFactorComponent } from './login/confirm-two-factor/confirm-tw
import { DashboardComponent } from './dashboard/dashboard.component';
import { HttpErrorInterceptor } from './shared/interceptors/http-error.interceptor';
import { FecDatePipe } from './shared/pipes/fec-date.pipe';
import { MenuReportComponent } from './layout/sidebar/menu-report/menu-report.component';

// Save ngrx store to localStorage dynamically
function localStorageSyncReducer(reducer: ActionReducer<AppState>): ActionReducer<AppState> {
return localStorageSync({
keys: ['committeeAccount', 'spinnerOn', 'userLoginData', 'reportCodeLabelList'],
keys: ['committeeAccount', 'spinnerOn', 'userLoginData', 'reportCodeLabelList', 'activeReport'],
storageKeySerializer: (key) => `fecfile_online_${key}`,
rehydrate: true,
})(reducer);
Expand All @@ -66,6 +68,7 @@ const metaReducers: Array<MetaReducer<AppState, Action>> = [localStorageSyncRedu
TwoFactorLoginComponent,
ConfirmTwoFactorComponent,
DashboardComponent,
MenuReportComponent,
],
imports: [
BrowserModule,
Expand All @@ -81,6 +84,7 @@ const metaReducers: Array<MetaReducer<AppState, Action>> = [localStorageSyncRedu
spinnerOn: spinnerReducer,
userLoginData: loginReducer,
reportCodeLabelList: labelLookupReducer,
activeReport: activeReportReducer,
},
{ metaReducers }
),
Expand Down
2 changes: 1 addition & 1 deletion front-end/src/app/layout/layout.component.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="grid">
<div class="grid flex-nowrap">
<div class="col-fixed fec-background-gradient">
<app-sidebar></app-sidebar>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div class="report-menu" *ngIf="showMenu">
<div class="grid report-info">
<div class="col-12">
<div class="title-2">REPORT PROGRESS</div>
<div class="report-type">{{ activeReport?.form_type | label: f3xFormTypeLabels }}</div>
<div class="sub-heading">{{ activeReport?.report_code | label: f3xReportCodeDetailedLabels }}</div>
<div class="sub-heading">
{{ activeReport!.coverage_from_date | fecDate }} — <wbr /> {{ activeReport!.coverage_through_date | fecDate }}
</div>
</div>
</div>
<div class="grid">
<div class="col-12">
<p-panelMenu [model]="items" [multiple]="false"></p-panelMenu>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.report-info {
margin-bottom: 3vw;
}

.report-info .title-2 {
border-bottom: 1px solid white;
margin-bottom: 1vw;
}

.report-info .report-type {
font-size: 1.5em;
}

.sub-heading {
font-size: 0.8em;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { provideMockStore } from '@ngrx/store/testing';
import { NavigationEnd, Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { selectActiveReport } from 'app/store/active-report.selectors';
import { selectReportCodeLabelList } from 'app/store/label-lookup.selectors';
import { F3xSummary } from '../../../shared/models/f3x-summary.model';
import { SharedModule } from '../../../shared/shared.module';
import { PanelMenuModule } from 'primeng/panelmenu';
import { MenuReportComponent } from './menu-report.component';

describe('MenuReportComponent', () => {
let component: MenuReportComponent;
let fixture: ComponentFixture<MenuReportComponent>;
let router: Router;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [MenuReportComponent],
providers: [
provideMockStore({
initialState: {
fecfile_online_activeReport: { id: 888 } as F3xSummary,
fecfile_online_reportCodeLabelList: [],
},
selectors: [
{ selector: selectActiveReport, value: { id: 999 } as F3xSummary },
{ selector: selectReportCodeLabelList, value: [] },
],
}),
],
imports: [
SharedModule,
PanelMenuModule,
BrowserAnimationsModule,
RouterTestingModule.withRoutes([{ path: 'reports/f3x/create/step3/999', redirectTo: '' }]),
],
}).compileComponents();
});

beforeEach(() => {
router = TestBed.inject(Router);
fixture = TestBed.createComponent(MenuReportComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should navigate to menu', () => {
router.navigateByUrl('/reports/f3x/create/step3/999');
expect(component.activeReport?.id).toBe(999);
});

it('should determine an active url', () => {
const urlMatch: RegExp[] = [/\/report\/999/];
let url = 'no-match';
expect(component.isActive(urlMatch, url)).toBe(false);

url = '/report/999';
expect(component.isActive(urlMatch, url)).toBe(true);
});

it('should process a NavigationEnd event', () => {
component.items = [];
component.currentReportId = 888;
component.handleNavigationEvent({ url: '/reports/f3x/create/step3/999' } as NavigationEnd);
expect(component.items.length).toBe(3);
});
});
134 changes: 134 additions & 0 deletions front-end/src/app/layout/sidebar/menu-report/menu-report.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, NavigationEnd, Event } from '@angular/router';
import { Observable, Subject, takeUntil } from 'rxjs';
import { Store } from '@ngrx/store';
import { MenuItem } from 'primeng/api';
import { selectActiveReport } from '../../../store/active-report.selectors';
import { selectReportCodeLabelList } from 'app/store/label-lookup.selectors';
import { Report } from '../../../shared/interfaces/report.interface';
import { ReportCodeLabelList } from '../../../shared/utils/reportCodeLabels.utils';
import { f3xReportCodeDetailedLabels, LabelList } from '../../../shared/utils/label.utils';
import { F3xFormTypeLabels } from '../../../shared/models/f3x-summary.model';

@Component({
selector: 'app-menu-report',
templateUrl: './menu-report.component.html',
styleUrls: ['./menu-report.component.scss'],
})
export class MenuReportComponent implements OnInit, OnDestroy {
activeReport: Report | null = null;
currentReportId: number | null = null;
items: MenuItem[] = [];
showMenu = false;
reportCodeLabelList$: Observable<ReportCodeLabelList> = new Observable<ReportCodeLabelList>();
f3xFormTypeLabels: LabelList = F3xFormTypeLabels;
f3xReportCodeDetailedLabels: LabelList = f3xReportCodeDetailedLabels;

private destroy$ = new Subject<boolean>(); //

// The order of the url regular expressions listed inthe urlMatch array is important
// because the order determines the expanded menu item group in the panal menu:
// 'Enter A Transaction', 'Review A Report', and 'Submit Your Report'.
urlMatch: RegExp[] = [
/^\/reports\/f3x\/create\/step3\/\d+/, // Enter a transaction group
/^\/transactions\/report\/\d+\/create/, // Enter a transaction group
/^\/reports\/f3x\/summary\/\d+/, // Review a report group
/^\/reports\/f3x\/detailed-summary\/\d+/, // Review a report group
/^\/reports\/f3x\/submit\/step1\/\d+/, // Submit your report group
/^\/reports\/f3x\/submit\/step2\/\d+/, // Submit your report group
/^\/reports\/f3x\/submit\/status\/\d+/, // Submit your report group
];

constructor(private router: Router, private store: Store) {}

ngOnInit(): void {
this.reportCodeLabelList$ = this.store.select<ReportCodeLabelList>(selectReportCodeLabelList);

// Update the active report whenever a new one is pushed to the ngrx store.
this.store
.select(selectActiveReport)
.pipe(takeUntil(this.destroy$))
.subscribe((report: Report | null) => {
this.activeReport = report;
});

// Watch the router changes and display menu if URL is in urlMatch list.
this.router.events.pipe(takeUntil(this.destroy$)).subscribe((event: Event) => {
if (event instanceof NavigationEnd) {
this.handleNavigationEvent(event);
}
});
}

handleNavigationEvent(event: NavigationEnd) {
// Show the sidebar report menu if the router url matches one of the url
// regular expressions in the matchUrl array.
this.showMenu = this.isActive(this.urlMatch, event.url);

if (this.showMenu && this.activeReport && this.activeReport.id !== this.currentReportId) {
this.currentReportId = this.activeReport.id;

this.items = [
{
label: 'ENTER A TRANSACTION',
expanded: this.isActive(this.urlMatch.slice(0, 1), event.url),
items: [
{
label: 'Manage your transactions',
routerLink: [`/reports/f3x/create/step3/${this.currentReportId}`],
},
{
label: 'Add a receipt',
routerLink: [`/transactions/report/${this.currentReportId}/create`],
},
{ label: 'Add a disbursements', styleClass: 'menu-item-disabled' },
{ label: 'Add loans and debts', styleClass: 'menu-item-disabled' },
{ label: 'Add other transactions', styleClass: 'menu-item-disabled' },
],
},
{
label: 'REVIEW A REPORT',
expanded: this.isActive(this.urlMatch.slice(2, 3), event.url),
items: [
{
label: 'View summary page',
routerLink: [`/reports/f3x/summary/${this.currentReportId}`],
},
{
label: 'View detailed summary page',
routerLink: [`/reports/f3x/detailed-summary/${this.currentReportId}`],
},
{ label: 'View print preview' },
{ label: 'Add a report level memo' },
],
},
{
label: 'SUBMIT YOUR REPORT',
expanded: this.isActive(this.urlMatch.slice(4, 6), event.url),
items: [
{ label: 'Confirm information', routerLink: [`/reports/f3x/submit/step1/${this.currentReportId}`] },
{ label: 'Submit report', routerLink: [`/reports/f3x/submit/step2/${this.currentReportId}`] },
{ label: 'Report status', routerLink: [`/reports/f3x/submit/status/${this.currentReportId}`] },
],
},
];
}
}

/**
* Determine if the given url matches one of the regular expressions that define which
* group of menu items is selected.
*
* @param urlMatch {RegExp{}} - List of url regular expressions to compare to current router url
* @param url {string} - Current url in browser address bar
* @returns {boolean} - True is url matches one of the matchUrl regular expressions
*/
isActive(urlMatch: RegExp[], url: string): boolean {
return urlMatch.reduce((prev: boolean, regex: RegExp) => prev || regex.test(url), false);
}

ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.complete();
}
}
16 changes: 1 addition & 15 deletions front-end/src/app/layout/sidebar/sidebar.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,5 @@
<div>FEDERAL ELECTION COMMISSION</div>
</div>
</div>
<div class="report-menu">
<div class="grid report-info">
<div class="col-12">
<div class="title-2">REPORT PROGRESS</div>
<div class="report-type">Form 3X</div>
<div>JULY 20 MONTHLY REPORT (M7)</div>
<div>06/01/2022 - 06/30/2022</div>
</div>
</div>
<div class="grid">
<div class="col-12">
<p-panelMenu [model]="items" [multiple]="false" styleClass="sidebar-report-menu"></p-panelMenu>
</div>
</div>
</div>
<app-menu-report></app-menu-report>
</div>
13 changes: 0 additions & 13 deletions front-end/src/app/layout/sidebar/sidebar.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,3 @@
.title .site-name {
font-size: 2.5em;
}

.report-info {
margin-bottom: 3vw;
}

.report-info .title-2 {
border-bottom: 1px solid white;
margin-bottom: 1vw;
}

.report-info .report-type {
font-size: 1.5em;
}
6 changes: 4 additions & 2 deletions front-end/src/app/layout/sidebar/sidebar.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideMockStore } from '@ngrx/store/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { UserLoginData } from 'app/shared/models/user.model';
import { selectUserLoginData } from 'app/store/login.selectors';
import { SidebarComponent } from './sidebar.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MenuModule } from 'primeng/menu';
import { PanelMenuModule } from 'primeng/panelmenu';
import { MenuReportComponent } from './menu-report/menu-report.component';

describe('SidebarComponent', () => {
let component: SidebarComponent;
Expand All @@ -20,8 +22,8 @@ describe('SidebarComponent', () => {
};

await TestBed.configureTestingModule({
imports: [BrowserAnimationsModule, MenuModule, PanelMenuModule],
declarations: [SidebarComponent],
imports: [BrowserAnimationsModule, MenuModule, PanelMenuModule, RouterTestingModule.withRoutes([])],
declarations: [SidebarComponent, MenuReportComponent],
providers: [
provideMockStore({
initialState: { fecfile_online_userLoginData: userLoginData },
Expand Down
Loading