Add Record List Modal

- Use base modal layout
- Use scrm-table
- Add table adapter to handle table configuration
-- Add public method to get record list module
- Add message when not properly configured
- Display loading spinner when loading metadata
- Make record list modal available in app
- Add Karma / Jasmine tests
This commit is contained in:
Clemente Raposo 2021-01-15 10:59:04 +00:00 committed by Dillon-Brown
parent c455b862fd
commit d609bf2c2e
7 changed files with 255 additions and 2 deletions

View file

@ -42,6 +42,8 @@ import {AuthService} from '@services/auth/auth.service';
import {GraphQLError} from 'graphql'; import {GraphQLError} from 'graphql';
import {MessageModalComponent} from '@components/modal/components/message-modal/message-modal.component'; import {MessageModalComponent} from '@components/modal/components/message-modal/message-modal.component';
import {MessageModalModule} from '@components/modal/components/message-modal/message-modal.module'; import {MessageModalModule} from '@components/modal/components/message-modal/message-modal.module';
import {RecordListModalComponent} from '@containers/record-list-modal/components/record-list-modal/record-list-modal.component';
import {RecordListModalModule} from '@containers/record-list-modal/components/record-list-modal/record-list-modal.module';
export const initializeApp = (appInitService: AppInit) => (): Promise<any> => appInitService.init(); export const initializeApp = (appInitService: AppInit) => (): Promise<any> => appInitService.init();
@ -70,7 +72,8 @@ export const initializeApp = (appInitService: AppInit) => (): Promise<any> => ap
BrowserAnimationsModule, BrowserAnimationsModule,
NgbModule, NgbModule,
FullPageSpinnerModule, FullPageSpinnerModule,
MessageModalModule MessageModalModule,
RecordListModalModule
], ],
providers: [ providers: [
{provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true},
@ -85,7 +88,7 @@ export const initializeApp = (appInitService: AppInit) => (): Promise<any> => ap
} }
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
entryComponents: [ColumnChooserComponent, MessageModalComponent] entryComponents: [ColumnChooserComponent, MessageModalComponent, RecordListModalComponent]
}) })
export class AppModule { export class AppModule {
constructor(apollo: Apollo, httpLink: HttpLink, protected auth: AuthService) { constructor(apollo: Apollo, httpLink: HttpLink, protected auth: AuthService) {

View file

@ -0,0 +1,37 @@
import {of} from 'rxjs';
import {Injectable} from '@angular/core';
import {SortDirection} from '@components/sort-button/sort-button.model';
import {TableConfig} from '@components/table/table.model';
import {RecordListModalStore} from '@containers/record-list-modal/store/record-list-modal/record-list-modal.store';
@Injectable()
export class ModalRecordListTableAdapter {
constructor(protected store: RecordListModalStore) {
}
getTable(): TableConfig {
return {
showHeader: true,
showFooter: true,
module: this.store.recordList.getModule(),
columns: this.store.columns$,
sort$: this.store.recordList.sort$,
maxColumns$: of(5),
loading$: this.store.recordList.loading$,
dataSource: this.store.recordList,
pagination: this.store.recordList,
toggleRecordSelection: (id: string): void => {
this.store.recordList.toggleSelection(id);
},
updateSorting: (orderBy: string, sortOrder: SortDirection): void => {
this.store.recordList.updateSorting(orderBy, sortOrder);
},
} as TableConfig;
}
}

View file

@ -0,0 +1,21 @@
<scrm-modal [closable]="true"
[close]="closeButton"
[title]="titleKey"
bodyKlass="m-0 small-font"
footerKlass="border-0"
headerKlass="border-0"
klass="record-list-modal">
<div modal-body>
<scrm-table *ngIf="tableConfig" [config]="tableConfig"></scrm-table>
<scrm-label *ngIf="!tableConfig" labelKey="LBL_CONFIG_NO_CONFIG"></scrm-label>
<ng-container *ngIf="tableConfig">
<div *ngIf="(loading$ | async) as loading">
<scrm-loading-spinner *ngIf="loading" [overlay]="true"></scrm-loading-spinner>
</div>
</ng-container>
</div>
</scrm-modal>

View file

@ -0,0 +1,82 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {RecordListModalComponent} from './record-list-modal.component';
import {Component, OnInit} from '@angular/core';
import {NgbModalRef} from '@ng-bootstrap/ng-bootstrap/modal/modal-ref';
import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
import {LanguageStore} from '@store/language/language.store';
import {languageStoreMock} from '@store/language/language.store.spec.mock';
import {RecordListModalModule} from '@containers/record-list-modal/components/record-list-modal/record-list-modal.module';
import {RecordListModalStoreFactory} from '@containers/record-list-modal/store/record-list-modal/record-list-modal.store.factory';
import {recordlistModalStoreFactoryMock} from '@containers/record-list-modal/store/record-list-modal/record-list-modal.store.spec.mock';
import {ThemeImagesStore} from '@store/theme-images/theme-images.store';
import {themeImagesStoreMock} from '@store/theme-images/theme-images.store.spec.mock';
import {ModuleNavigation} from '@services/navigation/module-navigation/module-navigation.service';
import {mockModuleNavigation} from '@services/navigation/module-navigation/module-navigation.service.spec.mock';
import {interval} from 'rxjs';
import {take} from 'rxjs/operators';
@Component({
selector: 'record-list-modal-test-host-component',
template: '<div></div>'
})
class RecordListModalTestHostComponent implements OnInit {
modal: NgbModalRef;
constructor(public modalService: NgbModal) {
}
ngOnInit(): void {
this.modal = this.modalService.open(RecordListModalComponent, {size: 'xl', scrollable: true});
this.modal.componentInstance.module = 'accounts';
}
}
describe('RecordListModalComponent', () => {
let component: RecordListModalTestHostComponent;
let fixture: ComponentFixture<RecordListModalTestHostComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [RecordListModalTestHostComponent],
imports: [
RecordListModalModule,
],
providers: [
{provide: LanguageStore, useValue: languageStoreMock},
{provide: RecordListModalStoreFactory, useValue: recordlistModalStoreFactoryMock},
{provide: ThemeImagesStore, useValue: themeImagesStoreMock},
{provide: ModuleNavigation, useValue: mockModuleNavigation},
],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RecordListModalTestHostComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should have a table', async (done) => {
expect(component).toBeTruthy();
fixture.detectChanges();
await fixture.whenRenderingDone();
await interval(1000).pipe(take(1)).toPromise();
const table = document.getElementsByTagName('scrm-table');
expect(table).toBeTruthy();
expect(table.length).toEqual(1);
component.modal.close();
await interval(1000).pipe(take(1)).toPromise();
done();
});
});

View file

@ -0,0 +1,85 @@
import {Component, Input, OnInit} from '@angular/core';
import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap';
import {animate, transition, trigger} from '@angular/animations';
import {ModalCloseFeedBack} from '@app-common/components/modal/modal.model';
import {ButtonInterface} from '@app-common/components/button/button.model';
import {ModalRecordListTableAdapter} from '@containers/record-list-modal/adapters/table.adapter';
import {LanguageStore} from '@store/language/language.store';
import {Observable, of} from 'rxjs';
import {TableConfig} from '@components/table/table.model';
import {RecordListModalStore} from '@containers/record-list-modal/store/record-list-modal/record-list-modal.store';
import {MaxColumnsCalculator} from '@services/ui/max-columns-calculator/max-columns-calculator.service';
import {RecordListModalStoreFactory} from '@containers/record-list-modal/store/record-list-modal/record-list-modal.store.factory';
@Component({
selector: 'scrm-record-list-modal',
templateUrl: './record-list-modal.component.html',
styleUrls: [],
providers: [MaxColumnsCalculator],
animations: [
trigger('modalFade', [
transition('void <=> *', [
animate('800ms')
]),
]),
]
})
export class RecordListModalComponent implements OnInit {
@Input() titleKey = '';
@Input() module: string;
loading$: Observable<boolean>;
closeButton: ButtonInterface;
adapter: ModalRecordListTableAdapter;
tableConfig: TableConfig;
protected store: RecordListModalStore;
constructor(
public activeModal: NgbActiveModal,
protected storeFactory: RecordListModalStoreFactory,
protected languages: LanguageStore,
protected maxColumnCalculator: MaxColumnsCalculator
) {
}
ngOnInit(): void {
this.closeButton = {
klass: ['btn', 'btn-outline-light', 'btn-sm'],
onClick: (): void => {
this.activeModal.close({
type: 'close-button'
} as ModalCloseFeedBack);
}
} as ButtonInterface;
this.init();
}
init(): void {
if (!this.module) {
return;
}
this.initStore();
this.initTableAdapter();
}
getMaxColumns(): Observable<number> {
return this.maxColumnCalculator.getMaxColumns(of(true));
}
protected initTableAdapter(): void {
this.adapter = new ModalRecordListTableAdapter(this.store);
this.tableConfig = this.adapter.getTable();
this.tableConfig.maxColumns$ = this.getMaxColumns();
}
protected initStore(): void {
this.store = this.storeFactory.create();
this.store.init(this.module);
this.loading$ = this.store.metadataLoading$;
}
}

View file

@ -0,0 +1,21 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {RecordListModalComponent} from './record-list-modal.component';
import {ModalModule} from '@components/modal/components/modal/modal.module';
import {TableModule} from '@components/table/table.module';
import {LabelModule} from '@components/label/label.module';
import {LoadingSpinnerModule} from '@components/loading-spinner/loading-spinner.module';
@NgModule({
declarations: [RecordListModalComponent],
imports: [
CommonModule,
ModalModule,
TableModule,
LabelModule,
LoadingSpinnerModule,
]
})
export class RecordListModalModule {
}

View file

@ -164,6 +164,10 @@ export class RecordListStore implements StateStore, DataSource<Record>, Selectio
return this.internalState.records; return this.internalState.records;
} }
getModule(): string {
return this.internalState.module;
}
getRecord(id: string): Record { getRecord(id: string): Record {
let record: Record = null; let record: Record = null;
this.records.some(item => { this.records.some(item => {