| @@ -1,12 +1,12 @@ | |||
| { | |||
| "name": "broker", | |||
| "version": "2.0.39", | |||
| "version": "2.0.50", | |||
| "lockfileVersion": 2, | |||
| "requires": true, | |||
| "packages": { | |||
| "": { | |||
| "name": "broker", | |||
| "version": "2.0.39", | |||
| "version": "2.0.50", | |||
| "dependencies": { | |||
| "@angular/animations": "^11.2.9", | |||
| "@angular/common": "^11.2.9", | |||
| @@ -1,6 +1,6 @@ | |||
| { | |||
| "name": "broker", | |||
| "version": "2.0.39", | |||
| "version": "2.0.50", | |||
| "scripts": { | |||
| "ng": "ng", | |||
| "versionIncrease": "npm --no-git-tag-version version patch", | |||
| @@ -126,6 +126,9 @@ import { LoanDetailPeopleComponent } from './loan-detail/loan-detail-people/loan | |||
| import { LoanDetailRewardComponent } from './loan-detail/loan-detail-reward/loan-detail-reward.component'; | |||
| import { LoanDetailProgressComponent } from './loan-detail/loan-detail-progress/loan-detail-progress.component'; | |||
| import { LoanDetailTrailComponent } from './loan-detail/loan-detail-trail/loan-detail-trail.component'; | |||
| import { FileSizePipe } from './pipe/file.size.pipe'; | |||
| import {LoanStepService} from './service/loan.step.service'; | |||
| import {ToastService} from './service/toast.service'; | |||
| @@ -220,6 +223,7 @@ export function initializeApp(appConfig: AppConfig): () => Promise<void> { | |||
| LoanDetailRewardComponent, | |||
| LoanDetailProgressComponent, | |||
| LoanDetailTrailComponent, | |||
| FileSizePipe, | |||
| ], | |||
| imports: [ | |||
| BrowserModule, | |||
| @@ -266,7 +270,9 @@ export function initializeApp(appConfig: AppConfig): () => Promise<void> { | |||
| SessionService, | |||
| WebSocketService, | |||
| LoanSummaryService, | |||
| ToastService, | |||
| LoanSingleService, | |||
| LoanStepService, | |||
| LenderNameService, | |||
| { | |||
| provide: HTTP_INTERCEPTORS, | |||
| @@ -1,3 +1,5 @@ | |||
| <div style="height: calc(100vh - 48px);"> | |||
| <app-chart-type-of-loans></app-chart-type-of-loans> | |||
| <app-chart-amount-of-loans></app-chart-amount-of-loans> | |||
| <app-broker-loan-list></app-broker-loan-list> | |||
| </div> | |||
| @@ -1,8 +1,6 @@ | |||
| <kendo-grid | |||
| [data]="brokerLoans | async" | |||
| [height]="610" | |||
| [loading]="brokerLoans.loading" | |||
| class="fullheight_grid" | |||
| > | |||
| <kendo-grid-column field="Id"></kendo-grid-column> | |||
| @@ -10,7 +8,7 @@ | |||
| <ng-template kendoGridCellTemplate let-dataItem> | |||
| <div *ngFor="let p of dataItem.Client, let idx=index "> | |||
| <div class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.ClientIds[idx])}"></div> | |||
| <div class="customer-name"> {{ p }}</div> | |||
| <div class="customer-name"> {{ p.FullName }}</div> | |||
| </div> | |||
| </ng-template> | |||
| </kendo-grid-column> | |||
| @@ -43,9 +41,12 @@ | |||
| <ng-template kendoGridCellTemplate let-dataItem> | |||
| <div *ngFor="let p of dataItem.Broker, let idx=index "> | |||
| <div class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.BrokerIds[idx])}"></div> | |||
| <div class="customer-name"> {{ p }}</div> | |||
| <div class="customer-name"> {{ p.FullName }}</div> | |||
| </div> | |||
| </ng-template> | |||
| </kendo-grid-column> | |||
| <ng-template kendoGridDetailTemplate let-dataItem> | |||
| <app-loan-steps [Loan]="dataItem"></app-loan-steps> | |||
| </ng-template> | |||
| </kendo-grid> | |||
| @@ -29,7 +29,7 @@ export class BrokerLoanListComponent implements OnInit { | |||
| filter.filters.push({field:'client_ids', operator: 'contains', value: this.broker.Id, ignoreCase: true}) | |||
| filter.filters.push({field:'broker_ids', operator: 'contains', value: this.broker.Id, ignoreCase: true}) | |||
| filter.filters.push({field:'other_rewarder_ids', operator: 'contains', value: this.broker.Id, ignoreCase: true}) | |||
| this.lss.query({ skip: 0, take: 1000, sort, filter}); | |||
| this.lss.queryAsLoanModel({ skip: 0, take: 1000, sort, filter}); | |||
| } | |||
| private photoURL(peopleId: any): string { | |||
| @@ -54,7 +54,7 @@ | |||
| <ng-template kendoGridCellTemplate let-dataItem> | |||
| <div *ngFor="let p of dataItem.Client, let idx=index "> | |||
| <div *ngIf="dataItem.ClientIds.length >0" class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.ClientIds[idx])}"></div> | |||
| <div *ngIf="dataItem.ClientIds.length >0" class="customer-name"> {{ p }}</div> | |||
| <div *ngIf="dataItem.ClientIds.length >0" class="customer-name"> {{ p.FullName }}</div> | |||
| </div> | |||
| </ng-template> | |||
| <ng-template kendoGridFilterCellTemplate let-filter let-column="column"> | |||
| @@ -67,7 +67,7 @@ | |||
| <ng-template kendoGridCellTemplate let-dataItem> | |||
| <div *ngFor="let p of dataItem.Broker, let idx=index "> | |||
| <div *ngIf="dataItem.BrokerIds.length >0" class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.BrokerIds[idx])}"></div> | |||
| <div *ngIf="dataItem.BrokerIds.length >0" class="customer-name"> {{ p }}</div> | |||
| <div *ngIf="dataItem.BrokerIds.length >0" class="customer-name"> {{ p.FullName }}</div> | |||
| </div> | |||
| </ng-template> | |||
| <ng-template kendoGridFilterCellTemplate let-filter let-column="column"> | |||
| @@ -80,7 +80,7 @@ | |||
| <ng-template kendoGridCellTemplate let-dataItem> | |||
| <div *ngFor="let p of dataItem.OtherRewarder, let idx=index "> | |||
| <div *ngIf="dataItem.OtherRewarderIds.length > 0 " class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.OtherRewarderIds[idx])}"></div> | |||
| <div *ngIf="dataItem.OtherRewarderIds.length > 0 " class="customer-name"> {{ p }}</div> | |||
| <div *ngIf="dataItem.OtherRewarderIds.length > 0 " class="customer-name"> {{ p.FullName }}</div> | |||
| </div> | |||
| </ng-template> | |||
| <ng-template kendoGridFilterCellTemplate let-filter let-column="column"> | |||
| @@ -126,7 +126,7 @@ export class ListAllLoansComponent implements OnInit { | |||
| console.log(filter); | |||
| } | |||
| private loadData(): void { | |||
| this.service.query({ skip: this.skip, take: this.pageSize, sort: this.sort, filter: this.filter}); | |||
| this.service.queryAsLoanModel({ skip: this.skip, take: this.pageSize, sort: this.sort, filter: this.filter}); | |||
| } | |||
| private photoURL(peopleId: any): string { | |||
| @@ -5,7 +5,7 @@ import {HttpErrorResponse} from '@angular/common/http'; | |||
| import {Observable} from 'rxjs'; | |||
| import {LoanSingleService} from '../../service/loan.single.service'; | |||
| import {LenderNameService} from '../../service/lender-name.service'; | |||
| import { NotificationService } from '@progress/kendo-angular-notification'; | |||
| import {ToastService} from '../../service/toast.service'; | |||
| @Component({ | |||
| selector: 'app-loan-detail-basic-info', | |||
| @@ -21,7 +21,7 @@ export class LoanDetailBasicInfoComponent implements OnInit { | |||
| public errorMessage=''; | |||
| constructor( private service: LoanSingleService, | |||
| private notificationService: NotificationService, | |||
| private toast: ToastService, | |||
| public lenderNameService: LenderNameService) { | |||
| } | |||
| @@ -44,11 +44,11 @@ export class LoanDetailBasicInfoComponent implements OnInit { | |||
| this.Loan.Id = resp.Id; | |||
| } | |||
| this.showSuccess(this.Loan.Id + ' saved successfully'); | |||
| this.toast.showSuccess(this.Loan.Id + ' saved successfully'); | |||
| }, | |||
| err => { | |||
| this.errorMessage = err instanceof HttpErrorResponse ? err.statusText : 'unknown error occured'; | |||
| this.showError(this.errorMessage); | |||
| this.toast.showError(this.errorMessage); | |||
| } | |||
| ); | |||
| } | |||
| @@ -75,25 +75,5 @@ export class LoanDetailBasicInfoComponent implements OnInit { | |||
| this.Loan.Description = DemoDescription; | |||
| } | |||
| public showSuccess(msg : string ): void { | |||
| this.notificationService.show({ | |||
| content: msg, | |||
| cssClass: "button-notification", | |||
| animation: { type: "slide", duration: 400 }, | |||
| position: { horizontal: "center", vertical: "bottom" }, | |||
| type: { style: "success", icon: true }, | |||
| closable: true, | |||
| }); | |||
| } | |||
| public showError(err: string): void { | |||
| this.notificationService.show({ | |||
| content: err, | |||
| cssClass: "button-notification", | |||
| animation: { type: "slide", duration: 400 }, | |||
| position: { horizontal: "center", vertical: "bottom" }, | |||
| type: { style: "error", icon: true }, | |||
| closable: true, | |||
| }); | |||
| } | |||
| } | |||
| @@ -1 +1,66 @@ | |||
| <p>loan-detail-people works! {{ Loan.Id}}</p> | |||
| <form #form="ngForm"> | |||
| <div class="container"> | |||
| <div class="row"> | |||
| <div class="col-sm-6"> | |||
| <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.Display}}</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-9"> | |||
| <app-people-card [PeopleId]="v.Id"></app-people-card> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="col-sm-6"> | |||
| <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)" | |||
| > | |||
| <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-9"> | |||
| <app-people-card [PeopleId]="v.Id"></app-people-card> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </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> | |||
| @@ -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,5 +1,13 @@ | |||
| import {Component, Input, OnInit} from '@angular/core'; | |||
| import {Component, Input, OnInit, QueryList, ViewChild, ViewChildren} from '@angular/core'; | |||
| import {LoanModel} from '../../models/loan.model'; | |||
| import {ComboBoxComponent, PreventableEvent} from '@progress/kendo-angular-dropdowns'; | |||
| import {FormGroup} from '@angular/forms'; | |||
| import {PeopleModel} from '../../models/people.model'; | |||
| import {BrokerModel} from '../../models/broker.model'; | |||
| import {PeopleService} from '../../service/people.service'; | |||
| import {AuthService} from '../../service/auth.service'; | |||
| import {ClonerService} from '../../service/clone.service'; | |||
| import {PeopleMapModel} from '../../models/people-map.model'; | |||
| @Component({ | |||
| selector: 'app-loan-detail-people', | |||
| @@ -8,9 +16,112 @@ import {LoanModel} from '../../models/loan.model'; | |||
| }) | |||
| export class LoanDetailPeopleComponent implements OnInit { | |||
| @Input() Loan: LoanModel; | |||
| constructor() { } | |||
| @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', 'Introducer', '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; | |||
| } | |||
| this.ps.syncPeople(this.Loan.cloneForJson()).subscribe( resp => {}); | |||
| } | |||
| public prev(): void { | |||
| } | |||
| 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); | |||
| } | |||
| } | |||
| @@ -9,7 +9,7 @@ | |||
| </div> | |||
| </kendo-expansionpanel> | |||
| <kendo-expansionpanel | |||
| <kendo-expansionpanel *ngIf="false" | |||
| [title]="'People'" | |||
| [subtitle]="Loan.Amount | currency " | |||
| [expanded]="false" | |||
| @@ -19,7 +19,7 @@ | |||
| </div> | |||
| </kendo-expansionpanel> | |||
| <kendo-expansionpanel | |||
| <kendo-expansionpanel *ngIf="false" | |||
| [title]="'Reward'" | |||
| [subtitle]="Loan.Amount | currency " | |||
| [expanded]="false" | |||
| @@ -29,7 +29,7 @@ | |||
| </div> | |||
| </kendo-expansionpanel> | |||
| <kendo-expansionpanel | |||
| <kendo-expansionpanel *ngIf="false" | |||
| [title]="'Progress'" | |||
| [subtitle]="Loan.Amount | currency " | |||
| [expanded]="false" | |||
| @@ -39,7 +39,7 @@ | |||
| </div> | |||
| </kendo-expansionpanel> | |||
| <kendo-expansionpanel | |||
| <kendo-expansionpanel *ngIf="false" | |||
| [title]="'Trail Income'" | |||
| [subtitle]="Loan.Amount | currency " | |||
| [expanded]="false" | |||
| @@ -9,7 +9,7 @@ | |||
| <ng-template kendoListViewHeaderTemplate> | |||
| <div class="header"> | |||
| <div class="title">All Loans</div> | |||
| <kendo-textbox | |||
| <kendo-textbox *ngIf="false" | |||
| placeholder="Filter items..." | |||
| (valueChange)="handleFilterChange($event)" | |||
| > | |||
| @@ -26,13 +26,13 @@ export class LoanRowListComponent implements OnInit { | |||
| const sort: Array <SortDescriptor> = [{dir: 'desc', field: 'Settlement'}]; | |||
| if ( this.ss.loggedIn.role.toLowerCase() == 'admin' || this.ss.loggedIn.role.toLowerCase() == 'manager' ){ | |||
| const filter: CompositeFilterDescriptor = {logic: 'and', filters: [] }; | |||
| this.lss.query({ skip: this.skip, take: this.take, sort, filter}); | |||
| this.lss.queryAsLoanModel({ skip: this.skip, take: this.take, sort, filter}); | |||
| }else{ // load broker's loans | |||
| const filter: CompositeFilterDescriptor = {logic: 'or', filters: [] }; | |||
| filter.filters.push({field:'client_ids', operator: 'contains', value: people.Id, ignoreCase: true}) | |||
| filter.filters.push({field:'broker_ids', operator: 'contains', value: people.Id, ignoreCase: true}) | |||
| filter.filters.push({field:'other_rewarder_ids', operator: 'contains', value: people.Id, ignoreCase: true}) | |||
| this.lss.query({ skip: this.skip, take: this.take, sort, filter}); | |||
| this.lss.queryAsLoanModel({ skip: this.skip, take: this.take, sort, filter}); | |||
| } | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| <div class="container" style="border-top-style: none; border-top-color: #ff0000; border-left: 10px groove #969696; padding: 10px; margin: 5px; background-color: #e9e9e9; box-shadow: inset 1 1 10 10;"> | |||
| <div class="container single-row"> | |||
| <div class="row" style="padding-bottom: 10px;"> | |||
| <div class="col-md-4"> | |||
| <h5>Overview<div *ngIf="Loading" class="spinner-border text-success" role="status"> | |||
| @@ -27,21 +27,21 @@ | |||
| <li class="list-group-item d-flex justify-content-between align-items-center"> | |||
| <div *ngFor="let p of Loan.Client, let idx=index "> | |||
| <div *ngIf="Loan.ClientIds.length >0" class="customer-photo" [ngStyle]="{'background-image' : photoURL(Loan.ClientIds[idx])}"></div> | |||
| <div *ngIf="Loan.ClientIds.length >0" class="customer-name"> {{ p }}</div> | |||
| <div *ngIf="Loan.ClientIds.length >0" class="customer-name"> {{ p.FullName }}</div> | |||
| </div> | |||
| <span class="badge badge-primary badge-pill">Client</span> | |||
| </li> | |||
| <li class="list-group-item d-flex justify-content-between align-items-center"> | |||
| <div *ngFor="let p of Loan.Broker, let idx=index "> | |||
| <div *ngIf="Loan.BrokerIds.length >0" class="customer-photo" [ngStyle]="{'background-image' : photoURL(Loan.BrokerIds[idx])}"></div> | |||
| <div *ngIf="Loan.BrokerIds.length >0" class="customer-name"> {{ p }}</div> | |||
| <div *ngIf="Loan.BrokerIds.length >0" class="customer-name"> {{ p.FullName }}</div> | |||
| </div> | |||
| <span class="badge badge-primary badge-pill">BDM</span> | |||
| </li> | |||
| <li class="list-group-item d-flex justify-content-between align-items-center"> | |||
| <div *ngFor="let p of Loan.OtherRewarder, let idx=index "> | |||
| <div *ngIf="Loan.OtherRewarderIds.length > 0 " class="customer-photo" [ngStyle]="{'background-image' : photoURL(Loan.OtherRewarderIds[idx])}"></div> | |||
| <div *ngIf="Loan.OtherRewarderIds.length > 0 " class="customer-name"> {{ p }}</div> | |||
| <div *ngIf="Loan.OtherRewarderIds.length > 0 " class="customer-name"> {{ p.FullName }}</div> | |||
| </div> | |||
| <span class="badge badge-primary badge-pill">Other</span> | |||
| </li> | |||
| @@ -61,13 +61,17 @@ | |||
| <span class="badge badge-primary badge-pill">#</span> | |||
| </li> | |||
| <li class="list-group-item d-flex justify-content-between align-items-center"> | |||
| <button kendoButton (click)="gotoSteps()">Actions List</button> | |||
| <button kendoButton (click)="editLoan()">Edit</button> | |||
| <button kendoButton [togglable]="true" [icon]="editIcon" (selectedChange)="onShowLoanDetails($event)">Edit</button> | |||
| <button kendoButton [togglable]="true" [icon]="stepIcon" (selectedChange)="onShowSteps($event)" >Steps</button> | |||
| <button *ngIf="false" kendoButton (click)="editLoan()">Admin Edit</button> | |||
| </li> | |||
| </ul> | |||
| </div> | |||
| <div class="col-md-12"> | |||
| <div class="col-md-12" *ngIf="isShowDetail"> | |||
| <app-loan-detail [Loan]="Loan"></app-loan-detail> | |||
| </div> | |||
| <div class="col-md-12" *ngIf="isShowSteps"> | |||
| <app-loan-steps [Loan]="Loan"> </app-loan-steps> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -18,3 +18,16 @@ | |||
| line-height: 16px; | |||
| padding-left: 10px; | |||
| } | |||
| div.container.single-row{ | |||
| border-top-style: none; | |||
| border-top-color: #ff0000; | |||
| border-left: 10px groove #969696; | |||
| padding: 10px; | |||
| margin-left: auto; | |||
| margin-right:auto; | |||
| margin-top: 10px; | |||
| margin-bottom:5px; | |||
| background-color: #e9e9e945; | |||
| box-shadow: inset -1px 0px 3px 0px black; | |||
| } | |||
| @@ -13,11 +13,17 @@ import {NotificationService} from '@progress/kendo-angular-notification'; | |||
| export class LoanSingleRowComponent implements OnInit { | |||
| @Input() public Loan: LoanModel = new LoanModel({}); | |||
| @Input() public Loading: boolean = false; | |||
| @Input() public isShowDetail = false; | |||
| @Input() public isShowSteps = false; | |||
| public editIcon = 'edit'; | |||
| public stepIcon = 'aggregate-fields'; | |||
| constructor(private service: LoanSummaryService, | |||
| private auth: AuthService, | |||
| private router: Router, | |||
| private notificationService: NotificationService) { } | |||
| ngOnInit(): void { | |||
| } | |||
| @@ -33,4 +39,14 @@ export class LoanSingleRowComponent implements OnInit { | |||
| public editLoan() { | |||
| this.router.navigate(['edit-loan/', this.Loan.Id]).then(); | |||
| } | |||
| public onShowLoanDetails(e: boolean): void{ | |||
| this.isShowDetail = e; | |||
| // this.editIcon = e? 'trash' : 'save'; | |||
| } | |||
| public onShowSteps(e: boolean ) : void { | |||
| this.isShowSteps = e; | |||
| // this.stepIcon = e? 'aggregate-fields': ''; | |||
| } | |||
| } | |||
| @@ -1,47 +1,105 @@ | |||
| {{LoanId}} | |||
| <kendo-listview | |||
| [data]="steps" | |||
| containerClass="k-d-flex k-flex-col k-flex-nowrap" | |||
| > | |||
| <ng-template | |||
| kendoListViewItemTemplate | |||
| let-dataItem="dataItem" | |||
| let-index="index" | |||
| let-isFirst="isFirst" | |||
| let-isLast="isLast" | |||
| > | |||
| <div class="product" [class.border-bottom]="!isLast"> | |||
| <strong>[{{ index }}]:</strong> | |||
| <span class="product-name">{{ dataItem.label }}</span> | |||
| <span class="item-position" | |||
| >({{ isFirst ? "first" : isLast ? "last" : "mid" }})</span | |||
| > | |||
| </div> | |||
| </ng-template> | |||
| </kendo-listview> | |||
| <kendo-tilelayout [columns]="1" [gap]="gap" [reorderable]="true"> | |||
| <kendo-tilelayout-item [col]="1" title="Tile 1"> | |||
| <kendo-tilelayout-item-body> | |||
| {{ firstTileContent }} | |||
| </kendo-tilelayout-item-body> | |||
| </kendo-tilelayout-item> | |||
| <kendo-tilelayout-item [col]="1" title="Tile 2"> | |||
| <kendo-tilelayout [columns]="1" [gap]="gap" [reorderable]="false"> | |||
| <span *ngIf="Loan.Steps.length <=0">No action available, administrator will add new actions</span> | |||
| <kendo-tilelayout-item *ngFor="let step of Loan.Steps; let i=index" [col]="1" | |||
| [title]="title(step)"> | |||
| <kendo-tilelayout-item-body> | |||
| {{ secondTileContent }} | |||
| </kendo-tilelayout-item-body> | |||
| </kendo-tilelayout-item> | |||
| <form class="k-form" #stepForm="ngForm" > | |||
| <div class="row" *ngIf="!step.Done"> | |||
| <div class="col-sm-1"> | |||
| <kendo-formfield> | |||
| <kendo-label [for]="done" text="Done"></kendo-label> | |||
| <kendo-switch [(ngModel)]="step.Done" [ngModelOptions]="{standalone: true}" | |||
| (valueChange)="onResolveStep(step, $event)" | |||
| [disabled]="!step.Editable && !isAdmin" | |||
| onLabel="Yes" | |||
| offLabel="No"></kendo-switch> | |||
| </kendo-formfield> | |||
| </div> | |||
| <div class="col-sm-3"> | |||
| <kendo-formfield> | |||
| <kendo-label [for]="description" text="Description:"></kendo-label> | |||
| <input kendoTextBox #description name="description" | |||
| [(ngModel)]="step.Description" [ngModelOptions]="{standalone: true}" | |||
| [maxlength]="301" [required]="true" | |||
| [readOnly]="step.Done" | |||
| [placeholder]="'upload passport'" | |||
| [disabled]="!step.Editable && !isAdmin" | |||
| /> | |||
| <kendo-formerror> required, and 0 < length < 300 </kendo-formerror> | |||
| </kendo-formfield> | |||
| </div> | |||
| <kendo-tilelayout-item title="Tile 3" [col]="1"> | |||
| <kendo-tilelayout-item-body> | |||
| {{ thirdTileContent }} | |||
| </kendo-tilelayout-item-body> | |||
| </kendo-tilelayout-item> | |||
| <div class="col-sm-2"> | |||
| <kendo-formfield> | |||
| <kendo-label [for]="receviedAt" text="ReceivedAt:"></kendo-label> | |||
| <kendo-datepicker #receviedAt name="receviedAt" | |||
| [readOnly]="step.Done" | |||
| [disabled]="!step.Editable && !isAdmin" | |||
| [(ngModel)]="step.ReceivedAt" [ngModelOptions]="{standalone: true}" | |||
| > | |||
| </kendo-datepicker> | |||
| </kendo-formfield> | |||
| </div> | |||
| <kendo-tilelayout-item title="Tile 4" [col]="1"> | |||
| <kendo-tilelayout-item-body> | |||
| {{ fourthTileContent }} | |||
| <div class="col-sm-2"> | |||
| <kendo-formfield> | |||
| <kendo-label [for]="resolvedAt" text="ResolvedAt:"></kendo-label> | |||
| <kendo-datepicker #resolvedAt name="resolvedAt" | |||
| [readOnly]="step.Done" | |||
| [disabled]="!step.Editable && !isAdmin" | |||
| [(ngModel)]="step.ResolvedAt" [ngModelOptions]="{standalone: true}" | |||
| > | |||
| </kendo-datepicker> | |||
| </kendo-formfield> | |||
| </div> | |||
| <div *ngIf="step.FileName !=='' " class="col-sm-4 align-right attachment"><span class="step-text"> | |||
| {{step.FileName}} <br> | |||
| Size: ( {{step.FileSize | filesize }} ) Uploaded at: {{step.UploadedAt | date:"yyyy-MMM-dd"}} <br> | |||
| </span> | |||
| <button [disabled]="!step.Editable && !isAdmin" kendoButton class="download_attach" (click)="onDownload(step)" [icon]="'download'"></button> | |||
| <button [disabled]="!step.Editable && !isAdmin" kendoButton class="remove-attach" (click)="onRemoveAttach(step)" [icon]="'trash'"></button> | |||
| </div> | |||
| <div *ngIf="step.FileName ==='' " class="col-sm-4"> | |||
| <kendo-upload class="bottom" [saveUrl]="uploadUrl(step)" | |||
| [disabled]="!step.Editable && !isAdmin" | |||
| [showFileList]="false" (success)="onSuccess($event)"> </kendo-upload> | |||
| </div> | |||
| </div> | |||
| <div class="row align-bottom" *ngIf="step.Done"> | |||
| <div class="col-sm-1"> | |||
| <kendo-formfield> | |||
| <kendo-label [for]="done" text="Done"></kendo-label> | |||
| <kendo-switch [(ngModel)]="step.Done" [ngModelOptions]="{standalone: true}" | |||
| (valueChange)="onResolveStep(step, $event)" | |||
| [disabled]="!step.Editable && !isAdmin" | |||
| onLabel="Yes" | |||
| offLabel="No"></kendo-switch> | |||
| </kendo-formfield> | |||
| </div> | |||
| <div class="col-sm-3"><span class="step-text"> {{step.Description}} </span></div> | |||
| <div class="col-sm-2"><span class="step-text"> Receive: {{step.ReceivedAt | date:"yyyy-MMM-dd"}} </span></div> | |||
| <div class="col-sm-2"><span class="step-text"> Resolve: {{step.ResolvedAt | date:"yyyy-MMM-dd"}} </span></div> | |||
| <div class="col-sm-4" *ngIf="step.FileName !== ''"> | |||
| <span class="step-text"> | |||
| {{step.FileName}} | |||
| <button kendoButton class="download-readonly" (click)="onDownload(step)" [icon]="'download'"></button> | |||
| </span> | |||
| </div> | |||
| <div class="col-sm-4" *ngIf="step.FileName === ''"> <span class="step-text"> No file attached </span> </div> | |||
| </div> | |||
| </form> | |||
| <button *ngIf="isAdmin" kendoButton [icon]="'close-outline'" class="delete" (click)="onDeleteStep(step)"></button> | |||
| <button [disabled]="!step.Editable && !isAdmin" kendoButton [icon]="'save'" class="save" (click)="onSaveStep(step)"></button> | |||
| </kendo-tilelayout-item-body> | |||
| </kendo-tilelayout-item> | |||
| <button *ngIf="isAdmin" kendoButton class="add-step" [icon]="'plus'" (click)="onAddNewStep()"> </button> | |||
| </kendo-tilelayout> | |||
| <a #downloadLink ></a> | |||
| @@ -0,0 +1,61 @@ | |||
| span.step-text { | |||
| position: absolute; | |||
| bottom:10px; | |||
| } | |||
| kendo-upload.bottom { | |||
| position: absolute; | |||
| bottom:0px; | |||
| } | |||
| button.delete{ | |||
| position: absolute; | |||
| right: -5px; | |||
| top: -5px; | |||
| z-index: 1; | |||
| border-radius: 100%; | |||
| color: black; | |||
| } | |||
| button.save{ | |||
| position: absolute; | |||
| right: 30px; | |||
| top: -5px; | |||
| z-index: 1; | |||
| color: black; | |||
| } | |||
| button.add-step{ | |||
| position: absolute; | |||
| right: 0px; | |||
| top: 0px; | |||
| border-radius: 100%; | |||
| } | |||
| button.download-readonly{ | |||
| border-radius: 100%; | |||
| background: whitesmoke; | |||
| } | |||
| div.attachment{ | |||
| background: lightgoldenrodyellow; | |||
| border-radius: 10px; | |||
| button.remove-attach{ | |||
| position: absolute; | |||
| right: 0px; | |||
| top: 0px; | |||
| border-radius: 100%; | |||
| background: lightcoral; | |||
| color: white; | |||
| } | |||
| button.download_attach{ | |||
| position: absolute; | |||
| right: 0px; | |||
| top: 30px; | |||
| border-radius: 100%; | |||
| background: whitesmoke; | |||
| color: black; | |||
| } | |||
| } | |||
| @@ -1,8 +1,17 @@ | |||
| import { Component, OnInit } from '@angular/core'; | |||
| import {Component, ElementRef, Input, OnInit, ViewChild} from '@angular/core'; | |||
| import {LoanSingleService} from '../service/loan.single.service'; | |||
| import {ActivatedRoute, Router} from '@angular/router'; | |||
| import {AuthService} from '../service/auth.service'; | |||
| import { TileLayoutGap } from '@progress/kendo-angular-layout'; | |||
| import {LoanModel} from '../models/loan.model'; | |||
| import { StepModel } from '../models/step.model'; | |||
| import {HttpParams} from '@angular/common/http'; | |||
| import {SuccessEvent} from '@progress/kendo-angular-upload'; | |||
| import {UploadMetaModel} from '../models/uploadMetaModel'; | |||
| import {LoanStepService} from '../service/loan.step.service'; | |||
| import {NotificationService} from '@progress/kendo-angular-notification'; | |||
| import {ToastService} from '../service/toast.service'; | |||
| import {SessionService} from '../service/session.service'; | |||
| @Component({ | |||
| selector: 'app-loan-steps', | |||
| @@ -10,42 +19,119 @@ import { TileLayoutGap } from '@progress/kendo-angular-layout'; | |||
| styleUrls: ['./loan-steps.component.scss'] | |||
| }) | |||
| export class LoanStepsComponent implements OnInit { | |||
| public LoanId: string; | |||
| public steps = [ | |||
| { label: "Step One" }, | |||
| { label: "Step Two" }, | |||
| { label: "Step Three", optional: true }, | |||
| { label: "Step Four" }, | |||
| { label: "Step Five" }, | |||
| ]; | |||
| @Input() public Loan: LoanModel; | |||
| @ViewChild('downloadLink', {static: true}) downloadLink: ElementRef<HTMLAnchorElement>; | |||
| public gap: TileLayoutGap = { | |||
| rows: 25, | |||
| columns: 40 | |||
| }; | |||
| public firstTileContent = `Reactive Netscape cherry pick domain contribution lazy Edge program.`; | |||
| public secondTileContent = `Lazy reflog freelancer Dijkstra directed acyclic graph concurrent uglify concurrency Safari.`; | |||
| public thirdTileContent = `Senior-engineer Edge backend UI subclass tech debt duck typing merge sort lazy.`; | |||
| public fourthTileContent = `Infrastructure tl;dr spy data store remote procedure call bootcamp pairing child keycaps.`; | |||
| constructor(private lss: LoanSingleService, private actRoute: ActivatedRoute, private auth: AuthService, private router: Router) { } | |||
| public isAdmin = false; | |||
| constructor(private lss: LoanSingleService, | |||
| private actRoute: ActivatedRoute, | |||
| private lstep: LoanStepService, | |||
| private auth: AuthService, | |||
| private ss: SessionService, | |||
| private toast: ToastService, | |||
| private router: Router) { } | |||
| ngOnInit(): void { | |||
| const LoanId = this.actRoute.snapshot.params.id; | |||
| this.isAdmin = this.ss.isAdmin(); | |||
| } | |||
| public onAddNewStep(): void{ | |||
| let s = new StepModel(); | |||
| s.LoanId = this.Loan.Id | |||
| s.StepIndex = this.Loan.Steps.length + 1; | |||
| this.lstep.getIdByLoanIdAndStepIndex(s.LoanId, s.StepIndex).subscribe(resp => { | |||
| s = new StepModel(resp); | |||
| let now = new Date(); | |||
| s.Description = "New_Step_"+now.getFullYear() + now.getMonth() + now.getDate(); | |||
| this.Loan.Steps.push(s); | |||
| }) | |||
| } | |||
| public uploadUrl( s: StepModel): string { | |||
| return this.auth.getUrl('step-upload/' + s.Id); | |||
| } | |||
| setTimeout(() => { | |||
| this.steps = this.stepsKeys(50); | |||
| }, 2000); | |||
| private normalizeDate(d: Date): string { | |||
| if (! d ) { d = new Date(); } | |||
| return d.toISOString(); | |||
| } | |||
| public onSuccess(ss: SuccessEvent ): void { | |||
| let step = new StepModel(ss.response.body); | |||
| private stepsKeys(num: number): {label: string}[] { | |||
| let ret = []; | |||
| for (let i=1; i<=num; i++){ | |||
| ret.push( {label: i, optional: false}); | |||
| } | |||
| return ret; | |||
| this.Loan.Steps.forEach( (v, idx, theArray) => { | |||
| if ( (v.Id === step.Id) || // match Id | |||
| ( (v.Id === '') && (v.LoanId === step.LoanId && v.StepIndex == step.StepIndex) ) ){// match LoanId and StepIndex when Id is empty | |||
| theArray[idx] = step; | |||
| } | |||
| }); | |||
| } | |||
| public onResolveStep(s: StepModel, resolved: boolean): void { | |||
| if ( resolved ) { | |||
| if ( !s.ResolvedAt ){ | |||
| s.ResolvedAt = new Date(); | |||
| } | |||
| } else { | |||
| s.ResolvedAt = null; | |||
| } | |||
| } | |||
| public onDownload(step: StepModel): void { | |||
| const el = this.downloadLink.nativeElement; | |||
| el.href = this.auth.getUrl('step-download/' + step.Id) | |||
| el.click(); | |||
| } | |||
| public onRemoveAttach(step: StepModel): void { | |||
| this.lstep.DeleteStepFileOnly(step.Id).subscribe( | |||
| resp => { | |||
| this.toast.showSuccess(step.FileName + " deleted"); | |||
| step.FileName = ""; | |||
| step.FileSize = 0; | |||
| step.FileMime = ""; | |||
| }, | |||
| err => { | |||
| this.toast.showError("cannot remove File"); | |||
| }, | |||
| ); | |||
| } | |||
| public onDeleteStep(step: StepModel): void { | |||
| this.lstep.DeleteStep(step.Id).subscribe( | |||
| resp=>{ | |||
| this.toast.showSuccess( step.Description + " deleted"); | |||
| this.Loan.Steps = this.Loan.Steps.filter( v => v.Id != step.Id); | |||
| }, | |||
| err =>{this.toast.showError( step.Description + " not deleted")}, | |||
| ); | |||
| } | |||
| public onSaveStep(step: StepModel): void { | |||
| this.lstep.SaveStep(step).subscribe( | |||
| resp => { | |||
| let result = new StepModel(resp); | |||
| step.StepIndex = result.StepIndex; | |||
| step.FileSize = result.FileSize; | |||
| step.FileMime = result.FileName; | |||
| step.FileName = result.FileName; | |||
| step.Description = result.Description; | |||
| step.ResolvedAt = result.ResolvedAt; | |||
| step.ReceivedAt = result.ReceivedAt; | |||
| console.log(result); | |||
| } | |||
| ); | |||
| } | |||
| public title(step: StepModel): string { | |||
| let lock = step.Editable ? '' : ' - 🔒' ; | |||
| return step.StepIndex + ' - ' + step.Description + lock; | |||
| } | |||
| } | |||
| @@ -19,8 +19,7 @@ export const mainMenuItems: any[] = [ | |||
| items: [ | |||
| { text: 'Start New Loan', icon: 'plus', url: './#edit-loan/' }, | |||
| { text: 'Export Loans', icon: 'table' , url: './#list-all-loans' }, | |||
| { text: 'List all', icon: 'table' , url: './#loan-row-list' }, | |||
| { text: 'Steps (demo)', icon: 'table' , url: './#loan-steps/6f024e92-6600-4a5a-8961-95fdba4ed31d' }, | |||
| { text: 'List all', icon: 'aggregate-fields' , url: './#loan-row-list' }, | |||
| { text: '--', separator: 'true' }, | |||
| { text: 'list income', icon: 'dollar', url: './#list-income' }, | |||
| { text: '--', separator: 'true' }, | |||
| @@ -6,6 +6,7 @@ import {RewardModel} from './reward.model'; | |||
| import {AuthService} from '../service/auth.service'; | |||
| import {LoanSingleService} from '../service/loan.single.service'; | |||
| import {MilestoneModel} from './milestone.model'; | |||
| import {StepModel} from './step.model'; | |||
| export interface LoanModelCallBacks { | |||
| getUserName(userId: string): string; | |||
| @@ -48,6 +49,8 @@ export class LoanModel { | |||
| public SettlementBooked: Date; | |||
| public SettlementCompleted: Date; | |||
| public Steps: StepModel[]; | |||
| constructor(payload: Partial<LoanModel>) { | |||
| this.Id = payload.Id || ''; | |||
| this.Amount = payload.Amount || 0; | |||
| @@ -141,6 +144,11 @@ export class LoanModel { | |||
| this.FormalApproved= this.getDate(payload.FormalApproved); | |||
| this.SettlementBooked= this.getDate(payload.SettlementBooked); | |||
| this.SettlementCompleted= this.getDate(payload.SettlementCompleted); | |||
| this.Steps = []; | |||
| if (payload.Steps && payload.Steps.length >0) { | |||
| payload.Steps.forEach(v => this.Steps.push(new StepModel(v))); | |||
| } | |||
| } | |||
| public static EmptyNew(lss: LoanSingleService, auth: AuthService): LoanModel { | |||
| @@ -0,0 +1,38 @@ | |||
| export class StepModel{ | |||
| public Id: string; | |||
| public LoanId: string; | |||
| public StepIndex : number; | |||
| public Description: string; | |||
| public Done: boolean; | |||
| public ReceivedAt: Date; | |||
| public ResolvedAt: Date; | |||
| public UploadedAt: Date; | |||
| public FileName: string; | |||
| public FileSize: number; | |||
| public FileMime: string; | |||
| //only for GUI | |||
| public Editable: boolean; | |||
| constructor(payload?: Partial<StepModel>) { | |||
| if( !payload ) { payload = {}; } | |||
| this.Id = payload.Id || ''; | |||
| this.LoanId = payload.LoanId || ''; | |||
| this.StepIndex = payload.StepIndex || -1; | |||
| this.Description = payload.Description || ''; | |||
| this.Done = payload.Done || false; | |||
| this.ReceivedAt = payload.ReceivedAt? new Date(payload.ReceivedAt) : new Date('1900-01-01'); | |||
| if ( this.ReceivedAt.getFullYear() <= 1900 ){ this.ReceivedAt = new Date() ; } | |||
| this.ResolvedAt = payload.ResolvedAt? new Date(payload.ResolvedAt) : new Date('1900-01-01'); | |||
| if ( this.ResolvedAt.getFullYear() <= 1900 ){ this.ResolvedAt = null ; } | |||
| this.UploadedAt = payload.UploadedAt? new Date(payload.UploadedAt) : new Date('1900-01-01'); | |||
| if ( this.UploadedAt.getFullYear() <= 1900 ){ this.UploadedAt = null ; } | |||
| this.FileName = payload.FileName || ''; | |||
| this.FileSize = payload.FileSize || 0 ; | |||
| this.FileMime = payload.FileMime || ''; | |||
| this.Editable = ! this.Done; // only when it started | |||
| } | |||
| } | |||
| @@ -17,8 +17,16 @@ | |||
| <button #enabled *ngIf="contact.Id.length > 10" kendoButton (selectedChange)="onSelectedChange($event)" look="outline" icon="tick" | |||
| [toggleable]="true" [selected]="contact.Enabled" | |||
| >Enable</button> | |||
| <button *ngIf="UserExtra.Login !=''" kendoButton (click)="onEmailPassword()" look="outline" icon="email">Email Password</button> | |||
| <!-- <button kendoButton (click)="onEditContact()" look="outline" icon="edit">Contacts</button>--> | |||
| <div *ngIf="sendingEmail" class="email-sending-indicator"> | |||
| <span>To...{{UserExtra.Login}}</span> | |||
| <kendo-loader [type]="'pulsing'" [themeColor]="'primary'" [size]="'small'" ></kendo-loader> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -209,6 +209,13 @@ $card-width: 330px; | |||
| flex-direction: row; | |||
| justify-content: center; | |||
| align-items: center; | |||
| div.email-sending-indicator{ | |||
| text-align:center; | |||
| font-size: 10px; | |||
| font-color: black; | |||
| width: 100% | |||
| } | |||
| } | |||
| .social-media-wrapper { | |||
| @@ -6,6 +6,7 @@ import {Button} from '@progress/kendo-angular-buttons'; | |||
| import {Router} from '@angular/router'; | |||
| import {UserExtraModel} from '../models/user-extra.model'; | |||
| import {BadgeAlign} from '@progress/kendo-angular-indicators'; | |||
| import {ToastService} from '../service/toast.service'; | |||
| @Component({ | |||
| selector: 'app-people-card', | |||
| @@ -20,8 +21,9 @@ export class PeopleCardComponent implements OnInit { | |||
| public UserExtra: UserExtraModel = UserExtraModel.EmptyNew(); | |||
| public badgeAlign: BadgeAlign = { vertical: 'bottom', horizontal: 'end' }; | |||
| public sendingEmail = false; | |||
| constructor(private ps: PeopleService, private auth: AuthService, private router: Router) { } | |||
| constructor(private ps: PeopleService, private auth: AuthService, private router: Router, private toast: ToastService) { } | |||
| ngOnInit(): void { | |||
| this.ps.getPeopleById(this.PeopleId).subscribe( | |||
| @@ -78,4 +80,18 @@ export class PeopleCardComponent implements OnInit { | |||
| this.contact.Enabled = resp; | |||
| }); | |||
| } | |||
| public onEmailPassword(): void { | |||
| this.sendingEmail = true; | |||
| this.ps.EmailPassword(this.PeopleId).subscribe( | |||
| resp => { | |||
| this.sendingEmail = false; | |||
| this.toast.showSuccess("Email seny to: " + this.UserExtra.Login ) | |||
| }, | |||
| err => { | |||
| this.sendingEmail = false; | |||
| this.toast.showError("Failed to email " + this.UserExtra.Login ) | |||
| } | |||
| ); | |||
| } | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| import { Pipe, PipeTransform } from '@angular/core'; | |||
| @Pipe({ | |||
| name: 'filesize' | |||
| }) | |||
| export class FileSizePipe implements PipeTransform { | |||
| transform(size: number, extension: string = 'MB') { | |||
| return (size / (1024 * 1024)).toFixed(2) + extension; | |||
| } | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| import {Injectable} from '@angular/core'; | |||
| import {HttpClient} from '@angular/common/http'; | |||
| import {AuthService} from './auth.service'; | |||
| import {ClonerService} from './clone.service'; | |||
| import {AppConfig} from '../app.config'; | |||
| import {Observable} from 'rxjs'; | |||
| import {StepModel} from '../models/step.model'; | |||
| @Injectable() | |||
| export class LoanStepService { | |||
| constructor(private http: HttpClient, private cfg: AppConfig){ } | |||
| public getIdByLoanIdAndStepIndex(LoanId: string, StepIndex: number): Observable<StepModel>{ | |||
| return this.http.post<StepModel>(this.cfg.getUrl('step-id/'), {LoanId,StepIndex}); | |||
| } | |||
| public SaveStep(step: StepModel): Observable<StepModel>{ | |||
| return this.http.post<StepModel>(this.cfg.getUrl('step-meta-update/'), step); | |||
| } | |||
| public DeleteStep(stepId: string): Observable<boolean> { | |||
| return this.http.delete<boolean>(this.cfg.getUrl('step/'+ stepId)); | |||
| } | |||
| public DeleteStepFileOnly(stepId: string): Observable<boolean> { | |||
| return this.http.delete<boolean>(this.cfg.getUrl('step-file/'+ stepId)); | |||
| } | |||
| } | |||
| @@ -21,6 +21,12 @@ export abstract class LoanQueryService extends BehaviorSubject<GridDataResult> { | |||
| } | |||
| public query(state: any): void { | |||
| this.fetch(this.tableName, state).subscribe(x => { | |||
| super.next(x); | |||
| }); | |||
| } | |||
| public queryAsLoanModel(state: any): void { | |||
| this.fetch(this.tableName, state).subscribe(x => { | |||
| var ret: GridDataResult = {total: x.total, data:[]}; | |||
| x.data.forEach( v => { | |||
| @@ -90,4 +90,8 @@ export class PeopleService { | |||
| return this.http.post<UserExModel>(this.auth.getUrl('user-ex/'), uex); | |||
| } | |||
| public EmailPassword(id: string): Observable<boolean> { | |||
| return this.http.post<boolean>(this.auth.getUrl('email-password/' + id), id); | |||
| } | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| import { Injectable } from "@angular/core"; | |||
| import {NotificationService} from '@progress/kendo-angular-notification'; | |||
| @Injectable() | |||
| export class ToastService { | |||
| constructor(private notificationService: NotificationService ) {} | |||
| public showSuccess(msg : string ): void { | |||
| this.notificationService.show({ | |||
| content: msg, | |||
| cssClass: "button-notification", | |||
| animation: { type: "slide", duration: 400 }, | |||
| position: { horizontal: "center", vertical: "bottom" }, | |||
| type: { style: "success", icon: true }, | |||
| hideAfter: 5000, | |||
| }); | |||
| } | |||
| public showError(err: string): void { | |||
| this.notificationService.show({ | |||
| content: err, | |||
| cssClass: "button-notification", | |||
| animation: { type: "slide", duration: 400 }, | |||
| position: { horizontal: "center", vertical: "bottom" }, | |||
| type: { style: "error", icon: true }, | |||
| hideAfter: 5000, | |||
| }); | |||
| } | |||
| } | |||