ソースを参照

loan select incorporated

tags/2.037
Patrick Sun 4年前
コミット
9e8e8ef36a
24個のファイルの変更732行の追加214行の削除
  1. +40
    -36
      src/app/app.module.ts
  2. +6
    -6
      src/app/chart-past-year-monthly-performance/chart-past-year-monthly-performance.component.spec.ts
  3. +28
    -4
      src/app/list-all-loans/list-all-loans.component.html
  4. +27
    -1
      src/app/list-all-loans/list-all-loans.component.scss
  5. +138
    -10
      src/app/list-all-loans/list-all-loans.component.ts
  6. +7
    -35
      src/app/list-income/list-income.component.html
  7. +1
    -4
      src/app/list-income/list-income.component.scss
  8. +43
    -6
      src/app/list-income/list-income.component.ts
  9. +2
    -1
      src/app/loan-card/loan-card.component.html
  10. +10
    -1
      src/app/loan-card/loan-card.component.scss
  11. +18
    -5
      src/app/loan-card/loan-card.component.ts
  12. +1
    -1
      src/app/loan-edit/trail-income/trail-income.component.ts
  13. +19
    -0
      src/app/loan-select/loan-select.component.html
  14. +25
    -0
      src/app/loan-select/loan-select.component.scss
  15. +25
    -0
      src/app/loan-select/loan-select.component.spec.ts
  16. +74
    -0
      src/app/loan-select/loan-select.component.ts
  17. +4
    -3
      src/app/main-menu-items.ts
  18. +13
    -0
      src/app/models/pay-in-ex.model.ts
  19. +18
    -3
      src/app/models/pay-in.model.ts
  20. +27
    -15
      src/app/pay-in/pay-in.component.html
  21. +16
    -14
      src/app/pay-in/pay-in.component.scss
  22. +139
    -53
      src/app/pay-in/pay-in.component.ts
  23. +44
    -16
      src/app/upload-detail/upload-detail.component.html
  24. +7
    -0
      src/app/upload-detail/upload-detail.component.scss

+ 40
- 36
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<void> {
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,

+ 6
- 6
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<ChartPastYearMontnlyPerformanceComponent>;
describe('ChartPastYearMonthlyPerformanceComponent', () => {
let component: ChartPastYearMonthlyPerformanceComponent;
let fixture: ComponentFixture<ChartPastYearMonthlyPerformanceComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ChartPastYearMontnlyPerformanceComponent ]
declarations: [ ChartPastYearMonthlyPerformanceComponent ]
})
.compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(ChartPastYearMontnlyPerformanceComponent);
fixture = TestBed.createComponent(ChartPastYearMonthlyPerformanceComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

+ 28
- 4
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"
>
<ng-template kendoGridToolbarTemplate>
<button kendoGridExcelCommand type="button" icon="file-excel" style="float:right;">Export to Excel</button>
<button kendoGridPDFCommand icon="file-pdf" style="float:right;">Export to PDF</button>
<ng-template *ngIf="EnableExportExcel || EnableExportPdf || EnableSelectButton" kendoGridToolbarTemplate>
<button *ngIf="EnableExportExcel" kendoGridExcelCommand type="button" icon="file-excel" style="float:right;">Export to Excel</button>
<button *ngIf="EnableExportPdf" kendoGridPDFCommand icon="file-pdf" style="float:right;">Export to PDF</button>
<div class="selected-loans" #notification>
<app-loan-card *ngFor="let l of SelectedLoans" [Loan]="l" [ShowCloseButton]="true"
(Closed)="RemoveFromSelection(l)"
@fadeIn @slideOutUp
></app-loan-card>
</div>
<div class="selection-percentage" *ngIf="SelectedLoans.length >0">
<span class="badge badge-info">{{SelectedLoans.length}}</span>/
<span class="badge badge-info">{{MaxSelect}}</span>
</div>
<button *ngIf="EnableSelectButton" kendoButton [primary]=true
class="select-loan" icon="close-circle" style="float:right;"
(click)="onFinishSelection()"
>
Finish Selection</button>

</ng-template>

<kendo-grid-column field="Id" width="50" [class]="'topAlign'" [sortable]="false" [filterable]="false">
<kendo-grid-checkbox-column *ngIf="EnableSelectButton" width="50" ></kendo-grid-checkbox-column>


<kendo-grid-column field="Id" title="#" width="50" [class]="'topAlign'" [sortable]="false" [filterable]="false">
<ng-template kendoGridCellTemplate let-dataItem let-rowIndex="rowIndex" >
<p title="{{dataItem.Id}}" > {{dataItem.Index}}</p>
</ng-template>
</kendo-grid-column>

<kendo-grid-column-group title="People" [columnMenu]="false" [headerClass]="'colGroupPeople'">
<kendo-grid-column field="Client" width="220" title="Client(s)" [class]="'topAlign'" [headerClass]="'colClient'">
<ng-template kendoGridCellTemplate let-dataItem>

+ 27
- 1
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;

+ 138
- 10
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<LoanModel[]|LoanModel> = new EventEmitter<LoanModel[]|LoanModel>();

@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,
});
}
}

