mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-08-29 11:00:40 +08:00
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:
parent
c455b862fd
commit
d609bf2c2e
7 changed files with 255 additions and 2 deletions
|
@ -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<any> => appInitService.init();
|
||||
|
||||
|
@ -70,7 +72,8 @@ export const initializeApp = (appInitService: AppInit) => (): Promise<any> => 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<any> => ap
|
|||
}
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
entryComponents: [ColumnChooserComponent, MessageModalComponent]
|
||||
entryComponents: [ColumnChooserComponent, MessageModalComponent, RecordListModalComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
constructor(apollo: Apollo, httpLink: HttpLink, protected auth: AuthService) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
||||
|
||||
});
|
|
@ -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$;
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -164,6 +164,10 @@ export class RecordListStore implements StateStore, DataSource<Record>, Selectio
|
|||
return this.internalState.records;
|
||||
}
|
||||
|
||||
getModule(): string {
|
||||
return this.internalState.module;
|
||||
}
|
||||
|
||||
getRecord(id: string): Record {
|
||||
let record: Record = null;
|
||||
this.records.some(item => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue