| @@ -58,7 +58,7 @@ export class LoanEditComponent implements OnInit { | |||
| ngOnInit(): void { | |||
| const demoId = '02ad4e5a-fb5c-4f29-bc89-381a49d77bb7'; | |||
| const demoId = '049836b8-07ad-448a-b048-865e8e85f1fe'; | |||
| this.loadLoanById(demoId); | |||
| } | |||
| @@ -14,31 +14,31 @@ | |||
| (remove)="removeHandler($event)" | |||
| > | |||
| <ng-template kendoGridToolbarTemplate> | |||
| <button kendoGridAddCommand>Add new</button> | |||
| <button kendoGridAddCommand icon="plus" [primary]="true">Add new</button> | |||
| </ng-template> | |||
| <kendo-grid-command-column title="command" width="220"> | |||
| <kendo-grid-command-column title="action" width="80"> | |||
| <ng-template kendoGridCellTemplate let-isNew="isNew" let-dataItem> | |||
| <button kendoGridEditCommand [primary]="true" *ngIf="!dataItem.Paid">Edit</button> | |||
| <button kendoGridRemoveCommand *ngIf="!dataItem.Paid" >Remove</button> | |||
| <button kendoGridSaveCommand [disabled]="formGroup?.invalid">{{ isNew ? 'Add' : 'Update' }}</button> | |||
| <button kendoGridCancelCommand>{{ isNew ? 'Discard changes' : 'Cancel' }}</button> | |||
| <button kendoGridEditCommand *ngIf="!dataItem.Paid" icon="edit"></button> | |||
| <button kendoGridRemoveCommand *ngIf="!dataItem.Paid" icon="delete"></button> | |||
| <button kendoGridSaveCommand [disabled]="formGroup?.invalid" icon="save"></button> | |||
| <button kendoGridCancelCommand icon="close"></button> | |||
| </ng-template> | |||
| </kendo-grid-command-column> | |||
| <kendo-grid-column field="To" title="Name" width="300"> | |||
| <kendo-grid-column field="To" title="Name" width="200"> | |||
| <ng-template kendoGridCellTemplate let-dataItem> | |||
| <div class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.To) }"></div> | |||
| <div class="customer-name"> {{ UserName(dataItem) }}</div> | |||
| </ng-template> | |||
| <ng-template | |||
| kendoGridEditTemplate let-fg="formGroup" let-column="column" let-dataItem="dataItem"> | |||
| <app-people-select [formControl]="formGroup.get('To')" [translateId]="true" [width]="200"></app-people-select> | |||
| <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="200"> | |||
| <kendo-grid-column field="Role" title="Role" width="100"> | |||
| <ng-template | |||
| kendoGridEditTemplate let-fg="formGroup" let-column="column" let-dataItem="dataItem"> | |||
| <kendo-combobox | |||
| @@ -51,7 +51,7 @@ | |||
| </ng-template> | |||
| </kendo-grid-column> | |||
| <kendo-grid-column field="Amount" title="Amount" width="200" editor="numeric" format="{0:c}"></kendo-grid-column> | |||
| <kendo-grid-column field="Amount" title="Amount" width="100" editor="numeric" format="{0:c}"></kendo-grid-column> | |||
| <kendo-grid-column field="Description" title="Description" width="200"> | |||
| <ng-template | |||
| kendoGridEditTemplate let-fg="formGroup" let-column="column" let-dataItem="dataItem"> | |||
| @@ -65,6 +65,11 @@ | |||
| </ng-template> | |||
| </kendo-grid-column> | |||
| <kendo-grid-column field="Ts" title="Date" width="100" editor="date" > | |||
| <ng-template kendoGridCellTemplate let-dataItem> | |||
| {{dataItem.Ts | date: 'yyyy-MM-dd'}} | |||
| </ng-template> | |||
| </kendo-grid-column> | |||
| </kendo-grid> | |||
| <bkp-divider-shadow-bottom></bkp-divider-shadow-bottom> | |||
| @@ -1,4 +1,4 @@ | |||
| import {Component, Input, OnInit, Output, EventEmitter} from '@angular/core'; | |||
| import {Component, Input, OnInit, Output, EventEmitter, ViewChild} from '@angular/core'; | |||
| import {FormControl, FormGroup, Validators} from '@angular/forms'; | |||
| import {AuthService} from '../../service/auth.service'; | |||
| import {LoanModel} from '../../models/loan.model'; | |||
| @@ -7,6 +7,7 @@ import {PeopleModel} 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'; | |||
| @@ -17,7 +18,7 @@ const createFormGroup = dataItem => new FormGroup({ | |||
| Role: new FormControl(dataItem.Role, [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)]), | |||
| PayOutId: new FormControl({value: dataItem.PayOutId, disabled: dataItem.PayOutId > 0 }) | |||
| Ts: new FormControl( dataItem.Ts, Validators.required) | |||
| }); | |||
| @Component({ | |||
| @@ -55,6 +56,7 @@ export class PeopleRewardComponent implements OnInit { | |||
| 'Discount', | |||
| 'Fringe Cost', | |||
| 'Unknown', | |||
| 'Trail' | |||
| ]; | |||
| private debounceFilter: any ; | |||
| @@ -63,6 +65,7 @@ export class PeopleRewardComponent implements OnInit { | |||
| } | |||
| public ngOnInit(): void { | |||
| this.initPeople(); // make sure people has the one that we need | |||
| this.initReward(); | |||
| console.log(this.Loan, this.pendingReward); | |||
| @@ -78,6 +81,10 @@ export class PeopleRewardComponent implements OnInit { | |||
| }, 500) ; | |||
| } | |||
| public initPeople(): void{ | |||
| this.contacts = this.Loan.getRelevantPeople(); | |||
| } | |||
| public initReward(): void { | |||
| this.Loan.Reward.forEach((r) => { | |||
| const o = this.dcs.deepClone<RewardModel>(r); | |||
| @@ -106,46 +113,53 @@ export class PeopleRewardComponent implements OnInit { | |||
| public editHandler({ sender, rowIndex, dataItem }): void { | |||
| this.closeEditor(sender); | |||
| // console.log(sender); | |||
| if ( dataItem.Paid ) { | |||
| alert( 'cannot edit'); | |||
| }else{ | |||
| this.formGroup = createFormGroup(dataItem); | |||
| this.editedRowIndex = rowIndex; | |||
| sender.editRow(rowIndex, this.formGroup); | |||
| } | |||
| this.formGroup = createFormGroup(dataItem); | |||
| this.editedRowIndex = rowIndex; | |||
| sender.editRow(rowIndex, this.formGroup); | |||
| console.log('editing' , dataItem, this.contacts, this.formGroup); | |||
| } | |||
| public cancelHandler({ sender, rowIndex }): void{ | |||
| console.log(sender); | |||
| //console.log(sender); | |||
| this.closeEditor(sender, rowIndex); | |||
| } | |||
| public saveHandler({ sender, rowIndex, formGroup, isNew }): void { | |||
| const reward = formGroup.value; | |||
| reward.From = '0'; // Admin | |||
| reward.Ts = new Date(); // Now | |||
| // 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); | |||
| this.pendingReward.push( this.Loan.addReward( | |||
| reward.Amount, | |||
| reward.Description, | |||
| reward.Id, | |||
| reward.LoanId, | |||
| reward.PayOutId, | |||
| reward.To, | |||
| reward.From, | |||
| reward.Ts | |||
| )); | |||
| 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); | |||
| this.pendingReward.unshift(r); | |||
| }else{ | |||
| const idx = this.pendingReward.findIndex( v => v.Id === reward.Id); | |||
| // update | |||
| this.pendingReward[idx].To = reward.To; | |||
| this.pendingReward[idx].Amount = reward.Amount; | |||
| this.pendingReward[idx].Description = reward.Description; | |||
| this.pendingReward[idx].Ts = reward.Ts; | |||
| } | |||
| console.log(resp); | |||
| } | |||
| ); | |||
| sender.closeRow(rowIndex); | |||
| } | |||
| public removeHandler({ dataItem }): void { | |||
| console.log(dataItem); | |||
| // this.service.remove(dataItem); | |||
| const na = this.pendingReward.filter(v => { | |||
| return v.Id !== dataItem.Id; | |||
| }); | |||
| this.pendingReward = na; | |||
| this.ls.removeReward(dataItem); | |||
| } | |||
| private closeEditor(grid, rowIndex = this.editedRowIndex): void { | |||
| @@ -7,3 +7,7 @@ kendo-multicolumncombobox { | |||
| margin-right: 8px; | |||
| border-radius: 50%; | |||
| } | |||
| .toolbarAddNew{ | |||
| padding-bottom: 20px | |||
| } | |||
| @@ -7,6 +7,7 @@ import {MultiColumnComboBoxComponent} from '@progress/kendo-angular-dropdowns'; | |||
| import {Observable, of} from 'rxjs'; | |||
| import {PercentPipe} from '@angular/common'; | |||
| import {map} from 'rxjs/operators'; | |||
| import {PeopleService} from '../../service/people.service'; | |||
| @Component({ | |||
| @@ -24,8 +25,9 @@ import {map} from 'rxjs/operators'; | |||
| export class PeopleSelectComponent implements OnInit, ControlValueAccessor { | |||
| @Input() disabled = false; | |||
| @Input() translateId: false; // true to out user Id, false output user name | |||
| @Input() translateId: true; // always tralsnate user Id, because name is not unique | |||
| @Input() width: number; | |||
| @Input() initSearch: PeopleModel[]; | |||
| @Input() formControl: FormControl = new FormControl(); // this is a dummy place holder | |||
| @ViewChild('list', {static: true}) public text: MultiColumnComboBoxComponent; | |||
| @@ -41,10 +43,18 @@ export class PeopleSelectComponent implements OnInit, ControlValueAccessor { | |||
| private onTouched = () => {}; | |||
| constructor(private auth: AuthService) { } | |||
| constructor(private auth: AuthService, private ps: PeopleService) { } | |||
| ngOnInit(): void { | |||
| this.prepareSearchPeople(); | |||
| this.initSearchResult(); | |||
| } | |||
| private initSearchResult(): void { | |||
| this.initSearch.forEach( v => { | |||
| this.searchResult.push(v); | |||
| }); | |||
| this.total = this.initSearch.length; | |||
| } | |||
| private prepareSearchPeople(): void{ | |||
| @@ -112,7 +122,6 @@ export class PeopleSelectComponent implements OnInit, ControlValueAccessor { | |||
| // Allows Angular to update the model (name or ID). | |||
| // Update the model and changes needed for the view here. | |||
| writeValue(nameOrId: string): void { | |||
| return; | |||
| if ( nameOrId === undefined ){ | |||
| console.log('who called me for write', this); | |||
| return; | |||
| @@ -151,29 +160,65 @@ export class PeopleSelectComponent implements OnInit, ControlValueAccessor { | |||
| if ( incoming === undefined || incoming === '' || incoming === this.value ) { | |||
| return false; | |||
| } | |||
| this.searchResult.forEach((person) => { | |||
| if ( this.translateId) { | |||
| if ( person.Id === this.value ) { | |||
| return false; // found in cache | |||
| } | |||
| }else{ | |||
| if (person.FullName.includes(this.value)){ | |||
| return false; | |||
| } | |||
| } | |||
| const idx = this.searchResult.findIndex( person => { | |||
| if ( this.translateId) { | |||
| return person.Id === incoming; | |||
| }else{ | |||
| return person.FullName.includes(incoming); | |||
| } | |||
| }); | |||
| return true; | |||
| return idx === -1; // not found need search | |||
| } | |||
| // Search a user either based on partial name or a complete ID | |||
| searchUser(nameOrId: string, translateId: boolean): Observable<PeopleModel>{ | |||
| if ( translateId ) { | |||
| return PeopleModel.searchPersonById( nameOrId) ; | |||
| return this.ps.searchById(nameOrId); | |||
| // return this.searchPersonById( nameOrId) ; | |||
| }else{ | |||
| return PeopleModel.searchPersonByName(nameOrId) ; | |||
| // return this.ps.searchById(nameOrId); | |||
| // return this.searchPersonByName(nameOrId) ; | |||
| } | |||
| } | |||
| // searchPersonByName(name: string): Observable<PeopleModel>{ | |||
| // return new Observable ( observer => { | |||
| // const dummy: PeopleModel = new PeopleModel( | |||
| // 'dummy-id', | |||
| // 'FSearch', | |||
| // 'LResult', | |||
| // '', | |||
| // 'Mr.', | |||
| // 'Display Name', | |||
| // 'Nick Name' | |||
| // ); | |||
| // setTimeout(() => { | |||
| // observer.next(dummy); | |||
| // observer.complete(); | |||
| // }, 1000); | |||
| // }); | |||
| // } | |||
| // | |||
| // searchPersonById( id: string): Observable<PeopleModel> { | |||
| // return new Observable ( observer => { | |||
| // const dummy: PeopleModel = new PeopleModel( | |||
| // id, | |||
| // 'FSearch', | |||
| // 'LResult', | |||
| // '', | |||
| // 'Mr.', | |||
| // 'P:' + id, | |||
| // 'Nick Name' | |||
| // ); | |||
| // setTimeout(() => { | |||
| // observer.next(dummy); | |||
| // observer.complete(); | |||
| // }, 1000); | |||
| // }); | |||
| // } | |||
| // Allows Angular to register a function to call when the model (rating) changes. | |||
| // Save the function as a property to call later here. | |||
| registerOnChange(fn: (nameOrId: string) => void): void { | |||
| @@ -1,19 +1,23 @@ | |||
| import {PeopleModel} from './people.model'; | |||
| export class BrokerModel{ | |||
| Id: string; | |||
| First: string; | |||
| Last: string; | |||
| Middle: string; | |||
| Title: string; | |||
| Display: string; | |||
| Nick: string; | |||
| Login: string; | |||
| Enabled: boolean; | |||
| BSB: string; | |||
| ACC: string; | |||
| License: string; | |||
| Organization: string; | |||
| constructor( | |||
| public Id: string, | |||
| public First: string, | |||
| public Last: string, | |||
| public Middle: string, | |||
| public Title: string, | |||
| public Display: string, | |||
| public Nick: string, | |||
| public Login: string, | |||
| public Enabled: boolean, | |||
| public BSB: string, | |||
| public ACC: string, | |||
| public License: string, | |||
| public Organization: string | |||
| ){} | |||
| public toPeopleModel(): PeopleModel{ | |||
| return new PeopleModel( this.Id, this.First, this.Last, this.Middle, this.Title, this.Display, this.Nick ); | |||
| } | |||
| } | |||
| @@ -44,9 +44,11 @@ export class LoanModel { | |||
| this.Description = resp.Description; | |||
| this.Lender = resp.Lender; | |||
| this.Client = resp.Client; | |||
| this.Broker = resp.Broker; | |||
| this.OtherRewarder = resp.OtherRewarder; | |||
| this.setClient(resp.Client); | |||
| this.setBroker(resp.Broker); | |||
| this.setOtherRewarder(resp.OtherRewarder); | |||
| this.setReward(resp.Reward); | |||
| this.PeopleMap = resp.PeopleMap; | |||
| this.PayIn = resp.PayIn; | |||
| @@ -68,6 +70,35 @@ export class LoanModel { | |||
| return; | |||
| } | |||
| private setClient(v: any[]): void{ | |||
| this.Client = []; | |||
| v.forEach((c) =>{ | |||
| this.Client.push( | |||
| new PeopleModel(c.Id, c.First, c.Last, c.Middle, c.Title, c.Display, c.Nick) | |||
| ); | |||
| }); | |||
| } | |||
| private setOtherRewarder(v: any[]): void{ | |||
| this.OtherRewarder = []; | |||
| v.forEach((c) =>{ | |||
| this.OtherRewarder.push( | |||
| new PeopleModel(c.Id, c.First, c.Last, c.Middle, c.Title, c.Display, c.Nick) | |||
| ); | |||
| }); | |||
| } | |||
| private setBroker(v: any[]): void{ | |||
| this.Broker = []; | |||
| v.forEach((b => { | |||
| this.Broker.push(new BrokerModel( | |||
| b.Id, b.First, b.Last, b.Middle, b.Title, b.Display, b.Nick, b.Login, b.Enaabled, b.BSB, b.ACC, b.License, b.Organization | |||
| ) | |||
| ); | |||
| })); | |||
| } | |||
| public addReward(Amount: number, Description: string, Id: number, | |||
| LoanId: string, PayOutId: number, To: string , From: string, Ts: Date): RewardModel { | |||
| const r = new RewardModel( | |||
| @@ -78,9 +109,10 @@ export class LoanModel { | |||
| PayOutId, | |||
| To, | |||
| From, | |||
| Ts, | |||
| new Date(Ts), | |||
| this.callBacks() | |||
| ); | |||
| r.LoanId = this.Id; // Make sure Loan Id is same | |||
| this.Reward.push(r); | |||
| return r; | |||
| } | |||
| @@ -101,26 +133,27 @@ export class LoanModel { | |||
| } | |||
| public getUserName(id: string): string { | |||
| let result = 'P:' + id; | |||
| // search client | |||
| this.Client.forEach(( c => { | |||
| if ( c.Id === id ) { | |||
| return c.First + ' ' + c.Last; | |||
| result = c.First + ' ' + c.Last; | |||
| } | |||
| })); | |||
| this.Broker.forEach(( c => { | |||
| if ( c.Id === id ) { | |||
| return c.First + ' ' + c.Last; | |||
| result = c.First + ' ' + c.Last; | |||
| } | |||
| })); | |||
| this.OtherRewarder.forEach(( c => { | |||
| if ( c.Id === id ) { | |||
| return c.First + ' ' + c.Last; | |||
| result = c.First + ' ' + c.Last; | |||
| } | |||
| })); | |||
| return 'P:' + id; | |||
| return result; | |||
| } | |||
| public getUserPhotoUrl(id: string): string { | |||
| @@ -129,12 +162,33 @@ export class LoanModel { | |||
| } | |||
| public getUserRole(id: string): string { | |||
| this.PeopleMap.forEach((row) =>{ | |||
| let result = 'R:'; | |||
| this.PeopleMap.forEach((row) => { | |||
| if ( row.PeopleId === id ){ | |||
| return row.Role; | |||
| result = row.Role; | |||
| } | |||
| }); | |||
| return 'R:'; | |||
| return result; | |||
| } | |||
| public getRelevantPeople(): PeopleModel[] { | |||
| const result: PeopleModel[] = []; | |||
| this.Client.forEach(( c => { | |||
| result.push(c); | |||
| })); | |||
| this.Broker.forEach(( c => { | |||
| console.log(c); | |||
| result.push(c.toPeopleModel()); | |||
| })); | |||
| this.OtherRewarder.forEach(( c => { | |||
| result.push(c); | |||
| })); | |||
| console.log(result); | |||
| return result; | |||
| } | |||
| } | |||
| @@ -1,4 +1,5 @@ | |||
| import {Observable} from 'rxjs'; | |||
| import {PeopleService} from '../service/people.service'; | |||
| export class PeopleModel{ | |||
| constructor( | |||
| @@ -18,40 +19,4 @@ export class PeopleModel{ | |||
| return this.First + ' ' + this.Middle + ' ' + this.Last; | |||
| } | |||
| } | |||
| static searchPersonByName(name: string): Observable<PeopleModel>{ | |||
| return new Observable ( observer => { | |||
| const dummy: PeopleModel = new PeopleModel( | |||
| 'dummy-id', | |||
| 'FSearch', | |||
| 'LResult', | |||
| '', | |||
| 'Mr.', | |||
| 'Display Name', | |||
| 'Nick Name' | |||
| ); | |||
| setTimeout(() => { | |||
| observer.next(dummy); | |||
| observer.complete(); | |||
| }, 1000); | |||
| }); | |||
| } | |||
| static searchPersonById( id: string): Observable<PeopleModel> { | |||
| return new Observable ( observer => { | |||
| const dummy: PeopleModel = new PeopleModel( | |||
| id, | |||
| 'FSearch', | |||
| 'LResult', | |||
| '', | |||
| 'Mr.', | |||
| 'P:' + id, | |||
| 'Nick Name' | |||
| ); | |||
| setTimeout(() => { | |||
| observer.next(dummy); | |||
| observer.complete(); | |||
| }, 1000); | |||
| }); | |||
| } | |||
| } | |||
| @@ -27,7 +27,17 @@ export class LoanSingleService { | |||
| return this.auth.apiUrlFunc(); | |||
| } | |||
| public saveReward(reward: RewardModel, isNew : boolean ): void { | |||
| public saveReward(reward: RewardModel, isNew: boolean ): Observable<RewardModel> { | |||
| console.log('saving', reward, isNew, this); | |||
| return this.http.post<RewardModel>(this.auth.getUrl('reward/'), reward); | |||
| } | |||
| public removeReward(reward: RewardModel): void { | |||
| console.log('remove', reward); | |||
| this.http.delete<RewardModel>(this.auth.getUrl('reward/' + reward.Id)).subscribe( | |||
| resp => { | |||
| console.log(resp); | |||
| } | |||
| ); | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| import {Injectable} from '@angular/core'; | |||
| import {HttpClient, HttpParams} from '@angular/common/http'; | |||
| import {AuthService} from './auth.service'; | |||
| import {Observable} from 'rxjs'; | |||
| import {PeopleModel} from '../models/people.model'; | |||
| @Injectable({providedIn: 'root'}) | |||
| export class PeopleService { | |||
| constructor(private http: HttpClient, private auth: AuthService ){ } | |||
| searchById(id: string): Observable<PeopleModel> { | |||
| return this.http.get<PeopleModel>(this.auth.getUrl('people/' + id)); | |||
| } | |||
| } | |||