+ 7
- 35
src/app/list-income/list-income.component.html ファイルの表示

@@ -1,37 +1,9 @@
<div class="income-container">
<kendo-splitter orientation="vertical" style="height: 100%;">

<kendo-splitter-pane [collapsible]="false">
<div class="pane-content">
<h3>Outer splitter / Middle pane</h3>
<p>Resizable only.</p>
</div>
</kendo-splitter-pane>


<kendo-splitter-pane [collapsible]="true" size="50%">
<kendo-splitter class="full-height">

<kendo-splitter-pane size="300px">
<div class="pane-content">
<app-lender-uploads (uploadSuccessful)="onSuccess($event)"></app-lender-uploads>
</div>
</kendo-splitter-pane>

<kendo-splitter-pane >
<div class="pane-content">
<div *ngFor="let u of uploads" class="aaaa">
Type: {{u.files[0].extension}}
Name: {{u.files[0].name}}
Funder: Name: {{u.response.body.Funder}}
</div>
</div>
</kendo-splitter-pane>
</kendo-splitter>




</kendo-splitter-pane>
</kendo-splitter>
<app-pay-in [uploadMeta]="uploadMeta"
[Loan]="Loan"
[showLoanColumn]="true"
[showUploadColumn]="true"
[LoadDataNow]="startLoad"
>
</app-pay-in>
</div>

+ 1
- 4
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;
}

+ 43
- 6
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
}
}

+ 2
- 1
src/app/loan-card/loan-card.component.html ファイルの表示

@@ -1,6 +1,7 @@
<div class="card loan" (click)="gotoLoan(LoanId)">
<div class="card loan" (click)="onClick()">
<span class="badge badge-success">{{Loan.Item}}</span>
<span> Amount: {{Loan.Amount | currency }}</span>
<span> Settlement: {{Loan.Settlement | date:'yyyy-mm-dd' }}</span>
<span *ngIf="ShowCloseButton" class="k-icon k-i-close-circle close" (click)="onClose()" size="large"></span>
</div>


+ 10
- 1
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%;
}
}

+ 18
- 5
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<LoanModel> = new EventEmitter<LoanModel>();
@Output() Closed: EventEmitter<LoanModel> = new EventEmitter<LoanModel>();
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 {

+ 1
- 1
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;


+ 19
- 0
src/app/loan-select/loan-select.component.html ファイルの表示

@@ -0,0 +1,19 @@
<div #anchor class="anchor wrapper">
<button kendoButton *ngIf="!readOnly" (click)="onToggle()" [icon]="'edit'" ></button>
<app-loan-card *ngIf="Loan!==undefined && Loan !== null && Loan.Id !=='' "
[Loan]="Loan" [ShowCloseButton]="false"></app-loan-card>
</div>

<kendo-popup [anchor]="anchor" [offset]="Offset" (anchorViewportLeave)="showPopup = false" *ngIf="showPopup">
<div class='popup-content'>
<app-list-all-loans #list
(LoanSelected)="onLoanSelected($event)"
[EnableSelectButton]="true"
[SingleSelection]="true"
[MaxSelect]="Max"
[Preselect]="[Loan]"
>
</app-list-all-loans>
</div>
</kendo-popup>


+ 25
- 0
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;
}
}

+ 25
- 0
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<LoanSelectComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LoanSelectComponent ]
})
.compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(LoanSelectComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 74
- 0
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;
}

}

+ 4
- 3
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' },
]
},
{

+ 13
- 0
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<PayInModelEx>){
super(payload);
this.Loan = new LoanModel( payload.Loan || {});
this.UploadMeta = new UploadMetaModel(payload.UploadMeta || {});
}
}

+ 18
- 3
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<PayInModel>){
@@ -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;
}
}



+ 27
- 15
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 @@
<div style="width:100%; margin:0px; display:block">
<div *ngIf="allowAddNew" class="add-new-tool-bar">
<button kendoGridAddCommand icon="plus" >Add new Income</button>

</div>
<div [ngStyle]="{'width': allowAddNew? '70%': '100%'}" class="filter-panel-wrapper" >
<button kendoButton icon="filter" (click)="showFilter()" >Filter</button>
<!-- <button kendoButton icon="filter" >Upload</button>-->
<span *ngIf="uploadMeta.Id > 0 " class="badge badge-pill badge-primary specific-upload"> {{uploadMeta.Id}} </span>
<span *ngIf="uploadMeta.Id > 0 " class="badge badge-secondary specific-upload"> {{uploadMeta.FileName}} </span>
<button kendoButton icon="filter" (click)="showFilter()" >Filter</button>
</div>
</div>
</ng-template>
@@ -46,7 +48,13 @@
</ng-template>
</kendo-grid-column>

<kendo-grid-column field="Trail" title="Trail Received" width="150" format="{0:c}" editor="numeric">
<kendo-grid-column field="Lender" title="Lender" width="150" editor="string">
</kendo-grid-column>

<kendo-grid-column field="IncomeAmount" title="Income" width="150" format="{0:c}" editor="numeric">
</kendo-grid-column>

<kendo-grid-column field="IncomeType" title="Type" width="150" editor="string">
</kendo-grid-column>

<kendo-grid-column field="Ts" title="Trail Date" editor="date" width="100">
@@ -55,10 +63,6 @@
</ng-template>
</kendo-grid-column>

<kendo-grid-column field="Amount" title="Loan Amount" width="150" format="{0:c}"
[editable]="false" [sortable]="false">
</kendo-grid-column>

<kendo-grid-column field="Balance" title="Balance" width="150">
<ng-template kendoGridCellTemplate let-dataItem>
<div *ngIf="dataItem.Balance >=0 "> {{ dataItem.Balance | currency}} </div>
@@ -77,7 +81,7 @@
>
</kendo-switch>

<kendo-numerictextbox *ngIf="showBalance" name="balance"
<kendo-numerictextbox *ngIf="showBalance"
[formControl]="incomeFormGroup.get('Balance')"
[min]="-1" [max]="999999999" [autoCorrect]="true" class="balance">
</kendo-numerictextbox>
@@ -103,26 +107,34 @@
>
</kendo-switch>

<kendo-numerictextbox *ngIf="showOffsetBalance" name="offsetBalance"
<kendo-numerictextbox *ngIf="showOffsetBalance"
[formControl]="incomeFormGroup.get('OffsetBalance')"
[min]="-1" [max]="999999999" [autoCorrect]="true" class="balance">
</kendo-numerictextbox>
</ng-template>
</kendo-grid-column>

<kendo-grid-column field="UploadId" title="Uploads" width="100" format="{0:c}" [editable]="false" [sortable]="false">
<kendo-grid-column *ngIf="showUploadColumn"
field="UploadId" title="Uploads" width="100" format="{0:c}" [editable]="false" [sortable]="false">
<ng-template kendoGridCellTemplate let-dataItem >
<button kendoButton *ngIf="dataItem.UploadId > 0"
(click)="showUpload(dataItem.UploadId)" icon="attachment"> {{ dataItem.UploadId }}
(click)="showUpload(dataItem.UploadId)" icon="attachment"> {{ dataItem.UploadId }}
</button>
<p *ngIf="dataItem.UploadId <=0" > - </p>
</ng-template>
</kendo-grid-column>

<kendo-grid-column field="LoanId" title="Loan" width="210" [editable]="false" [sortable]="false">
<kendo-grid-column *ngIf="showLoanColumn"
field="LoanId" title="Loan" width="210"
[editable]="true "
[sortable]="false">
<ng-template kendoGridCellTemplate let-dataItem>

