From 9e8e8ef36a75f8a4a1eb60fe782e4aa39c92e322 Mon Sep 17 00:00:00 2001 From: Patrick Sun Date: Wed, 28 Apr 2021 04:31:35 +1000 Subject: [PATCH] loan select incorporated --- src/app/app.module.ts | 76 +++---- ...year-monthly-performance.component.spec.ts | 12 +- .../list-all-loans.component.html | 32 ++- .../list-all-loans.component.scss | 28 ++- .../list-all-loans.component.ts | 148 +++++++++++++- .../list-income/list-income.component.html | 42 +--- .../list-income/list-income.component.scss | 5 +- src/app/list-income/list-income.component.ts | 49 ++++- src/app/loan-card/loan-card.component.html | 3 +- src/app/loan-card/loan-card.component.scss | 11 +- src/app/loan-card/loan-card.component.ts | 23 ++- .../trail-income/trail-income.component.ts | 2 +- .../loan-select/loan-select.component.html | 19 ++ .../loan-select/loan-select.component.scss | 25 +++ .../loan-select/loan-select.component.spec.ts | 25 +++ src/app/loan-select/loan-select.component.ts | 74 +++++++ src/app/main-menu-items.ts | 7 +- src/app/models/pay-in-ex.model.ts | 13 ++ src/app/models/pay-in.model.ts | 21 +- src/app/pay-in/pay-in.component.html | 42 ++-- src/app/pay-in/pay-in.component.scss | 30 +-- src/app/pay-in/pay-in.component.ts | 192 +++++++++++++----- .../upload-detail.component.html | 60 ++++-- .../upload-detail.component.scss | 7 + 24 files changed, 732 insertions(+), 214 deletions(-) create mode 100644 src/app/loan-select/loan-select.component.html create mode 100644 src/app/loan-select/loan-select.component.scss create mode 100644 src/app/loan-select/loan-select.component.spec.ts create mode 100644 src/app/loan-select/loan-select.component.ts create mode 100644 src/app/models/pay-in-ex.model.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 6eb9c85..66f9e91 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -97,6 +97,8 @@ import { PopupIncomeFilterComponent } from './popup-income-filter/popup-income-f import { LoanCardComponent } from './loan-card/loan-card.component'; import {AppConfig} from './app.config'; import { UploadingProgressCardComponent } from './uploading-progress-card/uploading-progress-card.component'; +import { LoanSelectComponent } from './loan-select/loan-select.component'; +import {PopupModule} from '@progress/kendo-angular-popup'; @@ -164,43 +166,45 @@ export function initializeApp(appConfig: AppConfig): () => Promise { ImagePopupDialogComponent, PopupIncomeFilterComponent, LoanCardComponent, - UploadingProgressCardComponent - ], - imports: [ - BrowserModule, - BrowserAnimationsModule, - FormsModule, - CommonModule, - HttpClientModule, - ReactiveFormsModule, - AppRoutingModule, - MenuModule, - ContextMenuModule, - BrowserAnimationsModule, - DialogsModule, - ButtonsModule, - FloatingActionButtonModule, - GridModule, - PDFModule, - ExcelModule, - InputsModule, - IconsModule, - FontAwesomeModule, - NavigationModule, - LayoutModule, - IndicatorsModule, - LabelModule, - NotificationModule, - ChartsModule, - DateInputsModule, - DropDownsModule, - ExcelExportModule, - EditorModule, - UploadModule, - FileSelectModule, - ProgressBarModule, - PagerModule + UploadingProgressCardComponent, + LoanSelectComponent ], + imports: [ + BrowserModule, + BrowserAnimationsModule, + FormsModule, + CommonModule, + HttpClientModule, + ReactiveFormsModule, + AppRoutingModule, + MenuModule, + ContextMenuModule, + BrowserAnimationsModule, + DialogsModule, + ButtonsModule, + FloatingActionButtonModule, + GridModule, + PDFModule, + ExcelModule, + InputsModule, + IconsModule, + FontAwesomeModule, + NavigationModule, + LayoutModule, + IndicatorsModule, + LabelModule, + NotificationModule, + ChartsModule, + DateInputsModule, + DropDownsModule, + ExcelExportModule, + EditorModule, + UploadModule, + FileSelectModule, + ProgressBarModule, + PagerModule, + PopupModule + ], providers: [ MenuService, AuthGuard, diff --git a/src/app/chart-past-year-monthly-performance/chart-past-year-monthly-performance.component.spec.ts b/src/app/chart-past-year-monthly-performance/chart-past-year-monthly-performance.component.spec.ts index ba5bf5d..095740c 100644 --- a/src/app/chart-past-year-monthly-performance/chart-past-year-monthly-performance.component.spec.ts +++ b/src/app/chart-past-year-monthly-performance/chart-past-year-monthly-performance.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ChartPastYearMontnlyPerformanceComponent } from './chart-past-year-monthly-performance.component'; +import { ChartPastYearMonthlyPerformanceComponent } from './chart-past-year-monthly-performance.component'; -describe('ChartPastYearMontnlyPerformanceComponent', () => { - let component: ChartPastYearMontnlyPerformanceComponent; - let fixture: ComponentFixture; +describe('ChartPastYearMonthlyPerformanceComponent', () => { + let component: ChartPastYearMonthlyPerformanceComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ ChartPastYearMontnlyPerformanceComponent ] + declarations: [ ChartPastYearMonthlyPerformanceComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(ChartPastYearMontnlyPerformanceComponent); + fixture = TestBed.createComponent(ChartPastYearMonthlyPerformanceComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/list-all-loans/list-all-loans.component.html b/src/app/list-all-loans/list-all-loans.component.html index 0e4748c..52525bb 100644 --- a/src/app/list-all-loans/list-all-loans.component.html +++ b/src/app/list-all-loans/list-all-loans.component.html @@ -10,21 +10,45 @@ [height]="1000" [navigable]="true" [filterable]="true" + [selectable]="selectableSettings" + kendoGridSelectBy="Id" + [selectedKeys]="gridSelection" + (selectionChange)="onSelectionChange($event)" (dataStateChange)="dataStateChange($event)" (filterChange)="filterChange($event)" (cellClick)="onCellClick($event)" class="fullheight_grid" > - - - + + + +
+ +
+
+ {{SelectedLoans.length}}/ + {{MaxSelect}} +
+ +
- + + + +

