| @@ -64,6 +64,7 @@ import { LoanDetailComponent } from './loan-detail/loan-detail.component'; | |||
| import {LoanSingleService} from './service/loan.single.service'; | |||
| import { RatingInputComponent } from './rating-input/rating-input.component'; | |||
| import { LoanEditPeopleComponent } from './loan-edit-people/loan-edit-people.component'; | |||
| import { PeopleCardComponent } from './people-card/people-card.component'; | |||
| @@ -98,7 +99,8 @@ import { LoanEditPeopleComponent } from './loan-edit-people/loan-edit-people.com | |||
| PeopleSelectComponent, | |||
| LoanDetailComponent, | |||
| RatingInputComponent, | |||
| LoanEditPeopleComponent | |||
| LoanEditPeopleComponent, | |||
| PeopleCardComponent | |||
| ], | |||
| imports: [ | |||
| BrowserModule, | |||
| @@ -1,3 +1,80 @@ | |||
| <p>loan-edit-people works!</p> | |||
| {{Loan.Id}} | |||
| <bkp-divider> | |||
| <kendo-icon [name]="'ascx'" > </kendo-icon>Loan Id: {{Loan.Id}} | |||
| </bkp-divider> | |||
| <form #form="ngForm"> | |||
| <h5 class="title">Select Clients</h5> | |||
| <kendo-multiselect name="client" [data]="AllPeople" [textField]="'Display'" | |||
| [valueField]="'Id'" [(ngModel)]="this.Loan.Client" [virtual]="virtual" required="true" | |||
| (close)="rebuildPeopleMap($event)"> | |||
| <ng-template kendoMultiSelectItemTemplate let-dataItem> | |||
| <img class="contact-image" [src]="getContactImageUrl(dataItem.Id)" /> | |||
| <span>{{ dataItem.First +' ' + dataItem.Last}}</span> | |||
| </ng-template> | |||
| </kendo-multiselect> | |||
| <div class="container"> | |||
| <div class="row clientCardContainer justify-content-center"> | |||
| <div *ngFor="let v of this.Loan.Client" class="col-sm-4"> | |||
| <app-people-card [peopleId]="v.Id"></app-people-card> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <h5 class="title">Assign Brokers</h5> | |||
| <kendo-multiselect name="brokers" [data]="AllBrokers" [textField]="'Display'" | |||
| [valueField]="'Id'" [(ngModel)]="this.Loan.Broker" [virtual]="virtual" | |||
| (close)="rebuildPeopleMap($event)" | |||
| required="true" | |||
| > | |||
| <ng-template kendoMultiSelectItemTemplate let-dataItem> | |||
| <img class="contact-image" [src]="getContactImageUrl(dataItem.Id)" /> | |||
| <span>{{ dataItem.First +' ' + dataItem.Last}}</span> | |||
| </ng-template> | |||
| </kendo-multiselect> | |||
| <div class="container"> | |||
| <div class="row brokerCardContainer justify-content-center"> | |||
| <div *ngFor="let v of this.Loan.Broker" class="col-sm-4"> | |||
| <app-people-card [peopleId]="v.Id"></app-people-card> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <h5 class="title">Other Related</h5> | |||
| <kendo-multiselect name="beneficiaries" [data]="AllPeople" [textField]="'Display'" | |||
| [valueField]="'Id'" [(ngModel)]="this.Loan.OtherRewarder" [virtual]="virtual" | |||
| (close)="rebuildPeopleMap($event)"> | |||
| <ng-template kendoMultiSelectItemTemplate let-dataItem> | |||
| <img class="contact-image" [src]="getContactImageUrl(dataItem.Id)" /> | |||
| <span>{{ dataItem.First +' ' + dataItem.Last}}</span> | |||
| </ng-template> | |||
| </kendo-multiselect> | |||
| <div class="container"> | |||
| <div class="row beneficiaryCardContainer justify-content-center"> | |||
| <div *ngFor="let v of this.Loan.OtherRewarder, let idx=index" class="col-sm-4"> | |||
| <app-people-card [peopleId]="v.Id"></app-people-card> | |||
| <kendo-combobox [name]="'beneficiary_'+idx" [data]="roles" [allowCustom]="true" [(ngModel)]="role[v.Id]" | |||
| class="beneficiaryRole" required="true"> </kendo-combobox> | |||
| </div> | |||
| </div> | |||
| </div>` | |||
| <bkp-divider-shadow-bottom></bkp-divider-shadow-bottom> | |||
| </form> | |||
| <div class="k-form-buttons k-buttons-end"> | |||
| <div> | |||
| <button class="k-button k-primary" (click)="prev()" > ◀ Prev</button> | |||
| <button kendoButton look="flat" [disabled]="true"> </button> | |||
| <button class="k-button k-primary" (click)="next()"> Next ▶</button> | |||
| </div> | |||
| </div> | |||
| @@ -0,0 +1,43 @@ | |||
| .contact-image { | |||
| width: 20px; | |||
| height: 20px; | |||
| margin-right: 8px; | |||
| border-radius: 50%; | |||
| } | |||
| .title { | |||
| margin-top:50px | |||
| } | |||
| .number { | |||
| display: inline-block; | |||
| background: #333; | |||
| color: #fff; | |||
| border-radius: 50%; | |||
| width: 18px; | |||
| height: 18px; | |||
| text-align: center; | |||
| } | |||
| .beneficiaryRole { | |||
| width: 100%; | |||
| } | |||
| .clientCardContainer, .brokerCardContainer, .beneficiaryCardContainer{ | |||
| padding-top: 20px; | |||
| padding-bottom: 50px; | |||
| border-radius: 0px 0px 50px 50px; | |||
| box-shadow: rgb(204, 219, 232) 3px 3px 6px 0px inset, rgba(255, 255, 255, 0.5) -3px -3px 6px 1px inset; | |||
| } | |||
| .clientCardContainer{ | |||
| background-color: #fbfbec; | |||
| } | |||
| .brokerCardContainer{ | |||
| background-color: #e0ffdf; | |||
| } | |||
| .beneficiaryCardContainer{ | |||
| background-color: #eceeff; | |||
| } | |||
| @@ -1,17 +1,136 @@ | |||
| import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; | |||
| import {Component, EventEmitter, Input, OnInit, Output, QueryList, ViewChild, ViewChildren, AfterViewInit} from '@angular/core'; | |||
| import {LoanModel} from '../models/loan.model'; | |||
| import {PeopleService} from '../service/people.service'; | |||
| import {PeopleModel} from '../models/people.model'; | |||
| import {AuthService} from '../service/auth.service'; | |||
| import {ClonerService} from '../service/clone.service'; | |||
| import {BrokerModel} from '../models/broker.model'; | |||
| import {PeopleMapModel} from '../models/people-map.model'; | |||
| import {ComboBoxComponent, PreventableEvent} from '@progress/kendo-angular-dropdowns'; | |||
| import {FormGroup} from '@angular/forms'; | |||
| @Component({ | |||
| selector: 'app-loan-edit-people', | |||
| templateUrl: './loan-edit-people.component.html', | |||
| styleUrls: ['./loan-edit-people.component.scss'] | |||
| }) | |||
| export class LoanEditPeopleComponent implements OnInit { | |||
| export class LoanEditPeopleComponent implements OnInit, AfterViewInit { | |||
| @Input() public Loan: LoanModel; | |||
| @Output() public NotifyNext = new EventEmitter<number>(); | |||
| constructor() { } | |||
| @Output() public NotifyNext = new EventEmitter<boolean>(); | |||
| @Output() public NotifyPrev = new EventEmitter<boolean>(); | |||
| @Output() public errorOccurred = new EventEmitter<string>(); | |||
| @ViewChildren(ComboBoxComponent) cb!: QueryList<ComboBoxComponent>; | |||
| @ViewChild('form', {static: true}) form: FormGroup; | |||
| public AllPeople: PeopleModel[]; | |||
| public AllBrokers: BrokerModel[]; | |||
| public role: any = {}; // string to string map | |||
| public roles = ['Admin', 'Referral', 'Observer', 'Verifier', 'Ex-Broker', 'Ex-Client', 'Banned', 'Unknown']; | |||
| constructor(private ps: PeopleService, private auth: AuthService, private dcs: ClonerService) { } | |||
| public virtual: any = { | |||
| itemHeight: 28 | |||
| }; | |||
| ngOnInit(): void { | |||
| this.ps.getPeopleList('').subscribe( | |||
| resp => { | |||
| this.AllPeople = resp.List; | |||
| } | |||
| ); | |||
| this.ps.getBrokerList('').subscribe( | |||
| resp => { | |||
| this.AllBrokers = resp.List; | |||
| } | |||
| ); | |||
| // ensure role strings are available for selection | |||
| this.Loan.PeopleMap.forEach( v => { | |||
| if ( v.Role.toLowerCase() !== 'client' && v.Role.toLocaleLowerCase() !== 'broker'){ | |||
| this.roles.push( v.Role); | |||
| this.roles = [...new Set(this.roles)]; // remove duplicates | |||
| this.role[v.PeopleId] = v.Role; | |||
| } | |||
| }); | |||
| } | |||
| ngAfterViewInit(): void { | |||
| this.cb.changes.subscribe( (data: QueryList<ComboBoxComponent> ) => { | |||
| data.forEach( v => { | |||
| if (v.value === undefined && !v.isOpen) { | |||
| v.toggle(true); | |||
| } | |||
| }); | |||
| }); | |||
| } | |||
| public getContactImageUrl(contactId: string): string { | |||
| return this.auth.getUrl('avatar/' + contactId); | |||
| } | |||
| public next(): void{ | |||
| const err = this.rebuildPeopleMap(null); | |||
| if (err) { | |||
| this.showError('Please Select The people\'s role'); | |||
| return; | |||
| } | |||
| if ( this.Loan.Client.length === 0) { | |||
| this.showError('Please Select at least 1 client'); | |||
| return; | |||
| } | |||
| if ( this.Loan.Broker.length === 0) { | |||
| this.showError('Please Select at least 1 Broker'); | |||
| return; | |||
| } | |||
| console.log(this.Loan.Client, this.Loan.Broker, this.Loan.OtherRewarder, this.role); | |||
| this.ps.syncPeople(this.Loan).subscribe( resp => {}); | |||
| this.NotifyNext.emit(true); | |||
| } | |||
| public prev(): void { | |||
| this.NotifyPrev.emit(true); | |||
| } | |||
| public rebuildPeopleMap(event: PreventableEvent): boolean{ | |||
| this.Loan.PeopleMap = []; | |||
| this.Loan.Client.forEach(v => { | |||
| this.Loan.PeopleMap.push(new PeopleMapModel( | |||
| this.Loan.Id, | |||
| 'Client', | |||
| v.Id | |||
| )); | |||
| }); | |||
| this.Loan.Broker.forEach(v => { | |||
| this.Loan.PeopleMap.push(new PeopleMapModel( | |||
| this.Loan.Id, | |||
| 'Broker', | |||
| v.Id | |||
| )); | |||
| }); | |||
| let err = false; | |||
| this.Loan.OtherRewarder.forEach(v => { | |||
| if (this.role[v.Id] === undefined){ | |||
| err = true; | |||
| } | |||
| this.Loan.PeopleMap.push(new PeopleMapModel( | |||
| this.Loan.Id, | |||
| this.role[v.Id] || 'Unknown', | |||
| v.Id | |||
| )); | |||
| }); | |||
| return err; | |||
| } | |||
| public showError(err: string): void { | |||
| this.errorOccurred.emit(err); | |||
| } | |||
| } | |||
| @@ -2,6 +2,7 @@ import {Component, Input, OnInit, Output, EventEmitter} from '@angular/core'; | |||
| import {FormControl, FormGroup, Validators} from '@angular/forms'; | |||
| import {LoanModel} from '../../models/loan.model'; | |||
| import {LoanSingleService} from '../../service/loan.single.service'; | |||
| import {HttpErrorResponse} from '@angular/common/http'; | |||
| @Component({ | |||
| @@ -16,6 +17,7 @@ export class BasicinfoComponent implements OnInit { | |||
| @Input() Loan: LoanModel; | |||
| @Output() lenderNameChanged = new EventEmitter<string>(); | |||
| @Output() NotifyNext = new EventEmitter<boolean>(); | |||
| @Output() errorOccurred = new EventEmitter<string>(); | |||
| public minSettlement: Date = new Date(2015, 0, 1); | |||
| public maxSettlement: Date = new Date(2030, 4, 31); | |||
| @@ -84,6 +86,11 @@ export class BasicinfoComponent implements OnInit { | |||
| // console.log(resp); | |||
| // move to next step | |||
| this.NotifyNext.emit(true); | |||
| }, | |||
| err => { | |||
| const msg = err instanceof HttpErrorResponse ? err.statusText : ''; | |||
| this.errorOccurred.emit('Network Error Occurred : ' + err.statusText); | |||
| this.NotifyNext.emit(false); | |||
| } | |||
| ); | |||
| } | |||
| @@ -20,22 +20,28 @@ | |||
| #basicInfo | |||
| [Loan]="Loan" | |||
| (lenderNameChanged)="onLenderNameChange($event)" | |||
| (NotifyNext) ="next(0)" | |||
| (NotifyNext) ="next(0, $event)" | |||
| (errorOccurred)="showError($event)" | |||
| ></app-loan-basic-info> | |||
| <app-loan-edit-people *ngIf="currentStep===1" | |||
| #people | |||
| [Loan]="Loan" | |||
| (NotifyNext)="next(1)"> | |||
| (NotifyPrev) ="prev(1, $event)" | |||
| (NotifyNext)="next(1, $event)" | |||
| (errorOccurred)="showError($event)" | |||
| > | |||
| </app-loan-edit-people> | |||
| <app-loan-people-reward *ngIf="currentStep === 2" | |||
| [Loan]="Loan" | |||
| (NotifyNext) ="next(2)" | |||
| (NotifyPrev) ="prev(2)" | |||
| (NotifyNext) ="next(2, $event)" | |||
| (NotifyPrev) ="prev(2, $event)" | |||
| (errorOccurred)="showError($event)" | |||
| > </app-loan-people-reward> | |||
| <app-loan-trail-income *ngIf="currentStep === 3" | |||
| [Loan]="Loan" | |||
| [LenderName]="LenderName" | |||
| (NotifyPrev) ="prev(3)" | |||
| (NotifyPrev) ="prev(3, $event)" | |||
| (errorOccurred)="showError($event)" | |||
| > </app-loan-trail-income> | |||
| <div class="row"> | |||
| @@ -61,4 +67,11 @@ | |||
| </div> | |||
| <kendo-dialog title="Message" *ngIf="dialogOpened" [minWidth]="250" [width]="450" (close)="action('close')"> | |||
| <p style="margin: 30px; text-align: center;">{{errorMessage}}</p> | |||
| <kendo-dialog-actions> | |||
| <button kendoButton (click)="action('ok')" primary="true">Ok, I got it.</button> | |||
| </kendo-dialog-actions> | |||
| </kendo-dialog> | |||
| @@ -21,7 +21,7 @@ export class LoanEditComponent implements OnInit { | |||
| public Loan: LoanModel = new LoanModel(''); | |||
| public LenderName: string; | |||
| public curStepError = false; | |||
| public curStepSuccess = false; | |||
| public showDelete = false; | |||
| @@ -32,16 +32,24 @@ export class LoanEditComponent implements OnInit { | |||
| { label: 'Trail Income', isValid: true, errMsg: 'There are some errors' } | |||
| ]; | |||
| public dialogOpened = false; | |||
| public errorMessage = ''; | |||
| constructor( private lss: LoanSingleService) { } | |||
| public get currentStepError(): boolean{ | |||
| const ret = false; // TODO: set the value | |||
| this.curStepError = ret; | |||
| return ret; | |||
| } | |||
| // public get currentStepError(): boolean{ | |||
| // const ret = false; // TODO: set the value | |||
| // this.curStepError = ret; | |||
| // return ret; | |||
| // } | |||
| public next(step: number): void { | |||
| if (! this.currentStepError && (this.currentStep !== this.steps.length)) { | |||
| public next(step: number, success: boolean): void { | |||
| this.steps[step].isValid = success; | |||
| if (step === this.currentStep) { | |||
| this.curStepSuccess = success; | |||
| } | |||
| console.log(step, success); | |||
| if ( this.curStepSuccess && (this.currentStep !== this.steps.length)) { | |||
| this.currentStep += 1; | |||
| return; | |||
| @@ -49,7 +57,8 @@ export class LoanEditComponent implements OnInit { | |||
| this.stepper.validateSteps(); | |||
| } | |||
| public prev(step: number): void { | |||
| public prev(step: number, event: boolean): void { | |||
| console.log(step, event); | |||
| this.currentStep -= 1; | |||
| } | |||
| @@ -80,4 +89,12 @@ export class LoanEditComponent implements OnInit { | |||
| ); | |||
| } | |||
| public action(status): void { | |||
| this.dialogOpened = false; | |||
| } | |||
| public showError(msg: string): void{ | |||
| this.errorMessage = msg; | |||
| this.dialogOpened = true; | |||
| } | |||
| } | |||
| @@ -33,22 +33,60 @@ | |||
| </ng-template> | |||
| <ng-template | |||
| kendoGridEditTemplate let-fg="formGroup" let-column="column" let-dataItem="dataItem"> | |||
| <app-people-select #pps [formControl]="formGroup.get('To')" [translateId]="true" [width]="200" [initSearch]="contacts" ></app-people-select> | |||
| </ng-template> | |||
| </kendo-grid-column> | |||
| <kendo-grid-column field="Role" title="Role" width="100"> | |||
| <ng-template | |||
| kendoGridEditTemplate let-fg="formGroup" let-column="column" let-dataItem="dataItem"> | |||
| <kendo-combobox | |||
| name="existingRoles" | |||
| [data]="existingRoles" | |||
| <kendo-multicolumncombobox | |||
| #selectRevelantPeople | |||
| [data]="relevantPeople" | |||
| [textField]="'Display'" | |||
| [valueField]="'Id'" | |||
| [valuePrimitive]="true" | |||
| [listHeight]="300" | |||
| placeholder="Type here to search" | |||
| [suggest]="true" | |||
| [clearButton]="true" | |||
| [allowCustom]="false" | |||
| [formControl]="formGroup.get(column.field)" | |||
| (valueChange)="peopleChanged($event)" | |||
| > | |||
| </kendo-combobox> | |||
| <kendo-combobox-column | |||
| [field]="'Display'" | |||
| [title]="'Contact Name'" | |||
| [width]="200" | |||
| > | |||
| <ng-template | |||
| kendoMultiColumnComboBoxColumnCellTemplate | |||
| let-dataItem | |||
| > | |||
| <img | |||
| class="contact-image" | |||
| [src]="getContactImageUrl(dataItem.Id)" | |||
| /> | |||
| <span>{{ dataItem.Display }}</span> | |||
| </ng-template> | |||
| </kendo-combobox-column> | |||
| <kendo-combobox-column | |||
| [field]="'Role'" | |||
| [title]="'Role of Loan'" | |||
| [width]="200" | |||
| > | |||
| </kendo-combobox-column> | |||
| <ng-template kendoMultiColumnComboBoxFooterTemplate> | |||
| <strong> | |||
| {{ total }} records in total | |||
| </strong> | |||
| </ng-template> | |||
| </kendo-multicolumncombobox> | |||
| </ng-template> | |||
| </kendo-grid-column> | |||
| <kendo-grid-column field="Role" title="Role" width="100" [editable]="true"> | |||
| </kendo-grid-column> | |||
| <kendo-grid-column field="Amount" title="Amount" width="100" editor="numeric" format="{0:c}"></kendo-grid-column> | |||
| @@ -18,3 +18,10 @@ | |||
| line-height: 32px; | |||
| padding-left: 10px; | |||
| } | |||
| .contact-image { | |||
| width: 20px; | |||
| height: 20px; | |||
| margin-right: 8px; | |||
| border-radius: 50%; | |||
| } | |||
| @@ -3,11 +3,13 @@ import {FormControl, FormGroup, Validators} from '@angular/forms'; | |||
| import {AuthService} from '../../service/auth.service'; | |||
| import {LoanModel} from '../../models/loan.model'; | |||
| import {RewardModel} from '../../models/reward.model'; | |||
| import {PeopleModel} from '../../models/people.model'; | |||
| import {PeopleModel, RelevantPeopleModel} from '../../models/people.model'; | |||
| import {debounce} from 'ts-debounce'; | |||
| import {LoanSingleService} from '../../service/loan.single.service'; | |||
| import {ClonerService} from '../../service/clone.service'; | |||
| import {PeopleSelectComponent} from '../../people-select/people-select.component'; | |||
| import {PeopleService} from '../../service/people.service'; | |||
| import {Observable, of} from 'rxjs'; | |||
| import {map} from 'rxjs/operators'; | |||
| @@ -15,7 +17,7 @@ const createFormGroup = dataItem => new FormGroup({ | |||
| Id: new FormControl(dataItem.Id), | |||
| To: new FormControl(dataItem.To, [Validators.required, Validators.maxLength(128), Validators.minLength(1)]), | |||
| From: new FormControl(dataItem.From), | |||
| Role: new FormControl(dataItem.Role, [Validators.required, Validators.minLength(2), Validators.maxLength(40)]), | |||
| Role: new FormControl({value: dataItem.Role, disabled: true} , [Validators.required, Validators.minLength(2), Validators.maxLength(40)]), | |||
| Amount: new FormControl(dataItem.Amount, Validators.compose([Validators.required, Validators.min(0), Validators.max(1000000)])), | |||
| Description: new FormControl(dataItem.Description, [Validators.required, Validators.maxLength(128)]), | |||
| Ts: new FormControl( dataItem.Ts, Validators.required) | |||
| @@ -31,8 +33,9 @@ export class PeopleRewardComponent implements OnInit { | |||
| @Input() public Loan: LoanModel; | |||
| @Output() public NotifyNext = new EventEmitter<boolean>(); | |||
| @Output() public NotifyPrev = new EventEmitter<boolean>(); | |||
| @Output() public errorOccurred = new EventEmitter<string>(); | |||
| public contacts: PeopleModel[] = []; | |||
| public relevantPeople: RelevantPeopleModel[] = []; | |||
| public total = 0; | |||
| public pendingReward: RewardModel[] = []; | |||
| @@ -61,7 +64,10 @@ export class PeopleRewardComponent implements OnInit { | |||
| private debounceFilter: any ; | |||
| constructor(private ls: LoanSingleService, private auth: AuthService, private dcs: ClonerService) { | |||
| constructor(private ls: LoanSingleService, | |||
| private auth: AuthService, | |||
| private dcs: ClonerService, | |||
| private ps: PeopleService) { | |||
| } | |||
| public ngOnInit(): void { | |||
| @@ -70,19 +76,19 @@ export class PeopleRewardComponent implements OnInit { | |||
| console.log(this.Loan, this.pendingReward); | |||
| this.debounceFilter = debounce( (filter: string): void => { | |||
| this.auth.getPeopleList(filter).subscribe( | |||
| resp => { | |||
| this.contacts = resp.List; | |||
| this.total = resp.Count; | |||
| console.log(resp); | |||
| } | |||
| ); | |||
| }, 500) ; | |||
| // this.debounceFilter = debounce( (filter: string): void => { | |||
| // this.ps.getPeopleList(filter).subscribe( | |||
| // resp => { | |||
| // this.contacts = resp.List; | |||
| // this.total = resp.Count; | |||
| // console.log(resp); | |||
| // } | |||
| // ); | |||
| // }, 500) ; | |||
| } | |||
| public initPeople(): void{ | |||
| this.contacts = this.Loan.getRelevantPeople(); | |||
| this.relevantPeople = this.Loan.getRelevantPeople(); | |||
| } | |||
| public initReward(): void { | |||
| @@ -103,7 +109,7 @@ export class PeopleRewardComponent implements OnInit { | |||
| Id: 0, | |||
| To: '', | |||
| From: '', | |||
| Role: '', | |||
| Role: 'Unknown', | |||
| Amount: 0, | |||
| Description: '', | |||
| PayOutId: 0, | |||
| @@ -119,7 +125,7 @@ export class PeopleRewardComponent implements OnInit { | |||
| this.formGroup = createFormGroup(dataItem); | |||
| this.editedRowIndex = rowIndex; | |||
| sender.editRow(rowIndex, this.formGroup); | |||
| console.log('editing' , dataItem, this.contacts, this.formGroup); | |||
| console.log('editing' , dataItem, this.formGroup); | |||
| } | |||
| public cancelHandler({ sender, rowIndex }): void{ | |||
| @@ -130,20 +136,14 @@ export class PeopleRewardComponent implements OnInit { | |||
| public saveHandler({ sender, rowIndex, formGroup, isNew }): void { | |||
| const reward = formGroup.value; | |||
| reward.From = '0'; // Admin | |||
| // check all pending rewards, see if there are conflicting roles | |||
| const result = this.checkConflictingRoles(reward); | |||
| if ( result.roles.length > 1 ) { | |||
| console.log ( result); | |||
| alert ('conflicting roles' + result.id); | |||
| return; | |||
| } | |||
| // reward.Ts = new Date(); // Now | |||
| reward.LoanId = this.Loan.Id; // Enforce LoanId | |||
| reward.PayOutId = 0; // make sure it is not paid | |||
| this.ls.saveReward(reward, isNew).subscribe( | |||
| resp => { | |||
| if ( reward.Id === 0 ) { | |||
| const r = this.Loan.addReward(resp.Amount, resp.Description, resp.Id, resp.LoanId,resp.PayOutId,resp.To,resp.From, resp.Ts); | |||
| const r = this.Loan.addReward(resp.Amount, resp.Description, resp.Id, resp.LoanId, resp.PayOutId, resp.To, resp.From, resp.Ts); | |||
| this.pendingReward.unshift(r); | |||
| }else{ | |||
| const idx = this.pendingReward.findIndex( v => v.Id === reward.Id); | |||
| @@ -160,18 +160,23 @@ export class PeopleRewardComponent implements OnInit { | |||
| sender.closeRow(rowIndex); | |||
| } | |||
| private checkConflictingRoles(reward: RewardModel): {id: string, roles: string[]} { | |||
| console.log(reward, this.pendingReward); | |||
| const roles = [reward.Role]; | |||
| this.pendingReward.forEach( v => { | |||
| if (v.To === reward.To) { | |||
| const idx = roles.findIndex(r => r === v.Role); | |||
| if (idx === -1) { roles.push (v.Role); } | |||
| } | |||
| }); | |||
| return {id: reward.To, roles}; | |||
| peopleChanged(id: string): void { | |||
| this.formGroup.get('Role').setValue(this.Loan.getRoleById(id)); | |||
| console.log(this.formGroup); | |||
| console.log(this.formGroup.getRawValue()); | |||
| } | |||
| // private checkConflictingRoles(reward: RewardModel): {id: string, roles: string[]} { | |||
| // console.log(reward, this.pendingReward); | |||
| // const roles = [reward.Role]; | |||
| // this.pendingReward.forEach( v => { | |||
| // if (v.To === reward.To) { | |||
| // const idx = roles.findIndex(r => r === v.Role); | |||
| // if (idx === -1) { roles.push (v.Role); } | |||
| // } | |||
| // }); | |||
| // | |||
| // return {id: reward.To, roles}; | |||
| // } | |||
| public removeHandler({ dataItem }): void { | |||
| @@ -189,12 +194,16 @@ export class PeopleRewardComponent implements OnInit { | |||
| this.formGroup = undefined; | |||
| } | |||
| private photoURL(peopleId: string): string { | |||
| public getContactImageUrl(contactId: string): string { | |||
| return this.auth.getUrl('avatar/' + contactId); | |||
| } | |||
| public photoURL(peopleId: string): string { | |||
| const url = this.auth.getUrl('avatar/') + peopleId; | |||
| return 'url("' + url + '")'; | |||
| } | |||
| private UserName(dataItem: RewardModel): string { | |||
| public UserName(dataItem: RewardModel): string { | |||
| return dataItem.UserName; | |||
| } | |||
| @@ -20,6 +20,7 @@ export class TrailIncomeComponent implements OnInit { | |||
| @Input() public LenderName: string; | |||
| @Output() public NotifyNext = new EventEmitter<boolean>(); | |||
| @Output() public NotifyPrev = new EventEmitter<boolean>(); | |||
| @Output() public errorOccurred = new EventEmitter<string>(); | |||
| public gridData: any[] = []; | |||
| @@ -1,4 +1,4 @@ | |||
| import { PeopleModel } from './people.model'; | |||
| import {PeopleModel, RelevantPeopleModel} from './people.model'; | |||
| import {PeopleMapModel} from './people-map.model'; | |||
| import {BrokerModel} from './broker.model'; | |||
| import {PayInModel} from './pay-in.model'; | |||
| @@ -27,6 +27,7 @@ export class LoanModel { | |||
| public Broker?: BrokerModel[], | |||
| public OtherRewarder?: PeopleModel[], | |||
| public Reward?: RewardModel[], | |||
| public RewardPeople?: PeopleModel[], | |||
| public PayIn?: PayInModel[], | |||
| public PeopleMap?: PeopleMapModel[], | |||
| @@ -50,6 +51,7 @@ export class LoanModel { | |||
| this.setOtherRewarder(resp.OtherRewarder); | |||
| this.setReward(resp.Reward); | |||
| this.setRewardPeople(resp.RewardPeople); | |||
| this.PeopleMap = resp.PeopleMap; | |||
| this.PayIn = resp.PayIn; | |||
| } | |||
| @@ -88,7 +90,6 @@ export class LoanModel { | |||
| }); | |||
| } | |||
| private setBroker(v: any[]): void{ | |||
| this.Broker = []; | |||
| v.forEach((b => { | |||
| @@ -99,6 +100,14 @@ export class LoanModel { | |||
| })); | |||
| } | |||
| private setRewardPeople(v: any[]): void { | |||
| this.RewardPeople = []; | |||
| v.forEach((c) => { | |||
| this.RewardPeople.push( | |||
| new PeopleModel(c.Id, c.First, c.Last, c.Middle, c.Title, c.Display, c.Nick) | |||
| ); | |||
| }); | |||
| } | |||
| public addReward(Amount: number, Description: string, Id: number, | |||
| LoanId: string, PayOutId: number, To: string , From: string, Ts: Date): RewardModel { | |||
| const r = new RewardModel( | |||
| @@ -122,12 +131,12 @@ export class LoanModel { | |||
| this.Reward = na; | |||
| } | |||
| public emptyReward(): RewardModel { | |||
| return new RewardModel( | |||
| 0, '', 0, this.Id, | |||
| 0, '', '', new Date(), this.callBacks() | |||
| ); | |||
| } | |||
| // public emptyReward(): RewardModel { | |||
| // return new RewardModel( | |||
| // 0, '', 0, this.Id, | |||
| // 0, '', '', new Date(), this.callBacks() | |||
| // ); | |||
| // } | |||
| private callBacks(): LoanModelCallBacks { | |||
| return { | |||
| @@ -158,6 +167,12 @@ export class LoanModel { | |||
| } | |||
| })); | |||
| this.RewardPeople.forEach( c => { | |||
| if (c.Id === id) { | |||
| result = c.First + ' ' + c.Last; | |||
| } | |||
| }); | |||
| return result; | |||
| } | |||
| @@ -167,7 +182,7 @@ export class LoanModel { | |||
| } | |||
| public getUserRole(id: string): string { | |||
| let result = 'R:'; | |||
| let result = ''; | |||
| this.PeopleMap.forEach((row) => { | |||
| if ( row.PeopleId === id ){ | |||
| result = row.Role; | |||
| @@ -176,24 +191,39 @@ export class LoanModel { | |||
| return result; | |||
| } | |||
| public getRelevantPeople(): PeopleModel[] { | |||
| const result: PeopleModel[] = []; | |||
| public getRelevantPeople(): RelevantPeopleModel[] { | |||
| const result: RelevantPeopleModel[] = []; | |||
| this.Client.forEach(( c => { | |||
| result.push(c); | |||
| result.push({Id: c.Id, Display: c.Display, Role: 'Client'} ) ; | |||
| })); | |||
| this.Broker.forEach(( c => { | |||
| console.log(c); | |||
| result.push(c.toPeopleModel()); | |||
| result.push({Id: c.Id, Display: c.Display, Role: 'Broker'}); | |||
| })); | |||
| this.OtherRewarder.forEach(( c => { | |||
| result.push(c); | |||
| result.push({Id: c.Id, Display: c.Display, Role: this.getRoleById(c.Id)}); | |||
| })); | |||
| console.log(result); | |||
| return result; | |||
| this.RewardPeople.forEach( c => { | |||
| result.push({Id: c.Id, Display: c.Display, Role: 'none'}); | |||
| }); | |||
| return [...new Set(result)]; // remove duplicates, if any | |||
| } | |||
| public getRoleById(id: string): string { | |||
| let role = ''; | |||
| this.PeopleMap.every(v => { | |||
| if (v.PeopleId === id) { | |||
| role = v.Role; | |||
| return false; // stop search | |||
| }else{ | |||
| return true; | |||
| } | |||
| }); | |||
| return role; | |||
| } | |||
| } | |||
| @@ -20,3 +20,8 @@ export class PeopleModel{ | |||
| } | |||
| } | |||
| } | |||
| export class RelevantPeopleModel{ | |||
| Id: string; Display: string; Role: string; | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| <div class="contact" > | |||
| <div class="k-hbox"> | |||
| <kendo-avatar [shape]="'circle'" [imageSrc]="photoURL()"></kendo-avatar> | |||
| <div> | |||
| <h5>{{ contact.Nick }} </h5> | |||
| <p>{{ contact.Title }} {{ contact.First}} {{ contact.Last}}</p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -0,0 +1,13 @@ | |||
| div.contact { | |||
| padding-top: 20px; | |||
| padding-left:10px; | |||
| margin-left: 0px; | |||
| margin-right: 0px; | |||
| border-bottom: darkgrey solid 1px; | |||
| background-color: wheat; | |||
| } | |||
| div.contact:hover { | |||
| box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px; | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||
| import { PeopleCardComponent } from './people-card.component'; | |||
| describe('PeopleCardComponent', () => { | |||
| let component: PeopleCardComponent; | |||
| let fixture: ComponentFixture<PeopleCardComponent>; | |||
| beforeEach(async () => { | |||
| await TestBed.configureTestingModule({ | |||
| declarations: [ PeopleCardComponent ] | |||
| }) | |||
| .compileComponents(); | |||
| }); | |||
| beforeEach(() => { | |||
| fixture = TestBed.createComponent(PeopleCardComponent); | |||
| component = fixture.componentInstance; | |||
| fixture.detectChanges(); | |||
| }); | |||
| it('should create', () => { | |||
| expect(component).toBeTruthy(); | |||
| }); | |||
| }); | |||
| @@ -0,0 +1,31 @@ | |||
| import {Component, Input, OnInit} from '@angular/core'; | |||
| import {PeopleModel} from '../models/people.model'; | |||
| import {PeopleService} from '../service/people.service'; | |||
| import {AuthService} from '../service/auth.service'; | |||
| @Component({ | |||
| selector: 'app-people-card', | |||
| templateUrl: './people-card.component.html', | |||
| styleUrls: ['./people-card.component.scss'] | |||
| }) | |||
| export class PeopleCardComponent implements OnInit { | |||
| @Input() peopleId: string; | |||
| public contact: PeopleModel = new PeopleModel('', '', '', '', '', '', ''); | |||
| constructor(private ps: PeopleService, private auth: AuthService) { } | |||
| ngOnInit(): void { | |||
| this.ps.getPeopleById(this.peopleId).subscribe( | |||
| resp => { | |||
| this.contact = new PeopleModel(resp.Id, resp.First, resp.Last, resp.Middle, resp.Title, resp.Display, resp.Nick); | |||
| // console.log(this.contact); | |||
| } | |||
| ); | |||
| } | |||
| photoURL(): string { | |||
| if (this.contact === undefined ) { return ''; } | |||
| const url = this.auth.getUrl('avatar/') + this.contact.Id; | |||
| return url; | |||
| } | |||
| } | |||
| @@ -33,6 +33,7 @@ | |||
| <span>{{ dataItem.First +' ' + dataItem.Last}}</span> | |||
| </ng-template> | |||
| </kendo-combobox-column> | |||
| <ng-template kendoMultiColumnComboBoxFooterTemplate> | |||
| <strong> | |||
| {{ total }} records in total | |||
| @@ -9,7 +9,6 @@ import {PercentPipe} from '@angular/common'; | |||
| import {map} from 'rxjs/operators'; | |||
| import {PeopleService} from '../service/people.service'; | |||
| @Component({ | |||
| selector: 'app-people-select', | |||
| templateUrl: './people-select.component.html', | |||
| @@ -59,7 +58,7 @@ export class PeopleSelectComponent implements OnInit, ControlValueAccessor { | |||
| private prepareSearchPeople(): void{ | |||
| this.debounceFilter = debounce( (filter: string): void => { | |||
| this.auth.getPeopleList(filter).subscribe( | |||
| this.ps.getPeopleList(filter).subscribe( | |||
| resp => { | |||
| this.text.loading = false; | |||
| this.searchResult = resp.List; | |||
| @@ -175,7 +174,7 @@ export class PeopleSelectComponent implements OnInit, ControlValueAccessor { | |||
| // Search a user either based on partial name or a complete ID | |||
| searchUser(nameOrId: string, translateId: boolean): Observable<PeopleModel>{ | |||
| if ( translateId ) { | |||
| return this.ps.searchById(nameOrId); | |||
| return this.ps.getPeopleById(nameOrId); | |||
| // return this.searchPersonById( nameOrId) ; | |||
| }else{ | |||
| // return this.ps.searchById(nameOrId); | |||
| @@ -233,4 +232,5 @@ export class PeopleSelectComponent implements OnInit, ControlValueAccessor { | |||
| setDisabledState(isDisabled: boolean): void { | |||
| this.disabled = isDisabled; | |||
| } | |||
| } | |||
| @@ -117,8 +117,5 @@ export class AuthService { | |||
| getUrl: this.getUrl.bind(this) | |||
| }; | |||
| } | |||
| public getPeopleList(filter: string): Observable<{Count: number, List: PeopleModel[]}> { | |||
| const params = new HttpParams().set('filter', filter); | |||
| return this.http.get<{Count:number, List: PeopleModel[]}>(this.apiUrl + 'people-list', { params}); | |||
| } | |||
| } | |||
| @@ -3,13 +3,29 @@ import {HttpClient, HttpParams} from '@angular/common/http'; | |||
| import {AuthService} from './auth.service'; | |||
| import {Observable} from 'rxjs'; | |||
| import {PeopleModel} from '../models/people.model'; | |||
| import {BrokerModel} from '../models/broker.model'; | |||
| import {LoanModel} from '../models/loan.model'; | |||
| @Injectable({providedIn: 'root'}) | |||
| export class PeopleService { | |||
| constructor(private http: HttpClient, private auth: AuthService ){ } | |||
| searchById(id: string): Observable<PeopleModel> { | |||
| public getPeopleById(id: string): Observable<PeopleModel> { | |||
| return this.http.get<PeopleModel>(this.auth.getUrl('people/' + id)); | |||
| } | |||
| public getPeopleList(filter: string): Observable<{Count: number, List: PeopleModel[]}> { | |||
| const params = new HttpParams().set('filter', filter); | |||
| return this.http.get<{Count: number, List: PeopleModel[]}>(this.auth.getUrl( 'people-list/'), { params}); | |||
| } | |||
| public getBrokerList(filter: string): Observable<{Count: number, List: BrokerModel[]}> { | |||
| const params = new HttpParams().set('filter', filter); | |||
| return this.http.get<{Count: number, List: BrokerModel[]}>(this.auth.getUrl( 'broker-list/'), { params}); | |||
| } | |||
| public syncPeople(loan: LoanModel): Observable<boolean> { | |||
| return this.http.post<boolean>(this.auth.getUrl('sync-people/'), loan); | |||
| } | |||
| } | |||