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 {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) {
|
||||||
|
|
|
@ -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;
|
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 => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue