diff --git a/core/app/src/app/app.module.ts b/core/app/src/app/app.module.ts index eb97fa1e7..d35f8df4c 100644 --- a/core/app/src/app/app.module.ts +++ b/core/app/src/app/app.module.ts @@ -42,6 +42,8 @@ import {AuthService} from '@services/auth/auth.service'; import {GraphQLError} from 'graphql'; import {MessageModalComponent} from '@components/modal/components/message-modal/message-modal.component'; 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 => appInitService.init(); @@ -70,7 +72,8 @@ export const initializeApp = (appInitService: AppInit) => (): Promise => ap BrowserAnimationsModule, NgbModule, FullPageSpinnerModule, - MessageModalModule + MessageModalModule, + RecordListModalModule ], providers: [ {provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true}, @@ -85,7 +88,7 @@ export const initializeApp = (appInitService: AppInit) => (): Promise => ap } ], bootstrap: [AppComponent], - entryComponents: [ColumnChooserComponent, MessageModalComponent] + entryComponents: [ColumnChooserComponent, MessageModalComponent, RecordListModalComponent] }) export class AppModule { constructor(apollo: Apollo, httpLink: HttpLink, protected auth: AuthService) { diff --git a/core/app/src/containers/record-list-modal/adapters/table.adapter.ts b/core/app/src/containers/record-list-modal/adapters/table.adapter.ts new file mode 100644 index 000000000..634aeb667 --- /dev/null +++ b/core/app/src/containers/record-list-modal/adapters/table.adapter.ts @@ -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; + } +} diff --git a/core/app/src/containers/record-list-modal/components/record-list-modal/record-list-modal.component.html b/core/app/src/containers/record-list-modal/components/record-list-modal/record-list-modal.component.html new file mode 100644 index 000000000..c21b02e60 --- /dev/null +++ b/core/app/src/containers/record-list-modal/components/record-list-modal/record-list-modal.component.html @@ -0,0 +1,21 @@ + + +
+ + + + + +
+ +
+
+ +
+
diff --git a/core/app/src/containers/record-list-modal/components/record-list-modal/record-list-modal.component.spec.ts b/core/app/src/containers/record-list-modal/components/record-list-modal/record-list-modal.component.spec.ts new file mode 100644 index 000000000..5a095d1fe --- /dev/null +++ b/core/app/src/containers/record-list-modal/components/record-list-modal/record-list-modal.component.spec.ts @@ -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: '
' +}) +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; + + 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(); + }); + +}); diff --git a/core/app/src/containers/record-list-modal/components/record-list-modal/record-list-modal.component.ts b/core/app/src/containers/record-list-modal/components/record-list-modal/record-list-modal.component.ts new file mode 100644 index 000000000..48a98ffd8 --- /dev/null +++ b/core/app/src/containers/record-list-modal/components/record-list-modal/record-list-modal.component.ts @@ -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; + + 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 { + 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$; + } +} diff --git a/core/app/src/containers/record-list-modal/components/record-list-modal/record-list-modal.module.ts b/core/app/src/containers/record-list-modal/components/record-list-modal/record-list-modal.module.ts new file mode 100644 index 000000000..c3a2aa55f --- /dev/null +++ b/core/app/src/containers/record-list-modal/components/record-list-modal/record-list-modal.module.ts @@ -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 { +} diff --git a/core/app/src/store/record-list/record-list.store.ts b/core/app/src/store/record-list/record-list.store.ts index 44bccce13..4166bad28 100644 --- a/core/app/src/store/record-list/record-list.store.ts +++ b/core/app/src/store/record-list/record-list.store.ts @@ -164,6 +164,10 @@ export class RecordListStore implements StateStore, DataSource, Selectio return this.internalState.records; } + getModule(): string { + return this.internalState.module; + } + getRecord(id: string): Record { let record: Record = null; this.records.some(item => {