<app-loan-card [LoanId]="dataItem.LoanId"></app-loan-card>
<app-loan-card *ngIf="dataItem.LoanId !== '' " [LoanId]="dataItem.LoanId"></app-loan-card>
</ng-template>
<ng-template kendoGridEditTemplate let-dataItem>
<app-loan-select [formControl]="incomeFormGroup.get('Loan')"
[readOnly]="this.filterLoan !== undefined && this.filterLoan.Id !==''"
></app-loan-select>
</ng-template>
</kendo-grid-column>


+ 16
- 14
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;
}



+ 139
- 53
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<string>();
@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{

+ 44
- 16
src/app/upload-detail/upload-detail.component.html ファイルの表示

@@ -52,20 +52,43 @@
</div>
</div>
<div *ngIf="scanDone" class="analysis-result">
<kendo-grid *ngIf="ua.AAA !==undefined && ua.AAA !== null" [data]="analysisAAA">
<ng-template kendoGridToolbarTemplate>
{{ ua.Funders[0] }}
</ng-template>
<kendo-grid-column field="Period" format='{0:yyyy MMMM}'> </kendo-grid-column>
<kendo-grid-column field="LoanNumber"> </kendo-grid-column>
<kendo-grid-column field="Settlement" format='{0:MM/dd/yyyy h:mm a}'> </kendo-grid-column>
<kendo-grid-column field="LoanAmount" format='{0:c}' > </kendo-grid-column>
<kendo-grid-column field="Balance" format='{0:c}' > </kendo-grid-column>
<kendo-grid-column field="InTrail" format='{0:c}'> </kendo-grid-column>
</kendo-grid>
<div *ngIf="ua.Funders.length === 0">
<kendo-grid> </kendo-grid>
</div>
<div *ngIf="ua.Funders === undefined || ua.Funders.length == 0"> No result analyzed </div>
<kendo-tabstrip *ngIf="ua.Funders !== undefined && ua.Funders.length > 0">
<kendo-tabstrip-tab *ngIf="ua.AAA !==undefined && ua.AAA !== null" [title]="'AAA'" [selected]="true">
<ng-template kendoTabContent>
<kendo-grid [data]="analysisAAA">
<ng-template kendoGridToolbarTemplate>
{{ ua.Funders[0] }}
</ng-template>
<kendo-grid-column field="Period" format='{0:yyyy MMMM}'> </kendo-grid-column>
<kendo-grid-column field="LoanNumber"> </kendo-grid-column>
<kendo-grid-column field="Settlement" format='{0:MM/dd/yyyy h:mm a}'> </kendo-grid-column>
<kendo-grid-column field="LoanAmount" format='{0:c}' > </kendo-grid-column>
<kendo-grid-column field="Balance" format='{0:c}' > </kendo-grid-column>
<kendo-grid-column field="InTrail" format='{0:c}'> </kendo-grid-column>
</kendo-grid>
</ng-template>
</kendo-tabstrip-tab>
<kendo-tabstrip-tab [title]="'Paris'" >
<ng-template kendoTabContent>
<p>
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.
</p>
</ng-template>
</kendo-tabstrip-tab>

<kendo-tabstrip-tab [title]="'Tallinn'">
<ng-template kendoTabContent>
<p>
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.
</p>
<p>
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.
</p>
</ng-template>
</kendo-tabstrip-tab>
<kendo-tabstrip-tab [title]="'Sydney'" [disabled]="true"></kendo-tabstrip-tab>
</kendo-tabstrip>
</div>

</ng-template>
@@ -80,8 +103,13 @@
(sizeChange)="onPayInSizeChange($event)"
[(size)]="PayInSize">
<div class="pane-content">
<h3>Outer splitter / Bottom pane</h3>
<p>Non-resizable and non-collapsible.</p>
<app-pay-in *ngIf="ua.Input !== undefined && ua.Input !== null"
[uploadMeta]="ua.Input"
[showUploadColumn]="false"
[showLoanColumn]="true"
[LoadDataNow] = "true"
>
</app-pay-in>
</div>
</kendo-splitter-pane>
</kendo-splitter>

+ 7
- 0
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%;
}

読み込み中…
キャンセル
保存