{{dataItem.Index}}

+ diff --git a/src/app/list-all-loans/list-all-loans.component.scss b/src/app/list-all-loans/list-all-loans.component.scss index 5a23354..6666efc 100644 --- a/src/app/list-all-loans/list-all-loans.component.scss +++ b/src/app/list-all-loans/list-all-loans.component.scss @@ -1,5 +1,5 @@ .fullheight_grid { - height: calc(100vh - 48px) !important; + height: 100% !important; } .k-grid td:first-child{ @@ -14,6 +14,32 @@ text-align: left; } + +div.selected-loans{ + position: relative; + padding-right:200px; + width: calc(100% - 150px); + min-height: 60px; + overflow: auto hidden; + app-loan-card{ + display: block; + } +} + +div.selection-percentage{ + float:right; + position:absolute; + right: 10px; + bottom: 1px; +} + + +button.select-loan{ + position: absolute; + right: 10px; + top: 10px; +} + .customer-photo{ display: inline-block; width: 32px; diff --git a/src/app/list-all-loans/list-all-loans.component.ts b/src/app/list-all-loans/list-all-loans.component.ts index d2fe131..f34c8a5 100644 --- a/src/app/list-all-loans/list-all-loans.component.ts +++ b/src/app/list-all-loans/list-all-loans.component.ts @@ -1,16 +1,41 @@ -import {Component, OnInit, ViewChild, ViewEncapsulation} from '@angular/core'; -import {CellCloseEvent, DataStateChangeEvent, GridComponent, GridDataResult} from '@progress/kendo-angular-grid'; +import {Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewContainerRef, ViewEncapsulation} from '@angular/core'; +import { + CellCloseEvent, + DataStateChangeEvent, + GridComponent, + GridDataResult, + SelectableSettings, + SelectionEvent +} from '@progress/kendo-angular-grid'; import {CompositeFilterDescriptor, SortDescriptor, toODataString} from '@progress/kendo-data-query'; import {LoanSummaryService} from '../service/loan_summary.service'; import {AuthService} from '../service/auth.service'; import {Observable} from 'rxjs'; import {Router} from '@angular/router'; +import {LoanModel} from '../models/loan.model'; +import {LoanSelectComponent} from '../loan-select/loan-select.component'; +import {NotificationService, Type} from '@progress/kendo-angular-notification'; +import {animate, style, transition, trigger} from '@angular/animations'; @Component({ selector: 'app-list-all-loans', templateUrl: './list-all-loans.component.html', styleUrls: ['./list-all-loans.component.scss'], encapsulation: ViewEncapsulation.None, + animations: [ + trigger('fadeIn', [ + transition(':enter', [ + style({ opacity: '0' }), + animate('.5s ease-out', style({ opacity: '1'})), + ]), + ]), + trigger('slideOutUp', [ + transition(':leave', [ + style({ opacity: '1' }), + animate('.5s ease-out', style({ opacity: '0' })), + ]), + ]), + ], }) export class ListAllLoansComponent implements OnInit { public view: LoanSummaryService; @@ -18,13 +43,25 @@ export class ListAllLoansComponent implements OnInit { public filter: CompositeFilterDescriptor; public pageSize = 10; public skip = 0; - - - - @ViewChild(GridComponent, { static: true }) public grid: GridComponent; - - - constructor(private service: LoanSummaryService, private auth: AuthService, private router: Router) { } + @Output() LoanSelected: EventEmitter = new EventEmitter(); + + @Input() EnableExportExcel = false; + @Input() EnableExportPdf = false; + @Input() EnableSelectButton = false; + @Input() MaxSelect = 1; + @Input() Preselect: LoanModel[] = []; + public SelectedLoans: LoanModel[] = []; + public selectableSettings: SelectableSettings|boolean; + private privateSingleSelection = true; + public gridSelection: string[] = []; + @ViewChild(GridComponent, { static: true }) public grid: GridComponent; + @ViewChild(GridComponent, { read: ViewContainerRef }) public gridVR: ViewContainerRef; + + + constructor(private service: LoanSummaryService, + private auth: AuthService, + private router: Router, + private notificationService: NotificationService) { } public ngOnInit(): void { // Bind directly to the service as it is a Subject @@ -32,6 +69,39 @@ export class ListAllLoansComponent implements OnInit { // Fetch the data with the initial state this.loadData(); + + // selectable + this.setSelectableSettings(); + + // Preselect on Init only + this.Preselect.forEach( v => { + if ( v !== undefined && v !== null && v.Id !== '' ){ + this.AddLoanToSelection(v); + this.gridSelection.unshift(v.Id); + } + }); + } + + @Input() set SingleSelection(single: boolean) { + this.privateSingleSelection = single; + this.setSelectableSettings(); + } + + get SingleSelection(): boolean{ + return this.privateSingleSelection; + } + + public setSelectableSettings(): void { + this.privateSingleSelection = this.MaxSelect === 1; + if ( this.EnableSelectButton ){ + this.selectableSettings = { + checkboxOnly: true, + mode: this.privateSingleSelection ? 'single' : 'multiple', + drag: false, + }; + }else{ + this.selectableSettings = false; + } } public dataStateChange({ skip, take, sort, filter }: DataStateChangeEvent): void { @@ -70,8 +140,66 @@ export class ListAllLoansComponent implements OnInit { } public onCellClick(event: CellCloseEvent): void { - this.router.navigate(['/edit-loan/' + event.dataItem.Id]); + // this.LoanSelected.emit(event.dataItem); + } + + public AddLoanToSelection(loan: LoanModel): void{ + const idx = this.SelectedLoans.findIndex( v => { + return v.Id === loan.Id; + }); + if ( idx === -1 ) { // not found + this.SelectedLoans.unshift(loan); + } } + public onFinishSelection(): void{ + if ( this.MaxSelect === 1 ){ + this.LoanSelected.emit(this.SelectedLoans[0]); + }else{ + this.LoanSelected.emit(this.SelectedLoans); + } + + console.log('loan selected', this); + this.notifyUser('ended'); + } + public RemoveFromSelection(l: LoanModel): void{ + this.SelectedLoans = this.SelectedLoans.filter( v => l.Id !== v.Id ); + this.gridSelection = this.gridSelection.filter( v => v !== l.Id ); + } + + public onSelectionChange(sel: SelectionEvent): void { + // to avoid race condition, we do action after 200ms + setTimeout( () => { + sel.deselectedRows.forEach(v => { + this.RemoveFromSelection(v.dataItem); + }); + + sel.selectedRows.forEach(v => { + if ( this.SelectedLoans.length < this.MaxSelect) { + this.AddLoanToSelection(v.dataItem); + }else{ + // remove those that was added by grid selection + this.gridSelection = this.gridSelection.filter( existing => existing !== v.dataItem.Id ); + this.notifyUser('Maximum selection reached', { style: 'warning', icon: true }); + } + }); + }, 200); + } + + public notifyUser( msg: string, lookAndFeel?: Type): void { + if (lookAndFeel === undefined ){ + lookAndFeel = { style: 'success', icon: true }; + } + this.notificationService.show({ + appendTo: this.gridVR, + content: msg, + cssClass: 'button-notification', + animation: { type: 'slide', duration: 400 }, + position: { horizontal: 'right', vertical: 'top' }, + type: lookAndFeel, + closable: false, + hideAfter: 5000, + }); + } } diff --git a/src/app/list-income/list-income.component.html b/src/app/list-income/list-income.component.html index 2dabbf9..fc634d8 100644 --- a/src/app/list-income/list-income.component.html +++ b/src/app/list-income/list-income.component.html @@ -1,37 +1,9 @@
- - - -
-

Outer splitter / Middle pane

-

Resizable only.

-
-
- - - - - - -
- -
-
- - -
-
- Type: {{u.files[0].extension}} - Name: {{u.files[0].name}} - Funder: Name: {{u.response.body.Funder}} -
-
-
-
- - - - -
-
+ +
diff --git a/src/app/list-income/list-income.component.scss b/src/app/list-income/list-income.component.scss index e7d3929..181efcf 100644 --- a/src/app/list-income/list-income.component.scss +++ b/src/app/list-income/list-income.component.scss @@ -1,7 +1,4 @@ div.income-container { height: calc(100vh - 48px); -} - -div.pane-content{ - height:100%; + overflow:hidden; } diff --git a/src/app/list-income/list-income.component.ts b/src/app/list-income/list-income.component.ts index 0ed3a87..3e80720 100644 --- a/src/app/list-income/list-income.component.ts +++ b/src/app/list-income/list-income.component.ts @@ -1,5 +1,8 @@ import { Component, OnInit } from '@angular/core'; -import {SuccessEvent} from '@progress/kendo-angular-upload'; +import {UploadMetaModel} from '../models/uploadMetaModel'; +import {UploadAttachService} from '../service/upload.attach.service'; +import {LoanModel} from '../models/loan.model'; +import {LoanSingleService} from '../service/loan.single.service'; @Component({ selector: 'app-list-income', @@ -7,15 +10,49 @@ import {SuccessEvent} from '@progress/kendo-angular-upload'; styleUrls: ['./list-income.component.scss'] }) export class ListIncomeComponent implements OnInit { - public uploads: SuccessEvent[] = []; + public uploadMeta: UploadMetaModel = new UploadMetaModel({}); + public Loan: LoanModel = new LoanModel({}); + public startLoad = false - constructor() { } + private UploadMetaFilterIsReady = false; + private LoanFilterIsReady = false; + constructor(private uas: UploadAttachService, private lss: LoanSingleService) { } ngOnInit(): void { + this.UploadMetaFilterIsReady = true; + + // this.UploadMetaFilterIsReady = false; + // this.uas.getUploadMeta(1).subscribe( + // resp => { + // this.UploadMetaFilterIsReady = true; + // this.checkStartUpload(); + // // this.uploadMeta = new UploadMetaModel(resp); + // }, error => { + // this.UploadMetaFilterIsReady = true; + // this.checkStartUpload(); + // }, () => { + // this.UploadMetaFilterIsReady = true; + // this.checkStartUpload(); + // } + // ); + + // this.lss.getLoan('0482b524-d396-4b03-8abb-f8325c87e2ed').subscribe( + // resp => { + // this.Loan = new LoanModel(resp); + // this.LoanFilterIsReady = true; + // this.checkStartUpload(); + // }, err => { + // this.LoanFilterIsReady = true; + // this.checkStartUpload(); + // }, () => { + // this.LoanFilterIsReady = true; + // this.checkStartUpload(); + // } + // ); + this.startLoad = true; } - public onSuccess(e: SuccessEvent): void { - this.uploads.push (e); - console.log(e); + private checkStartUpload(): void { + this.startLoad = this.UploadMetaFilterIsReady } } diff --git a/src/app/loan-card/loan-card.component.html b/src/app/loan-card/loan-card.component.html index a8d2f65..cc52765 100644 --- a/src/app/loan-card/loan-card.component.html +++ b/src/app/loan-card/loan-card.component.html @@ -1,6 +1,7 @@ -
+
{{Loan.Item}} Amount: {{Loan.Amount | currency }} Settlement: {{Loan.Settlement | date:'yyyy-mm-dd' }} +
diff --git a/src/app/loan-card/loan-card.component.scss b/src/app/loan-card/loan-card.component.scss index 98b51c3..b10a625 100644 --- a/src/app/loan-card/loan-card.component.scss +++ b/src/app/loan-card/loan-card.component.scss @@ -11,7 +11,16 @@ div.loan.card { -o-transition: all 0.4s cubic-bezier(0.645, 0.045, 0.355, 1); transition: all 0.4s cubic-bezier(0.645, 0.045, 0.355, 1); border-radius: 10px; - overflow: hidden; + overflow: unset; -webkit-box-shadow: 15px 15px 27px #e1e1e3, -15px -15px 27px #ffffff; box-shadow: 15px 15px 27px #e1e1e3, -15px -15px 27px #ffffff; + margin-top:10px; + margin-right: 10px; + + span.k-icon.close{ + position:absolute; + top: -10px; + right: -10px; + border-radius: 100%; + } } diff --git a/src/app/loan-card/loan-card.component.ts b/src/app/loan-card/loan-card.component.ts index fb32b2d..3f5f24f 100644 --- a/src/app/loan-card/loan-card.component.ts +++ b/src/app/loan-card/loan-card.component.ts @@ -1,4 +1,4 @@ -import {Component, Input, OnInit} from '@angular/core'; +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {LoanModel} from '../models/loan.model'; import {LoanSingleService} from '../service/loan.single.service'; import {Router} from '@angular/router'; @@ -10,13 +10,26 @@ import {Router} from '@angular/router'; }) export class LoanCardComponent implements OnInit { @Input() LoanId = ''; - public Loan: LoanModel = new LoanModel({}); + @Input() ShowCloseButton = false; + @Input() public Loan: LoanModel = new LoanModel({}); + @Output() Selected: EventEmitter = new EventEmitter(); + @Output() Closed: EventEmitter = new EventEmitter(); constructor(private lss: LoanSingleService, private router: Router) { } ngOnInit(): void { - this.lss.getLoan(this.LoanId).subscribe( resp => { - this.Loan.Response = resp; - }); + if ( this.LoanId !== undefined && this.LoanId !== '' && this.Loan.Id === ''){ + this.lss.getLoan(this.LoanId).subscribe( resp => { + this.Loan.Response = resp; + }); + } + } + + public onClick(): void { + this.Selected.emit(this.Loan); + } + + public onClose(): void{ + this.Closed.emit(this.Loan); } public gotoLoan(id: string): void { diff --git a/src/app/loan-edit/trail-income/trail-income.component.ts b/src/app/loan-edit/trail-income/trail-income.component.ts index 8a7f116..5b65cfd 100644 --- a/src/app/loan-edit/trail-income/trail-income.component.ts +++ b/src/app/loan-edit/trail-income/trail-income.component.ts @@ -107,7 +107,7 @@ export class TrailIncomeComponent implements OnInit { pi.LoanNumber = this.Loan.LenderLoanNumber; pi.OffsetBalance = v.OffsetBalance; pi.Settlement = this.Loan.Settlement; - pi.Trail = v.Trail; + pi.IncomeAmount = v.Trail; //TODO: rename Trail to incomeAmount and add IncomeType pi.Ts = new Date(v.Ts); pi.UploadId = v.UploadId; diff --git a/src/app/loan-select/loan-select.component.html b/src/app/loan-select/loan-select.component.html new file mode 100644 index 0000000..c3137e7 --- /dev/null +++ b/src/app/loan-select/loan-select.component.html @@ -0,0 +1,19 @@ +
+ + +
+ + + + + diff --git a/src/app/loan-select/loan-select.component.scss b/src/app/loan-select/loan-select.component.scss new file mode 100644 index 0000000..664d0e7 --- /dev/null +++ b/src/app/loan-select/loan-select.component.scss @@ -0,0 +1,25 @@ +.popup-content { + box-shadow: 1px 1px 10px black; + padding: 10px; + width: 90vw; + height: 50vw; + color: #787878; + background-color: #fcf7f8; + border: 1px solid rgba(0,0,0,.05); + border-radius:20px; +} + +div.anchor.wrapper { + width:100%; + position: relative; + + button{ + position: absolute; + /* left: 0px; */ + right: 0px; + top: -10px; + color: red; + border-radius: 100%; + z-index: 1; + } +} diff --git a/src/app/loan-select/loan-select.component.spec.ts b/src/app/loan-select/loan-select.component.spec.ts new file mode 100644 index 0000000..0d818a2 --- /dev/null +++ b/src/app/loan-select/loan-select.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoanSelectComponent } from './loan-select.component'; + +describe('LoanSelectComponent', () => { + let component: LoanSelectComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LoanSelectComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LoanSelectComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/loan-select/loan-select.component.ts b/src/app/loan-select/loan-select.component.ts new file mode 100644 index 0000000..1948115 --- /dev/null +++ b/src/app/loan-select/loan-select.component.ts @@ -0,0 +1,74 @@ +import { Component, Input, OnInit, ViewChild} from '@angular/core'; +import {LoanModel} from '../models/loan.model'; +import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {ListAllLoansComponent} from '../list-all-loans/list-all-loans.component'; + +@Component({ + selector: 'app-loan-select', + templateUrl: './loan-select.component.html', + styleUrls: ['./loan-select.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + multi: true, + useExisting: LoanSelectComponent + } + ] +}) +export class LoanSelectComponent implements OnInit, ControlValueAccessor { + @ViewChild('list', {static: false} ) list: ListAllLoansComponent; + @Input() Max = 1; // we do not support multiple loan selection at the moment + @Input() readOnly = false; + public Loan: LoanModel; // for multiple selection ,it has to be array; we don't support it now. + + // popup + public showPopup = false; + public Offset = { left: 0, top: 0 }; + + // formControl state + private disabled = false; + private touched = false; + + // Control Value Accessor + onChange = (Loan) => {}; // empty + onTouched = () => {}; + + constructor() { } + + ngOnInit(): void { + console.log('init loan-select', this); + } + + writeValue(loan: LoanModel): void { + this.Loan = loan; + } + + registerOnChange(onChange: any): void { + this.onChange = onChange; + } + + registerOnTouched(onTouched: any): void { + this.onTouched = onTouched; + } + + private markAsTouched(): void { + if (!this.touched) { + this.onTouched(); + this.touched = true; + } + } + + onLoanSelected(loan: LoanModel): void { + this.Loan = loan; + this.markAsTouched(); + this.onTouched(); + this.onChange(loan); + this.showPopup = false; + } + + + public onToggle(): void { + this.showPopup = !this.showPopup; + } + +} diff --git a/src/app/main-menu-items.ts b/src/app/main-menu-items.ts index 035fabf..e095272 100644 --- a/src/app/main-menu-items.ts +++ b/src/app/main-menu-items.ts @@ -20,11 +20,12 @@ export const mainMenuItems: any[] = [ { text: 'Start New Loan', icon: 'plus', url: './#edit-loan/' }, { text: 'List All', icon: 'table' , url: './#list-all-loans' }, { text: '--', separator: 'true' }, - { text: 'Income', icon: 'dollar', url: './#pay-in' }, + { text: 'list income', icon: 'dollar', url: './#list-income' }, { text: '--', separator: 'true' }, { text: 'Uploads', icon: 'dollar', url: './#lender-uploads' }, - { text: 'Uploads by Id', icon: 'dollar', url: './#upload-details/30' }, - { text: 'list income', icon: 'dollar', url: './#list-income' }, + { text: '--', separator: 'true' }, + { text: 'Test Uploads by Id', icon: 'dollar', url: './#upload-details/1' }, + { text: 'Test Pay-in', icon: 'dollar', url: './#pay-in' }, ] }, { diff --git a/src/app/models/pay-in-ex.model.ts b/src/app/models/pay-in-ex.model.ts new file mode 100644 index 0000000..0553dc6 --- /dev/null +++ b/src/app/models/pay-in-ex.model.ts @@ -0,0 +1,13 @@ +import {LoanModel} from './loan.model'; +import {UploadMetaModel} from './uploadMetaModel'; +import {PayInModel} from './pay-in.model'; + +export class PayInModelEx extends PayInModel{ + public Loan: LoanModel; + public UploadMeta: UploadMetaModel; + constructor(payload: Partial){ + super(payload); + this.Loan = new LoanModel( payload.Loan || {}); + this.UploadMeta = new UploadMetaModel(payload.UploadMeta || {}); + } +} diff --git a/src/app/models/pay-in.model.ts b/src/app/models/pay-in.model.ts index e8be8d1..d85ff29 100644 --- a/src/app/models/pay-in.model.ts +++ b/src/app/models/pay-in.model.ts @@ -1,3 +1,5 @@ +import {LoanModel} from './loan.model'; +import {UploadMetaModel} from './uploadMetaModel'; export class PayInModel { public Id: number; @@ -8,7 +10,8 @@ export class PayInModel { public LoanNumber: string; public OffsetBalance: number; public Settlement: Date; - public Trail: number; + public IncomeAmount: number; + public IncomeType: string; public Ts: Date; public UploadId: number; constructor(payload: Partial){ @@ -22,9 +25,21 @@ export class PayInModel { this.LoanId = payload.LoanId || '' ; this.LoanNumber = payload.LoanNumber || ''; this.OffsetBalance = payload.OffsetBalance || 0; + if (payload.Settlement === undefined || payload.Settlement === null) { + this.Settlement = new Date(); + }else{ + this.Settlement = new Date(payload.Settlement); + } this.Settlement = new Date(payload.Settlement); - this.Trail = payload.Trail || 0 ; - this.Ts = new Date(payload.Ts); + this.IncomeAmount = payload.IncomeAmount || 0 ; + this.IncomeType = payload.IncomeType || ''; + if (payload.Ts === undefined || payload.Ts === null) { + this.Ts = new Date(); + }else{ + this.Ts = new Date(payload.Ts); + } this.UploadId = payload.UploadId; } } + + diff --git a/src/app/pay-in/pay-in.component.html b/src/app/pay-in/pay-in.component.html index e1d2dfc..96b186b 100644 --- a/src/app/pay-in/pay-in.component.html +++ b/src/app/pay-in/pay-in.component.html @@ -3,7 +3,7 @@ [pageSize]="filter.Take" [skip]="filter.Skip" [sortable]="sortable" - [filterable]="filterable" + [filterable]="false" [loading]="loading" [sort]="filter.Sort" @@ -21,10 +21,12 @@
+
- - + {{uploadMeta.Id}} + {{uploadMeta.FileName}} +
@@ -46,7 +48,13 @@ - + + + + + + + @@ -55,10 +63,6 @@ - - -
{{ dataItem.Balance | currency}}
@@ -77,7 +81,7 @@ > - @@ -103,26 +107,34 @@ > -
- +

-

- + - - + + + + diff --git a/src/app/pay-in/pay-in.component.scss b/src/app/pay-in/pay-in.component.scss index 54daec3..26c77df 100644 --- a/src/app/pay-in/pay-in.component.scss +++ b/src/app/pay-in/pay-in.component.scss @@ -1,21 +1,23 @@ kendo-grid { - height: calc(100vh - 48px); + height: 100%; // calc(100vh - 48px); - .add-new-tool-bar{ - width:30%; - display: inline-block; - } + .add-new-tool-bar{ + width:30%; + display: inline-block; + } - .filter-panel-wrapper{ - display: inline-block; - text-align:right; - } + .filter-panel-wrapper{ + display: inline-block; + text-align:right; + + span.specific-upload{ + margin-right: 10px; + } + } } .balance { - display: inline-block; - margin-left: 0px !important; - width: calc(100% - 60px) !important; + display: inline-block; + margin-left: 0px !important; + width: calc(100% - 60px) !important; } - - diff --git a/src/app/pay-in/pay-in.component.ts b/src/app/pay-in/pay-in.component.ts index 26df171..ff1c0f8 100644 --- a/src/app/pay-in/pay-in.component.ts +++ b/src/app/pay-in/pay-in.component.ts @@ -1,4 +1,4 @@ -import {Component, Input, OnInit, ViewChild} from '@angular/core'; +import {Component, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core'; import {PayInModel} from '../models/pay-in.model'; import {FormControl, FormGroup, Validators} from '@angular/forms'; import {PayInListFilterModel} from '../models/pay-in-list.filter.model'; @@ -8,14 +8,13 @@ import {Router} from '@angular/router'; import {PopupIncomeFilterComponent} from '../popup-income-filter/popup-income-filter.component'; import {PageChangeEvent, SortSettings} from '@progress/kendo-angular-grid'; import {SortDescriptor} from '@progress/kendo-data-query'; +import {UploadMetaModel} from '../models/uploadMetaModel'; +import {debounce} from 'ts-debounce'; +import {LoanModel} from '../models/loan.model'; +import {LoanSingleService} from '../service/loan.single.service'; +import {PayInModelEx} from '../models/pay-in-ex.model'; + -const createFormGroup = dataItem => new FormGroup({ - Id: new FormControl({value: dataItem.Id, disabled: true}, Validators.required), - Trail : new FormControl(dataItem.Trail, Validators.required), - Ts: new FormControl(dataItem.Ts, Validators.required), - Balance: new FormControl(dataItem.Balance, Validators.required), - OffsetBalance: new FormControl(dataItem.OffsetBalance, Validators.required), -}); @Component({ @@ -26,8 +25,15 @@ const createFormGroup = dataItem => new FormGroup({ export class PayInComponent implements OnInit { @Input() allowAddNew = true; @Input() allowEdit = true; + @Input() showLoanColumn = true; + @Input() showUploadColumn = true; + private filterUploadMeta: UploadMetaModel = new UploadMetaModel({}); + public filterLoan = new LoanModel({}); @Input() filter: PayInListFilterModel = new PayInListFilterModel({}); + @Output() errorOccurred = new EventEmitter(); @ViewChild('filterDialog', {static: true}) filterDialog: PopupIncomeFilterComponent; + private privateLoadDataNow = false; + public gridData: PayInListResult = { data: [], total: 0}; public incomeFormGroup: FormGroup; @@ -40,14 +46,14 @@ export class PayInComponent implements OnInit { public sortable: SortSettings = { mode: 'single' }; - public filterable = false; public loading = false; + private debouncedLoadFilteredData = () => {}; - constructor(private pis: PayInService, private router: Router) { } + constructor(private pis: PayInService, private lss: LoanSingleService, private router: Router) { } ngOnInit(): void { - this.loadFilteredData(); + this.debouncedLoadFilteredData = debounce(this.loadFilteredData, 100); } public loadFilteredData(): void{ @@ -57,9 +63,9 @@ export class PayInComponent implements OnInit { this.gridData.total = resp.total; this.gridData.data = []; resp.data.forEach(v => { - this.gridData.data.push(new PayInModel(v)); - this.loading = false; + this.gridData.data.push(new PayInModelEx(v)); }); + this.loading = false; }, err => { this.loading = false; }, () => { @@ -68,6 +74,48 @@ export class PayInComponent implements OnInit { ); } + // Upload Filter + @Input() set uploadMeta(value: UploadMetaModel) { + this.filterUploadMeta = value; + if ( value.Id <= 0 ) { + this.filter.UploadIds = []; + } else{ + this.filter.UploadIds = [ value.Id.toString() ]; + } + this.debouncedLoadFilteredData(); + console.log('upload filter changed', value, this.filter); + } + + get uploadMeta(): UploadMetaModel { + return this.filterUploadMeta; + } + + // LoanId filter + @Input() set Loan(value: LoanModel){ + this.filterLoan = value; + if ( value.Id === '' ) { + this.filter.LoanIds = []; + }else{ + this.filter.LoanIds = [ value.Id ]; + } + this.debouncedLoadFilteredData(); + console.log('filter loanId changed', value, this.filter); + } + + get Loan(): LoanModel { + return this.filterLoan; + } + + @Input() set LoadDataNow( value: boolean) { + this.privateLoadDataNow = value; + if ( value === true ) { + this.debouncedLoadFilteredData(); + } + } + get LoadDataNow(): boolean { + return this.privateLoadDataNow; + } + public addHandler({ sender }): void { this.closeEditor(sender); @@ -75,23 +123,37 @@ export class PayInComponent implements OnInit { const offsetBalance = -1; this.showBalance = balance >= 0 ; this.showOffsetBalance = offsetBalance >= 0 ; - this.incomeFormGroup = createFormGroup({ - Id: 0, - Trail: 168, - Ts: new Date(), - Balance: balance, - OffsetBalance: offsetBalance - }); + this.incomeFormGroup = this.createFormGroup(new PayInModelEx({})); + console.log(this.incomeFormGroup); sender.addRow(this.incomeFormGroup); } + private createFormGroup(dataItem: PayInModelEx): FormGroup { + const Loan = this.filterLoan.Id !== '' ? this.filterLoan : dataItem.Loan; + const UploadMeta = this.filterUploadMeta.Id !== 0 ? this.filterUploadMeta : dataItem.UploadMeta ; + + return new FormGroup({ + Id: new FormControl({value: dataItem.Id, disabled: true}, Validators.required), + Lender: new FormControl(dataItem.Lender), + Amount: new FormControl(dataItem.Amount), + LoanNumber: new FormControl(dataItem.LoanNumber), + IncomeAmount: new FormControl(dataItem.IncomeAmount, Validators.required), + IncomeType: new FormControl(dataItem.IncomeType, Validators.required), + Ts: new FormControl(dataItem.Ts, Validators.required), + Balance: new FormControl(dataItem.Balance, Validators.required), + OffsetBalance: new FormControl(dataItem.OffsetBalance, Validators.required), + Loan: new FormControl(Loan), + UploadMeta: new FormControl(UploadMeta) + }); + } + public editHandler({ sender, rowIndex, dataItem }): void { this.closeEditor(sender); this.showBalance = dataItem.Balance >= 0 ; this.showOffsetBalance = dataItem.OffsetBalance >= 0 ; - this.incomeFormGroup = createFormGroup(dataItem); + this.incomeFormGroup = this.createFormGroup(dataItem); this.editedRowIndex = rowIndex; sender.editRow(rowIndex, this.incomeFormGroup); @@ -108,42 +170,66 @@ export class PayInComponent implements OnInit { if ( !this.showBalance) { v.Balance = -1; } if ( !this.showOffsetBalance) { v.OffsetBalance = -1; } - // const pi = new PayInModel( - // v.Id, - // this.Loan.Amount, - // v.Balance, - // this.Loan.Lender, - // this.Loan.Id, - // this.Loan.LenderLoanNumber, - // v.OffsetBalance, - // this.Loan.Settlement, - // v.Trail, - // v.Ts, - // 0, - // ); - - // console.log('saving PayIn', pi); - // this.ls.savePayIn(pi, isNew).subscribe( - // (resp: PayInModel) => { - // this.Loan.cuPayIn(resp); - // }, - // err => { - // this.errorOccurred.emit('Error saving Income'); - // } - // ); - + const pi = this.buildPayInForSave(v, isNew); + this.lss.savePayIn(pi, isNew).subscribe( + (resp: PayInModel) => { + this.Loan.cuPayIn(resp); + this.updatePiInGrid(new PayInModel(resp)); + }, + err => { + // TODO: this.errorOccurred.emit('Error saving Income'); + } + ); sender.closeRow(rowIndex); } + private buildPayInForSave(v: any, isNew: boolean): PayInModel { + const pi = new PayInModel({}); + if ( isNew ) { pi.Id = 0; } + + // are we using filtered loan? + let loan = v.Loan; + + if (loan === null || this.filterLoan !== undefined && this.filterLoan.Id !== '') { + loan = this.filterLoan; + } + + pi.Id = v.Id; + pi.Amount = loan.Amount; + pi.Balance = v.Balance; + pi.Lender = loan.Id === '' ? v.Lender : loan.Lender; + pi.LoanId = loan.Id; + pi.LoanNumber = loan.LenderLoanNumber; + pi.OffsetBalance = v.OffsetBalance; + pi.Settlement = loan.Settlement; + pi.IncomeAmount = v.IncomeAmount; + pi.IncomeType = v.IncomeType; + pi.UploadId = this.filterUploadMeta.Id; + pi.Ts = v.Ts; + return pi; + } + + private updatePiInGrid(pi: PayInModel): void { + const notFound = this.gridData.data.every( v => { + if ( v.Id === pi.Id ){ + Object.assign(v, pi); + return false; + } + return true; + }); + + if ( notFound ) { + this.gridData.data.unshift(pi); + } + } + public removeHandler({ dataItem }): void { - console.log(dataItem); - - // const na = this.Loan.PayIn.filter(v => { - // return v.Id !== dataItem.Id; - // }); - // - // this.Loan.PayIn = na; - // this.ls.removePayIn(dataItem.Id); + console.log('delete', dataItem); + + this.Loan.PayIn = this.Loan.PayIn.filter(v => v.Id !== dataItem.Id ); + this.gridData.data = this.gridData.data.filter(v => v.Id !== dataItem.Id) ; + + this.lss.removePayIn(dataItem.Id); } private closeEditor(grid, rowIndex = this.editedRowIndex): void{ diff --git a/src/app/upload-detail/upload-detail.component.html b/src/app/upload-detail/upload-detail.component.html index 3fef6d1..6e4d7e7 100644 --- a/src/app/upload-detail/upload-detail.component.html +++ b/src/app/upload-detail/upload-detail.component.html @@ -52,20 +52,43 @@
- - - {{ ua.Funders[0] }} - - - - - - - - -
- -
+
No result analyzed
+ + + + + + {{ ua.Funders[0] }} + + + + + + + + + + + + +

+ Paris is the capital and most populous city of France. It has an area of 105 square kilometres (41 square miles) and a population in 2013 of 2,229,621 within its administrative limits. The city is both a commune and department, and forms the centre and headquarters of the Île-de-France, or Paris Region, which has an area of 12,012 square kilometres (4,638 square miles) and a population in 2014 of 12,005,077, comprising 18.2 percent of the population of France. +

+
+
+ + + +

+ Tallinn is the capital and largest city of Estonia. It is situated on the northern coast of the country, on the shore of the Gulf of Finland, 80 km (50 mi) south of Helsinki, east of Stockholm and west of Saint Petersburg. From the 13th century until 1918 (and briefly during the Nazi occupation of Estonia from 1941 to 1944), the city was known as Reval. Tallinn occupies an area of 159.2 km2 (61.5 sq mi) and has a population of 443,894. Approximately 32% of Estonia's total population lives in Tallinn. +

+

+ Tallinn was founded in 1248, but the earliest human settlements are over 5,000 years old, making it one of the oldest capital cities of Northern Europe. Due to its strategic location, the city became a major trade hub, especially from the 14th to the 16th century, when it grew in importance as part of the Hanseatic League. +

+
+
+ +
@@ -80,8 +103,13 @@ (sizeChange)="onPayInSizeChange($event)" [(size)]="PayInSize">
-

Outer splitter / Bottom pane

-

Non-resizable and non-collapsible.

+ +
diff --git a/src/app/upload-detail/upload-detail.component.scss b/src/app/upload-detail/upload-detail.component.scss index b4a61b3..4e2fd49 100644 --- a/src/app/upload-detail/upload-detail.component.scss +++ b/src/app/upload-detail/upload-detail.component.scss @@ -181,6 +181,13 @@ div.analysis-result{ height: 100%; width: 100%; background-color: lightgoldenrodyellow; + padding: 10px; + box-shadow: inset 1px 1px 10px #9c9c8a; + overflow: scroll; + + kendo-tabstrip{ + box-shadow: 1px 1px 20px black; + } kendo-grid{ height: 100%; }